下载证书附带pfx

ssh部署兼容带密码的密钥登录
ssh部署日志新增标准和错误输出
pull/295/head
v-me-50 2025-06-24 09:49:21 +08:00
parent f499afc060
commit 58e4c0ff11
5 changed files with 121 additions and 34 deletions

View File

@ -98,30 +98,61 @@ func DownloadCert(c *gin.Context) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
zipWriter := zip.NewWriter(buf) zipWriter := zip.NewWriter(buf)
for filename, content := range certData { // 写入PEM文件
if filename == "cert" || filename == "key" { // cert.pem
writer, err := zipWriter.Create(filename + ".pem") certStr := certData["cert"]
certWriter, err := zipWriter.Create("Nginx/cert.pem")
if err != nil { if err != nil {
public.FailMsg(c, err.Error()) public.FailMsg(c, err.Error())
return return
} }
_, err = writer.Write([]byte(content)) if _, err := certWriter.Write([]byte(certStr)); err != nil {
public.FailMsg(c, err.Error())
return
}
// key.pem
keyStr := certData["key"]
keyWriter, err := zipWriter.Create("Nginx/key.pem")
if err != nil { if err != nil {
public.FailMsg(c, err.Error()) public.FailMsg(c, err.Error())
return return
} }
if _, err := keyWriter.Write([]byte(keyStr)); err != nil {
public.FailMsg(c, err.Error())
return
}
// cert.pfx
pfxPassword := "allinssl"
pfxData, err := public.PEMToPFX(certStr, keyStr, pfxPassword)
if err == nil && len(pfxData) > 0 {
pfxWriter, err := zipWriter.Create("IIS/cert.pfx")
if err != nil {
public.FailMsg(c, err.Error())
return
}
if _, err := pfxWriter.Write(pfxData); err != nil {
public.FailMsg(c, err.Error())
return
}
txtWriter, err := zipWriter.Create("IIS/passwd.txt")
if err != nil {
public.FailMsg(c, err.Error())
return
}
if _, err := txtWriter.Write([]byte(pfxPassword)); err != nil {
public.FailMsg(c, err.Error())
return
} }
} }
// 关闭 zipWriter // 关闭 zipWriter
if err := zipWriter.Close(); err != nil { if err := zipWriter.Close(); err != nil {
public.FailMsg(c, err.Error()) public.FailMsg(c, err.Error())
return return
} }
// 设置响应头 // 设置响应头
zipName := strings.ReplaceAll(certData["domains"], ".", "_") zipName := strings.ReplaceAll(certData["domains"], ".", "_")
zipName = strings.ReplaceAll(zipName, ",", "-") zipName = strings.ReplaceAll(zipName, ",", "-")
c.Header("Content-Type", "application/zip") c.Header("Content-Type", "application/zip")
c.Header("Content-Disposition", "attachment; filename="+zipName+".zip") c.Header("Content-Disposition", "attachment; filename="+zipName+".zip")
c.Data(200, "application/zip", buf.Bytes()) c.Data(200, "application/zip", buf.Bytes())

View File

@ -53,7 +53,7 @@ func Deploy(cfg map[string]any, logger *public.Logger) error {
return Deploy1panelSite(cfg) return Deploy1panelSite(cfg)
case "ssh": case "ssh":
logger.Debug("使用ssh部署到指定路径...") logger.Debug("使用ssh部署到指定路径...")
return DeploySSH(cfg) return DeploySSH(cfg, logger)
case "aliyun-cdn": case "aliyun-cdn":
logger.Debug("部署到阿里云CDN...") logger.Debug("部署到阿里云CDN...")
return DeployAliCdn(cfg) return DeployAliCdn(cfg)

View File

@ -30,25 +30,34 @@ func buildAuthMethods(password, privateKey string) ([]ssh.AuthMethod, error) {
var methods []ssh.AuthMethod var methods []ssh.AuthMethod
if privateKey != "" { if privateKey != "" {
signer, err := ssh.ParsePrivateKey([]byte(privateKey)) var signer ssh.Signer
var err error
if password != "" {
signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(password))
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to parse private key: %v", err) return nil, fmt.Errorf("无法解析带密码的私钥: %v", err)
}
} else {
signer, err = ssh.ParsePrivateKey([]byte(privateKey))
if err != nil {
return nil, fmt.Errorf("无法解析私钥: %v", err)
}
} }
methods = append(methods, ssh.PublicKeys(signer)) methods = append(methods, ssh.PublicKeys(signer))
} }
if password != "" { if password != "" && privateKey == "" {
methods = append(methods, ssh.Password(password)) methods = append(methods, ssh.Password(password))
} }
if len(methods) == 0 { if len(methods) == 0 {
return nil, fmt.Errorf("no authentication methods provided") return nil, fmt.Errorf("未提供有效的认证方式")
} }
return methods, nil return methods, nil
} }
func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, postCmd string) error { func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, postCmd string, logger *public.Logger) error {
var port string var port string
switch v := config.Port.(type) { switch v := config.Port.(type) {
case float64: case float64:
@ -91,8 +100,9 @@ func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, post
return fmt.Errorf("会话创建失败: %v", err) return fmt.Errorf("会话创建失败: %v", err)
} }
defer session.Close() defer session.Close()
var script, stdoutBuf, stderrBuf bytes.Buffer
var script bytes.Buffer session.Stdout = &stdoutBuf
session.Stderr = &stderrBuf
if preCmd != "" { if preCmd != "" {
script.WriteString(preCmd + " && ") script.WriteString(preCmd + " && ")
@ -115,14 +125,14 @@ func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, post
cmd := script.String() cmd := script.String()
if err := session.Run(cmd); err != nil { err = session.Run(cmd)
return fmt.Errorf("运行出错: %v", err) logger.Debug("[STDOUT]", stdoutBuf.String())
} logger.Debug("[STDERR]", stderrBuf.String())
return nil return err
} }
func DeploySSH(cfg map[string]any) error { func DeploySSH(cfg map[string]any, logger *public.Logger) error {
cert, ok := cfg["certificate"].(map[string]any) cert, ok := cfg["certificate"].(map[string]any)
if !ok { if !ok {
return fmt.Errorf("证书不存在") return fmt.Errorf("证书不存在")
@ -180,7 +190,7 @@ func DeploySSH(cfg map[string]any) error {
{Path: certPath, Content: certPem}, {Path: certPath, Content: certPem},
{Path: keyPath, Content: keyPem}, {Path: keyPath, Content: keyPem},
} }
err = writeMultipleFilesViaSSH(providerConfig, files, beforeCmd, afterCmd) err = writeMultipleFilesViaSSH(providerConfig, files, beforeCmd, afterCmd, logger)
if err != nil { if err != nil {
return fmt.Errorf("SSH 部署失败: %v", err) return fmt.Errorf("SSH 部署失败: %v", err)
} }

View File

@ -48,7 +48,7 @@ func NotifyWorkWx(params map[string]any) error {
return err return err
} }
configStr := providerData["config"].(string) configStr := providerData["config"].(string)
fmt.Println(configStr) //fmt.Println(configStr)
var config map[string]string var config map[string]string
err = json.Unmarshal([]byte(configStr), &config) err = json.Unmarshal([]byte(configStr), &config)
if err != nil { if err != nil {

View File

@ -12,6 +12,8 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"software.sslmate.com/src/go-pkcs12"
"strings"
"time" "time"
) )
@ -138,3 +140,47 @@ func GetSHA256(certStr string) (string, error) {
sha256Hash := sha256.Sum256(cert.Raw) sha256Hash := sha256.Sum256(cert.Raw)
return hex.EncodeToString(sha256Hash[:]), nil return hex.EncodeToString(sha256Hash[:]), nil
} }
// PEMToPFX 将PEM格式的证书和私钥转换为PFX格式
func PEMToPFX(certPEM, keyPEM, pfxPassword string) ([]byte, error) {
// 使用默认密码如果未提供
if pfxPassword == "" {
pfxPassword = "allinssl"
}
// 解析证书PEM
certBlock, _ := pem.Decode([]byte(certPEM))
if certBlock == nil || certBlock.Type != "CERTIFICATE" {
return nil, fmt.Errorf("无效的证书PEM格式")
}
// 解析私钥PEM
keyBlock, _ := pem.Decode([]byte(keyPEM))
if keyBlock == nil || (!strings.Contains(keyBlock.Type, "PRIVATE KEY")) {
return nil, fmt.Errorf("无效的私钥PEM格式")
}
// 解析X.509证书
cert, err := x509.ParseCertificate(certBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("解析证书失败: %v", err)
}
// 尝试解析私钥(PKCS8或PKCS1格式)
var privKey interface{}
privKey, err = x509.ParsePKCS8PrivateKey(keyBlock.Bytes)
if err != nil {
privKey, err = x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
if err != nil {
return nil, fmt.Errorf("解析私钥失败: %v", err)
}
}
// 编码为PFX格式
pfxData, err := pkcs12.Encode(rand.Reader, privKey, cert, nil, pfxPassword)
if err != nil {
return nil, fmt.Errorf("编码PFX失败: %v", err)
}
return pfxData, nil
}