diff --git a/agent/app/api/v2/ssh.go b/agent/app/api/v2/ssh.go index 461768fe7..e0bfcb78b 100644 --- a/agent/app/api/v2/ssh.go +++ b/agent/app/api/v2/ssh.go @@ -70,42 +70,20 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) { // @Tags SSH // @Summary Generate host SSH secret // @Accept json -// @Param request body dto.CreateRootCert true "request" +// @Param request body dto.RootCertOperate true "request" // @Success 200 // @Security ApiKeyAuth // @Security Timestamp // @Router /hosts/ssh/cert [post] // @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"} func (b *BaseApi) CreateRootCert(c *gin.Context) { - var req dto.CreateRootCert + var req dto.RootCertOperate if err := helper.CheckBindAndValidate(&req, c); err != nil { return } - if len(req.PassPhrase) != 0 { - passPhrase, err := base64.StdEncoding.DecodeString(req.PassPhrase) - if err != nil { - helper.BadRequest(c, err) - return - } - req.PassPhrase = string(passPhrase) + if err := loadCertAfterDecrypt(&req); err != nil { + helper.BadRequest(c, err) } - if len(req.PrivateKey) != 0 { - privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) - if err != nil { - helper.BadRequest(c, err) - return - } - req.PrivateKey = string(privateKey) - } - if len(req.PublicKey) != 0 { - publicKey, err := base64.StdEncoding.DecodeString(req.PublicKey) - if err != nil { - helper.BadRequest(c, err) - return - } - req.PublicKey = string(publicKey) - } - if err := sshService.CreateRootCert(req); err != nil { helper.InternalServer(c, err) return @@ -113,6 +91,30 @@ func (b *BaseApi) CreateRootCert(c *gin.Context) { helper.Success(c) } +// @Tags SSH +// @Summary Update host SSH secret +// @Accept json +// @Param request body dto.RootCertOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/cert/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"} +func (b *BaseApi) EditRootCert(c *gin.Context) { + var req dto.RootCertOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := loadCertAfterDecrypt(&req); err != nil { + helper.BadRequest(c, err) + } + if err := sshService.EditRootCert(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + // @Tags SSH // @Summary Sycn host SSH secret // @Success 200 @@ -265,3 +267,28 @@ func (b *BaseApi) UpdateSSHByFile(c *gin.Context) { } helper.Success(c) } + +func loadCertAfterDecrypt(req *dto.RootCertOperate) error { + if len(req.PassPhrase) != 0 { + passPhrase, err := base64.StdEncoding.DecodeString(req.PassPhrase) + if err != nil { + return err + } + req.PassPhrase = string(passPhrase) + } + if len(req.PrivateKey) != 0 { + privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) + if err != nil { + return err + } + req.PrivateKey = string(privateKey) + } + if len(req.PublicKey) != 0 { + publicKey, err := base64.StdEncoding.DecodeString(req.PublicKey) + if err != nil { + return err + } + req.PublicKey = string(publicKey) + } + return nil +} diff --git a/agent/app/dto/ssh.go b/agent/app/dto/ssh.go index 2b977135c..29f11d4ea 100644 --- a/agent/app/dto/ssh.go +++ b/agent/app/dto/ssh.go @@ -22,7 +22,8 @@ type SSHInfo struct { CurrentUser string `json:"currentUser"` } -type CreateRootCert struct { +type RootCertOperate struct { + ID uint `json:"id"` Name string `json:"name"` Mode string `json:"mode"` EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"` diff --git a/agent/app/repo/host.go b/agent/app/repo/host.go index d9d69ad04..3160a1ec3 100644 --- a/agent/app/repo/host.go +++ b/agent/app/repo/host.go @@ -19,7 +19,7 @@ type IHostRepo interface { GetCert(opts ...DBOption) (model.RootCert, error) PageCert(limit, offset int, opts ...DBOption) (int64, []model.RootCert, error) ListCert(opts ...DBOption) ([]model.RootCert, error) - CreateCert(cert *model.RootCert) error + SaveCert(cert *model.RootCert) error UpdateCert(id uint, vars map[string]interface{}) error DeleteCert(opts ...DBOption) error } @@ -107,8 +107,8 @@ func (u *HostRepo) ListCert(opts ...DBOption) ([]model.RootCert, error) { return ops, err } -func (u *HostRepo) CreateCert(cert *model.RootCert) error { - return global.DB.Create(cert).Error +func (u *HostRepo) SaveCert(cert *model.RootCert) error { + return global.DB.Save(cert).Error } func (u *HostRepo) UpdateCert(id uint, vars map[string]interface{}) error { diff --git a/agent/app/service/ssh.go b/agent/app/service/ssh.go index 3927a38ad..181737c2d 100644 --- a/agent/app/service/ssh.go +++ b/agent/app/service/ssh.go @@ -46,7 +46,8 @@ type ISSHService interface { ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error) SyncRootCert() error - CreateRootCert(req dto.CreateRootCert) error + CreateRootCert(req dto.RootCertOperate) error + EditRootCert(req dto.RootCertOperate) error SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error) DeleteRootCerts(req dto.ForceDelete) error } @@ -260,7 +261,7 @@ func (u *SSHService) SyncRootCert() error { return hostRepo.SyncCert(rootCerts) } -func (u *SSHService) CreateRootCert(req dto.CreateRootCert) error { +func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error { if cmd.CheckIllegal(req.EncryptionMode, req.PassPhrase) { return buserr.New("ErrCmdIllegal") } @@ -280,6 +281,19 @@ func (u *SSHService) CreateRootCert(req dto.CreateRootCert) error { publicPath := fmt.Sprintf("%s/.ssh/%s.pub", currentUser.HomeDir, req.Name) authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys" + authFileItem, _ := os.ReadFile(authFilePath) + authFile := string(authFileItem) + if authFile != "" && !strings.HasSuffix(authFile, "\n") { + file, err := os.OpenFile(authFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return err + } + defer file.Close() + if _, err := file.WriteString("\n"); err != nil { + return err + } + } + if req.Mode == "input" || req.Mode == "import" { if err := os.WriteFile(privatePath, []byte(req.PrivateKey), constant.FilePerm); err != nil { return err @@ -308,7 +322,58 @@ func (u *SSHService) CreateRootCert(req dto.CreateRootCert) error { if len(cert.PassPhrase) != 0 { cert.PassPhrase, _ = encrypt.StringEncrypt(cert.PassPhrase) } - return hostRepo.CreateCert(&cert) + return hostRepo.SaveCert(&cert) +} + +func (u *SSHService) EditRootCert(req dto.RootCertOperate) error { + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("load current user failed, err: %v", err) + } + certItem, _ := hostRepo.GetCert(repo.WithByID(req.ID)) + if certItem.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + oldPublicItem, err := os.ReadFile(certItem.PublicKeyPath) + if err != nil { + return err + } + + var cert model.RootCert + if err := copier.Copy(&cert, req); err != nil { + return err + } + cert.PrivateKeyPath = fmt.Sprintf("%s/.ssh/%s", currentUser.HomeDir, req.Name) + cert.PublicKeyPath = fmt.Sprintf("%s/.ssh/%s.pub", currentUser.HomeDir, req.Name) + if err := os.WriteFile(cert.PrivateKeyPath, []byte(req.PrivateKey), constant.FilePerm); err != nil { + return err + } + if err := os.WriteFile(cert.PublicKeyPath, []byte(req.PublicKey), constant.FilePerm); err != nil { + return err + } + + authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys" + authItem, err := os.ReadFile(authFilePath) + if err != nil { + return err + } + oldPublic := strings.ReplaceAll(string(oldPublicItem), "\n", "") + newPublic := strings.ReplaceAll(string(req.PublicKey), "\n", "") + lines := strings.Split(string(authItem), "\n") + var newFiles []string + for i := 0; i < len(lines); i++ { + if len(lines[i]) != 0 && lines[i] != oldPublic && lines[i] != newPublic { + newFiles = append(newFiles, lines[i]) + } + } + newFiles = append(newFiles, newPublic) + if err := os.WriteFile(authFilePath, []byte(strings.Join(newFiles, "\n")), constant.FilePerm); err != nil { + return fmt.Errorf("refresh authorized_keys failed, err: %v", err) + } + if len(cert.PassPhrase) != 0 { + cert.PassPhrase, _ = encrypt.StringEncrypt(cert.PassPhrase) + } + return hostRepo.SaveCert(&cert) } func (u *SSHService) SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error) { diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index 5bc862399..46ca9a22d 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -529,7 +529,7 @@ var InitLocalSSHConn = &gormigrate.Migration{ itemPath = path.Join(currentInfo.HomeDir, ".ssh/id_ed25519_1panel") } if _, err := os.Stat(itemPath); err != nil { - _ = service.NewISSHService().CreateRootCert(dto.CreateRootCert{EncryptionMode: "ed25519", Name: "id_ed25519_1panel", Description: "1Panel Terminal"}) + _ = service.NewISSHService().CreateRootCert(dto.RootCertOperate{EncryptionMode: "ed25519", Name: "id_ed25519_1panel", Description: "1Panel Terminal"}) } privateKey, _ := os.ReadFile(itemPath) connWithKey := ssh.ConnInfo{ diff --git a/agent/router/ro_host.go b/agent/router/ro_host.go index ce238a34b..58efc6df9 100644 --- a/agent/router/ro_host.go +++ b/agent/router/ro_host.go @@ -38,6 +38,7 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) { hostRouter.POST("/ssh/file/update", baseApi.UpdateSSHByFile) hostRouter.POST("/ssh/cert", baseApi.CreateRootCert) + hostRouter.POST("/ssh/cert/update", baseApi.EditRootCert) hostRouter.POST("/ssh/cert/sync", baseApi.SyncRootCert) hostRouter.POST("/ssh/cert/search", baseApi.SearchRootCert) hostRouter.POST("/ssh/cert/delete", baseApi.DeleteRootCert) diff --git a/frontend/src/api/interface/host.ts b/frontend/src/api/interface/host.ts index 0bb5ecf15..7aa35a919 100644 --- a/frontend/src/api/interface/host.ts +++ b/frontend/src/api/interface/host.ts @@ -183,6 +183,7 @@ export namespace Host { id: number; createAt: Date; name: string; + mode: string; encryptionMode: string; passPhrase: string; description: string; diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index 4fc6da273..f394e3885 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -93,6 +93,19 @@ export const createCert = (params: Host.RootCert) => { } return http.post(`/hosts/ssh/cert`, request); }; +export const editCert = (params: Host.RootCert) => { + let request = deepCopy(params) as Host.RootCert; + if (request.passPhrase) { + request.passPhrase = Base64.encode(request.passPhrase); + } + if (request.privateKey) { + request.privateKey = Base64.encode(request.privateKey); + } + if (request.publicKey) { + request.publicKey = Base64.encode(request.publicKey); + } + return http.post(`/hosts/ssh/cert/update`, request); +}; export const searchCert = (params: ReqPage) => { return http.post>(`/hosts/ssh/cert/search`, params); }; diff --git a/frontend/src/views/host/ssh/ssh/certification/index.vue b/frontend/src/views/host/ssh/ssh/certification/index.vue index cf8e99df1..ea7e89a58 100644 --- a/frontend/src/views/host/ssh/ssh/certification/index.vue +++ b/frontend/src/views/host/ssh/ssh/certification/index.vue @@ -4,7 +4,7 @@
{{ $t('ssh.pubKeyHelper', [currentUser]) }}
- + {{ $t('commons.button.create') }} @@ -33,115 +33,6 @@ - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ $t('ssh.generate') }} - {{ $t('ssh.input') }} - {{ $t('ssh.import') }} - - -
- - - - - - - - - - - - -
-
- - - - - - {{ $t('commons.button.upload') }} - - - - - - - - - {{ $t('commons.button.upload') }} - - - - - -
- - - -
-
- -
- @@ -198,16 +89,16 @@ +