mirror of
https://github.com/allinssl/allinssl.git
synced 2025-12-15 09:55:37 +08:00
新增部署阿里云dcdn、lecdn
新增dns厂商constellix
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/go-acme/lego/v4/providers/dns/bunny"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
|
||||
"github.com/go-acme/lego/v4/providers/dns/cloudns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/constellix"
|
||||
"github.com/go-acme/lego/v4/providers/dns/gcore"
|
||||
"github.com/go-acme/lego/v4/providers/dns/godaddy"
|
||||
"github.com/go-acme/lego/v4/providers/dns/huaweicloud"
|
||||
@@ -78,8 +79,12 @@ func GetDNSProvider(providerName string, creds map[string]string, httpClient *ht
|
||||
return tencentcloud.NewDNSProviderConfig(config)
|
||||
case "cloudflare":
|
||||
config := cloudflare.NewDefaultConfig()
|
||||
config.AuthEmail = creds["email"]
|
||||
config.AuthKey = creds["api_key"]
|
||||
if creds["email"] == "" {
|
||||
config.AuthToken = creds["api_key"]
|
||||
} else {
|
||||
config.AuthEmail = creds["email"]
|
||||
config.AuthKey = creds["api_key"]
|
||||
}
|
||||
config.PropagationTimeout = maxWait
|
||||
return cloudflare.NewDNSProviderConfig(config)
|
||||
case "aliyun":
|
||||
@@ -191,6 +196,12 @@ func GetDNSProvider(providerName string, creds map[string]string, httpClient *ht
|
||||
config.RegionId = "cn-north-1"
|
||||
config.PropagationTimeout = maxWait
|
||||
return jdcloud.NewDNSProviderConfig(config)
|
||||
case "constellix":
|
||||
config := constellix.NewDefaultConfig()
|
||||
config.APIKey = creds["api_key"]
|
||||
config.SecretKey = creds["secret_key"]
|
||||
config.PropagationTimeout = maxWait
|
||||
return constellix.NewDNSProviderConfig(config)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName)
|
||||
|
||||
92
backend/internal/cert/deploy/aliyun/dcdn.go
Normal file
92
backend/internal/cert/deploy/aliyun/dcdn.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package aliyun
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/access"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
dcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func CreateDcdnClient(accessKey, accessSecret string) (*dcdn.Client, error) {
|
||||
config := &openapi.Config{
|
||||
AccessKeyId: tea.String(accessKey),
|
||||
AccessKeySecret: tea.String(accessSecret),
|
||||
RegionId: tea.String("cn-hangzhou"),
|
||||
}
|
||||
return dcdn.NewClient(config)
|
||||
}
|
||||
|
||||
func DeployCertToDcdn(client *dcdn.Client, domain, certPEM, privkeyPEM string) error {
|
||||
request := &dcdn.SetDcdnDomainSSLCertificateRequest{
|
||||
DomainName: tea.String(domain),
|
||||
SSLPri: tea.String(privkeyPEM),
|
||||
SSLPub: tea.String(certPEM),
|
||||
SSLProtocol: tea.String("on"),
|
||||
CertType: tea.String("upload"),
|
||||
}
|
||||
|
||||
runtime := &util.RuntimeOptions{}
|
||||
|
||||
_, err := client.SetDcdnDomainSSLCertificateWithOptions(request, runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func DeployAliyunDcdn(cfg map[string]any) error {
|
||||
cert, ok := cfg["certificate"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书不存在")
|
||||
}
|
||||
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")
|
||||
}
|
||||
//
|
||||
providerData, err := access.GetAccess(providerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
providerConfigStr, ok := providerData["config"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("api配置错误")
|
||||
}
|
||||
// 解析 JSON 配置
|
||||
var providerConfig map[string]string
|
||||
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := CreateDcdnClient(providerConfig["access_key_id"], providerConfig["access_key_secret"])
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建 DCDN 客户端失败: %w", err)
|
||||
}
|
||||
certPEM, ok := cert["cert"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书内容不存在或格式错误")
|
||||
}
|
||||
privkeyPEM, ok := cert["key"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("私钥内容不存在或格式错误")
|
||||
}
|
||||
domain, ok := cfg["domain"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("域名不存在或格式错误")
|
||||
}
|
||||
err = DeployCertToDcdn(client, domain, certPEM, privkeyPEM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("部署证书到 DCDN 失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -66,6 +66,9 @@ func Deploy(cfg map[string]any, logger *public.Logger) error {
|
||||
case "aliyun-esa":
|
||||
logger.Debug("部署到阿里云ESA...")
|
||||
return aliyun.DeployAliyunESA(cfg)
|
||||
case "aliyun-dcdn":
|
||||
logger.Debug("部署到阿里云DCDN...")
|
||||
return aliyun.DeployAliyunDcdn(cfg)
|
||||
case "safeline-site":
|
||||
logger.Debug("部署雷池WAF网站...")
|
||||
return DeploySafeLineWafSite(cfg, logger)
|
||||
|
||||
310
backend/internal/cert/deploy/lecdn/lecdn.go
Normal file
310
backend/internal/cert/deploy/lecdn/lecdn.go
Normal file
@@ -0,0 +1,310 @@
|
||||
package lecdn
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/access"
|
||||
"ALLinSSL/backend/public"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// LecdnConfig 代表 LeCDN 的配置
|
||||
type LecdnConfig struct {
|
||||
BaseURL string `json:"baseurl"`
|
||||
Token string `json:"token"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
IgnoreSSL bool `json:"ignore_ssl"`
|
||||
}
|
||||
|
||||
// NewLecdnConfig 创建一个新的 LecdnConfig 实例
|
||||
func NewLecdnConfig(providerID string) (*LecdnConfig, error) {
|
||||
providerData, err := access.GetAccess(providerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providerConfigStr, ok := providerData["config"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("api配置错误")
|
||||
}
|
||||
// 解析 JSON 配置
|
||||
var providerConfig map[string]string
|
||||
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := &LecdnConfig{
|
||||
BaseURL: providerConfig["url"],
|
||||
Username: providerConfig["username"],
|
||||
Password: providerConfig["password"],
|
||||
IgnoreSSL: providerConfig["ignore_ssl"] == "1",
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// requestLecdn 发送 HTTP 请求到 LeCDN API
|
||||
func requestLecdn(url, method, token string, params map[string]any, ignoreSsl bool) (map[string]any, error) {
|
||||
var res map[string]any
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
},
|
||||
}
|
||||
|
||||
jsonData, _ := json.Marshal(params)
|
||||
req, err := http.NewRequest(method, url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+token)
|
||||
req.Header.Set("cookie", "LeCDN-Client="+token)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return res, fmt.Errorf("请求失败,状态码:%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, string(body))
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// client 登录 LeCDN 并获取 Token
|
||||
func (l *LecdnConfig) client() error {
|
||||
url := fmt.Sprintf("%s/prod-api/login", l.BaseURL)
|
||||
resp, err := requestLecdn(url, "POST", "", map[string]any{
|
||||
"username": l.Username,
|
||||
"password": l.Password,
|
||||
}, l.IgnoreSSL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("登录失败: %v", err)
|
||||
}
|
||||
data, ok := resp["data"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("登录响应格式错误: %v", resp)
|
||||
}
|
||||
token, ok := data["token"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("登录响应中未找到 token: %v", resp)
|
||||
}
|
||||
l.Token = token
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCertList 获取证书列表
|
||||
func (l *LecdnConfig) GetCertList() ([]map[string]any, error) {
|
||||
url := fmt.Sprintf("%s/prod-api/certificate?current_page=1&total=9999&page_size=9999", l.BaseURL)
|
||||
resp, err := requestLecdn(url, "GET", l.Token, nil, l.IgnoreSSL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取证书列表失败: %v", err)
|
||||
}
|
||||
data, ok := resp["data"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("获取证书列表响应格式错误: %v", resp)
|
||||
}
|
||||
list, ok := data["items"].([]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("获取证书列表响应中未找到 items: %v", resp)
|
||||
}
|
||||
if len(list) == 0 {
|
||||
return nil, fmt.Errorf("未找到任何证书")
|
||||
}
|
||||
var certs []map[string]any
|
||||
for _, item := range list {
|
||||
cert, ok := item.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("证书列表项格式错误: %v", item)
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// UploadCert 上传证书到 LeCDN
|
||||
func (l *LecdnConfig) UploadCert(cert, key, name string) (int, error) {
|
||||
url := fmt.Sprintf("%s/prod-api/certificate", l.BaseURL)
|
||||
keyBase64 := base64.StdEncoding.EncodeToString([]byte(key))
|
||||
certBase64 := base64.StdEncoding.EncodeToString([]byte(cert))
|
||||
|
||||
params := map[string]any{
|
||||
"ssl_key": keyBase64,
|
||||
"ssl_pem": certBase64,
|
||||
"type": "upload",
|
||||
"auto_renewal": false,
|
||||
"name": name,
|
||||
}
|
||||
resp, err := requestLecdn(url, "POST", l.Token, params, l.IgnoreSSL)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("上传证书失败: %v", err)
|
||||
}
|
||||
data, ok := resp["data"].(map[string]any)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("上传证书响应格式错误: %v", resp)
|
||||
}
|
||||
id, ok := data["id"].(float64)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("上传证书响应中未找到 id: %v", resp)
|
||||
}
|
||||
return int(id), nil
|
||||
}
|
||||
|
||||
// GetDomainIdMapFromSite 获取指定站点的域名列表,并返回域名到 ID 的映射
|
||||
func (l *LecdnConfig) GetDomainIdMapFromSite(siteId int) (map[string]int, error) {
|
||||
url := fmt.Sprintf("%s/prod-api/site/%d/domain_name", l.BaseURL, siteId)
|
||||
resp, err := requestLecdn(url, "GET", l.Token, nil, l.IgnoreSSL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取域名列表失败: %v", err)
|
||||
}
|
||||
data, ok := resp["data"].([]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("获取域名列表响应格式错误: %v", resp)
|
||||
}
|
||||
domains := make(map[string]int)
|
||||
for _, domain := range data {
|
||||
domainData, ok := domain.(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("域名列表项格式错误: %v", domain)
|
||||
}
|
||||
domainName, ok := domainData["domain_name"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("域名列表项中未找到 domain_name: %v", domainData)
|
||||
}
|
||||
id, ok := domainData["id"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("域名列表项中未找到 id: %v", domainData)
|
||||
}
|
||||
domains[domainName] = int(id)
|
||||
}
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
// DeployCert 部署证书到指定站点的域名
|
||||
func (l *LecdnConfig) DeployCert(siteId, domainId, certId int) error {
|
||||
url := fmt.Sprintf("%s/prod-api/site/%d/domain_name/certificate", l.BaseURL, siteId)
|
||||
params := map[string]any{"certificate_enable": true, "certificate_id": certId, "domain_name_id": domainId}
|
||||
_, err := requestLecdn(url, "PUT", l.Token, params, l.IgnoreSSL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("部署证书失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeployLeCDN 部署 LeCDN 证书主方法
|
||||
func DeployLeCDN(cfg map[string]any) error {
|
||||
cert, ok := cfg["certificate"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书不存在")
|
||||
}
|
||||
certPem, ok := cert["cert"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
keyPem, ok := cert["key"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
certName, err := public.GetSHA256(certPem)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取证书名称失败: %v", err)
|
||||
}
|
||||
certName = "ALLinSSL-" + certName
|
||||
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")
|
||||
}
|
||||
domainName, ok := cfg["domain_name"].(string)
|
||||
if !ok || domainName == "" {
|
||||
return fmt.Errorf("参数错误:domain_name")
|
||||
}
|
||||
var siteId int
|
||||
switch v := cfg["site_id"].(type) {
|
||||
case float64:
|
||||
siteId = int(v)
|
||||
case string:
|
||||
siteId, err = strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("参数错误:site_id")
|
||||
}
|
||||
case int:
|
||||
siteId = v
|
||||
default:
|
||||
return fmt.Errorf("参数错误:site_id")
|
||||
}
|
||||
|
||||
l, err := NewLecdnConfig(providerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = l.client()
|
||||
if err != nil {
|
||||
return fmt.Errorf("登录 LeCDN 失败: %v", err)
|
||||
}
|
||||
|
||||
certId := 0
|
||||
// 获取证书列表
|
||||
certList, err := l.GetCertList()
|
||||
if err == nil && len(certList) > 0 {
|
||||
for _, c := range certList {
|
||||
name, ok := c["name"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
id, ok := c["id"].(float64)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if name == certName {
|
||||
certId = int(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
if certId == 0 {
|
||||
// 上传证书
|
||||
certId, err = l.UploadCert(certPem, keyPem, certName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("上传证书失败: %v", err)
|
||||
}
|
||||
}
|
||||
// 获取域名列表
|
||||
domainList, err := l.GetDomainIdMapFromSite(siteId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取域名列表失败: %v", err)
|
||||
}
|
||||
domainId, ok := domainList[domainName]
|
||||
if !ok {
|
||||
return fmt.Errorf("未找到域名 %s 的 ID", domainName)
|
||||
}
|
||||
// 部署证书到域名
|
||||
err = l.DeployCert(siteId, domainId, certId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("部署证书到域名失败: %v", err)
|
||||
}
|
||||
// 部署完成,返回成功
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user