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

238 lines
5.3 KiB
Go
Raw Permalink 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"
"ALLinSSL/backend/public"
"bytes"
"encoding/json"
"fmt"
"golang.org/x/crypto/ssh"
"os"
"path/filepath"
"strconv"
)
type SSHConfig struct {
User string
Password string // 可选
PrivateKey string `json:"key"` // 可选
Host string
Port any
}
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 {
var port string
switch v := config.Port.(type) {
case float64:
port = strconv.Itoa(int(v))
case string:
port = v
case int:
port = strconv.Itoa(v)
default:
port = "22"
}
addr := fmt.Sprintf("%s:%s", config.Host, 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 {
beforeCmd = ""
}
afterCmd, ok := cfg["afterCmd"].(string)
if !ok {
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
}
func DeployLocalhost(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")
}
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 {
_, errout, err := public.ExecCommand(beforeCmd)
if err != nil {
return fmt.Errorf("前置命令执行失败: %v, %s", err, errout)
}
}
dir := filepath.Dir(certPath)
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
panic("创建证书保存目录失败: " + err.Error())
}
dir = filepath.Dir(keyPath)
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
panic("创建私钥保存目录失败: " + err.Error())
}
err := os.WriteFile(certPath, []byte(certPem), 0644)
if err != nil {
return fmt.Errorf("写入证书失败: %v", err)
}
err = os.WriteFile(keyPath, []byte(keyPem), 0644)
if err != nil {
return fmt.Errorf("写入私钥失败: %v", err)
}
afterCmd, ok := cfg["afterCmd"].(string)
if ok {
_, errout, err := public.ExecCommand(afterCmd)
if err != nil {
return fmt.Errorf("后置命令执行失败: %v, %s", err, errout)
}
}
return nil
}