mirror of
https://github.com/veops/oneterm.git
synced 2025-11-02 23:34:13 +08:00
feat(api): cmd filter and record
This commit is contained in:
@@ -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{
|
||||||
|
|||||||
@@ -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,15 +160,18 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func handleParentId(parentId int) (pids []int, err error) {
|
func handleParentId(parentId int) (pids []int, err error) {
|
||||||
nodes := make([]*model.NodeIdPid, 0)
|
nodes := make([]*model.NodeIdPid, 0)
|
||||||
|
|||||||
@@ -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{}).
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cnt > 0
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:]...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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=
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gorm.io/plugin/soft_delete"
|
"gorm.io/plugin/soft_delete"
|
||||||
@@ -9,8 +10,10 @@ import (
|
|||||||
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"`
|
||||||
|
IsRe bool `json:"is_re" gorm:"column:is_re"`
|
||||||
Enable bool `json:"enable" gorm:"column:enable"`
|
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"`
|
||||||
|
|||||||
@@ -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
206
backend/session/parser.go
Normal 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
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user