Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 32 additions & 25 deletions agent/app/api/v2/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
78 changes: 53 additions & 25 deletions agent/app/service/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions agent/router/ro_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/api/modules/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,17 @@ export const updateMonitorSetting = (key: string, value: string) => {
export const getSSHInfo = () => {
return http.post<Host.SSHInfo>(`/hosts/ssh/search`);
};
export const getSSHConf = () => {
return http.get<string>(`/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<string>(`/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;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/es-es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,8 @@ const message = {
syncHelper: '同期操作は無効なキーをクリーンアップし、新しい完全なキーペアを同期します。続行しますか?',
input: '手動入力',
import: 'ファイルアップロード',
authKeys: '公開鍵管理',
authKeysHelper: '現在の公開鍵情報を保存しますか?',
pubkey: '重要な情報',
encryptionMode: '暗号化モード',
pubKeyHelper: '現在の鍵情報はユーザー {0} にのみ有効です',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1506,6 +1506,8 @@ const message = {
syncHelper: '동기화 작업으로 유효하지 않은 키를 정리하고 새로운 완전한 키 쌍을 동기화합니다. 계속하시겠습니까?',
input: '수동 입력',
import: '파일 업로드',
authKeys: '공개 키 관리',
authKeysHelper: '현재 공개 키 정보를 저장하시겠습니까?',
pubkey: '키 정보',
encryptionMode: '암호화 모드',
pubKeyHelper: '현재 키 정보는 사용자 {0}에게만 적용됩니다',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/ms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/pt-br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,8 @@ const message = {
'Операция синхронизации удалит недействительные ключи и синхронизирует новые полные ключевые пары. Продолжить?',
input: 'Ручной ввод',
import: 'Загрузка файла',
authKeys: 'Управление Открытыми Ключами',
authKeysHelper: 'Сохранить текущую информацию об открытом ключе?',
pubkey: 'Информация о ключе',
pubKeyHelper: 'Текущая информация о ключе действительна только для пользователя {0}',
encryptionMode: 'Режим шифрования',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/zh-Hant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1500,6 +1500,8 @@ const message = {
syncHelper: '同步操作將清理失效金鑰並同步新增的完整金鑰對,是否繼續?',
input: '手動輸入',
import: '文件上傳',
authKeys: '公鑰管理',
authKeysHelper: '是否儲存目前公鑰資訊?',
pubkey: '金鑰資訊',
pubKeyHelper: '目前金鑰資訊僅對使用者 {0} 生效',
encryptionMode: '加密方式',
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/lang/modules/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,8 @@ const message = {
syncHelper: '同步操作将清理失效密钥并同步新增的完整密钥对,是否继续?',
input: '手动输入',
import: '文件上传',
authKeys: '公钥管理',
authKeysHelper: '是否保存当前公钥信息?',
pubkey: '密钥信息',
pubKeyHelper: '当前密钥信息仅对用户 {0} 生效',
encryptionMode: '加密方式',
Expand Down
62 changes: 62 additions & 0 deletions frontend/src/views/host/ssh/ssh/auth-keys/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<template>
<div>
<DrawerPro v-model="open" :header="$t('ssh.authKeys')" size="large">
<div v-loading="loading">
<CodemirrorPro
:heightDiff="160"
v-model="conf"
:lineWrapping="true"
placeholder="# The authorized_keys file does not exist or is empty (~/ssh/authorized_keys)"
/>
</div>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="loading" type="primary" @click="onSaveFile">
{{ $t('commons.button.save') }}
</el-button>
</span>
</template>
</DrawerPro>
</div>
</template>

<script setup lang="ts">
import { loadSSHFile, updateSSHByFile } from '@/api/modules/host';
import i18n from '@/lang';
import { MsgSuccess } from '@/utils/message';
import { ref } from 'vue';
const conf = ref();
const loading = ref();
const open = ref();

const acceptParams = async (): Promise<void> => {
loadFile();
open.value = true;
};
const loadFile = async () => {
const res = await loadSSHFile('authKeys');
conf.value = res.data || '';
};
const onSaveFile = async () => {
ElMessageBox.confirm(i18n.global.t('ssh.authKeysHelper'), i18n.global.t('ssh.authKeys'), {
confirmButtonText: i18n.global.t('commons.button.confirm'),
cancelButtonText: i18n.global.t('commons.button.cancel'),
type: 'info',
}).then(async () => {
loading.value = true;
await updateSSHByFile('authKeys', conf.value)
.then(() => {
loading.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
open.value = false;
})
.catch(() => {
loading.value = false;
});
});
};

defineExpose({
acceptParams,
});
</script>
Loading
Loading