allinssl/backend/internal/cert/deploy/ssh.go

167 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package deploy
import (
"ALLinSSL/backend/internal/access"
"bytes"
"encoding/json"
"fmt"
"golang.org/x/crypto/ssh"
"strconv"
)
type SSHConfig struct {
User string
Password string // 可选
PrivateKey string // 可选
Host string
Port float64
}
type RemoteFile struct {
Path string
Content string
}
func buildAuthMethods(password, privateKey string) ([]ssh.AuthMethod, error) {
var methods []ssh.AuthMethod
if privateKey != "" {
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, fmt.Errorf("unable to parse private key: %v", err)
}
methods = append(methods, ssh.PublicKeys(signer))
}
if password != "" {
methods = append(methods, ssh.Password(password))
}
if len(methods) == 0 {
return nil, fmt.Errorf("no authentication methods provided")
}
return methods, nil
}
func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, postCmd string) error {
addr := fmt.Sprintf("%s:%d", config.Host, int(config.Port))
authMethods, err := buildAuthMethods(config.Password, config.PrivateKey)
if err != nil {
return err
}
sshConfig := &ssh.ClientConfig{
User: config.User,
Auth: authMethods,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
client, err := ssh.Dial("tcp", addr, sshConfig)
if err != nil {
return fmt.Errorf("failed to dial: %v", err)
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
return fmt.Errorf("会话创建失败: %v", err)
}
defer session.Close()
var script bytes.Buffer
if preCmd != "" {
script.WriteString(preCmd + " && ")
}
for i, file := range files {
if i > 0 {
script.WriteString(" && ")
}
dirCmd := fmt.Sprintf("mkdir -p $(dirname %q)", file.Path)
writeCmd := fmt.Sprintf("printf %%s '%s' > %s", file.Content, file.Path)
script.WriteString(dirCmd + " && " + writeCmd)
}
if postCmd != "" {
script.WriteString(" && " + postCmd)
}
cmd := script.String()
if err := session.Run(cmd); err != nil {
return fmt.Errorf("运行出错: %v", err)
}
return nil
}
func DeploySSH(cfg map[string]any) error {
cert, ok := cfg["certificate"].(map[string]any)
if !ok {
return fmt.Errorf("证书不存在")
}
// 设置证书
keyPem, ok := cert["key"].(string)
if !ok {
return fmt.Errorf("证书错误key")
}
certPem, ok := cert["cert"].(string)
if !ok {
return fmt.Errorf("证书错误cert")
}
var providerID string
switch v := cfg["provider_id"].(type) {
case float64:
providerID = strconv.Itoa(int(v))
case string:
providerID = v
default:
return fmt.Errorf("参数错误provider_id")
}
keyPath, ok := cfg["keyPath"].(string)
if !ok {
return fmt.Errorf("参数错误keyPath")
}
certPath, ok := cfg["certPath"].(string)
if !ok {
return fmt.Errorf("参数错误certPath")
}
beforeCmd, ok := cfg["beforeCmd"].(string)
if !ok {
return fmt.Errorf("参数错误beforeCmd")
}
afterCmd, ok := cfg["afterCmd"].(string)
if !ok {
return fmt.Errorf("参数错误afterCmd")
}
providerData, err := access.GetAccess(providerID)
if err != nil {
return err
}
providerConfigStr, ok := providerData["config"].(string)
if !ok {
return fmt.Errorf("api配置错误")
}
// 解析 JSON 配置
var providerConfig SSHConfig
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
if err != nil {
return err
}
// 自动创建多级目录
files := []RemoteFile{
{Path: certPath, Content: certPem},
{Path: keyPath, Content: keyPem},
}
err = writeMultipleFilesViaSSH(providerConfig, files, beforeCmd, afterCmd)
if err != nil {
return fmt.Errorf("SSH 部署失败: %v", err)
}
return nil
}