mirror of
https://github.com/usual2970/certimate.git
synced 2025-10-04 21:44:54 +00:00
feat: ssh jump server support
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -16,6 +17,23 @@ import (
|
||||
certutil "github.com/usual2970/certimate/internal/pkg/utils/cert"
|
||||
)
|
||||
|
||||
type JumpServerConfig struct {
|
||||
// SSH 主机。
|
||||
// 零值时默认为 "localhost"。
|
||||
SshHost string `json:"sshHost,omitempty"`
|
||||
// SSH 端口。
|
||||
// 零值时默认为 22。
|
||||
SshPort int32 `json:"sshPort,omitempty"`
|
||||
// SSH 登录用户名。
|
||||
SshUsername string `json:"sshUsername,omitempty"`
|
||||
// SSH 登录密码。
|
||||
SshPassword string `json:"sshPassword,omitempty"`
|
||||
// SSH 登录私钥。
|
||||
SshKey string `json:"sshKey,omitempty"`
|
||||
// SSH 登录私钥口令。
|
||||
SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"`
|
||||
}
|
||||
|
||||
type DeployerConfig struct {
|
||||
// SSH 主机。
|
||||
// 零值时默认为 "localhost"。
|
||||
@@ -31,6 +49,8 @@ type DeployerConfig struct {
|
||||
SshKey string `json:"sshKey,omitempty"`
|
||||
// SSH 登录私钥口令。
|
||||
SshKeyPassphrase string `json:"sshKeyPassphrase,omitempty"`
|
||||
// 跳板机配置
|
||||
JumpServerConfig []JumpServerConfig `json:"jumpServerConfig,omitempty"`
|
||||
// 是否回退使用 SCP。
|
||||
UseSCP bool `json:"useSCP,omitempty"`
|
||||
// 前置命令。
|
||||
@@ -97,8 +117,60 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
|
||||
return nil, fmt.Errorf("failed to extract certs: %w", err)
|
||||
}
|
||||
|
||||
// 连接
|
||||
var targetConn net.Conn
|
||||
|
||||
// 连接到跳板机
|
||||
if len(d.config.JumpServerConfig) > 0 {
|
||||
var jumpClient *ssh.Client
|
||||
for i, jumpServerConf := range d.config.JumpServerConfig {
|
||||
d.logger.Info(fmt.Sprintf("connecting to jump server [%d]", i+1), slog.String("host", jumpServerConf.SshHost))
|
||||
|
||||
var jumpConn net.Conn
|
||||
// 第一个连接是主机发起,后续通过跳板机发起
|
||||
if jumpClient == nil {
|
||||
jumpConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", jumpServerConf.SshHost, jumpServerConf.SshPort))
|
||||
} else {
|
||||
jumpConn, err = jumpClient.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", jumpServerConf.SshHost, jumpServerConf.SshPort))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to jump server [%d]: %w", i+1, err)
|
||||
}
|
||||
defer jumpConn.Close()
|
||||
|
||||
newClient, err := createSshClient(
|
||||
jumpConn,
|
||||
jumpServerConf.SshHost,
|
||||
jumpServerConf.SshPort,
|
||||
jumpServerConf.SshUsername,
|
||||
jumpServerConf.SshPassword,
|
||||
jumpServerConf.SshKey,
|
||||
jumpServerConf.SshKeyPassphrase,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create jump server ssh client[%d]: %w", i+1, err)
|
||||
}
|
||||
defer newClient.Close()
|
||||
|
||||
jumpClient = newClient
|
||||
d.logger.Info(fmt.Sprintf("jump server connected [%d]", i+1), slog.String("host", jumpServerConf.SshHost))
|
||||
}
|
||||
// 通过跳板机发起到目标服务器的TCP连接
|
||||
targetConn, err = jumpClient.DialContext(ctx, "tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to target server: %w", err)
|
||||
}
|
||||
} else {
|
||||
// 直接TCP连接到目标服务器
|
||||
targetConn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", d.config.SshHost, d.config.SshPort))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to target server: %w", err)
|
||||
}
|
||||
}
|
||||
defer targetConn.Close()
|
||||
|
||||
// 通过已有的连接创建目标服务器SSH客户端
|
||||
client, err := createSshClient(
|
||||
targetConn,
|
||||
d.config.SshHost,
|
||||
d.config.SshPort,
|
||||
d.config.SshUsername,
|
||||
@@ -189,7 +261,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func createSshClient(host string, port int32, username string, password string, key string, keyPassphrase string) (*ssh.Client, error) {
|
||||
func createSshClient(conn net.Conn, host string, port int32, username string, password string, key string, keyPassphrase string) (*ssh.Client, error) {
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
@@ -217,11 +289,16 @@ func createSshClient(host string, port int32, username string, password string,
|
||||
authMethod = ssh.Password(password)
|
||||
}
|
||||
|
||||
return ssh.Dial("tcp", fmt.Sprintf("%s:%d", host, port), &ssh.ClientConfig{
|
||||
sshConn, chans, reqs, err := ssh.NewClientConn(conn, fmt.Sprintf("%s:%d", host, port), &ssh.ClientConfig{
|
||||
User: username,
|
||||
Auth: []ssh.AuthMethod{authMethod},
|
||||
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) {
|
||||
|
Reference in New Issue
Block a user