From b19cdd9339c85c21e371f3c02d0dfeed348eea73 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Mon, 15 May 2023 19:00:40 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=94=9F=E6=88=90=20ssh=20=E5=AF=86?= =?UTF-8?q?=E9=92=A5=E5=8A=A0=E5=AF=86=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/ssh.go | 54 +++++++++++ backend/app/dto/ssh.go | 16 ++-- backend/app/service/ssh.go | 56 ++++++++++- backend/app/service/ssh_test.go | 47 ---------- backend/router/ro_host.go | 2 + frontend/src/api/interface/host.ts | 4 + frontend/src/api/modules/host.ts | 7 +- frontend/src/lang/modules/zh.ts | 1 + frontend/src/views/host/ssh/index.vue | 4 +- frontend/src/views/host/ssh/pubkey/index.vue | 99 ++++++++++++++++++-- 10 files changed, 221 insertions(+), 69 deletions(-) delete mode 100644 backend/app/service/ssh_test.go diff --git a/backend/app/api/v1/ssh.go b/backend/app/api/v1/ssh.go index b7b958b7e..236c3b0a2 100644 --- a/backend/app/api/v1/ssh.go +++ b/backend/app/api/v1/ssh.go @@ -49,3 +49,57 @@ func (b *BaseApi) UpdateSSH(c *gin.Context) { } helper.SuccessWithData(c, nil) } + +// @Tags SSH +// @Summary Generate host ssh secret +// @Description 生成 ssh 密钥 +// @Accept json +// @Param request body dto.GenerateSSH true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /host/ssh/generate [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFuntions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"} +func (b *BaseApi) GenerateSSH(c *gin.Context) { + var req dto.GenerateSSH + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + if err := sshService.GenerateSSH(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags SSH +// @Summary Load host ssh secret +// @Description 获取 ssh 密钥 +// @Accept json +// @Param request body dto.GenerateLoad true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Router /host/ssh/secret [post] +func (b *BaseApi) LoadSSHSecret(c *gin.Context) { + var req dto.GenerateLoad + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + data, err := sshService.LoadSSHSecret(req.EncryptionMode) + if err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, data) +} diff --git a/backend/app/dto/ssh.go b/backend/app/dto/ssh.go index 578b828d2..6191ccd34 100644 --- a/backend/app/dto/ssh.go +++ b/backend/app/dto/ssh.go @@ -1,15 +1,19 @@ package dto type SSHInfo struct { - Port string `json:"port"` + Port string `json:"port" validate:"required,number,max=65535,min=1"` ListenAddress string `json:"listenAddress"` - PasswordAuthentication string `json:"passwordAuthentication"` - PubkeyAuthentication string `json:"pubkeyAuthentication"` - PermitRootLogin string `json:"permitRootLogin"` - UseDNS string `json:"useDNS"` + PasswordAuthentication string `json:"passwordAuthentication" validate:"required,oneof=yes no"` + PubkeyAuthentication string `json:"pubkeyAuthentication" validate:"required,oneof=yes no"` + PermitRootLogin string `json:"permitRootLogin" validate:"required,oneof=yes no without-password forced-commands-only"` + UseDNS string `json:"useDNS" validate:"required,oneof=yes no"` } type GenerateSSH struct { - EncryptionMode string `json:"encryptionMode"` + EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"` Password string `json:"password"` } + +type GenerateLoad struct { + EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"` +} diff --git a/backend/app/service/ssh.go b/backend/app/service/ssh.go index 0a4b27042..ab0c45924 100644 --- a/backend/app/service/ssh.go +++ b/backend/app/service/ssh.go @@ -3,13 +3,15 @@ package service import ( "fmt" "os" + "os/user" "strings" "github.com/1Panel-dev/1Panel/backend/app/dto" "github.com/1Panel-dev/1Panel/backend/utils/cmd" + "github.com/1Panel-dev/1Panel/backend/utils/files" ) -const sshPath = "Downloads/sshd_config" +const sshPath = "/etc/ssh/sshd_config" type SSHService struct{} @@ -17,6 +19,7 @@ type ISSHService interface { GetSSHInfo() (*dto.SSHInfo, error) Update(key, value string) error GenerateSSH(req dto.GenerateSSH) error + LoadSSHSecret(mode string) (string, error) } func NewISSHService() ISSHService { @@ -82,13 +85,62 @@ func (u *SSHService) Update(key, value string) error { } func (u *SSHService) GenerateSSH(req dto.GenerateSSH) error { - stdout, err := cmd.Exec(fmt.Sprintf("ssh-keygen -t %s -P %s -f ~/.ssh/id_%s |echo y", req.EncryptionMode, req.Password, req.EncryptionMode)) + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("load current user failed, err: %v", err) + } + secretFile := fmt.Sprintf("%s/.ssh/id_item_%s", currentUser.HomeDir, req.EncryptionMode) + secretPubFile := fmt.Sprintf("%s/.ssh/id_item_%s.pub", currentUser.HomeDir, req.EncryptionMode) + authFile := currentUser.HomeDir + "/.ssh/authorized_keys" + + command := fmt.Sprintf("ssh-keygen -t %s -f %s/.ssh/id_item_%s | echo y", req.EncryptionMode, currentUser.HomeDir, req.EncryptionMode) + if len(req.Password) != 0 { + command = fmt.Sprintf("ssh-keygen -t %s -P %s -f %s/.ssh/id_item_%s | echo y", req.EncryptionMode, req.Password, currentUser.HomeDir, req.EncryptionMode) + } + stdout, err := cmd.Exec(command) if err != nil { return fmt.Errorf("generate failed, err: %v, message: %s", err, stdout) } + defer func() { + _ = os.Remove(secretFile) + }() + defer func() { + _ = os.Remove(secretPubFile) + }() + + if _, err := os.Stat(authFile); err != nil { + _, _ = os.Create(authFile) + } + stdout1, err := cmd.Execf("cat %s >> %s/.ssh/authorized_keys", secretPubFile, currentUser.HomeDir) + if err != nil { + return fmt.Errorf("generate failed, err: %v, message: %s", err, stdout1) + } + + fileOp := files.NewFileOp() + if err := fileOp.Rename(secretFile, fmt.Sprintf("%s/.ssh/id_%s", currentUser.HomeDir, req.EncryptionMode)); err != nil { + return err + } + if err := fileOp.Rename(secretPubFile, fmt.Sprintf("%s/.ssh/id_%s.pub", currentUser.HomeDir, req.EncryptionMode)); err != nil { + return err + } + return nil } +func (u *SSHService) LoadSSHSecret(mode string) (string, error) { + currentUser, err := user.Current() + if err != nil { + return "", fmt.Errorf("load current user failed, err: %v", err) + } + + homeDir := currentUser.HomeDir + if _, err := os.Stat(fmt.Sprintf("%s/.ssh/id_%s", homeDir, mode)); err != nil { + return "", nil + } + file, err := os.ReadFile(fmt.Sprintf("%s/.ssh/id_%s", homeDir, mode)) + return string(file), err +} + func updateSSHConf(oldFiles []string, param string, value interface{}) []string { hasKey := false var newFiles []string diff --git a/backend/app/service/ssh_test.go b/backend/app/service/ssh_test.go deleted file mode 100644 index c105651c8..000000000 --- a/backend/app/service/ssh_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package service - -import ( - "fmt" - "os" - "strings" - "testing" - - "github.com/1Panel-dev/1Panel/backend/app/dto" -) - -func TestSfq(t *testing.T) { - data := dto.SSHInfo{ - Port: "22", - ListenAddress: "0.0.0.0", - PasswordAuthentication: "yes", - PubkeyAuthentication: "yes", - PermitRootLogin: "yes", - UseDNS: "yes", - } - sshConf, err := os.ReadFile("/Downloads/sshd_config") - if err != nil { - fmt.Println(err) - } - lines := strings.Split(string(sshConf), "\n") - for _, line := range lines { - if strings.HasPrefix(line, "Port ") { - data.Port = strings.ReplaceAll(line, "Port ", "") - } - if strings.HasPrefix(line, "ListenAddress ") { - data.ListenAddress = strings.ReplaceAll(line, "ListenAddress ", "") - } - if strings.HasPrefix(line, "PasswordAuthentication ") { - data.PasswordAuthentication = strings.ReplaceAll(line, "PasswordAuthentication ", "") - } - if strings.HasPrefix(line, "PubkeyAuthentication ") { - data.PubkeyAuthentication = strings.ReplaceAll(line, "PubkeyAuthentication ", "") - } - if strings.HasPrefix(line, "PermitRootLogin ") { - data.PermitRootLogin = strings.ReplaceAll(line, "PermitRootLogin ", "") - } - if strings.HasPrefix(line, "UseDNS ") { - data.UseDNS = strings.ReplaceAll(line, "UseDNS ", "") - } - } - fmt.Println(data) -} diff --git a/backend/router/ro_host.go b/backend/router/ro_host.go index cc7350a3c..51a4ea74d 100644 --- a/backend/router/ro_host.go +++ b/backend/router/ro_host.go @@ -37,6 +37,8 @@ func (s *HostRouter) InitHostRouter(Router *gin.RouterGroup) { hostRouter.POST("/ssh/search", baseApi.GetSSHInfo) hostRouter.POST("/ssh/update", baseApi.UpdateSSH) + hostRouter.POST("/ssh/generate", baseApi.GenerateSSH) + hostRouter.POST("/ssh/secret", baseApi.LoadSSHSecret) hostRouter.GET("/command", baseApi.ListCommand) hostRouter.POST("/command", baseApi.CreateCommand) diff --git a/frontend/src/api/interface/host.ts b/frontend/src/api/interface/host.ts index 127b3780b..72031e5e9 100644 --- a/frontend/src/api/interface/host.ts +++ b/frontend/src/api/interface/host.ts @@ -114,4 +114,8 @@ export namespace Host { permitRootLogin: string; useDNS: string; } + export interface SSHGenerate { + encryptionMode: string; + password: string; + } } diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts index 8a2e41787..10195a3cc 100644 --- a/frontend/src/api/modules/host.ts +++ b/frontend/src/api/modules/host.ts @@ -104,6 +104,9 @@ export const getSSHInfo = () => { export const updateSSH = (key: string, value: string) => { return http.post(`/hosts/ssh/update`, { key: key, value: value }); }; -export const generatePubKey = (encryptionMode: string) => { - return http.post(`/hosts/ssh/generate`, { encryptionMode: encryptionMode }); +export const generateSecret = (params: Host.SSHGenerate) => { + return http.post(`/hosts/ssh/generate`, params); +}; +export const loadSecret = (mode: string) => { + return http.post(`/hosts/ssh/secret`, { encryptionMode: mode }); }; diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index b504de74c..f38a77c04 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -839,6 +839,7 @@ const message = { key: '密钥', pubkey: '密钥信息', encryptionMode: '加密方式', + passwordHelper: '请输入 6-10 位加密密码', generate: '生成密钥', reGenerate: '重新生成密钥', keyAuthHelper: '是否启用密钥认证,默认启用。', diff --git a/frontend/src/views/host/ssh/index.vue b/frontend/src/views/host/ssh/index.vue index 7c612d1bc..902ab4bba 100644 --- a/frontend/src/views/host/ssh/index.vue +++ b/frontend/src/views/host/ssh/index.vue @@ -147,7 +147,7 @@ const form = reactive({ const onSaveFile = async () => { loading.value = true; - await SaveFileContent({ path: '/Users/slooop/Downloads/sshd_config', content: sshConf.value }) + await SaveFileContent({ path: '/etc/ssh/sshd_config', content: sshConf.value }) .then(() => { loading.value = false; MsgSuccess(i18n.global.t('commons.msg.operationSuccess')); @@ -202,7 +202,7 @@ function callback(error: any) { } const loadSSHConf = async () => { - const res = await LoadFile({ path: '/Users/slooop/Downloads/sshd_config' }); + const res = await LoadFile({ path: '/etc/ssh/sshd_config' }); sshConf.value = res.data || ''; }; diff --git a/frontend/src/views/host/ssh/pubkey/index.vue b/frontend/src/views/host/ssh/pubkey/index.vue index c58664aff..1868b2069 100644 --- a/frontend/src/views/host/ssh/pubkey/index.vue +++ b/frontend/src/views/host/ssh/pubkey/index.vue @@ -5,26 +5,34 @@ :destroy-on-close="true" @close="handleClose" :close-on-click-modal="false" - size="50%" + size="30%" > - + - + - - + + + + + + {{ form.primaryKey ? $t('ssh.reGenerate') : $t('ssh.generate') }} +
- + {{ $t('file.copy') }} @@ -52,27 +66,92 @@