diff --git a/docs/faq.md b/docs/faq.md index 9402d86..934d932 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -108,7 +108,7 @@ Mar 5 20:00:16.923 [DEBU] 用户「admin」密码初始化为: next-terminal / | \_/ __ \\ \/ /\ __\ | |_/ __ \_ __ \/ \| |/ \\__ \ | | / | \ ___/ > < | | | |\ ___/| | \/ Y Y \ | | \/ __ \| |__ \____|__ /\___ >__/\_ \ |__| |____| \___ >__| |__|_| /__|___| (____ /____/ - \/ \/ \/ \/ \/ \/ \/ v0.3.0 + \/ \/ \/ \/ \/ \/ \/ v0.4.0 当前数据库模式为:mysql Mar 5 20:00:16.923 [DEBU] 用户「admin」已重置TOTP @@ -117,3 +117,32 @@ Mar 5 20:00:16.923 [DEBU] 用户「admin」已重置TOTP +
+ 想要修改数据库敏感信息加密的key怎么办? +首先需要进入程序所在目录,使用docker安装的程序目录为:/usr/local/next-terminal + +执行命令 + +```shell +./next-terminal --encryption-key 旧的加密key new-encryption-key 新的的加密key +``` + +成功之后会输出 + +``` shell + + _______ __ ___________ .__ .__ + \ \ ____ ___ ____/ |_ \__ ___/__________ _____ |__| ____ _____ | | + / | \_/ __ \\ \/ /\ __\ | |_/ __ \_ __ \/ \| |/ \\__ \ | | +/ | \ ___/ > < | | | |\ ___/| | \/ Y Y \ | | \/ __ \| |__ +\____|__ /\___ >__/\_ \ |__| |____| \___ >__| |__|_| /__|___| (____ /____/ + \/ \/ \/ \/ \/ \/ \/ v0.4.0 + +当前数据库模式为:mysql +Mar 5 20:00:16.923 [DEBU] encryption key has being changed. + +``` + +最后重新启动程序,并且把加密key修改为新的。 +
+ diff --git a/docs/install-docker.md b/docs/install-docker.md index 89ad9a9..77eb4c5 100644 --- a/docs/install-docker.md +++ b/docs/install-docker.md @@ -104,6 +104,7 @@ docker run -d \ | MYSQL_PASSWORD | `mysql`数据库密码 | | MYSQL_DATABASE | `mysql`数据库名称 | | SERVER_ADDR | 服务器监听地址,默认`0.0.0.0:8088` | +| ENCRYPTION_KEY | 授权凭证和资产的密码,密钥等敏感信息加密的key,默认`next-terminal` | ## 其他 diff --git a/docs/install-naive.md b/docs/install-naive.md index 9dfc927..43605ad 100644 --- a/docs/install-naive.md +++ b/docs/install-naive.md @@ -147,9 +147,12 @@ sqlite: file: 'next-terminal.db' server: addr: 0.0.0.0:8088 -# 当设置下面两个参数时会自动开启https模式 +# 当设置下面两个参数时会自动开启https模式(前提是证书文件存在) # cert: /root/next-terminal/cert.pem # key: /root/next-terminal/key.pem + +# 授权凭证和资产的密码,密钥等敏感信息加密的key,默认`next-terminal` +#encryption-key: next-terminal ``` 启动 diff --git a/main.go b/main.go index 5a9b938..d5e953b 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "crypto/md5" "fmt" "next-terminal/pkg/config" @@ -12,7 +13,7 @@ import ( "github.com/labstack/gommon/log" ) -const Version = "v0.3.4" +const Version = "v0.4.0" func main() { err := Run() @@ -34,6 +35,12 @@ func Run() error { // 为了兼容之前调用global包的代码 后期预期会改为调用pgk/config global.Config = config.GlobalCfg + if global.Config.EncryptionKey == "" { + global.Config.EncryptionKey = "next-terminal" + } + md5Sum := fmt.Sprintf("%x", md5.Sum([]byte(global.Config.EncryptionKey))) + global.Config.EncryptionPassword = []byte(md5Sum) + global.Cache = api.SetupCache() db := api.SetupDB() e := api.SetupRoutes(db) @@ -44,6 +51,11 @@ func Run() error { if global.Config.ResetTotp != "" { return api.ResetTotp(global.Config.ResetTotp) } + + if global.Config.NewEncryptionKey != "" { + return api.ChangeEncryptionKey(global.Config.EncryptionKey, global.Config.NewEncryptionKey) + } + sessionRepo := repository.NewSessionRepository(db) propertyRepo := repository.NewPropertyRepository(db) ticker := task.NewTicker(sessionRepo, propertyRepo) diff --git a/pkg/config/config.go b/pkg/config/config.go index 03e79e5..f7b31a5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -11,14 +11,17 @@ import ( var GlobalCfg *Config type Config struct { - Debug bool - Demo bool - DB string - Server *Server - Mysql *Mysql - Sqlite *Sqlite - ResetPassword string - ResetTotp string + Debug bool + Demo bool + DB string + Server *Server + Mysql *Mysql + Sqlite *Sqlite + ResetPassword string + ResetTotp string + EncryptionKey string + EncryptionPassword []byte + NewEncryptionKey string } type Mysql struct { @@ -83,10 +86,12 @@ func SetupConfig() *Config { Cert: viper.GetString("server.cert"), Key: viper.GetString("server.key"), }, - ResetPassword: viper.GetString("reset-password"), - ResetTotp: viper.GetString("reset-totp"), - Debug: viper.GetBool("debug"), - Demo: viper.GetBool("demo"), + ResetPassword: viper.GetString("reset-password"), + ResetTotp: viper.GetString("reset-totp"), + Debug: viper.GetBool("debug"), + Demo: viper.GetBool("demo"), + EncryptionKey: viper.GetString("encryption-key"), + NewEncryptionKey: viper.GetString("new-encryption-key"), } GlobalCfg = config return config diff --git a/pkg/service/asset.go b/pkg/service/asset.go new file mode 100644 index 0000000..934e127 --- /dev/null +++ b/pkg/service/asset.go @@ -0,0 +1,58 @@ +package service + +import ( + "encoding/base64" + + "next-terminal/pkg/global" + "next-terminal/server/repository" + "next-terminal/server/utils" +) + +type AssetService struct { + assetRepository *repository.AssetRepository +} + +func NewAssetService(assetRepository *repository.AssetRepository) *AssetService { + return &AssetService{assetRepository: assetRepository} +} + +func (r AssetService) Encrypt() error { + items, err := r.assetRepository.FindAll() + if err != nil { + return err + } + for i := range items { + item := items[i] + if item.Encrypted { + continue + } + if item.Password != "" && item.Password != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Password), global.Config.EncryptionPassword) + if err != nil { + return err + } + item.Password = base64.StdEncoding.EncodeToString(encryptedCBC) + } + + if item.PrivateKey != "" && item.PrivateKey != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.PrivateKey), global.Config.EncryptionPassword) + if err != nil { + return err + } + item.PrivateKey = base64.StdEncoding.EncodeToString(encryptedCBC) + } + + if item.Passphrase != "" && item.Passphrase != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Passphrase), global.Config.EncryptionPassword) + if err != nil { + return err + } + item.Passphrase = base64.StdEncoding.EncodeToString(encryptedCBC) + } + err = r.assetRepository.EncryptedById(true, item.Password, item.PrivateKey, item.Passphrase, item.ID) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/service/credential.go b/pkg/service/credential.go new file mode 100644 index 0000000..89d0371 --- /dev/null +++ b/pkg/service/credential.go @@ -0,0 +1,58 @@ +package service + +import ( + "encoding/base64" + + "next-terminal/pkg/global" + "next-terminal/server/repository" + "next-terminal/server/utils" +) + +type CredentialService struct { + credentialRepository *repository.CredentialRepository +} + +func NewCredentialService(credentialRepository *repository.CredentialRepository) *CredentialService { + return &CredentialService{credentialRepository: credentialRepository} +} + +func (r CredentialService) Encrypt() error { + items, err := r.credentialRepository.FindAll() + if err != nil { + return err + } + for i := range items { + item := items[i] + if item.Encrypted { + continue + } + if item.Password != "" && item.Password != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Password), global.Config.EncryptionPassword) + if err != nil { + return err + } + item.Password = base64.StdEncoding.EncodeToString(encryptedCBC) + } + + if item.PrivateKey != "" && item.PrivateKey != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.PrivateKey), global.Config.EncryptionPassword) + if err != nil { + return err + } + item.PrivateKey = base64.StdEncoding.EncodeToString(encryptedCBC) + } + + if item.Passphrase != "" && item.Passphrase != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Passphrase), global.Config.EncryptionPassword) + if err != nil { + return err + } + item.Passphrase = base64.StdEncoding.EncodeToString(encryptedCBC) + } + err = r.credentialRepository.EncryptedById(true, item.Password, item.PrivateKey, item.Passphrase, item.ID) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/service/job.go b/pkg/service/job.go index d923ebc..9c592f6 100644 --- a/pkg/service/job.go +++ b/pkg/service/job.go @@ -160,7 +160,7 @@ func (r ShellJob) Run() { msgChan := make(chan string) for i := range assets { - asset, err := r.jobService.assetRepository.FindById(assets[i].ID) + asset, err := r.jobService.assetRepository.FindByIdAndDecrypt(assets[i].ID) if err != nil { msgChan <- fmt.Sprintf("资产「%v」Shell执行失败,查询数据异常「%v」", assets[i].Name, err.Error()) return @@ -176,7 +176,7 @@ func (r ShellJob) Run() { ) if asset.AccountType == "credential" { - credential, err := r.jobService.credentialRepository.FindById(asset.CredentialId) + credential, err := r.jobService.credentialRepository.FindByIdAndDecrypt(asset.CredentialId) if err != nil { msgChan <- fmt.Sprintf("资产「%v」Shell执行失败,查询授权凭证数据异常「%v」", assets[i].Name, err.Error()) return diff --git a/pkg/service/session.go b/pkg/service/session.go index ae6b267..6a8be9f 100644 --- a/pkg/service/session.go +++ b/pkg/service/session.go @@ -33,3 +33,7 @@ func (r SessionService) FixSessionState() error { } return nil } + +func (r SessionService) EmptyPassword() error { + return r.sessionRepository.EmptyPassword() +} diff --git a/pkg/service/user.go b/pkg/service/user.go index b57f67c..9f0bcd5 100644 --- a/pkg/service/user.go +++ b/pkg/service/user.go @@ -58,7 +58,7 @@ func (r UserService) InitUser() (err error) { return nil } -func (r UserService) FixedUserOnlineState() error { +func (r UserService) FixUserOnlineState() error { // 修正用户登录状态 onlineUsers, err := r.userRepository.FindOnlineUsers() if err != nil { diff --git a/server/api/asset.go b/server/api/asset.go index 2e4e470..3a22fed 100644 --- a/server/api/asset.go +++ b/server/api/asset.go @@ -9,6 +9,7 @@ import ( "strings" "next-terminal/pkg/constant" + "next-terminal/pkg/global" "next-terminal/server/model" "next-terminal/server/utils" @@ -199,6 +200,9 @@ func AssetUpdateEndpoint(c echo.Context) error { item.Description = "-" } + if err := assetRepository.Encrypt(&item, global.Config.EncryptionPassword); err != nil { + return err + } if err := assetRepository.UpdateById(&item, id); err != nil { return err } @@ -264,7 +268,7 @@ func AssetGetEndpoint(c echo.Context) (err error) { } var item model.Asset - if item, err = assetRepository.FindById(id); err != nil { + if item, err = assetRepository.FindByIdAndDecrypt(id); err != nil { return err } attributeMap, err := assetRepository.FindAssetAttrMapByAssetId(id) @@ -289,9 +293,12 @@ func AssetTcpingEndpoint(c echo.Context) (err error) { active := utils.Tcping(item.IP, item.Port) - if err := assetRepository.UpdateActiveById(active, item.ID); err != nil { - return err + if item.Active != active { + if err := assetRepository.UpdateActiveById(active, item.ID); err != nil { + return err + } } + return Success(c, active) } diff --git a/server/api/credential.go b/server/api/credential.go index 25caf1a..49a4643 100644 --- a/server/api/credential.go +++ b/server/api/credential.go @@ -1,11 +1,13 @@ package api import ( + "encoding/base64" "errors" "strconv" "strings" "next-terminal/pkg/constant" + "next-terminal/pkg/global" "next-terminal/server/model" "next-terminal/server/utils" @@ -32,27 +34,28 @@ func CredentialCreateEndpoint(c echo.Context) error { case constant.Custom: item.PrivateKey = "-" item.Passphrase = "-" - if len(item.Username) == 0 { + if item.Username == "" { item.Username = "-" } - if len(item.Password) == 0 { + if item.Password == "" { item.Password = "-" } case constant.PrivateKey: item.Password = "-" - if len(item.Username) == 0 { + if item.Username == "" { item.Username = "-" } - if len(item.PrivateKey) == 0 { + if item.PrivateKey == "" { item.PrivateKey = "-" } - if len(item.Passphrase) == 0 { + if item.Passphrase == "" { item.Passphrase = "-" } default: return Fail(c, -1, "类型错误") } + item.Encrypted = true if err := credentialRepository.Create(&item); err != nil { return err } @@ -96,26 +99,48 @@ func CredentialUpdateEndpoint(c echo.Context) error { case constant.Custom: item.PrivateKey = "-" item.Passphrase = "-" - if len(item.Username) == 0 { + if item.Username == "" { item.Username = "-" } - if len(item.Password) == 0 { + if item.Password == "" { item.Password = "-" } + if item.Password != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Password), global.Config.EncryptionPassword) + if err != nil { + return err + } + item.Password = base64.StdEncoding.EncodeToString(encryptedCBC) + } case constant.PrivateKey: item.Password = "-" - if len(item.Username) == 0 { + if item.Username == "" { item.Username = "-" } - if len(item.PrivateKey) == 0 { + if item.PrivateKey == "" { item.PrivateKey = "-" } - if len(item.Passphrase) == 0 { + if item.PrivateKey != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.PrivateKey), global.Config.EncryptionPassword) + if err != nil { + return err + } + item.PrivateKey = base64.StdEncoding.EncodeToString(encryptedCBC) + } + if item.Passphrase == "" { item.Passphrase = "-" } + if item.Passphrase != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Passphrase), global.Config.EncryptionPassword) + if err != nil { + return err + } + item.Passphrase = base64.StdEncoding.EncodeToString(encryptedCBC) + } default: return Fail(c, -1, "类型错误") } + item.Encrypted = true if err := credentialRepository.UpdateById(&item, id); err != nil { return err @@ -149,7 +174,7 @@ func CredentialGetEndpoint(c echo.Context) error { return err } - item, err := credentialRepository.FindById(id) + item, err := credentialRepository.FindByIdAndDecrypt(id) if err != nil { return err } diff --git a/server/api/routes.go b/server/api/routes.go index ad545da..4785536 100644 --- a/server/api/routes.go +++ b/server/api/routes.go @@ -1,8 +1,10 @@ package api import ( + "crypto/md5" "fmt" "net/http" + "os" "strings" "time" @@ -45,6 +47,7 @@ var ( sessionService *service.SessionService mailService *service.MailService numService *service.NumService + assetService *service.AssetService ) func SetupRoutes(db *gorm.DB) *echo.Echo { @@ -54,6 +57,7 @@ func SetupRoutes(db *gorm.DB) *echo.Echo { if err := InitDBData(); err != nil { log.WithError(err).Error("初始化数据异常") + os.Exit(0) } if err := ReloadData(); err != nil { @@ -251,6 +255,7 @@ func InitService() { sessionService = service.NewSessionService(sessionRepository) mailService = service.NewMailService(propertyRepository) numService = service.NewNumService(numRepository) + assetService = service.NewAssetService(assetRepository) } func InitDBData() (err error) { @@ -266,12 +271,18 @@ func InitDBData() (err error) { if err := jobService.InitJob(); err != nil { return err } - if err := userService.FixedUserOnlineState(); err != nil { + if err := userService.FixUserOnlineState(); err != nil { return err } if err := sessionService.FixSessionState(); err != nil { return err } + if err := sessionService.EmptyPassword(); err != nil { + return err + } + if err := assetService.Encrypt(); err != nil { + return err + } return nil } @@ -312,6 +323,47 @@ func ResetTotp(username string) error { return nil } +func ChangeEncryptionKey(oldEncryptionKey, newEncryptionKey string) error { + + oldPassword := []byte(fmt.Sprintf("%x", md5.Sum([]byte(oldEncryptionKey)))) + newPassword := []byte(fmt.Sprintf("%x", md5.Sum([]byte(newEncryptionKey)))) + + credentials, err := credentialRepository.FindAll() + if err != nil { + return err + } + for i := range credentials { + credential := credentials[i] + if err := credentialRepository.Decrypt(&credential, oldPassword); err != nil { + return err + } + if err := credentialRepository.Encrypt(&credential, newPassword); err != nil { + return err + } + if err := credentialRepository.UpdateById(&credential, credential.ID); err != nil { + return err + } + } + assets, err := assetRepository.FindAll() + if err != nil { + return err + } + for i := range assets { + asset := assets[i] + if err := assetRepository.Decrypt(&asset, oldPassword); err != nil { + return err + } + if err := assetRepository.Encrypt(&asset, newPassword); err != nil { + return err + } + if err := assetRepository.UpdateById(&asset, asset.ID); err != nil { + return err + } + } + log.Infof("encryption key has being changed.") + return nil +} + func SetupCache() *cache.Cache { // 配置缓存器 mCache := cache.New(5*time.Minute, 10*time.Minute) diff --git a/server/api/session.go b/server/api/session.go index 9a91d83..00e3a9f 100644 --- a/server/api/session.go +++ b/server/api/session.go @@ -137,6 +137,9 @@ func CloseSessionById(sessionId string, code int, reason string) { session.DisconnectedTime = utils.NowJsonTime() session.Code = code session.Message = reason + session.Password = "-" + session.PrivateKey = "-" + session.Passphrase = "-" _ = sessionRepository.UpdateById(&session, sessionId) } @@ -359,7 +362,7 @@ type File struct { func SessionLsEndpoint(c echo.Context) error { sessionId := c.Param("id") - session, err := sessionRepository.FindById(sessionId) + session, err := sessionRepository.FindByIdAndDecrypt(sessionId) if err != nil { return err } diff --git a/server/api/ssh.go b/server/api/ssh.go index a76be4b..1827369 100644 --- a/server/api/ssh.go +++ b/server/api/ssh.go @@ -54,7 +54,7 @@ func SSHEndpoint(c echo.Context) (err error) { cols, _ := strconv.Atoi(c.QueryParam("cols")) rows, _ := strconv.Atoi(c.QueryParam("rows")) - session, err := sessionRepository.FindById(sessionId) + session, err := sessionRepository.FindByIdAndDecrypt(sessionId) if err != nil { msg := Message{ Type: Closed, diff --git a/server/api/tunnel.go b/server/api/tunnel.go index a1330cc..e8a3bc7 100644 --- a/server/api/tunnel.go +++ b/server/api/tunnel.go @@ -65,7 +65,7 @@ func TunEndpoint(c echo.Context) error { configuration.SetParameter("width", width) configuration.SetParameter("height", height) configuration.SetParameter("dpi", dpi) - session, err = sessionRepository.FindById(sessionId) + session, err = sessionRepository.FindByIdAndDecrypt(sessionId) if err != nil { CloseSessionById(sessionId, NotFoundSession, "会话不存在") return err diff --git a/server/model/asset.go b/server/model/asset.go index 0a69c2f..ed6f344 100644 --- a/server/model/asset.go +++ b/server/model/asset.go @@ -21,6 +21,7 @@ type Asset struct { Created utils.JsonTime `json:"created"` Tags string `json:"tags"` Owner string `gorm:"index" json:"owner"` + Encrypted bool `json:"encrypted"` } type AssetForPage struct { diff --git a/server/model/credential.go b/server/model/credential.go index a2fb006..cbf7f32 100644 --- a/server/model/credential.go +++ b/server/model/credential.go @@ -14,6 +14,7 @@ type Credential struct { Passphrase string `json:"passphrase"` Created utils.JsonTime `json:"created"` Owner string `gorm:"index" json:"owner"` + Encrypted bool `json:"encrypted"` } func (r *Credential) TableName() string { diff --git a/server/repository/asset.go b/server/repository/asset.go index 8c9b684..10f9bce 100644 --- a/server/repository/asset.go +++ b/server/repository/asset.go @@ -1,6 +1,7 @@ package repository import ( + "encoding/base64" "fmt" "strings" @@ -145,7 +146,36 @@ func (r AssetRepository) Find(pageIndex, pageSize int, name, protocol, tags stri return } +func (r AssetRepository) Encrypt(item *model.Asset, password []byte) error { + if item.Password != "" && item.Password != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Password), password) + if err != nil { + return err + } + item.Password = base64.StdEncoding.EncodeToString(encryptedCBC) + } + if item.PrivateKey != "" && item.PrivateKey != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.PrivateKey), password) + if err != nil { + return err + } + item.PrivateKey = base64.StdEncoding.EncodeToString(encryptedCBC) + } + if item.Passphrase != "" && item.Passphrase != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Passphrase), password) + if err != nil { + return err + } + item.Passphrase = base64.StdEncoding.EncodeToString(encryptedCBC) + } + item.Encrypted = true + return nil +} + func (r AssetRepository) Create(o *model.Asset) (err error) { + if err := r.Encrypt(o, global.Config.EncryptionPassword); err != nil { + return err + } if err = r.DB.Create(o).Error; err != nil { return err } @@ -157,8 +187,54 @@ func (r AssetRepository) FindById(id string) (o model.Asset, err error) { return } +func (r AssetRepository) Decrypt(item *model.Asset, password []byte) error { + if item.Encrypted { + if item.Password != "" && item.Password != "-" { + origData, err := base64.StdEncoding.DecodeString(item.Password) + if err != nil { + return err + } + decryptedCBC, err := utils.AesDecryptCBC(origData, password) + if err != nil { + return err + } + item.Password = string(decryptedCBC) + } + if item.PrivateKey != "" && item.PrivateKey != "-" { + origData, err := base64.StdEncoding.DecodeString(item.PrivateKey) + if err != nil { + return err + } + decryptedCBC, err := utils.AesDecryptCBC(origData, password) + if err != nil { + return err + } + item.PrivateKey = string(decryptedCBC) + } + if item.Passphrase != "" && item.Passphrase != "-" { + origData, err := base64.StdEncoding.DecodeString(item.Passphrase) + if err != nil { + return err + } + decryptedCBC, err := utils.AesDecryptCBC(origData, password) + if err != nil { + return err + } + item.Passphrase = string(decryptedCBC) + } + } + return nil +} + +func (r AssetRepository) FindByIdAndDecrypt(id string) (o model.Asset, err error) { + err = r.DB.Where("id = ?", id).First(&o).Error + if err == nil { + err = r.Decrypt(&o, global.Config.EncryptionPassword) + } + return +} + func (r AssetRepository) UpdateById(o *model.Asset, id string) error { - o.ID = id return r.DB.Updates(o).Error } @@ -167,6 +243,11 @@ func (r AssetRepository) UpdateActiveById(active bool, id string) error { return r.DB.Exec(sql, active, id).Error } +func (r AssetRepository) EncryptedById(encrypted bool, password, privateKey, passphrase, id string) error { + sql := "update assets set encrypted = ?, password = ?,private_key = ?, passphrase = ? where id = ?" + return r.DB.Exec(sql, encrypted, password, privateKey, passphrase, id).Error +} + func (r AssetRepository) DeleteById(id string) error { return r.DB.Where("id = ?", id).Delete(&model.Asset{}).Error } diff --git a/server/repository/credential.go b/server/repository/credential.go index 0a29b2d..120ac82 100644 --- a/server/repository/credential.go +++ b/server/repository/credential.go @@ -1,8 +1,12 @@ package repository import ( + "encoding/base64" + "next-terminal/pkg/constant" + "next-terminal/pkg/global" "next-terminal/server/model" + "next-terminal/server/utils" "gorm.io/gorm" ) @@ -65,6 +69,9 @@ func (r CredentialRepository) Find(pageIndex, pageSize int, name, order, field s } func (r CredentialRepository) Create(o *model.Credential) (err error) { + if err := r.Encrypt(o, global.Config.EncryptionPassword); err != nil { + return err + } if err = r.DB.Create(o).Error; err != nil { return err } @@ -76,6 +83,79 @@ func (r CredentialRepository) FindById(id string) (o model.Credential, err error return } +func (r CredentialRepository) Encrypt(item *model.Credential, password []byte) error { + if item.Password != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Password), password) + if err != nil { + return err + } + item.Password = base64.StdEncoding.EncodeToString(encryptedCBC) + } + if item.PrivateKey != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.PrivateKey), password) + if err != nil { + return err + } + item.PrivateKey = base64.StdEncoding.EncodeToString(encryptedCBC) + } + if item.Passphrase != "-" { + encryptedCBC, err := utils.AesEncryptCBC([]byte(item.Passphrase), password) + if err != nil { + return err + } + item.Passphrase = base64.StdEncoding.EncodeToString(encryptedCBC) + } + item.Encrypted = true + return nil +} + +func (r CredentialRepository) Decrypt(item *model.Credential, password []byte) error { + if item.Encrypted { + if item.Password != "" && item.Password != "-" { + origData, err := base64.StdEncoding.DecodeString(item.Password) + if err != nil { + return err + } + decryptedCBC, err := utils.AesDecryptCBC(origData, password) + if err != nil { + return err + } + item.Password = string(decryptedCBC) + } + if item.PrivateKey != "" && item.PrivateKey != "-" { + origData, err := base64.StdEncoding.DecodeString(item.PrivateKey) + if err != nil { + return err + } + decryptedCBC, err := utils.AesDecryptCBC(origData, password) + if err != nil { + return err + } + item.PrivateKey = string(decryptedCBC) + } + if item.Passphrase != "" && item.Passphrase != "-" { + origData, err := base64.StdEncoding.DecodeString(item.Passphrase) + if err != nil { + return err + } + decryptedCBC, err := utils.AesDecryptCBC(origData, password) + if err != nil { + return err + } + item.Passphrase = string(decryptedCBC) + } + } + return nil +} + +func (r CredentialRepository) FindByIdAndDecrypt(id string) (o model.Credential, err error) { + err = r.DB.Where("id = ?", id).First(&o).Error + if err == nil { + err = r.Decrypt(&o, global.Config.EncryptionPassword) + } + return +} + func (r CredentialRepository) UpdateById(o *model.Credential, id string) error { o.ID = id return r.DB.Updates(o).Error @@ -107,3 +187,13 @@ func (r CredentialRepository) CountByUserId(userId string) (total int64, err err err = db.Find(&model.Credential{}).Count(&total).Error return } + +func (r CredentialRepository) FindAll() (o []model.Credential, err error) { + err = r.DB.Find(&o).Error + return +} + +func (r CredentialRepository) EncryptedById(encrypted bool, password, privateKey, passphrase, id string) error { + sql := "update assets set encrypted = ?, password = ?,private_key = ?, passphrase = ? where id = ?" + return r.DB.Exec(sql, encrypted, password, privateKey, passphrase, id).Error +} diff --git a/server/repository/session.go b/server/repository/session.go index 66f082f..284dfac 100644 --- a/server/repository/session.go +++ b/server/repository/session.go @@ -1,12 +1,15 @@ package repository import ( + "encoding/base64" "os" "path" "time" "next-terminal/pkg/constant" + "next-terminal/pkg/global" "next-terminal/server/model" + "next-terminal/server/utils" "gorm.io/gorm" ) @@ -93,6 +96,51 @@ func (r SessionRepository) FindById(id string) (o model.Session, err error) { return } +func (r SessionRepository) FindByIdAndDecrypt(id string) (o model.Session, err error) { + err = r.DB.Where("id = ?", id).First(&o).Error + if err == nil { + err = r.Decrypt(&o) + } + return +} + +func (r SessionRepository) Decrypt(item *model.Session) error { + if item.Password != "" && item.Password != "-" { + origData, err := base64.StdEncoding.DecodeString(item.Password) + if err != nil { + return err + } + decryptedCBC, err := utils.AesDecryptCBC(origData, global.Config.EncryptionPassword) + if err != nil { + return err + } + item.Password = string(decryptedCBC) + } + if item.PrivateKey != "" && item.PrivateKey != "-" { + origData, err := base64.StdEncoding.DecodeString(item.PrivateKey) + if err != nil { + return err + } + decryptedCBC, err := utils.AesDecryptCBC(origData, global.Config.EncryptionPassword) + if err != nil { + return err + } + item.PrivateKey = string(decryptedCBC) + } + if item.Passphrase != "" && item.Passphrase != "-" { + origData, err := base64.StdEncoding.DecodeString(item.Passphrase) + if err != nil { + return err + } + decryptedCBC, err := utils.AesDecryptCBC(origData, global.Config.EncryptionPassword) + if err != nil { + return err + } + item.Passphrase = string(decryptedCBC) + } + return nil +} + func (r SessionRepository) FindByConnectionId(connectionId string) (o model.Session, err error) { err = r.DB.Where("connection_id = ?", connectionId).First(&o).Error return @@ -167,3 +215,8 @@ func (r SessionRepository) CountSessionByDay(day int) (results []D, err error) { return } + +func (r SessionRepository) EmptyPassword() error { + sql := "update sessions set password = '-',private_key = '-', passphrase = '-' where 1=1" + return r.DB.Exec(sql).Error +} diff --git a/server/utils/util_test.go b/server/utils/util_test.go index 70c6330..b8d4fba 100644 --- a/server/utils/util_test.go +++ b/server/utils/util_test.go @@ -1,7 +1,10 @@ package utils_test import ( + "crypto/md5" "encoding/base64" + "encoding/hex" + "fmt" "net" "testing" @@ -51,3 +54,26 @@ func TestAesDecryptCBC(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "Hello Next Terminal", string(decryptCBC)) } + +func TestPbkdf2(t *testing.T) { + pbkdf2, err := utils.Pbkdf2("1234") + assert.NoError(t, err) + println(hex.EncodeToString(pbkdf2)) +} + +func TestAesEncryptCBCWithAnyKey(t *testing.T) { + origData := []byte("admin") // 待加密的数据 + key := []byte(fmt.Sprintf("%x", md5.Sum([]byte("next-terminal")))) // 加密的密钥 + encryptedCBC, err := utils.AesEncryptCBC(origData, key) + assert.NoError(t, err) + assert.Equal(t, "3qwawlPxghyiLS5hdr/p0g==", base64.StdEncoding.EncodeToString(encryptedCBC)) +} + +func TestAesDecryptCBCWithAnyKey(t *testing.T) { + origData, err := base64.StdEncoding.DecodeString("3qwawlPxghyiLS5hdr/p0g==") // 待解密的数据 + assert.NoError(t, err) + key := []byte(fmt.Sprintf("%x", md5.Sum([]byte("next-terminal")))) // 加密的密钥 + decryptCBC, err := utils.AesDecryptCBC(origData, key) + assert.NoError(t, err) + assert.Equal(t, "admin", string(decryptCBC)) +} diff --git a/server/utils/utils.go b/server/utils/utils.go index 0f94e50..0d5333c 100644 --- a/server/utils/utils.go +++ b/server/utils/utils.go @@ -5,6 +5,8 @@ import ( "crypto/aes" "crypto/cipher" "crypto/md5" + "crypto/rand" + "crypto/sha256" "database/sql/driver" "encoding/base64" "fmt" @@ -19,6 +21,8 @@ import ( "strings" "time" + "golang.org/x/crypto/pbkdf2" + "github.com/gofrs/uuid" "github.com/sirupsen/logrus" "golang.org/x/crypto/bcrypt" @@ -239,6 +243,7 @@ func PKCS5UnPadding(origData []byte) []byte { return origData[:(length - unPadding)] } +// AesEncryptCBC /* func AesEncryptCBC(origData, key []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { @@ -266,3 +271,15 @@ func AesDecryptCBC(encrypted, key []byte) ([]byte, error) { origData = PKCS5UnPadding(origData) return origData, nil } + +func Pbkdf2(password string) ([]byte, error) { + //生成随机盐 + salt := make([]byte, 32) + _, err := rand.Read(salt) + if err != nil { + return nil, err + } + //生成密文 + dk := pbkdf2.Key([]byte(password), salt, 1, 32, sha256.New) + return dk, nil +} diff --git a/web/package.json b/web/package.json index ae3f55c..5390912 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "next-terminal", - "version": "0.3.4", + "version": "0.4.0", "private": true, "dependencies": { "@ant-design/icons": "^4.3.0", diff --git a/web/src/components/asset/Asset.js b/web/src/components/asset/Asset.js index 0c39582..5d50347 100644 --- a/web/src/components/asset/Asset.js +++ b/web/src/components/asset/Asset.js @@ -355,10 +355,10 @@ class Asset extends Component { window.open(`#/access?assetId=${id}&assetName=${name}&protocol=${protocol}`); } } else { - message.warn('您访问的资产未在线,请确认网络状态。', 10); + message.warn({content: '您访问的资产未在线,请确认网络状态。', key: id, duration: 10}); } } else { - message.error('操作失败 :( ' + result.message, 10); + message.error({content: result.message, key: id, duration: 10}); } }