diff --git a/agent/app/api/v2/ssh.go b/agent/app/api/v2/ssh.go index 0f0d47c710d8..461768fe707f 100644 --- a/agent/app/api/v2/ssh.go +++ b/agent/app/api/v2/ssh.go @@ -67,28 +67,6 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) { helper.Success(c) } -// @Tags SSH -// @Summary Update host SSH setting by file -// @Accept json -// @Param request body dto.SSHConf true "request" -// @Success 200 -// @Security ApiKeyAuth -// @Security Timestamp -// @Router /hosts/ssh/conffile/update [post] -// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件","formatEN":"update SSH conf"} -func (b *BaseApi) UpdateSSHByfile(c *gin.Context) { - var req dto.SSHConf - if err := helper.CheckBindAndValidate(&req, c); err != nil { - return - } - - if err := sshService.UpdateByFile(req.File); err != nil { - helper.InternalServer(c, err) - return - } - helper.Success(c) -} - // @Tags SSH // @Summary Generate host SSH secret // @Accept json @@ -246,15 +224,44 @@ func (b *BaseApi) ExportSSHLogs(c *gin.Context) { // @Tags SSH // @Summary Load host SSH conf +// @Accept json +// @Param request body dto.OperationWithName true "request" // @Success 200 {string} conf // @Security ApiKeyAuth // @Security Timestamp -// @Router /hosts/ssh/conf [get] -func (b *BaseApi) LoadSSHConf(c *gin.Context) { - data, err := sshService.LoadSSHConf() +// @Router /hosts/ssh/file [post] +func (b *BaseApi) LoadSSHFile(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := sshService.LoadSSHFile(req.Name) if err != nil { helper.InternalServer(c, err) return } helper.SuccessWithData(c, data) } + +// @Tags SSH +// @Summary Update host SSH setting by file +// @Accept json +// @Param request body dto.SSHConf true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/file/update [post] +// @x-panel-log {"bodyKeys":["key"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件 [key]","formatEN":"update SSH conf [key]"} +func (b *BaseApi) UpdateSSHByFile(c *gin.Context) { + var req dto.SettingUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := sshService.UpdateByFile(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/service/ssh.go b/agent/app/service/ssh.go index e7a360bb7cac..3927a38ad997 100644 --- a/agent/app/service/ssh.go +++ b/agent/app/service/ssh.go @@ -38,9 +38,9 @@ type SSHService struct{} type ISSHService interface { GetSSHInfo() (*dto.SSHInfo, error) OperateSSH(operation string) error - UpdateByFile(value string) error Update(req dto.SSHUpdate) error - LoadSSHConf() (string, error) + LoadSSHFile(name string) (string, error) + UpdateByFile(req dto.SettingUpdate) error LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error) ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error) @@ -223,25 +223,6 @@ func (u *SSHService) Update(req dto.SSHUpdate) error { return nil } -func (u *SSHService) UpdateByFile(value string) error { - serviceName, err := loadServiceName() - if err != nil { - return err - } - - file, err := os.OpenFile(sshPath, os.O_WRONLY|os.O_TRUNC, constant.FilePerm) - if err != nil { - return err - } - defer file.Close() - if _, err = file.WriteString(value); err != nil { - return err - } - sudo := cmd.SudoHandleCmd() - _, _ = cmd.RunDefaultWithStdoutBashCf("%s systemctl restart %s", sudo, serviceName) - return nil -} - func (u *SSHService) SyncRootCert() error { currentUser, err := user.Current() if err != nil { @@ -500,17 +481,64 @@ func (u *SSHService) ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, return tmpFileName, nil } -func (u *SSHService) LoadSSHConf() (string, error) { - if _, err := os.Stat("/etc/ssh/sshd_config"); err != nil { - return "", buserr.New("ErrHttpReqNotFound") +func (u *SSHService) LoadSSHFile(name string) (string, error) { + var fileName string + switch name { + case "authKeys": + currentUser, err := user.Current() + if err != nil { + return "", fmt.Errorf("load current user failed, err: %v", err) + } + fileName = currentUser.HomeDir + "/.ssh/authorized_keys" + case "sshdConf": + fileName = "/etc/ssh/sshd_config" + default: + return "", buserr.WithName("ErrNotSupportType", name) + } + if _, err := os.Stat(fileName); err != nil { + return "", buserr.WithErr("ErrHttpReqNotFound", err) } - content, err := os.ReadFile("/etc/ssh/sshd_config") + content, err := os.ReadFile(fileName) if err != nil { return "", err } return string(content), nil } +func (u *SSHService) UpdateByFile(req dto.SettingUpdate) error { + var fileName string + switch req.Key { + case "authKeys": + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("load current user failed, err: %v", err) + } + fileName = currentUser.HomeDir + "/.ssh/authorized_keys" + case "sshdConf": + fileName = "/etc/ssh/sshd_config" + default: + return buserr.WithName("ErrNotSupportType", req.Key) + } + file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + if _, err = file.WriteString(req.Value); err != nil { + return err + } + if req.Key == "authKeys" { + return nil + } + serviceName, err := loadServiceName() + if err != nil { + return err + } + sudo := cmd.SudoHandleCmd() + _, _ = cmd.RunDefaultWithStdoutBashCf("%s systemctl restart %s", sudo, serviceName) + return nil +} + func sortFileList(fileNames []sshFileItem) []sshFileItem { if len(fileNames) < 2 { return fileNames diff --git a/agent/router/ro_host.go b/agent/router/ro_host.go index 59a5ae725306..ce238a34bb97 100644 --- a/agent/router/ro_host.go +++ b/agent/router/ro_host.go @@ -29,13 +29,13 @@ func (s *HostRouter) InitRouter(Router *gin.RouterGroup) { hostRouter.GET("/monitor/setting", baseApi.LoadMonitorSetting) hostRouter.POST("/monitor/setting/update", baseApi.UpdateMonitorSetting) - hostRouter.GET("/ssh/conf", baseApi.LoadSSHConf) hostRouter.POST("/ssh/search", baseApi.GetSSHInfo) hostRouter.POST("/ssh/update", baseApi.UpdateSSH) hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs) hostRouter.POST("/ssh/log/export", baseApi.ExportSSHLogs) - hostRouter.POST("/ssh/conffile/update", baseApi.UpdateSSHByfile) hostRouter.POST("/ssh/operate", baseApi.OperateSSH) + hostRouter.POST("/ssh/file", baseApi.LoadSSHFile) + hostRouter.POST("/ssh/file/update", baseApi.UpdateSSHByFile) hostRouter.POST("/ssh/cert", baseApi.CreateRootCert) hostRouter.POST("/ssh/cert/sync", baseApi.SyncRootCert) diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index f7cefcf55202..82aa55a1629d 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -68,17 +68,17 @@ export const updateMonitorSetting = (key: string, value: string) => { export const getSSHInfo = () => { return http.post(`/hosts/ssh/search`); }; -export const getSSHConf = () => { - return http.get(`/hosts/ssh/conf`); -}; export const operateSSH = (operation: string) => { return http.post(`/hosts/ssh/operate`, { operation: operation }, TimeoutEnum.T_40S); }; export const updateSSH = (params: Host.SSHUpdate) => { return http.post(`/hosts/ssh/update`, params, TimeoutEnum.T_40S); }; -export const updateSSHByfile = (file: string) => { - return http.post(`/hosts/ssh/conffile/update`, { file: file }, TimeoutEnum.T_40S); +export const loadSSHFile = (name: string) => { + return http.post(`/hosts/ssh/file`, { name: name }); +}; +export const updateSSHByFile = (key: string, file: string) => { + return http.post(`/hosts/ssh/file/update`, { key: key, value: file }, TimeoutEnum.T_60S); }; export const createCert = (params: Host.RootCert) => { let request = deepCopy(params) as Host.RootCert; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 3ea9384590f5..b0ae5490a502 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1576,6 +1576,8 @@ const message = { syncHelper: 'The sync operation will clean invalid keys and sync new complete key pairs. Continue?', input: 'Manual Input', import: 'File Upload', + authKeys: 'Public Key Management', + authKeysHelper: 'Save current public key information?', pubkey: 'Key info', pubKeyHelper: 'The current key information only takes effect for user {0}', encryptionMode: 'Encryption mode', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index c317211ec209..9ccfa1923af7 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -1572,6 +1572,8 @@ const message = { 'La operación de sincronización limpiará las claves inválidas y sincronizará nuevos pares de claves completos. ¿Desea continuar?', input: 'Entrada manual', import: 'Subir archivo', + authKeys: 'Gestión de Claves Públicas', + authKeysHelper: '¿Guardar información actual de clave pública?', pubkey: 'Información de clave', pubKeyHelper: 'La información de la clave actual solo tiene efecto para el usuario {0}', encryptionMode: 'Modo de cifrado', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index c37e6acddbfa..50aa015351d0 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1523,6 +1523,8 @@ const message = { syncHelper: '同期操作は無効なキーをクリーンアップし、新しい完全なキーペアを同期します。続行しますか?', input: '手動入力', import: 'ファイルアップロード', + authKeys: '公開鍵管理', + authKeysHelper: '現在の公開鍵情報を保存しますか?', pubkey: '重要な情報', encryptionMode: '暗号化モード', pubKeyHelper: '現在の鍵情報はユーザー {0} にのみ有効です', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index 216c6b289fce..4024cc052352 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1506,6 +1506,8 @@ const message = { syncHelper: '동기화 작업으로 유효하지 않은 키를 정리하고 새로운 완전한 키 쌍을 동기화합니다. 계속하시겠습니까?', input: '수동 입력', import: '파일 업로드', + authKeys: '공개 키 관리', + authKeysHelper: '현재 공개 키 정보를 저장하시겠습니까?', pubkey: '키 정보', encryptionMode: '암호화 모드', pubKeyHelper: '현재 키 정보는 사용자 {0}에게만 적용됩니다', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 432efb04cffa..def1c1fdea4e 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1565,6 +1565,8 @@ const message = { 'Operasi segerak akan membersihkan kunci tidak sah dan menyegerakkan pasangan kunci baru yang lengkap. Teruskan?', input: 'Input Manual', import: 'Muat Naik Fail', + authKeys: 'Pengurusan Kunci Awam', + authKeysHelper: 'Simpan maklumat kunci awam semasa?', pubkey: 'Maklumat kunci', pubKeyHelper: 'Maklumat kunci semasa hanya berkuat kuasa untuk pengguna {0}', encryptionMode: 'Mod penyulitan', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index aa4a35e5f854..d95fbb9dd35b 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1554,6 +1554,8 @@ const message = { 'A operação de sincronização limpará chaves inválidas e sincronizará novos pares de chaves completos. Continuar?', input: 'Entrada Manual', import: 'Upload de Arquivo', + authKeys: 'Gerenciamento de Chaves Públicas', + authKeysHelper: 'Salvar informações atuais da chave pública?', pubkey: 'Informações da chave', pubKeyHelper: 'A informação da chave atual só tem efeito para o usuário {0}', encryptionMode: 'Modo de criptografia', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index 8174b9785af8..b162f9b784e7 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1556,6 +1556,8 @@ const message = { 'Операция синхронизации удалит недействительные ключи и синхронизирует новые полные ключевые пары. Продолжить?', input: 'Ручной ввод', import: 'Загрузка файла', + authKeys: 'Управление Открытыми Ключами', + authKeysHelper: 'Сохранить текущую информацию об открытом ключе?', pubkey: 'Информация о ключе', pubKeyHelper: 'Текущая информация о ключе действительна только для пользователя {0}', encryptionMode: 'Режим шифрования', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index e7bcef37e218..673b4b3b2c72 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1594,6 +1594,8 @@ const message = { 'Eşitleme işlemi geçersiz anahtarları temizleyecek ve yeni tam anahtar çiftlerini eşitleyecek. Devam edilsin mi?', input: 'Manuel Giriş', import: 'Dosya Yükleme', + authKeys: 'Ortak Anahtar Yönetimi', + authKeysHelper: 'Mevcut ortak anahtar bilgilerini kaydet?', pubkey: 'Anahtar bilgisi', pubKeyHelper: 'Mevcut anahtar bilgileri yalnızca {0} kullanıcısı için geçerlidir', encryptionMode: 'Şifreleme modu', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 73d3af56095d..36381f1cc60d 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1500,6 +1500,8 @@ const message = { syncHelper: '同步操作將清理失效金鑰並同步新增的完整金鑰對,是否繼續?', input: '手動輸入', import: '文件上傳', + authKeys: '公鑰管理', + authKeysHelper: '是否儲存目前公鑰資訊?', pubkey: '金鑰資訊', pubKeyHelper: '目前金鑰資訊僅對使用者 {0} 生效', encryptionMode: '加密方式', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 09c1acc6faa4..0cdf2ea3bfd6 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1496,6 +1496,8 @@ const message = { syncHelper: '同步操作将清理失效密钥并同步新增的完整密钥对,是否继续?', input: '手动输入', import: '文件上传', + authKeys: '公钥管理', + authKeysHelper: '是否保存当前公钥信息?', pubkey: '密钥信息', pubKeyHelper: '当前密钥信息仅对用户 {0} 生效', encryptionMode: '加密方式', diff --git a/frontend/src/views/host/ssh/ssh/auth-keys/index.vue b/frontend/src/views/host/ssh/ssh/auth-keys/index.vue new file mode 100644 index 000000000000..5ed86fe43b96 --- /dev/null +++ b/frontend/src/views/host/ssh/ssh/auth-keys/index.vue @@ -0,0 +1,62 @@ + + + diff --git a/frontend/src/views/host/ssh/ssh/index.vue b/frontend/src/views/host/ssh/ssh/index.vue index bd7f37d0c962..2e4a725b14b6 100644 --- a/frontend/src/views/host/ssh/ssh/index.vue +++ b/frontend/src/views/host/ssh/ssh/index.vue @@ -38,13 +38,16 @@ -