diff --git a/internal/certdeploy/deployers/sp_ssh.go b/internal/certdeploy/deployers/sp_ssh.go index e939e6f16..d2591905f 100644 --- a/internal/certdeploy/deployers/sp_ssh.go +++ b/internal/certdeploy/deployers/sp_ssh.go @@ -44,10 +44,10 @@ func init() { PreCommand: xmaps.GetString(options.ProviderExtendedConfig, "preCommand"), PostCommand: xmaps.GetString(options.ProviderExtendedConfig, "postCommand"), OutputFormat: ssh.OutputFormatType(xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(ssh.OUTPUT_FORMAT_PEM))), + OutputKeyPath: xmaps.GetString(options.ProviderExtendedConfig, "keyPath"), OutputCertPath: xmaps.GetString(options.ProviderExtendedConfig, "certPath"), OutputServerCertPath: xmaps.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"), OutputIntermediaCertPath: xmaps.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"), - OutputKeyPath: xmaps.GetString(options.ProviderExtendedConfig, "keyPath"), PfxPassword: xmaps.GetString(options.ProviderExtendedConfig, "pfxPassword"), JksAlias: xmaps.GetString(options.ProviderExtendedConfig, "jksAlias"), JksKeypass: xmaps.GetString(options.ProviderExtendedConfig, "jksKeypass"), diff --git a/pkg/core/ssl-applicator/acme-http01/providers/ssh/ssh.go b/pkg/core/ssl-applicator/acme-http01/providers/ssh/ssh.go index 9863d6aa7..d23055ab2 100644 --- a/pkg/core/ssl-applicator/acme-http01/providers/ssh/ssh.go +++ b/pkg/core/ssl-applicator/acme-http01/providers/ssh/ssh.go @@ -6,7 +6,6 @@ import ( "net" "path/filepath" "strconv" - "strings" "github.com/go-acme/lego/v4/challenge/http01" "golang.org/x/crypto/ssh" @@ -239,54 +238,17 @@ func (p *provider) createSshClient(conn net.Conn, host string, port int32, authM } } - authentications := make([]ssh.AuthMethod, 0) switch authMethod { case AUTH_METHOD_NONE: - { - } + return xssh.NewClient(conn, host, int(port), username) case AUTH_METHOD_PASSWORD: - { - authentications = append(authentications, ssh.Password(password)) - authentications = append(authentications, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) { - if len(questions) == 1 { - return []string{password}, nil - } - return nil, fmt.Errorf("unexpected keyboard interactive question [%s]", strings.Join(questions, ", ")) - })) - } + return xssh.NewClientWithPassword(conn, host, int(port), username, password) case AUTH_METHOD_KEY: - { - var signer ssh.Signer - var err error - - if keyPassphrase != "" { - signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key), []byte(keyPassphrase)) - } else { - signer, err = ssh.ParsePrivateKey([]byte(key)) - } - - if err != nil { - return nil, err - } - - authentications = append(authentications, ssh.PublicKeys(signer)) - } + return xssh.NewClientWithKey(conn, host, int(port), username, key, keyPassphrase) default: return nil, fmt.Errorf("unsupported auth method '%s'", authMethod) } - - addr := net.JoinHostPort(host, strconv.Itoa(int(port))) - sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, &ssh.ClientConfig{ - User: username, - Auth: authentications, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - }) - if err != nil { - return nil, err - } - - return ssh.NewClient(sshConn, chans, reqs), nil } diff --git a/pkg/core/ssl-deployer/providers/ssh/ssh.go b/pkg/core/ssl-deployer/providers/ssh/ssh.go index 666cc6c79..3ff297f20 100644 --- a/pkg/core/ssl-deployer/providers/ssh/ssh.go +++ b/pkg/core/ssl-deployer/providers/ssh/ssh.go @@ -8,7 +8,6 @@ import ( "log/slog" "net" "strconv" - "strings" "golang.org/x/crypto/ssh" @@ -52,6 +51,8 @@ type SSLDeployerProviderConfig struct { PostCommand string `json:"postCommand,omitempty"` // 输出证书格式。 OutputFormat OutputFormatType `json:"outputFormat,omitempty"` + // 输出私钥文件路径。 + OutputKeyPath string `json:"outputKeyPath,omitempty"` // 输出证书文件路径。 OutputCertPath string `json:"outputCertPath,omitempty"` // 输出服务器证书文件路径。 @@ -60,8 +61,6 @@ type SSLDeployerProviderConfig struct { // 输出中间证书文件路径。 // 选填。 OutputIntermediaCertPath string `json:"outputIntermediaCertPath,omitempty"` - // 输出私钥文件路径。 - OutputKeyPath string `json:"outputKeyPath,omitempty"` // PFX 导出密码。 // 证书格式为 PFX 时必填。 PfxPassword string `json:"pfxPassword,omitempty"` @@ -192,6 +191,11 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 上传证书和私钥文件 switch d.config.OutputFormat { case OUTPUT_FORMAT_PEM: + if err := xssh.WriteRemoteString(client, d.config.OutputKeyPath, privkeyPEM, d.config.UseSCP); err != nil { + return nil, fmt.Errorf("failed to upload private key file: %w", err) + } + d.logger.Info("ssl private key file uploaded", slog.String("path", d.config.OutputKeyPath)) + if err := xssh.WriteRemoteString(client, d.config.OutputCertPath, certPEM, d.config.UseSCP); err != nil { return nil, fmt.Errorf("failed to upload certificate file: %w", err) } @@ -211,11 +215,6 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke d.logger.Info("ssl intermedia certificate file uploaded", slog.String("path", d.config.OutputIntermediaCertPath)) } - if err := xssh.WriteRemoteString(client, d.config.OutputKeyPath, privkeyPEM, d.config.UseSCP); err != nil { - return nil, fmt.Errorf("failed to upload private key file: %w", err) - } - d.logger.Info("ssl private key file uploaded", slog.String("path", d.config.OutputKeyPath)) - case OUTPUT_FORMAT_PFX: pfxData, err := xcert.TransformCertificateFromPEMToPFX(certPEM, privkeyPEM, d.config.PfxPassword) if err != nil { @@ -282,56 +281,19 @@ func createSshClient(conn net.Conn, host string, port int32, authMethod string, } } - authentications := make([]ssh.AuthMethod, 0) switch authMethod { case AUTH_METHOD_NONE: - { - } + return xssh.NewClient(conn, host, int(port), username) case AUTH_METHOD_PASSWORD: - { - authentications = append(authentications, ssh.Password(password)) - authentications = append(authentications, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) { - if len(questions) == 1 { - return []string{password}, nil - } - return nil, fmt.Errorf("unexpected keyboard interactive question [%s]", strings.Join(questions, ", ")) - })) - } + return xssh.NewClientWithPassword(conn, host, int(port), username, password) case AUTH_METHOD_KEY: - { - var signer ssh.Signer - var err error - - if keyPassphrase != "" { - signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key), []byte(keyPassphrase)) - } else { - signer, err = ssh.ParsePrivateKey([]byte(key)) - } - - if err != nil { - return nil, err - } - - authentications = append(authentications, ssh.PublicKeys(signer)) - } + return xssh.NewClientWithKey(conn, host, int(port), username, key, keyPassphrase) default: return nil, fmt.Errorf("unsupported auth method '%s'", authMethod) } - - addr := net.JoinHostPort(host, strconv.Itoa(int(port))) - sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, &ssh.ClientConfig{ - User: username, - Auth: authentications, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - }) - if err != nil { - return nil, err - } - - return ssh.NewClient(sshConn, chans, reqs), nil } func execSshCommand(sshCli *ssh.Client, command string) (string, string, error) { diff --git a/pkg/utils/ssh/client.go b/pkg/utils/ssh/client.go new file mode 100644 index 000000000..af5189b73 --- /dev/null +++ b/pkg/utils/ssh/client.go @@ -0,0 +1,59 @@ +package ssh + +import ( + "fmt" + "net" + "strconv" + "strings" + + "golang.org/x/crypto/ssh" +) + +func NewClient(conn net.Conn, host string, port int, username string) (*ssh.Client, error) { + authentications := make([]ssh.AuthMethod, 0) + return newClientWithAuthMethods(conn, host, port, username, authentications) +} + +func NewClientWithPassword(conn net.Conn, host string, port int, username string, password string) (*ssh.Client, error) { + authentications := make([]ssh.AuthMethod, 0) + authentications = append(authentications, ssh.Password(password)) + authentications = append(authentications, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) { + if len(questions) == 1 { + return []string{password}, nil + } + return nil, fmt.Errorf("unexpected keyboard interactive question [%s]", strings.Join(questions, ", ")) + })) + return newClientWithAuthMethods(conn, host, port, username, authentications) +} + +func NewClientWithKey(conn net.Conn, host string, port int, username string, key, keyPassphrase string) (*ssh.Client, error) { + var signer ssh.Signer + var err error + if keyPassphrase != "" { + signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(key), []byte(keyPassphrase)) + } else { + signer, err = ssh.ParsePrivateKey([]byte(key)) + } + if err != nil { + return nil, err + } + + authentications := make([]ssh.AuthMethod, 0) + authentications = append(authentications, ssh.PublicKeys(signer)) + return newClientWithAuthMethods(conn, host, port, username, authentications) +} + +func newClientWithAuthMethods(conn net.Conn, host string, port int, username string, authMethods []ssh.AuthMethod) (*ssh.Client, error) { + addr := net.JoinHostPort(host, strconv.Itoa(int(port))) + + sshConn, chans, reqs, err := ssh.NewClientConn(conn, addr, &ssh.ClientConfig{ + User: username, + Auth: authMethods, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }) + if err != nil { + return nil, err + } + + return ssh.NewClient(sshConn, chans, reqs), nil +}