feat(api): cmd filter and record

This commit is contained in:
ttk
2024-09-14 18:19:10 +08:00
parent de9129703a
commit 3d7e3bb6fc
16 changed files with 323 additions and 51 deletions

View File

@@ -18,7 +18,7 @@ func GetRoleResources(ctx context.Context, rid int, resourceTypeId string) (res
} }
data := &ResourceResult{} data := &ResourceResult{}
url := fmt.Sprintf("%v/acl/roles/%v/resources", conf.Cfg.Auth.Acl.Url, rid) url := fmt.Sprintf("%s/acl/roles/%d/resources", conf.Cfg.Auth.Acl.Url, rid)
resp, err := remote.RC.R(). resp, err := remote.RC.R().
SetHeader("App-Access-Token", token). SetHeader("App-Access-Token", token).
SetQueryParams(map[string]string{ SetQueryParams(map[string]string{

View File

@@ -107,18 +107,19 @@ func (c *Controller) GetAssets(ctx *gin.Context) {
handleRemoteErr(ctx, err) handleRemoteErr(ctx, err)
return return
} }
ids := make([]int, 0) ids := make([]*model.AuthorizationIds, 0)
if err = mysql.DB. if err = mysql.DB.
Model(&model.Authorization{}). Model(&model.Authorization{}).
Where("resource_id IN ?", authorizationResourceIds). Where("resource_id IN ?", authorizationResourceIds).
Distinct(). Find(&ids).
Pluck("asset_id", &ids).
Error; err != nil { Error; err != nil {
ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}}) ctx.AbortWithError(http.StatusInternalServerError, &ApiError{Code: ErrInternal, Data: map[string]any{"err": err}})
return return
} }
assetIds := lo.Uniq(lo.Map(ids, func(item *model.AuthorizationIds, _ int) int { return item.AssetId }))
db = db.Where("id IN ?", assetIds)
db = db.Where("id IN ?", ids) ctx.Set("authorizationIds", ids)
} }
db = db.Order("name") db = db.Order("name")
@@ -159,12 +160,15 @@ func assetPostHookAuth(ctx *gin.Context, data []*model.Asset) {
if acl.IsAdmin(currentUser) { if acl.IsAdmin(currentUser) {
return return
} }
authorizationIds, _ := ctx.Value("authorizationIds").([]*model.AuthorizationIds)
for _, a := range data { for _, a := range data {
for k, v := range a.Authorization { accountIds := lo.Uniq(
if lo.Contains(v, currentUser.GetRid()) { lo.Map(lo.Filter(authorizationIds, func(item *model.AuthorizationIds, _ int) bool { return item.AssetId == a.Id }),
continue func(item *model.AuthorizationIds, _ int) int { return item.AccountId }))
for k := range a.Authorization {
if !lo.Contains(accountIds, k) {
delete(a.Authorization, k)
} }
delete(a.Authorization, k)
} }
} }
} }

View File

@@ -154,14 +154,20 @@ func GetAutorizationResourceIds(ctx *gin.Context) (resourceIds []int, err error)
return return
} }
func HasAuthorization(ctx *gin.Context) (ok bool) { func HasAuthorization(ctx *gin.Context, assetId, accountId int) (ok bool) {
currentUser, _ := acl.GetSessionFromCtx(ctx) ids, err := GetAutorizationResourceIds(ctx)
rs, err := acl.GetRoleResources(ctx, currentUser.Acl.Rid, conf.RESOURCE_AUTHORIZATION)
if err != nil { if err != nil {
logger.L().Error("check authorization failed", zap.Error(err)) logger.L().Error("", zap.Error(err))
return return
} }
k := fmt.Sprintf("%s-%s", ctx.Param("asset_id"), ctx.Param("account_id")) cnt := int64(0)
_, ok = lo.Find(rs, func(r *acl.Resource) bool { return k == r.Name }) err = mysql.DB.Model(&model.Authorization{}).
return Where("asset_id =? AND account_id =? AND resource_id IN (?)", assetId, accountId, ids).
Count(&cnt).Error
if err != nil {
logger.L().Error("", zap.Error(err))
return
}
return cnt > 0
} }

View File

@@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"regexp"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -18,6 +19,17 @@ import (
) )
var ( var (
commandPreHooks = []preHook[*model.Command]{
func(ctx *gin.Context, data *model.Command) {
if !data.IsRe {
return
}
_, err := regexp.Compile(data.Cmd)
if err != nil {
ctx.AbortWithError(http.StatusBadRequest, &ApiError{Code: ErrBadRequest, Data: map[string]any{"err": err}})
}
},
}
commandDcs = []deleteCheck{ commandDcs = []deleteCheck{
func(ctx *gin.Context, id int) { func(ctx *gin.Context, id int) {
assetName := "" assetName := ""
@@ -44,7 +56,7 @@ var (
// @Success 200 {object} HttpResponse // @Success 200 {object} HttpResponse
// @Router /command [post] // @Router /command [post]
func (c *Controller) CreateCommand(ctx *gin.Context) { func (c *Controller) CreateCommand(ctx *gin.Context) {
doCreate(ctx, true, &model.Command{}, conf.RESOURCE_COMMAND) doCreate(ctx, true, &model.Command{}, conf.RESOURCE_COMMAND, commandPreHooks...)
} }
// DeleteCommand godoc // DeleteCommand godoc
@@ -65,7 +77,7 @@ func (c *Controller) DeleteCommand(ctx *gin.Context) {
// @Success 200 {object} HttpResponse // @Success 200 {object} HttpResponse
// @Router /command/:id [put] // @Router /command/:id [put]
func (c *Controller) UpdateCommand(ctx *gin.Context) { func (c *Controller) UpdateCommand(ctx *gin.Context) {
doUpdate(ctx, true, &model.Command{}) doUpdate(ctx, true, &model.Command{}, commandPreHooks...)
} }
// GetCommands godoc // GetCommands godoc

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
@@ -41,6 +42,8 @@ var (
return true return true
}, },
} }
clear = []byte("\x1b[2k\r")
) )
func read(sess *gsession.Session) error { func read(sess *gsession.Session) error {
@@ -61,7 +64,7 @@ func read(sess *gsession.Session) error {
switch t { switch t {
case websocket.TextMessage: case websocket.TextMessage:
chs.InChan <- msg chs.InChan <- msg
if (sess.IsSsh() && len(msg) > 0 && msg[0] != '9') || guacd.IsActive(msg) { if (sess.IsSsh() && len(msg) > 0 && msg[0] != '9') || (!sess.IsSsh() && guacd.IsActive(msg)) {
sess.SetIdle() sess.SetIdle()
} }
} }
@@ -95,7 +98,7 @@ func write(sess *gsession.Session) {
func writeErrMsg(sess *gsession.Session, msg string) { func writeErrMsg(sess *gsession.Session, msg string) {
chs := sess.Chans chs := sess.Chans
out := append([]byte("\r\n \033[31m "), msg...) out := []byte(fmt.Sprintf("\r\n \033[31m %s \x1b[0m", msg))
chs.OutBuf.Write(out) chs.OutBuf.Write(out)
write(sess) write(sess)
} }
@@ -148,7 +151,7 @@ func HandleSsh(sess *gsession.Session) (err error) {
msg := in[1:] msg := in[1:]
switch rt { switch rt {
case '1': case '1':
chs.Win.Write(msg) in = msg
case '9': case '9':
continue continue
case 'w': case 'w':
@@ -161,11 +164,17 @@ func HandleSsh(sess *gsession.Session) (err error) {
Height: cast.ToInt(wh[1]), Height: cast.ToInt(wh[1]),
} }
} }
} else if sess.SessionType == model.SESSIONTYPE_CLIENT {
chs.Win.Write(in)
} }
if cmd, forbidden := sess.SshParser.AddInput(in); forbidden {
writeErrMsg(sess, fmt.Sprintf("%s is forbidden\n", cmd))
sess.SshParser.AddInput(clear)
chs.Win.Write(clear)
continue
}
chs.Win.Write(in)
case out := <-chs.OutChan: case out := <-chs.OutChan:
chs.OutBuf.Write(out) chs.OutBuf.Write(out)
sess.SshParser.AddOutput(out)
case <-tk.C: case <-tk.C:
write(sess) write(sess)
case <-tk1s.C: case <-tk1s.C:
@@ -270,6 +279,16 @@ func DoConnect(ctx *gin.Context, ws *websocket.Conn) (sess *gsession.Session, er
} }
if sess.IsSsh() { if sess.IsSsh() {
w, h := cast.ToInt(ctx.Query("w")), cast.ToInt(ctx.Query("h")) w, h := cast.ToInt(ctx.Query("w")), cast.ToInt(ctx.Query("h"))
sess.SshParser = gsession.NewParser(sess.SessionId, w, h)
if err = mysql.DB.Model(sess.SshParser.Cmds).Where("id IN ? AND enable=?", []int(asset.AccessAuth.CmdIds), true).
Find(&sess.SshParser.Cmds).Error; err != nil {
return
}
for _, c := range sess.SshParser.Cmds {
if c.IsRe {
c.Re, _ = regexp.Compile(c.Cmd)
}
}
if sess.SshRecoder, err = gsession.NewAsciinema(sess.SessionId, w, h); err != nil { if sess.SshRecoder, err = gsession.NewAsciinema(sess.SessionId, w, h); err != nil {
return return
} }
@@ -285,9 +304,9 @@ func DoConnect(ctx *gin.Context, ws *websocket.Conn) (sess *gsession.Session, er
ctx.AbortWithError(http.StatusBadRequest, err) ctx.AbortWithError(http.StatusBadRequest, err)
return return
} }
if !checkAuthorization(currentUser, asset, accountId) { if !acl.IsAdmin(currentUser) && !HasAuthorization(ctx, assetId, accountId) {
err = &ApiError{Code: ErrLogin} err = &ApiError{Code: ErrUnauthorized}
ctx.AbortWithError(http.StatusInternalServerError, err) ctx.AbortWithError(http.StatusForbidden, err)
return return
} }
@@ -410,6 +429,7 @@ func connectSsh(ctx *gin.Context, sess *gsession.Session, asset *model.Asset, ac
continue continue
} }
sess.SshRecoder.Resize(window.Width, window.Height) sess.SshRecoder.Resize(window.Width, window.Height)
sess.SshParser.Resize(window.Width, window.Height)
} }
} }
}) })
@@ -733,10 +753,6 @@ func checkTime(data model.AccessAuth) bool {
return !has || in == data.Allow return !has || in == data.Allow
} }
func checkAuthorization(user *acl.Session, asset *model.Asset, accountId int) bool {
return acl.IsAdmin(user) || lo.Contains(asset.Authorization[accountId], user.GetRid())
}
func handleError(ctx *gin.Context, sess *gsession.Session, err error, ws *websocket.Conn, chs *gsession.SessionChans) { func handleError(ctx *gin.Context, sess *gsession.Session, err error, ws *websocket.Conn, chs *gsession.SessionChans) {
defer func() { defer func() {
if chs == nil { if chs == nil {

View File

@@ -61,7 +61,7 @@ func (c *Controller) GetFileHistory(ctx *gin.Context) {
// @Router /file/ls/:asset_id/:account_id [post] // @Router /file/ls/:asset_id/:account_id [post]
func (c *Controller) FileLS(ctx *gin.Context) { func (c *Controller) FileLS(ctx *gin.Context) {
currentUser, _ := acl.GetSessionFromCtx(ctx) currentUser, _ := acl.GetSessionFromCtx(ctx)
if !acl.IsAdmin(currentUser) && !HasAuthorization(ctx) { if !acl.IsAdmin(currentUser) && !HasAuthorization(ctx, cast.ToInt(ctx.Param("account_id")), cast.ToInt(ctx.Param("account_id"))) {
ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{}}) ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{}})
return return
} }
@@ -101,7 +101,7 @@ func (c *Controller) FileLS(ctx *gin.Context) {
// @Router /file/mkdir/:asset_id/:account_id [post] // @Router /file/mkdir/:asset_id/:account_id [post]
func (c *Controller) FileMkdir(ctx *gin.Context) { func (c *Controller) FileMkdir(ctx *gin.Context) {
currentUser, _ := acl.GetSessionFromCtx(ctx) currentUser, _ := acl.GetSessionFromCtx(ctx)
if !acl.IsAdmin(currentUser) && !HasAuthorization(ctx) { if !acl.IsAdmin(currentUser) && !HasAuthorization(ctx, cast.ToInt(ctx.Param("account_id")), cast.ToInt(ctx.Param("account_id"))) {
ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{}}) ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{}})
return return
} }
@@ -141,7 +141,7 @@ func (c *Controller) FileMkdir(ctx *gin.Context) {
// @Router /file/upload/:asset_id/:account_id [post] // @Router /file/upload/:asset_id/:account_id [post]
func (c *Controller) FileUpload(ctx *gin.Context) { func (c *Controller) FileUpload(ctx *gin.Context) {
currentUser, _ := acl.GetSessionFromCtx(ctx) currentUser, _ := acl.GetSessionFromCtx(ctx)
if !acl.IsAdmin(currentUser) && !HasAuthorization(ctx) { if !acl.IsAdmin(currentUser) && !HasAuthorization(ctx, cast.ToInt(ctx.Param("account_id")), cast.ToInt(ctx.Param("account_id"))) {
ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{}}) ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{}})
return return
} }
@@ -202,7 +202,7 @@ func (c *Controller) FileUpload(ctx *gin.Context) {
// @Router /file/download/:asset_id/:account_id [get] // @Router /file/download/:asset_id/:account_id [get]
func (c *Controller) FileDownload(ctx *gin.Context) { func (c *Controller) FileDownload(ctx *gin.Context) {
currentUser, _ := acl.GetSessionFromCtx(ctx) currentUser, _ := acl.GetSessionFromCtx(ctx)
if !acl.IsAdmin(currentUser) && !HasAuthorization(ctx) { if !acl.IsAdmin(currentUser) && !HasAuthorization(ctx, cast.ToInt(ctx.Param("account_id")), cast.ToInt(ctx.Param("account_id"))) {
ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{}}) ctx.AbortWithError(http.StatusForbidden, &ApiError{Code: ErrNoPerm, Data: map[string]any{}})
return return
} }

View File

@@ -52,7 +52,11 @@ func (i *Instruction) Parse(content string) *Instruction {
var args = make([]string, len(elements)) var args = make([]string, len(elements))
for i, e := range elements { for i, e := range elements {
args[i] = strings.Split(e, ".")[1] ss := strings.Split(e, ".")
if len(ss) < 2 {
continue
}
args[i] = ss[1]
} }
return NewInstruction(args[0], args[1:]...) return NewInstruction(args[0], args[1:]...)
} }

View File

@@ -51,6 +51,7 @@ func init() {
if err = viper.Unmarshal(Cfg); err != nil { if err = viper.Unmarshal(Cfg); err != nil {
panic(fmt.Sprintf("parse config from config.yaml failed:%s", err)) panic(fmt.Sprintf("parse config from config.yaml failed:%s", err))
} }
} }
type HttpConfig struct { type HttpConfig struct {
@@ -84,8 +85,8 @@ type AclConfig struct {
} }
type AesConfig struct { type AesConfig struct {
Key []byte `yaml:"key"` Key string `yaml:"key"`
Iv []byte `yaml:"iv"` Iv string `yaml:"iv"`
} }
type LogConfig struct { type LogConfig struct {

View File

@@ -99,6 +99,7 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
github.com/veops/go-ansiterm v0.0.5
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.8.0 // indirect golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect

View File

@@ -218,6 +218,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/veops/go-ansiterm v0.0.5 h1:1VXuTvcokN6kSLWb75FrbHfKUGfcIn9NvwyVv1Nx3xw=
github.com/veops/go-ansiterm v0.0.5/go.mod h1:ydBaqvRRi5lOTT0dMyYcJjQ1rfJSai/7uNveoT/uRLU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=

View File

@@ -27,6 +27,15 @@ type InfoModel interface {
GetId() int GetId() int
} }
type AuthorizationIds struct {
AssetId int `json:"asset_id" gorm:"column:asset_id"`
AccountId int `json:"account_id" gorm:"column:account_id"`
}
func (m *AuthorizationIds) TableName() string {
return "authorization"
}
type AssetInfo struct { type AssetInfo struct {
Id int `json:"id" gorm:"column:id;primarykey;autoIncrement"` Id int `json:"id" gorm:"column:id;primarykey;autoIncrement"`
Name string `json:"name" gorm:"column:name"` Name string `json:"name" gorm:"column:name"`

View File

@@ -1,16 +1,19 @@
package model package model
import ( import (
"regexp"
"time" "time"
"gorm.io/plugin/soft_delete" "gorm.io/plugin/soft_delete"
) )
type Command struct { type Command struct {
Id int `json:"id" gorm:"column:id;primarykey;autoIncrement"` Id int `json:"id" gorm:"column:id;primarykey;autoIncrement"`
Name string `json:"name" gorm:"column:name;uniqueIndex:name_del;size:128"` Name string `json:"name" gorm:"column:name;uniqueIndex:name_del;size:128"`
Cmds Slice[string] `json:"cmds" gorm:"column:cmds"` Cmd string `json:"cmd" gorm:"column:cmd"`
Enable bool `json:"enable" gorm:"column:enable"` IsRe bool `json:"is_re" gorm:"column:is_re"`
Enable bool `json:"enable" gorm:"column:enable"`
Re *regexp.Regexp `json:"-" gorm:"-"`
ResourceId int `json:"resource_id" gorm:"column:resource_id"` ResourceId int `json:"resource_id" gorm:"column:resource_id"`
CreatorId int `json:"creator_id" gorm:"column:creator_id"` CreatorId int `json:"creator_id" gorm:"column:creator_id"`

View File

@@ -1,7 +1,6 @@
package schedule package schedule
import ( import (
"fmt"
"time" "time"
redis "github.com/veops/oneterm/cache" redis "github.com/veops/oneterm/cache"
@@ -11,6 +10,10 @@ import (
func UpdateConfig() { func UpdateConfig() {
cfg := &model.Config{} cfg := &model.Config{}
defer func() {
redis.SetEx(ctx, "config", cfg, time.Hour)
model.GlobalConfig.Store(cfg)
}()
err := redis.Get(ctx, "config", cfg) err := redis.Get(ctx, "config", cfg)
if err == nil { if err == nil {
return return
@@ -19,9 +22,4 @@ func UpdateConfig() {
if err != nil { if err != nil {
return return
} }
redis.SetEx(ctx, "config", cfg, time.Hour)
model.GlobalConfig.Store(cfg)
fmt.Println("--------------------------", *model.GlobalConfig.Load())
} }

206
backend/session/parser.go Normal file
View File

@@ -0,0 +1,206 @@
package session
import (
"bytes"
"fmt"
"strings"
"github.com/veops/go-ansiterm"
mysql "github.com/veops/oneterm/db"
"github.com/veops/oneterm/logger"
"github.com/veops/oneterm/model"
"go.uber.org/zap"
)
var (
enterMarks = [][]byte{
[]byte("\x1b[?1049h"),
[]byte("\x1b[?1048h"),
[]byte("\x1b[?1047h"),
[]byte("\x1b[?47h"),
[]byte("\x1b[?25l"),
}
exitMarks = [][]byte{
[]byte("\x1b[?1049l"),
[]byte("\x1b[?1048l"),
[]byte("\x1b[?1047l"),
[]byte("\x1b[?47l"),
[]byte("\x1b[?25h"),
}
screenMarks = [][]byte{
{0x1b, 0x5b, 0x4b, 0x0d, 0x0a},
{0x1b, 0x5b, 0x34, 0x6c},
}
)
func NewParser(sessionId string, w, h int) *Parser {
screen := ansiterm.NewScreen(w, h)
stream := ansiterm.InitByteStream(screen, false)
stream.Attach(screen)
p := &Parser{
OutputStream: stream,
isEdit: false,
first: true,
}
return p
}
type Parser struct {
OutputStream *ansiterm.ByteStream
Input []byte
Output []byte
SessionId string
Cmds []*model.Command
first bool
prompt string
isEdit bool
lastCmd string
lastRes string
}
func (p *Parser) AddInput(bs []byte) (cmd string, forbidden bool) {
if p.first {
p.GetOutput()
p.first = false
}
p.Input = append(p.Input, bs...)
if !bytes.HasSuffix(p.Input, []byte("\r")) {
return
}
cmd = p.GetCmd()
fmt.Println("----------------------cmd", cmd)
p.Reset()
filter := ""
if filter, forbidden = p.IsForbidden(cmd); forbidden {
cmd = filter
return
}
p.lastCmd = cmd
p.WriteDb()
return
}
func (p *Parser) IsForbidden(cmd string) (string, bool) {
for _, c := range p.Cmds {
if c.IsRe {
if c.Re.MatchString(cmd) {
return fmt.Sprintf("Regex: %s", c.Cmd), true
}
} else {
if strings.Contains(cmd, c.Cmd) {
return c.Cmd, true
}
}
}
return "", false
}
func (p *Parser) WriteDb() {
if p.lastCmd == "" {
return
}
m := &model.SessionCmd{
SessionId: p.SessionId,
Cmd: p.lastCmd,
Result: p.lastRes,
}
err := mysql.DB.Model(m).Create(m).Error
if err != nil {
logger.L().Error("write session cmd failed", zap.Error(err))
}
}
func (p *Parser) AddOutput(bs []byte) {
fmt.Println("-----------out", string(bs))
if !p.isEdit {
p.Output = append(p.Output, bs...)
end := bytes.LastIndex(p.Output, []byte("\r"))
if end < 0 {
return
}
begin := end - 1
for ; begin > 0; begin-- {
if p.Output[begin] == '\r' {
break
}
}
if begin+1 > end-1 {
return
}
p.prompt = string(p.Output[begin+1 : end-1])
}
}
func (p *Parser) GetCmd() string {
s := p.GetOutput()
// TODO: some promot may change with its dir
fmt.Println("============", s)
fmt.Println("============", p.prompt)
return strings.TrimPrefix(s, p.prompt)
}
func (p *Parser) Resize(w, h int) {
p.OutputStream.Listener.Resize(w, h)
}
func (p *Parser) Reset() {
p.OutputStream.Listener.Reset()
p.Output = nil
p.Input = nil
}
func (p *Parser) GetOutput() string {
p.OutputStream.Feed(p.Output)
res := parseOutput(p.OutputStream.Listener.Display())
if len(res) == 0 {
return ""
}
p.lastRes = res[len(res)-1]
return p.lastRes
}
func parseOutput(data []string) (output []string) {
for _, line := range data {
if strings.TrimSpace(line) != "" {
output = append(output, line)
}
}
return output
}
func (p *Parser) State(b []byte) bool {
if !p.isEdit && IsEditEnterMode(b) {
if !isNewScreen(b) {
p.isEdit = true
}
}
if p.isEdit && IsEditExitMode(b) {
p.Reset()
p.isEdit = false
}
return p.isEdit
}
func isNewScreen(p []byte) bool {
return matchMark(p, screenMarks)
}
func IsEditEnterMode(p []byte) bool {
return matchMark(p, enterMarks)
}
func IsEditExitMode(p []byte) bool {
return matchMark(p, exitMarks)
}
func matchMark(p []byte, marks [][]byte) bool {
for _, item := range marks {
if bytes.Contains(p, item) {
return true
}
}
return false
}

View File

@@ -120,6 +120,7 @@ type Session struct {
GuacdTunnel *guacd.Tunnel `json:"-" gorm:"-"` GuacdTunnel *guacd.Tunnel `json:"-" gorm:"-"`
IdleTk *time.Ticker `json:"-" gorm:"-"` IdleTk *time.Ticker `json:"-" gorm:"-"`
SshRecoder *Asciinema `json:"-" gorm:"-"` SshRecoder *Asciinema `json:"-" gorm:"-"`
SshParser *Parser `json:"-" gorm:"-"`
} }
func (m *Session) HasMonitors() (has bool) { func (m *Session) HasMonitors() (has bool) {

View File

@@ -9,12 +9,21 @@ import (
"github.com/veops/oneterm/conf" "github.com/veops/oneterm/conf"
) )
var (
key, iv []byte
)
func init() {
key = []byte(conf.Cfg.Auth.Aes.Key)
iv = []byte(conf.Cfg.Auth.Aes.Iv)
}
func EncryptAES(plainText string) string { func EncryptAES(plainText string) string {
block, _ := aes.NewCipher(conf.Cfg.Auth.Aes.Key) block, _ := aes.NewCipher(key)
bs := []byte(plainText) bs := []byte(plainText)
bs = paddingPKCS7(bs, aes.BlockSize) bs = paddingPKCS7(bs, aes.BlockSize)
mode := cipher.NewCBCEncrypter(block, conf.Cfg.Auth.Aes.Iv) mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(bs, bs) mode.CryptBlocks(bs, bs)
return base64.StdEncoding.EncodeToString(bs) return base64.StdEncoding.EncodeToString(bs)
@@ -22,12 +31,12 @@ func EncryptAES(plainText string) string {
func DecryptAES(cipherText string) string { func DecryptAES(cipherText string) string {
bs, _ := base64.StdEncoding.DecodeString(cipherText) bs, _ := base64.StdEncoding.DecodeString(cipherText)
block, err := aes.NewCipher(conf.Cfg.Auth.Aes.Key) block, err := aes.NewCipher(key)
if err != nil { if err != nil {
panic(err) panic(err)
} }
mode := cipher.NewCBCDecrypter(block, conf.Cfg.Auth.Aes.Iv) mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(bs, bs) mode.CryptBlocks(bs, bs)
return string(unPaddingPKCS7(bs)) return string(unPaddingPKCS7(bs))