From 89ace2360c82a09c5ffbab2e60b43732ccc11daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Thu, 8 Jan 2026 23:24:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20ssh=E6=94=AF=E6=8C=81=E7=A7=81=E9=92=A5?= =?UTF-8?q?=E5=AF=86=E7=A0=81=EF=BC=8Cclose=20#774?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/data/ssh.go | 9 +++++++-- internal/http/request/ssh.go | 6 ++++-- pkg/ssh/ssh.go | 32 +++++++++++++++++++++++++------ web/src/views/ssh/CreateModal.vue | 14 ++++++++++++-- web/src/views/ssh/UpdateModal.vue | 14 ++++++++++++-- 5 files changed, 61 insertions(+), 14 deletions(-) diff --git a/internal/data/ssh.go b/internal/data/ssh.go index 32a19917..ae130664 100644 --- a/internal/data/ssh.go +++ b/internal/data/ssh.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/leonelquinteros/gotext" + cryptossh "golang.org/x/crypto/ssh" "gorm.io/gorm" "github.com/acepanel/panel/internal/biz" @@ -47,11 +48,13 @@ func (r *sshRepo) Create(req *request.SSHCreate) error { User: req.User, Password: req.Password, Key: req.Key, + Passphrase: req.Passphrase, } - _, err := pkgssh.NewSSHClient(conf) + client, err := pkgssh.NewSSHClient(conf) if err != nil { return errors.New(r.t.Get("failed to check ssh connection: %v", err)) } + defer func(client *cryptossh.Client) { _ = client.Close() }(client) ssh := &biz.SSH{ Name: req.Name, @@ -71,11 +74,13 @@ func (r *sshRepo) Update(req *request.SSHUpdate) error { User: req.User, Password: req.Password, Key: req.Key, + Passphrase: req.Passphrase, } - _, err := pkgssh.NewSSHClient(conf) + client, err := pkgssh.NewSSHClient(conf) if err != nil { return errors.New(r.t.Get("failed to check ssh connection: %v", err)) } + defer func(client *cryptossh.Client) { _ = client.Close() }(client) ssh := &biz.SSH{ ID: req.ID, diff --git a/internal/http/request/ssh.go b/internal/http/request/ssh.go index 52dab1c7..ee98e1ae 100644 --- a/internal/http/request/ssh.go +++ b/internal/http/request/ssh.go @@ -5,9 +5,10 @@ type SSHCreate struct { Host string `json:"host" form:"host" validate:"required"` Port uint `json:"port" form:"port" validate:"required|min:1|max:65535"` AuthMethod string `json:"auth_method" form:"auth_method" validate:"required|in:password,publickey"` - User string `json:"user" form:"user" validate:"requiredIf:AuthMethod,password"` + User string `json:"user" form:"user" validate:"required"` Password string `json:"password" form:"password" validate:"requiredIf:AuthMethod,password"` Key string `json:"key" form:"key" validate:"requiredIf:AuthMethod,publickey"` + Passphrase string `json:"passphrase" form:"passphrase"` Remark string `json:"remark" form:"remark"` } @@ -17,8 +18,9 @@ type SSHUpdate struct { Host string `json:"host" form:"host" validate:"required"` Port uint `json:"port" form:"port" validate:"required|min:1|max:65535"` AuthMethod string `json:"auth_method" form:"auth_method" validate:"required|in:password,publickey"` - User string `json:"user" form:"user" validate:"requiredIf:AuthMethod,password"` + User string `json:"user" form:"user" validate:"required"` Password string `json:"password" form:"password" validate:"requiredIf:AuthMethod,password"` Key string `json:"key" form:"key" validate:"requiredIf:AuthMethod,publickey"` + Passphrase string `json:"passphrase" form:"passphrase"` Remark string `json:"remark" form:"remark"` } diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go index 278d2e31..fb44d177 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/ssh/ssh.go @@ -1,6 +1,7 @@ package ssh import ( + "errors" "time" "golang.org/x/crypto/ssh" @@ -19,26 +20,28 @@ type ClientConfig struct { User string `json:"user"` Password string `json:"password"` Key string `json:"key"` + Passphrase string `json:"passphrase"` Timeout time.Duration `json:"timeout"` } -func ClientConfigPassword(host, user, Password string) *ClientConfig { +func ClientConfigPassword(host, user, password string) *ClientConfig { return &ClientConfig{ Timeout: 10 * time.Second, AuthMethod: PASSWORD, Host: host, User: user, - Password: Password, + Password: password, } } -func ClientConfigPublicKey(host, user, key string) *ClientConfig { +func ClientConfigPublicKey(host, user, key, passphrase string) *ClientConfig { return &ClientConfig{ Timeout: 10 * time.Second, AuthMethod: PUBLICKEY, Host: host, User: user, Key: key, + Passphrase: passphrase, } } @@ -57,7 +60,7 @@ func NewSSHClient(conf ClientConfig) (*ssh.Client, error) { case PASSWORD: config.Auth = []ssh.AuthMethod{ssh.Password(conf.Password)} case PUBLICKEY: - signer, err := parseKey(conf.Key) + signer, err := parseKey(conf.Key, conf.Passphrase) if err != nil { return nil, err } @@ -71,6 +74,23 @@ func NewSSHClient(conf ClientConfig) (*ssh.Client, error) { return c, nil } -func parseKey(key string) (ssh.Signer, error) { - return ssh.ParsePrivateKey([]byte(key)) +// parseKey 解析私钥 +func parseKey(key, passphrase string) (ssh.Signer, error) { + keyBytes := []byte(key) + + if passphrase != "" { + return ssh.ParsePrivateKeyWithPassphrase(keyBytes, []byte(passphrase)) + } + + signer, err := ssh.ParsePrivateKey(keyBytes) + if err != nil { + // 密钥被加密 + var passphraseMissingError *ssh.PassphraseMissingError + if errors.As(err, &passphraseMissingError) { + return nil, passphraseMissingError + } + return nil, err + } + + return signer, nil } diff --git a/web/src/views/ssh/CreateModal.vue b/web/src/views/ssh/CreateModal.vue index eed45fe6..228f8389 100644 --- a/web/src/views/ssh/CreateModal.vue +++ b/web/src/views/ssh/CreateModal.vue @@ -15,6 +15,7 @@ const model = ref({ user: 'root', password: '', key: '', + passphrase: '', remark: '' }) @@ -32,6 +33,7 @@ const handleSubmit = () => { user: 'root', password: '', key: '', + passphrase: '', remark: '' } window.$bus.emit('ssh:refresh') @@ -80,14 +82,22 @@ const handleSubmit = () => { > - + - + + + + diff --git a/web/src/views/ssh/UpdateModal.vue b/web/src/views/ssh/UpdateModal.vue index 95708177..bd24e700 100644 --- a/web/src/views/ssh/UpdateModal.vue +++ b/web/src/views/ssh/UpdateModal.vue @@ -16,6 +16,7 @@ const model = ref({ user: 'root', password: '', key: '', + passphrase: '', remark: '' }) @@ -44,6 +45,7 @@ watch(show, async () => { model.value.user = data.config.user model.value.password = data.config.password model.value.key = data.config.key + model.value.passphrase = data.config.passphrase || '' model.value.remark = data.remark } }) @@ -86,14 +88,22 @@ watch(show, async () => { > - + - + + + +