feat: support ssh key passphrase

This commit is contained in:
Fu Diwei 2024-10-16 12:30:15 +08:00
parent 79dce124a7
commit 71f43c5bd4
8 changed files with 70 additions and 349 deletions

View File

@ -18,11 +18,12 @@ type ssh struct {
} }
type sshAccess struct { type sshAccess struct {
Host string `json:"host"` Host string `json:"host"`
Username string `json:"username"` Port string `json:"port"`
Password string `json:"password"` Username string `json:"username"`
Key string `json:"key"` Password string `json:"password"`
Port string `json:"port"` Key string `json:"key"`
KeyPassphrase string `json:"keyPassphrase"`
} }
func NewSSH(option *DeployerOption) (Deployer, error) { func NewSSH(option *DeployerOption) (Deployer, error) {
@ -45,6 +46,7 @@ func (s *ssh) Deploy(ctx context.Context) error {
if err := json.Unmarshal([]byte(s.option.Access), access); err != nil { if err := json.Unmarshal([]byte(s.option.Access), access); err != nil {
return err return err
} }
// 连接 // 连接
client, err := s.getClient(access) client, err := s.getClient(access)
if err != nil { if err != nil {
@ -57,7 +59,7 @@ func (s *ssh) Deploy(ctx context.Context) error {
// 执行前置命令 // 执行前置命令
preCommand := getDeployString(s.option.DeployConfig, "preCommand") preCommand := getDeployString(s.option.DeployConfig, "preCommand")
if preCommand != "" { if preCommand != "" {
err, stdout, stderr := s.sshExecCommand(client, preCommand) stdout, stderr, err := s.sshExecCommand(client, preCommand)
if err != nil { if err != nil {
return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr) return fmt.Errorf("failed to run pre-command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
} }
@ -78,7 +80,7 @@ func (s *ssh) Deploy(ctx context.Context) error {
s.infos = append(s.infos, toStr("ssh上传私钥成功", nil)) s.infos = append(s.infos, toStr("ssh上传私钥成功", nil))
// 执行命令 // 执行命令
err, stdout, stderr := s.sshExecCommand(client, getDeployString(s.option.DeployConfig, "command")) stdout, stderr, err := s.sshExecCommand(client, getDeployString(s.option.DeployConfig, "command"))
if err != nil { if err != nil {
return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr) return fmt.Errorf("failed to run command: %w, stdout: %s, stderr: %s", err, stdout, stderr)
} }
@ -88,18 +90,19 @@ func (s *ssh) Deploy(ctx context.Context) error {
return nil return nil
} }
func (s *ssh) sshExecCommand(client *sshPkg.Client, command string) (error, string, string) { func (s *ssh) sshExecCommand(client *sshPkg.Client, command string) (string, string, error) {
session, err := client.NewSession() session, err := client.NewSession()
if err != nil { if err != nil {
return fmt.Errorf("failed to create ssh session: %w", err), "", "" return "", "", fmt.Errorf("failed to create ssh session: %w", err)
} }
defer session.Close() defer session.Close()
var stdoutBuf bytes.Buffer var stdoutBuf bytes.Buffer
session.Stdout = &stdoutBuf session.Stdout = &stdoutBuf
var stderrBuf bytes.Buffer var stderrBuf bytes.Buffer
session.Stderr = &stderrBuf session.Stderr = &stderrBuf
err = session.Run(command) err = session.Run(command)
return err, stdoutBuf.String(), stderrBuf.String() return stdoutBuf.String(), stderrBuf.String(), err
} }
func (s *ssh) upload(client *sshPkg.Client, content, path string) error { func (s *ssh) upload(client *sshPkg.Client, content, path string) error {
@ -131,7 +134,15 @@ func (s *ssh) getClient(access *sshAccess) (*sshPkg.Client, error) {
var authMethod sshPkg.AuthMethod var authMethod sshPkg.AuthMethod
if access.Key != "" { if access.Key != "" {
signer, err := sshPkg.ParsePrivateKey([]byte(access.Key)) var signer sshPkg.Signer
var err error
if access.KeyPassphrase != "" {
signer, err = sshPkg.ParsePrivateKeyWithPassphrase([]byte(access.Key), []byte(access.KeyPassphrase))
} else {
signer, err = sshPkg.ParsePrivateKey([]byte(access.Key))
}
if err != nil { if err != nil {
return nil, err return nil, err
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
ui/dist/index.html vendored
View File

@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Certimate - Your Trusted SSL Automation Partner</title> <title>Certimate - Your Trusted SSL Automation Partner</title>
<script type="module" crossorigin src="/assets/index-DipHpsma.js"></script> <script type="module" crossorigin src="/assets/index-DIhd7QG6.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CV_7sKTK.css"> <link rel="stylesheet" crossorigin href="/assets/index-CV_7sKTK.css">
</head> </head>
<body class="bg-background"> <body class="bg-background">

View File

@ -66,17 +66,21 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
.max(5, t("common.errmsg.string_max", { max: 5 })), .max(5, t("common.errmsg.string_max", { max: 5 })),
username: z username: z
.string() .string()
.min(1, "username.not.empty") .min(1, "access.authorization.form.ssh_username.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })), .max(64, t("common.errmsg.string_max", { max: 64 })),
password: z password: z
.string() .string()
.min(0, "password.not.empty") .min(0, "access.authorization.form.ssh_password.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })), .max(64, t("common.errmsg.string_max", { max: 64 })),
key: z key: z
.string() .string()
.min(0, "access.authorization.form.ssh_key.placeholder") .min(0, "access.authorization.form.ssh_key.placeholder")
.max(20480, t("common.errmsg.string_max", { max: 20480 })), .max(20480, t("common.errmsg.string_max", { max: 20480 })),
keyFile: z.any().optional(), keyFile: z.any().optional(),
keyPassphrase: z
.string()
.min(0, "access.authorization.form.ssh_key_passphrase.placeholder")
.max(2048, t("common.errmsg.string_max", { max: 2048 })),
}); });
let config: SSHConfig = { let config: SSHConfig = {
@ -86,6 +90,7 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
password: "", password: "",
key: "", key: "",
keyFile: "", keyFile: "",
keyPassphrase: "",
}; };
if (data) config = data.config as SSHConfig; if (data) config = data.config as SSHConfig;
@ -102,6 +107,7 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
password: config.password, password: config.password,
key: config.key, key: config.key,
keyFile: config.keyFile, keyFile: config.keyFile,
keyPassphrase: config.keyPassphrase,
}, },
}); });
@ -121,6 +127,7 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
username: data.username, username: data.username,
password: data.password, password: data.password,
key: data.key, key: data.key,
keyPassphrase: data.keyPassphrase,
}, },
}; };
@ -322,9 +329,9 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
name="username" name="username"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t("access.authorization.form.username.label")}</FormLabel> <FormLabel>{t("access.authorization.form.ssh_username.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t("access.authorization.form.username.placeholder")} {...field} /> <Input placeholder={t("access.authorization.form.ssh_username.placeholder")} {...field} />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -337,9 +344,9 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
name="password" name="password"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel>{t("access.authorization.form.password.label")}</FormLabel> <FormLabel>{t("access.authorization.form.ssh_password.label")}</FormLabel>
<FormControl> <FormControl>
<Input placeholder={t("access.authorization.form.password.placeholder")} {...field} type="password" /> <Input placeholder={t("access.authorization.form.ssh_password.placeholder")} {...field} type="password" />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -390,6 +397,21 @@ const AccessSSHForm = ({ data, op, onAfterReq }: AccessSSHFormProps) => {
)} )}
/> />
<FormField
control={form.control}
name="keyPassphrase"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.authorization.form.ssh_key_passphrase.label")}</FormLabel>
<FormControl>
<Input placeholder={t("access.authorization.form.ssh_key_passphrase.placeholder")} {...field} type="password" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormMessage /> <FormMessage />
<div className="flex justify-end"> <div className="flex justify-end">

View File

@ -100,6 +100,7 @@ export type SSHConfig = {
password?: string; password?: string;
key?: string; key?: string;
keyFile?: string; keyFile?: string;
keyPassphrase?: string;
}; };
export type WebhookConfig = { export type WebhookConfig = {

View File

@ -46,9 +46,15 @@
"access.authorization.form.ssh_host.placeholder": "Please enter Host", "access.authorization.form.ssh_host.placeholder": "Please enter Host",
"access.authorization.form.ssh_port.label": "SSH Port", "access.authorization.form.ssh_port.label": "SSH Port",
"access.authorization.form.ssh_port.placeholder": "Please enter Port", "access.authorization.form.ssh_port.placeholder": "Please enter Port",
"access.authorization.form.ssh_key.label": "Key (Log in using private key)", "access.authorization.form.ssh_username.label": "Username",
"access.authorization.form.ssh_username.placeholder": "Please enter username",
"access.authorization.form.ssh_password.label": "Password (Log-in using password)",
"access.authorization.form.ssh_password.placeholder": "Please enter password",
"access.authorization.form.ssh_key.label": "Key (Log-in using private key)",
"access.authorization.form.ssh_key.placeholder": "Please enter Key", "access.authorization.form.ssh_key.placeholder": "Please enter Key",
"access.authorization.form.ssh_key_file.placeholder": "Please select file", "access.authorization.form.ssh_key_file.placeholder": "Please select file",
"access.authorization.form.ssh_key_passphrase.label": "Key Passphrase (Log-in using private key)",
"access.authorization.form.ssh_key_passphrase.placeholder": "Please enter Key Passphrase",
"access.authorization.form.ssh_key_path.label": "Private Key Save Path", "access.authorization.form.ssh_key_path.label": "Private Key Save Path",
"access.authorization.form.ssh_key_path.placeholder": "Please enter private key save path", "access.authorization.form.ssh_key_path.placeholder": "Please enter private key save path",
"access.authorization.form.ssh_cert_path.label": "Certificate Save Path", "access.authorization.form.ssh_cert_path.label": "Certificate Save Path",

View File

@ -46,10 +46,15 @@
"access.authorization.form.ssh_host.placeholder": "请输入 Host", "access.authorization.form.ssh_host.placeholder": "请输入 Host",
"access.authorization.form.ssh_port.label": "SSH 端口", "access.authorization.form.ssh_port.label": "SSH 端口",
"access.authorization.form.ssh_port.placeholder": "请输入 Port", "access.authorization.form.ssh_port.placeholder": "请输入 Port",
"access.authorization.form.ssh_username.label": "用户名",
"access.authorization.form.ssh_username.placeholder": "请输入用户名",
"access.authorization.form.ssh_password.label": "密码(使用密码登录)",
"access.authorization.form.ssh_password.placeholder": "请输入密码",
"access.authorization.form.ssh_key.label": "Key使用私钥登录", "access.authorization.form.ssh_key.label": "Key使用私钥登录",
"access.authorization.form.ssh_key.placeholder": "请输入 Key", "access.authorization.form.ssh_key.placeholder": "请输入 Key",
"access.authorization.form.ssh_key_file.placeholder": "请选择文件", "access.authorization.form.ssh_key_file.placeholder": "请选择文件",
"access.authorization.form.ssh_key.label.passphrase": "私钥密码", "access.authorization.form.ssh_key_passphrase.label": "Key 口令(使用私钥登录)",
"access.authorization.form.ssh_key_passphrase.placeholder": "请输入 Key 口令",
"access.authorization.form.ssh_key_path.label": "私钥保存路径", "access.authorization.form.ssh_key_path.label": "私钥保存路径",
"access.authorization.form.ssh_key_path.placeholder": "请输入私钥保存路径", "access.authorization.form.ssh_key_path.placeholder": "请输入私钥保存路径",
"access.authorization.form.ssh_cert_path.label": "证书保存路径", "access.authorization.form.ssh_cert_path.label": "证书保存路径",