mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 01:57:19 +08:00
feat: ssh支持私钥密码,close #774
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 = () => {
|
||||
>
|
||||
</n-select>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="model.auth_method == 'password'" :label="$gettext('Username')">
|
||||
<n-form-item :label="$gettext('Username')">
|
||||
<n-input v-model:value="model.user" placeholder="root" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="model.auth_method == 'password'" :label="$gettext('Password')">
|
||||
<n-input v-model:value="model.password" type="password" show-password-on="click" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="model.auth_method == 'publickey'" :label="$gettext('Private Key')">
|
||||
<n-input v-model:value="model.key" type="textarea" />
|
||||
<n-input v-model:value="model.key" type="textarea" :rows="5" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="model.auth_method == 'publickey'" :label="$gettext('Key Passphrase')">
|
||||
<n-input
|
||||
v-model:value="model.passphrase"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
:placeholder="$gettext('Leave empty if key has no passphrase')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Remarks')">
|
||||
<n-input v-model:value="model.remark" type="textarea" />
|
||||
|
||||
@@ -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 () => {
|
||||
>
|
||||
</n-select>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="model.auth_method == 'password'" :label="$gettext('Username')">
|
||||
<n-form-item :label="$gettext('Username')">
|
||||
<n-input v-model:value="model.user" placeholder="root" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="model.auth_method == 'password'" :label="$gettext('Password')">
|
||||
<n-input v-model:value="model.password" type="password" show-password-on="click" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="model.auth_method == 'publickey'" :label="$gettext('Private Key')">
|
||||
<n-input v-model:value="model.key" type="textarea" />
|
||||
<n-input v-model:value="model.key" type="textarea" :rows="5" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="model.auth_method == 'publickey'" :label="$gettext('Key Passphrase')">
|
||||
<n-input
|
||||
v-model:value="model.passphrase"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
:placeholder="$gettext('Leave empty if key has no passphrase')"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item :label="$gettext('Remarks')">
|
||||
<n-input v-model:value="model.remark" type="textarea" />
|
||||
|
||||
Reference in New Issue
Block a user