diff --git a/main.go b/main.go index fcfa9b9..939dd7a 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "io" "next-terminal/pkg/api" "next-terminal/pkg/config" + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/handle" "next-terminal/pkg/model" @@ -132,7 +133,7 @@ func Run() error { Username: "admin", Password: string(pass), Nickname: "超级管理员", - Type: model.TypeAdmin, + Type: constant.TypeAdmin, Created: utils.NowJsonTime(), } if err := model.CreateNewUser(&user); err != nil { @@ -144,7 +145,7 @@ func Run() error { // 修正默认用户类型为管理员 if users[i].Type == "" { user := model.User{ - Type: model.TypeAdmin, + Type: constant.TypeAdmin, } model.UpdateUserById(&user, users[i].ID) logrus.Infof("自动修正用户「%v」ID「%v」类型为管理员", users[i].Nickname, users[i].ID) @@ -191,6 +192,12 @@ func Run() error { if err := global.DB.AutoMigrate(&model.JobLog{}); err != nil { return err } + if err := global.DB.AutoMigrate(&model.AccessSecurity{}); err != nil { + return err + } + if err := api.ReloadAccessSecurity(); err != nil { + return err + } if len(model.FindAllTemp()) == 0 { for i := 0; i <= 30; i++ { @@ -216,7 +223,7 @@ func Run() error { global.Cron = cron.New(cron.WithSeconds()) //精确到秒 global.Cron.Start() - jobs, err := model.FindJobByFunc(model.FuncCheckAssetStatusJob) + jobs, err := model.FindJobByFunc(constant.FuncCheckAssetStatusJob) if err != nil { return err } @@ -224,10 +231,10 @@ func Run() error { job := model.Job{ ID: utils.UUID(), Name: "资产状态检测", - Func: model.FuncCheckAssetStatusJob, + Func: constant.FuncCheckAssetStatusJob, Cron: "0 0 0/1 * * ?", - Mode: model.JobModeAll, - Status: model.JobStatusRunning, + Mode: constant.JobModeAll, + Status: constant.JobStatusRunning, Created: utils.NowJsonTime(), Updated: utils.NowJsonTime(), } @@ -237,8 +244,8 @@ func Run() error { logrus.Debugf("创建计划任务「%v」cron「%v」", job.Name, job.Cron) } else { for i := range jobs { - if jobs[i].Status == model.JobStatusRunning { - err := model.ChangeJobStatusById(jobs[i].ID, model.JobStatusRunning) + if jobs[i].Status == constant.JobStatusRunning { + err := model.ChangeJobStatusById(jobs[i].ID, constant.JobStatusRunning) if err != nil { return err } diff --git a/pkg/api/asset.go b/pkg/api/asset.go index 4e2f30d..0c8b77b 100644 --- a/pkg/api/asset.go +++ b/pkg/api/asset.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "github.com/labstack/echo/v4" + "next-terminal/pkg/constant" "next-terminal/pkg/model" "next-terminal/pkg/utils" "strconv" @@ -85,7 +86,7 @@ func AssetImportEndpoint(c echo.Context) error { Protocol: record[1], IP: record[2], Port: port, - AccountType: model.Custom, + AccountType: constant.Custom, Username: record[4], Password: record[5], PrivateKey: record[6], diff --git a/pkg/api/credential.go b/pkg/api/credential.go index aa2848e..f2ee42b 100644 --- a/pkg/api/credential.go +++ b/pkg/api/credential.go @@ -3,6 +3,7 @@ package api import ( "errors" "github.com/labstack/echo/v4" + "next-terminal/pkg/constant" "next-terminal/pkg/model" "next-terminal/pkg/utils" "strconv" @@ -26,7 +27,7 @@ func CredentialCreateEndpoint(c echo.Context) error { item.Created = utils.NowJsonTime() switch item.Type { - case model.Custom: + case constant.Custom: item.PrivateKey = "-" item.Passphrase = "-" if len(item.Username) == 0 { @@ -35,7 +36,7 @@ func CredentialCreateEndpoint(c echo.Context) error { if len(item.Password) == 0 { item.Password = "-" } - case model.PrivateKey: + case constant.PrivateKey: item.Password = "-" if len(item.Username) == 0 { item.Username = "-" @@ -90,7 +91,7 @@ func CredentialUpdateEndpoint(c echo.Context) error { } switch item.Type { - case model.Custom: + case constant.Custom: item.PrivateKey = "-" item.Passphrase = "-" if len(item.Username) == 0 { @@ -99,7 +100,7 @@ func CredentialUpdateEndpoint(c echo.Context) error { if len(item.Password) == 0 { item.Password = "-" } - case model.PrivateKey: + case constant.PrivateKey: item.Password = "-" if len(item.Username) == 0 { item.Username = "-" diff --git a/pkg/api/middleware.go b/pkg/api/middleware.go index acb8173..55d4635 100644 --- a/pkg/api/middleware.go +++ b/pkg/api/middleware.go @@ -3,8 +3,10 @@ package api import ( "fmt" "github.com/labstack/echo/v4" + "net" + "next-terminal/pkg/constant" "next-terminal/pkg/global" - "next-terminal/pkg/model" + "next-terminal/pkg/utils" "regexp" "strings" "time" @@ -26,6 +28,62 @@ func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc { } } +func TcpWall(next echo.HandlerFunc) echo.HandlerFunc { + + return func(c echo.Context) error { + + if global.Securities == nil { + return next(c) + } + + ip := c.RealIP() + for i := 0; i < len(global.Securities); i++ { + security := global.Securities[i] + + if strings.Contains(security.IP, "/") { + // CIDR + _, ipNet, err := net.ParseCIDR(security.IP) + if err != nil { + continue + } + if !ipNet.Contains(net.ParseIP(ip)) { + continue + } + } else if strings.Contains(security.IP, "-") { + // 范围段 + split := strings.Split(security.IP, "-") + if len(split) < 2 { + continue + } + start := split[0] + end := split[1] + intReqIP := utils.IpToInt(ip) + if intReqIP < utils.IpToInt(start) || intReqIP > utils.IpToInt(end) { + continue + } + } else { + // IP + if security.IP != ip { + continue + } + } + + if security.Rule == constant.AccessRuleAllow { + return next(c) + } + if security.Rule == constant.AccessRuleReject { + if c.Request().Header.Get("X-Requested-With") != "" || c.Request().Header.Get(Token) != "" { + return Fail(c, 0, "您的访问请求被拒绝 :(") + } else { + return c.HTML(666, "您的访问请求被拒绝 :(") + } + } + } + + return next(c) + } +} + func Auth(next echo.HandlerFunc) echo.HandlerFunc { startWithUrls := []string{"/login", "/static", "/favicon.ico", "/logo.svg", "/asciinema"} @@ -80,7 +138,7 @@ func Admin(next echo.HandlerFunc) echo.HandlerFunc { return Fail(c, 401, "您的登录信息已失效,请重新登录后再试。") } - if account.Type != model.TypeAdmin { + if account.Type != constant.TypeAdmin { return Fail(c, 403, "permission denied") } diff --git a/pkg/api/overview.go b/pkg/api/overview.go index cacac77..174a6a7 100644 --- a/pkg/api/overview.go +++ b/pkg/api/overview.go @@ -2,6 +2,7 @@ package api import ( "github.com/labstack/echo/v4" + "next-terminal/pkg/constant" "next-terminal/pkg/model" ) @@ -21,7 +22,7 @@ func OverviewCounterEndPoint(c echo.Context) error { credential int64 asset int64 ) - if model.TypeUser == account.Type { + if constant.TypeUser == account.Type { countUser, _ = model.CountOnlineUser() countOnlineSession, _ = model.CountOnlineSession() credential, _ = model.CountCredentialByUserId(account.ID) diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 2805deb..b81396f 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -2,6 +2,7 @@ package api import ( "net/http" + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/log" "next-terminal/pkg/model" @@ -34,6 +35,7 @@ func SetupRoutes() *echo.Echo { AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete}, })) e.Use(ErrorHandler) + e.Use(TcpWall) e.Use(Auth) e.POST("/login", LoginEndpoint) @@ -121,7 +123,7 @@ func SetupRoutes() *echo.Echo { sessions.POST("/:id/mkdir", SessionMkDirEndpoint) sessions.POST("/:id/rm", SessionRmEndpoint) sessions.POST("/:id/rename", SessionRenameEndpoint) - sessions.DELETE("/:id", SessionDeleteEndpoint) + sessions.DELETE("/:id", Admin(SessionDeleteEndpoint)) sessions.GET("/:id/recording", SessionRecordingEndpoint) } @@ -158,6 +160,15 @@ func SetupRoutes() *echo.Echo { jobs.DELETE("/:id/logs", JobDeleteLogsEndpoint) } + securities := e.Group("/securities", Admin) + { + securities.POST("", SecurityCreateEndpoint) + securities.GET("/paging", SecurityPagingEndpoint) + securities.PUT("/:id", SecurityUpdateEndpoint) + securities.DELETE("/:id", SecurityDeleteEndpoint) + securities.GET("/:id", SecurityGetEndpoint) + } + return e } @@ -218,7 +229,7 @@ func HasPermission(c echo.Context, owner string) bool { return false } // 检测是否为管理人员 - if model.TypeAdmin == account.Type { + if constant.TypeAdmin == account.Type { return true } // 检测是否为所有者 diff --git a/pkg/api/security.go b/pkg/api/security.go new file mode 100644 index 0000000..23dccfc --- /dev/null +++ b/pkg/api/security.go @@ -0,0 +1,114 @@ +package api + +import ( + "github.com/labstack/echo/v4" + "next-terminal/pkg/global" + "next-terminal/pkg/model" + "next-terminal/pkg/utils" + "strconv" + "strings" +) + +func SecurityCreateEndpoint(c echo.Context) error { + var item model.AccessSecurity + if err := c.Bind(&item); err != nil { + return err + } + + item.ID = utils.UUID() + item.Source = "管理员添加" + + if err := model.CreateNewSecurity(&item); err != nil { + return err + } + // 更新内存中的安全规则 + if err := ReloadAccessSecurity(); err != nil { + return err + } + return Success(c, "") +} + +func ReloadAccessSecurity() error { + rules, err := model.FindAllAccessSecurities() + if err != nil { + return err + } + if rules != nil && len(rules) > 0 { + var securities []*global.Security + for i := 0; i < len(rules); i++ { + rule := global.Security{ + IP: rules[i].IP, + Rule: rules[i].Rule, + } + securities = append(securities, &rule) + } + global.Securities = securities + } + return nil +} + +func SecurityPagingEndpoint(c echo.Context) error { + pageIndex, _ := strconv.Atoi(c.QueryParam("pageIndex")) + pageSize, _ := strconv.Atoi(c.QueryParam("pageSize")) + ip := c.QueryParam("ip") + rule := c.QueryParam("rule") + + order := c.QueryParam("order") + field := c.QueryParam("field") + + items, total, err := model.FindPageSecurity(pageIndex, pageSize, ip, rule, order, field) + if err != nil { + return err + } + + return Success(c, H{ + "total": total, + "items": items, + }) +} + +func SecurityUpdateEndpoint(c echo.Context) error { + id := c.Param("id") + + var item model.AccessSecurity + if err := c.Bind(&item); err != nil { + return err + } + + if err := model.UpdateSecurityById(&item, id); err != nil { + return err + } + // 更新内存中的安全规则 + if err := ReloadAccessSecurity(); err != nil { + return err + } + return Success(c, nil) +} + +func SecurityDeleteEndpoint(c echo.Context) error { + ids := c.Param("id") + + split := strings.Split(ids, ",") + for i := range split { + jobId := split[i] + if err := model.DeleteSecurityById(jobId); err != nil { + return err + } + } + // 更新内存中的安全规则 + if err := ReloadAccessSecurity(); err != nil { + return err + } + return Success(c, nil) +} + +func SecurityGetEndpoint(c echo.Context) error { + id := c.Param("id") + + item, err := model.FindSecurityById(id) + if err != nil { + return err + } + + return Success(c, item) +} diff --git a/pkg/api/session.go b/pkg/api/session.go index 89d857a..dad4282 100644 --- a/pkg/api/session.go +++ b/pkg/api/session.go @@ -10,6 +10,7 @@ import ( "io" "io/ioutil" "net/http" + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/model" "next-terminal/pkg/utils" @@ -36,10 +37,10 @@ func SessionPagingEndpoint(c echo.Context) error { } for i := 0; i < len(items); i++ { - if status == model.Disconnected && len(items[i].Recording) > 0 { + if status == constant.Disconnected && len(items[i].Recording) > 0 { var recording string - if items[i].Mode == model.Naive { + if items[i].Mode == constant.Naive { recording = items[i].Recording } else { recording = items[i].Recording + "/recording" @@ -77,7 +78,7 @@ func SessionConnectEndpoint(c echo.Context) error { session := model.Session{} session.ID = sessionId - session.Status = model.Connected + session.Status = constant.Connected session.ConnectedTime = utils.NowJsonTime() if err := model.UpdateSessionById(&session, sessionId); err != nil { @@ -118,11 +119,11 @@ func CloseSessionById(sessionId string, code int, reason string) { return } - if s.Status == model.Disconnected { + if s.Status == constant.Disconnected { return } - if s.Status == model.Connecting { + if s.Status == constant.Connecting { // 会话还未建立成功,无需保留数据 _ = model.DeleteSessionById(sessionId) return @@ -130,7 +131,7 @@ func CloseSessionById(sessionId string, code int, reason string) { session := model.Session{} session.ID = sessionId - session.Status = model.Disconnected + session.Status = constant.Disconnected session.DisconnectedTime = utils.NowJsonTime() session.Code = code session.Message = reason @@ -161,15 +162,15 @@ func SessionCreateEndpoint(c echo.Context) error { assetId := c.QueryParam("assetId") mode := c.QueryParam("mode") - if mode == model.Naive { - mode = model.Naive + if mode == constant.Naive { + mode = constant.Naive } else { - mode = model.Guacd + mode = constant.Guacd } user, _ := GetCurrentAccount(c) - if model.TypeUser == user.Type { + if constant.TypeUser == user.Type { // 检测是否有访问权限 assetIds, err := model.FindAssetIdsByUserId(user.ID) if err != nil { @@ -196,7 +197,7 @@ func SessionCreateEndpoint(c echo.Context) error { Protocol: asset.Protocol, IP: asset.IP, Port: asset.Port, - Status: model.NoConnect, + Status: constant.NoConnect, Creator: user.ID, ClientIP: c.RealIP(), Mode: mode, @@ -208,7 +209,7 @@ func SessionCreateEndpoint(c echo.Context) error { return err } - if credential.Type == model.Custom { + if credential.Type == constant.Custom { session.Username = credential.Username session.Password = credential.Password } else { @@ -273,7 +274,7 @@ func SessionUploadEndpoint(c echo.Context) error { } else if "rdp" == session.Protocol { if strings.Contains(remoteFile, "../") { - logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP()) + SafetyRuleTrigger(c) return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。") } @@ -331,7 +332,7 @@ func SessionDownloadEndpoint(c echo.Context) error { return c.Stream(http.StatusOK, echo.MIMEOctetStream, bytes.NewReader(buff.Bytes())) } else if "rdp" == session.Protocol { if strings.Contains(remoteFile, "../") { - logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP()) + SafetyRuleTrigger(c) return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。") } drivePath, err := model.GetDrivePath() @@ -413,7 +414,7 @@ func SessionLsEndpoint(c echo.Context) error { return Success(c, files) } else if "rdp" == session.Protocol { if strings.Contains(remoteDir, "../") { - logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP()) + SafetyRuleTrigger(c) return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。") } drivePath, err := model.GetDrivePath() @@ -446,6 +447,18 @@ func SessionLsEndpoint(c echo.Context) error { return errors.New("当前协议不支持此操作") } +func SafetyRuleTrigger(c echo.Context) { + logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP()) + security := model.AccessSecurity{ + ID: utils.UUID(), + Source: "安全规则触发", + IP: c.RealIP(), + Rule: constant.AccessRuleReject, + } + + _ = model.CreateNewSecurity(&security) +} + func SessionMkDirEndpoint(c echo.Context) error { sessionId := c.Param("id") session, err := model.FindSessionById(sessionId) @@ -464,7 +477,7 @@ func SessionMkDirEndpoint(c echo.Context) error { return Success(c, nil) } else if "rdp" == session.Protocol { if strings.Contains(remoteDir, "../") { - logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP()) + SafetyRuleTrigger(c) return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。") } drivePath, err := model.GetDrivePath() @@ -525,7 +538,7 @@ func SessionRmEndpoint(c echo.Context) error { return Success(c, nil) } else if "rdp" == session.Protocol { if strings.Contains(key, "../") { - logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP()) + SafetyRuleTrigger(c) return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。") } drivePath, err := model.GetDrivePath() @@ -566,7 +579,7 @@ func SessionRenameEndpoint(c echo.Context) error { return Success(c, nil) } else if "rdp" == session.Protocol { if strings.Contains(oldName, "../") { - logrus.Warnf("IP %v 尝试进行攻击,请ban掉此IP", c.RealIP()) + SafetyRuleTrigger(c) return Fail(c, -1, ":) 您的IP已被记录,请去向管理员自首。") } drivePath, err := model.GetDrivePath() @@ -591,7 +604,7 @@ func SessionRecordingEndpoint(c echo.Context) error { } var recording string - if session.Mode == model.Naive { + if session.Mode == constant.Naive { recording = session.Recording } else { recording = session.Recording + "/recording" diff --git a/pkg/api/ssh.go b/pkg/api/ssh.go index 5621233..f7641e1 100644 --- a/pkg/api/ssh.go +++ b/pkg/api/ssh.go @@ -6,6 +6,7 @@ import ( "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" "net/http" + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/guacd" "next-terminal/pkg/model" @@ -62,7 +63,7 @@ func SSHEndpoint(c echo.Context) (err error) { } user, _ := GetCurrentAccount(c) - if model.TypeUser == user.Type { + if constant.TypeUser == user.Type { // 检测是否有访问权限 assetIds, err := model.FindAssetIdsByUserId(user.ID) if err != nil { @@ -137,7 +138,7 @@ func SSHEndpoint(c echo.Context) (err error) { ConnectionId: sessionId, Width: cols, Height: rows, - Status: model.Connecting, + Status: constant.Connecting, Recording: recording, } // 创建新会话 diff --git a/pkg/api/tunnel.go b/pkg/api/tunnel.go index 500141a..ea8788c 100644 --- a/pkg/api/tunnel.go +++ b/pkg/api/tunnel.go @@ -5,6 +5,7 @@ import ( "github.com/gorilla/websocket" "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/guacd" "next-terminal/pkg/model" @@ -49,7 +50,7 @@ func TunEndpoint(c echo.Context) error { logrus.Warnf("会话不存在") return err } - if session.Status != model.Connected { + if session.Status != constant.Connected { logrus.Warnf("会话未在线") return errors.New("会话未在线") } @@ -188,7 +189,7 @@ func TunEndpoint(c echo.Context) error { ConnectionId: tunnel.UUID, Width: intWidth, Height: intHeight, - Status: model.Connecting, + Status: constant.Connecting, Recording: configuration.GetParameter(guacd.RecordingPath), } // 创建新会话 diff --git a/pkg/constant/const.go b/pkg/constant/const.go new file mode 100644 index 0000000..cdba074 --- /dev/null +++ b/pkg/constant/const.go @@ -0,0 +1,33 @@ +package constant + +const ( + AccessRuleAllow = "allow" // 允许访问 + AccessRuleReject = "reject" // 拒绝访问 + + Custom = "custom" // 密码 + PrivateKey = "private-key" // 密钥 + + JobStatusRunning = "running" // 计划任务运行状态 + JobStatusNotRunning = "not-running" // 计划任务未运行状态 + FuncCheckAssetStatusJob = "check-asset-status-job" // 检测资产是否在线 + FuncShellJob = "shell-job" // 执行Shell脚本 + JobModeAll = "all" // 全部资产 + JobModeCustom = "custom" // 自定义选择资产 + + SshMode = "ssh-mode" // ssh模式 + MailHost = "mail-host" // 邮件服务器地址 + MailPort = "mail-port" // 邮件服务器端口 + MailUsername = "mail-username" // 邮件服务账号 + MailPassword = "mail-password" // 邮件服务密码 + + NoConnect = "no_connect" // 会话状态:未连接 + Connecting = "connecting" // 会话状态:连接中 + Connected = "connected" // 会话状态:已连接 + Disconnected = "disconnected" // 会话状态:已断开连接 + + Guacd = "guacd" // 接入模式:guacd + Naive = "naive" // 接入模式:原生 + + TypeUser = "user" // 普通用户 + TypeAdmin = "admin" // 管理员 +) diff --git a/pkg/global/global.go b/pkg/global/global.go index 78d0202..95d9ee0 100644 --- a/pkg/global/global.go +++ b/pkg/global/global.go @@ -16,3 +16,10 @@ var Config *config.Config var Store *TunStore var Cron *cron.Cron + +type Security struct { + Rule string + IP string +} + +var Securities []*Security diff --git a/pkg/guacd/guacd.go b/pkg/guacd/guacd.go index a58b01f..ee3b1e6 100644 --- a/pkg/guacd/guacd.go +++ b/pkg/guacd/guacd.go @@ -34,6 +34,7 @@ const ( DisableOffscreenCaching = "disable-offscreen-caching" DisableGlyphCaching = "disable-glyph-caching" + Domain = "domain" RemoteApp = "remote-app" RemoteAppDir = "remote-app-dir" RemoteAppArgs = "remote-app-args" diff --git a/pkg/handle/runner.go b/pkg/handle/runner.go index 11f2429..f9ae0c9 100644 --- a/pkg/handle/runner.go +++ b/pkg/handle/runner.go @@ -2,6 +2,7 @@ package handle import ( "github.com/sirupsen/logrus" + "next-terminal/pkg/constant" "next-terminal/pkg/guacd" "next-terminal/pkg/model" "next-terminal/pkg/utils" @@ -16,7 +17,7 @@ func RunTicker() { unUsedSessionTicker := time.NewTicker(time.Minute * 60) go func() { for range unUsedSessionTicker.C { - sessions, _ := model.FindSessionByStatusIn([]string{model.NoConnect, model.Connecting}) + sessions, _ := model.FindSessionByStatusIn([]string{constant.NoConnect, constant.Connecting}) if sessions != nil && len(sessions) > 0 { now := time.Now() for i := range sessions { @@ -65,14 +66,14 @@ func RunTicker() { } func RunDataFix() { - sessions, _ := model.FindSessionByStatus(model.Connected) + sessions, _ := model.FindSessionByStatus(constant.Connected) if sessions == nil { return } for i := range sessions { session := model.Session{ - Status: model.Disconnected, + Status: constant.Disconnected, DisconnectedTime: utils.NowJsonTime(), } diff --git a/pkg/model/access_security.go b/pkg/model/access_security.go new file mode 100644 index 0000000..1453bc6 --- /dev/null +++ b/pkg/model/access_security.go @@ -0,0 +1,83 @@ +package model + +import ( + "next-terminal/pkg/global" +) + +type AccessSecurity struct { + ID string `json:"id"` + Rule string `json:"rule"` + IP string `json:"ip"` + Source string `json:"source"` + Priority int64 `json:"priority"` // 越小优先级越高 +} + +func (r *AccessSecurity) TableName() string { + return "access_securities" +} + +func FindAllAccessSecurities() (o []AccessSecurity, err error) { + db := global.DB + err = db.Order("priority asc").Find(&o).Error + return +} + +func FindPageSecurity(pageIndex, pageSize int, ip, rule, order, field string) (o []AccessSecurity, total int64, err error) { + t := AccessSecurity{} + db := global.DB.Table(t.TableName()) + dbCounter := global.DB.Table(t.TableName()) + + if len(ip) > 0 { + db = db.Where("ip like ?", "%"+ip+"%") + dbCounter = dbCounter.Where("ip like ?", "%"+ip+"%") + } + + if len(rule) > 0 { + db = db.Where("rule = ?", rule) + dbCounter = dbCounter.Where("rule = ?", rule) + } + + err = dbCounter.Count(&total).Error + if err != nil { + return nil, 0, err + } + + if order == "descend" { + order = "desc" + } else { + order = "asc" + } + + if field == "ip" { + field = "ip" + } else if field == "rule" { + field = "rule" + } else { + field = "priority" + } + + err = db.Order(field + " " + order).Find(&o).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Error + if o == nil { + o = make([]AccessSecurity, 0) + } + return +} + +func CreateNewSecurity(o *AccessSecurity) error { + return global.DB.Create(o).Error +} + +func UpdateSecurityById(o *AccessSecurity, id string) error { + o.ID = id + return global.DB.Updates(o).Error +} + +func DeleteSecurityById(id string) error { + + return global.DB.Where("id = ?", id).Delete(AccessSecurity{}).Error +} + +func FindSecurityById(id string) (o *AccessSecurity, err error) { + err = global.DB.Where("id = ?", id).First(&o).Error + return +} diff --git a/pkg/model/asset.go b/pkg/model/asset.go index 9e6fe52..a119510 100644 --- a/pkg/model/asset.go +++ b/pkg/model/asset.go @@ -1,6 +1,7 @@ package model import ( + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/utils" "strings" @@ -66,7 +67,7 @@ func FindAssetByProtocolAndIds(protocol string, assetIds []string) (o []Asset, e func FindAssetByConditions(protocol string, account User) (o []Asset, err error) { db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id") - if TypeUser == account.Type { + if constant.TypeUser == account.Type { owner := account.ID db = db.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner) } @@ -82,7 +83,7 @@ func FindPageAsset(pageIndex, pageSize int, name, protocol, tags string, account db := global.DB.Table("assets").Select("assets.id,assets.name,assets.ip,assets.port,assets.protocol,assets.active,assets.owner,assets.created,assets.tags, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on assets.owner = users.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id") dbCounter := global.DB.Table("assets").Select("DISTINCT assets.id").Joins("left join resource_sharers on assets.id = resource_sharers.resource_id").Group("assets.id") - if TypeUser == account.Type { + if constant.TypeUser == account.Type { owner := account.ID db = db.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner) dbCounter = dbCounter.Where("assets.owner = ? or resource_sharers.user_id = ?", owner, owner) diff --git a/pkg/model/asset-attribute.go b/pkg/model/asset_attribute.go similarity index 94% rename from pkg/model/asset-attribute.go rename to pkg/model/asset_attribute.go index 5b58f29..de65465 100644 --- a/pkg/model/asset-attribute.go +++ b/pkg/model/asset_attribute.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/labstack/echo/v4" "gorm.io/gorm" + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/guacd" "next-terminal/pkg/utils" @@ -20,8 +21,8 @@ func (r *AssetAttribute) TableName() string { return "asset_attributes" } -var SSHParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, SshMode} -var RDPParameterNames = []string{guacd.RemoteApp, guacd.RemoteAppDir, guacd.RemoteAppArgs} +var SSHParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, constant.SshMode} +var RDPParameterNames = []string{guacd.Domain, guacd.RemoteApp, guacd.RemoteAppDir, guacd.RemoteAppArgs} var VNCParameterNames = []string{guacd.ColorDepth, guacd.Cursor, guacd.SwapRedBlue, guacd.DestHost, guacd.DestPort} var TelnetParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.UsernameRegex, guacd.PasswordRegex, guacd.LoginSuccessRegex, guacd.LoginFailureRegex} var KubernetesParameterNames = []string{guacd.FontName, guacd.FontSize, guacd.ColorScheme, guacd.Backspace, guacd.TerminalType, guacd.Namespace, guacd.Pod, guacd.Container, guacd.UesSSL, guacd.ClientCert, guacd.ClientKey, guacd.CaCert, guacd.IgnoreCert} diff --git a/pkg/model/command.go b/pkg/model/command.go index 1100ee5..be2390b 100644 --- a/pkg/model/command.go +++ b/pkg/model/command.go @@ -1,6 +1,7 @@ package model import ( + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/utils" ) @@ -32,7 +33,7 @@ func FindPageCommand(pageIndex, pageSize int, name, content, order, field string db := global.DB.Table("commands").Select("commands.id,commands.name,commands.content,commands.owner,commands.created, users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on commands.owner = users.id").Joins("left join resource_sharers on commands.id = resource_sharers.resource_id").Group("commands.id") dbCounter := global.DB.Table("commands").Select("DISTINCT commands.id").Joins("left join resource_sharers on commands.id = resource_sharers.resource_id").Group("commands.id") - if TypeUser == account.Type { + if constant.TypeUser == account.Type { owner := account.ID db = db.Where("commands.owner = ? or resource_sharers.user_id = ?", owner, owner) dbCounter = dbCounter.Where("commands.owner = ? or resource_sharers.user_id = ?", owner, owner) diff --git a/pkg/model/credential.go b/pkg/model/credential.go index 89eb895..c333cd7 100644 --- a/pkg/model/credential.go +++ b/pkg/model/credential.go @@ -1,16 +1,11 @@ package model import ( + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/utils" ) -// 密码 -const Custom = "custom" - -// 密钥 -const PrivateKey = "private-key" - type Credential struct { ID string `gorm:"primary_key" json:"id"` Name string `json:"name"` @@ -45,7 +40,7 @@ type CredentialSimpleVo struct { func FindAllCredential(account User) (o []CredentialSimpleVo, err error) { db := global.DB.Table("credentials").Select("DISTINCT credentials.id,credentials.name").Joins("left join resource_sharers on credentials.id = resource_sharers.resource_id") - if account.Type == TypeUser { + if account.Type == constant.TypeUser { db = db.Where("credentials.owner = ? or resource_sharers.user_id = ?", account.ID, account.ID) } err = db.Find(&o).Error @@ -56,7 +51,7 @@ func FindPageCredential(pageIndex, pageSize int, name, order, field string, acco db := global.DB.Table("credentials").Select("credentials.id,credentials.name,credentials.type,credentials.username,credentials.owner,credentials.created,users.nickname as owner_name,COUNT(resource_sharers.user_id) as sharer_count").Joins("left join users on credentials.owner = users.id").Joins("left join resource_sharers on credentials.id = resource_sharers.resource_id").Group("credentials.id") dbCounter := global.DB.Table("credentials").Select("DISTINCT credentials.id").Joins("left join resource_sharers on credentials.id = resource_sharers.resource_id").Group("credentials.id") - if TypeUser == account.Type { + if constant.TypeUser == account.Type { owner := account.ID db = db.Where("credentials.owner = ? or resource_sharers.user_id = ?", owner, owner) dbCounter = dbCounter.Where("credentials.owner = ? or resource_sharers.user_id = ?", owner, owner) diff --git a/pkg/model/job.go b/pkg/model/job.go index 7647a2a..7f76888 100644 --- a/pkg/model/job.go +++ b/pkg/model/job.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/robfig/cron/v3" "github.com/sirupsen/logrus" + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/term" "next-terminal/pkg/utils" @@ -13,17 +14,6 @@ import ( "time" ) -const ( - JobStatusRunning = "running" - JobStatusNotRunning = "not-running" - - FuncCheckAssetStatusJob = "check-asset-status-job" - FuncShellJob = "shell-job" - - JobModeAll = "all" - JobModeCustom = "custom" -) - type Job struct { ID string `gorm:"primary_key" json:"id"` CronJobId int `json:"cronJobId"` @@ -91,7 +81,7 @@ func FindJobByFunc(function string) (o []Job, err error) { func CreateNewJob(o *Job) (err error) { - if o.Status == JobStatusRunning { + if o.Status == constant.JobStatusRunning { j, err := getJob(o) if err != nil { return err @@ -107,11 +97,12 @@ func CreateNewJob(o *Job) (err error) { } func UpdateJobById(o *Job, id string) (err error) { - if o.Status == JobStatusRunning { + if o.Status == constant.JobStatusRunning { return errors.New("请先停止定时任务后再修改") } - return global.DB.Where("id = ?", id).Updates(o).Error + o.ID = id + return global.DB.Updates(o).Error } func UpdateJonUpdatedById(id string) (err error) { @@ -125,7 +116,7 @@ func ChangeJobStatusById(id, status string) (err error) { if err != nil { return err } - if status == JobStatusRunning { + if status == constant.JobStatusRunning { j, err := getJob(&job) if err != nil { return err @@ -136,11 +127,11 @@ func ChangeJobStatusById(id, status string) (err error) { } logrus.Debugf("开启计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries())) - return global.DB.Updates(Job{ID: id, Status: JobStatusRunning, CronJobId: int(entryID)}).Error + return global.DB.Updates(Job{ID: id, Status: constant.JobStatusRunning, CronJobId: int(entryID)}).Error } else { global.Cron.Remove(cron.EntryID(job.CronJobId)) logrus.Debugf("关闭计划任务「%v」,运行中计划任务数量「%v」", job.Name, len(global.Cron.Entries())) - return global.DB.Updates(Job{ID: id, Status: JobStatusNotRunning}).Error + return global.DB.Updates(Job{ID: id, Status: constant.JobStatusNotRunning}).Error } } @@ -167,8 +158,8 @@ func DeleteJobById(id string) error { if err != nil { return err } - if job.Status == JobStatusRunning { - if err := ChangeJobStatusById(id, JobStatusNotRunning); err != nil { + if job.Status == constant.JobStatusRunning { + if err := ChangeJobStatusById(id, constant.JobStatusNotRunning); err != nil { return err } } @@ -177,9 +168,9 @@ func DeleteJobById(id string) error { func getJob(j *Job) (job cron.Job, err error) { switch j.Func { - case FuncCheckAssetStatusJob: + case constant.FuncCheckAssetStatusJob: job = CheckAssetStatusJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata} - case FuncShellJob: + case constant.FuncShellJob: job = ShellJob{ID: j.ID, Mode: j.Mode, ResourceIds: j.ResourceIds, Metadata: j.Metadata} default: return nil, errors.New("未识别的任务") @@ -200,7 +191,7 @@ func (r CheckAssetStatusJob) Run() { } var assets []Asset - if r.Mode == JobModeAll { + if r.Mode == constant.JobModeAll { assets, _ = FindAllAsset() } else { assets, _ = FindAssetByIds(strings.Split(r.ResourceIds, ",")) @@ -258,7 +249,7 @@ func (r ShellJob) Run() { } var assets []Asset - if r.Mode == JobModeAll { + if r.Mode == constant.JobModeAll { assets, _ = FindAssetByProtocol("ssh") } else { assets, _ = FindAssetByProtocolAndIds("ssh", strings.Split(r.ResourceIds, ",")) @@ -299,7 +290,7 @@ func (r ShellJob) Run() { return } - if credential.Type == Custom { + if credential.Type == constant.Custom { username = credential.Username password = credential.Password } else { diff --git a/pkg/model/login-log.go b/pkg/model/login_log.go similarity index 100% rename from pkg/model/login-log.go rename to pkg/model/login_log.go diff --git a/pkg/model/property.go b/pkg/model/property.go index f5a8294..00febff 100644 --- a/pkg/model/property.go +++ b/pkg/model/property.go @@ -4,18 +4,11 @@ import ( "github.com/jordan-wright/email" "github.com/sirupsen/logrus" "net/smtp" + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/guacd" ) -const ( - SshMode = "ssh-mode" - MailHost = "mail-host" - MailPort = "mail-port" - MailUsername = "mail-username" - MailPassword = "mail-password" -) - type Property struct { Name string `gorm:"primary_key" json:"name"` Value string `json:"value"` @@ -74,10 +67,10 @@ func GetRecordingPath() (string, error) { func SendMail(to, subject, text string) { propertiesMap := FindAllPropertiesMap() - host := propertiesMap[MailHost] - port := propertiesMap[MailPort] - username := propertiesMap[MailUsername] - password := propertiesMap[MailPassword] + host := propertiesMap[constant.MailHost] + port := propertiesMap[constant.MailPort] + username := propertiesMap[constant.MailUsername] + password := propertiesMap[constant.MailPassword] if host == "" || port == "" || username == "" || password == "" { logrus.Debugf("邮箱信息不完整,跳过发送邮件。") diff --git a/pkg/model/session.go b/pkg/model/session.go index b0cbf08..cd7b296 100644 --- a/pkg/model/session.go +++ b/pkg/model/session.go @@ -1,6 +1,7 @@ package model import ( + "next-terminal/pkg/constant" "next-terminal/pkg/global" "next-terminal/pkg/utils" "os" @@ -8,18 +9,6 @@ import ( "time" ) -const ( - NoConnect = "no_connect" - Connecting = "connecting" - Connected = "connected" - Disconnected = "disconnected" -) - -const ( - Guacd = "guacd" - Naive = "naive" -) - type Session struct { ID string `gorm:"primary_key" json:"id"` Protocol string `json:"protocol"` @@ -130,7 +119,7 @@ func FindSessionByStatusIn(statuses []string) (o []Session, err error) { func FindOutTimeSessions(dayLimit int) (o []Session, err error) { limitTime := time.Now().Add(time.Duration(-dayLimit*24) * time.Hour) - err = global.DB.Where("status = ? and connected_time < ?", Disconnected, limitTime).Find(&o).Error + err = global.DB.Where("status = ? and connected_time < ?", constant.Disconnected, limitTime).Find(&o).Error return } @@ -187,7 +176,7 @@ func DeleteSessionByStatus(status string) { } func CountOnlineSession() (total int64, err error) { - err = global.DB.Where("status = ?", Connected).Find(&Session{}).Count(&total).Error + err = global.DB.Where("status = ?", constant.Connected).Find(&Session{}).Count(&total).Error return } diff --git a/pkg/model/user-attribute.go b/pkg/model/user-attribute.go index ba48254..c97a8f4 100644 --- a/pkg/model/user-attribute.go +++ b/pkg/model/user-attribute.go @@ -2,10 +2,6 @@ package model import "next-terminal/pkg/global" -const ( - FontSize = "font-size" -) - type UserAttribute struct { Id string `gorm:"index" json:"id"` UserId string `gorm:"index" json:"userId"` diff --git a/pkg/model/user.go b/pkg/model/user.go index 226f43f..7ed9028 100644 --- a/pkg/model/user.go +++ b/pkg/model/user.go @@ -6,11 +6,6 @@ import ( "reflect" ) -const ( - TypeUser = "user" - TypeAdmin = "admin" -) - type User struct { ID string `gorm:"primary_key" json:"id"` Username string `gorm:"index" json:"username"` diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 22fb258..6dad7bb 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -182,3 +182,30 @@ func StructToMap(obj interface{}) map[string]interface{} { } return data } + +func IpToInt(ip string) int64 { + if len(ip) == 0 { + return 0 + } + bits := strings.Split(ip, ".") + if len(bits) < 4 { + return 0 + } + b0 := StringToInt(bits[0]) + b1 := StringToInt(bits[1]) + b2 := StringToInt(bits[2]) + b3 := StringToInt(bits[3]) + + var sum int64 + sum += int64(b0) << 24 + sum += int64(b1) << 16 + sum += int64(b2) << 8 + sum += int64(b3) + + return sum +} + +func StringToInt(in string) (out int) { + out, _ = strconv.Atoi(in) + return +} diff --git a/sample.csv b/sample.csv index ccdea43..2712e07 100644 --- a/sample.csv +++ b/sample.csv @@ -1 +1 @@ -测试阿里云,ssh,10.1.1.2,22,username,password,privateKey,passphrase,description \ No newline at end of file +资产名称,ssh,127.0.0.1,22,username,password,privateKey,passphrase,description \ No newline at end of file diff --git a/web/src/App.js b/web/src/App.js index eff4174..0868860 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -24,6 +24,7 @@ import { IdcardOutlined, LinkOutlined, LoginOutlined, + SafetyCertificateOutlined, SettingOutlined, SolutionOutlined, TeamOutlined, @@ -38,11 +39,12 @@ import BatchCommand from "./components/command/BatchCommand"; import {isEmpty, NT_PACKAGE} from "./utils/utils"; import {isAdmin} from "./service/permission"; import UserGroup from "./components/user/UserGroup"; -import LoginLog from "./components/session/LoginLog"; +import LoginLog from "./components/devops/LoginLog"; import Term from "./components/access/Term"; -import Job from "./components/job/Job"; +import Job from "./components/devops/Job"; import {Header} from "antd/es/layout/layout"; import LayoutHeader from "./components/user/LayoutHeader"; +import Security from "./components/devops/Security"; const {Footer, Sider} = Layout; @@ -203,6 +205,12 @@ class App extends Component { 计划任务 + +