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

242 lines
6.0 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 doge
import (
"ALLinSSL/backend/internal/access"
"ALLinSSL/backend/public"
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
)
type Auth struct {
AccessKey string `json:"access_key"`
SecretKey string `json:"secret_key"`
}
func NewAuth(accessKey, secretKey string) *Auth {
return &Auth{
AccessKey: accessKey,
SecretKey: secretKey,
}
}
func DeployCdn(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
}
domain, ok := cfg["domain"].(string)
if !ok || domain == "" {
return fmt.Errorf("domain is required and must be a string")
}
certStr, ok := cert["cert"].(string)
if !ok || certStr == "" {
return fmt.Errorf("cert is required and must be a string")
}
keyStr, ok := cert["key"].(string)
if !ok || keyStr == "" {
return fmt.Errorf("key is required and must be a string")
}
sha256, err := public.GetSHA256(certStr)
if err != nil {
return fmt.Errorf("failed to get SHA256 of cert: %w", err)
}
note := fmt.Sprintf("allinssl-%s", sha256)
a := NewAuth(providerConfig["access_key"], providerConfig["secret_key"])
// 检查证书是否已存在于 CDN
certList, err := a.listCertFromCdn()
if err != nil {
return fmt.Errorf("failed to list certs from CDN: %w", err)
}
var certID float64
for _, cert := range certList {
if cert["note"] == note {
certID, ok = cert["id"].(float64)
if !ok {
certID = 0
}
}
}
// 如果证书不存在,则上传证书到 CDN
if certID == 0 {
certID, err = a.uploadCertToCdn(certStr, keyStr, note)
if err != nil || certID == 0 {
return fmt.Errorf("failed to upload to CDN: %w", err)
}
}
// 绑定证书到域名
_, err = a.bindCertToCdn(certID, domain)
if err != nil {
return fmt.Errorf("failed to bind cert to CDN: %w", err)
}
return nil
}
func (a Auth) uploadCertToCdn(cert, key, note string) (float64, error) {
params := map[string]any{
"cert": cert,
"private": key,
"note": note,
}
res, err := a.DogeCloudAPI("/cdn/cert/upload.json", params, true)
if err != nil {
return 0, fmt.Errorf("failed to call DogeCloud API: %w", err)
}
code, ok := res["code"].(float64)
if !ok {
return 0, fmt.Errorf("invalid response format: code not found")
}
if code != 200 {
return 0, fmt.Errorf("DogeCloud API error: %s", res["msg"])
}
data, ok := res["data"].(map[string]any)
if !ok {
return 0, fmt.Errorf("invalid response format: data not found")
}
certID, ok := data["id"].(float64)
if !ok {
return 0, fmt.Errorf("invalid response format: id not found")
}
return certID, nil
}
func (a Auth) listCertFromCdn() ([]map[string]any, error) {
res, err := a.DogeCloudAPI("/cdn/cert/list.json", map[string]interface{}{}, true)
if err != nil {
return nil, fmt.Errorf("failed to call DogeCloud API: %w", err)
}
code, ok := res["code"].(float64)
if !ok {
return nil, fmt.Errorf("invalid response format: code not found")
}
if code != 200 {
return nil, fmt.Errorf("DogeCloud API error: %s", res["msg"])
}
data, ok := res["data"].(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid response format: data not found")
}
certList, ok := data["certs"].([]any)
if !ok {
return nil, fmt.Errorf("invalid response format: certs not found")
}
certs := make([]map[string]any, 0, len(certList))
for _, cert := range certList {
certMap, ok := cert.(map[string]any)
if !ok {
return nil, fmt.Errorf("invalid response format: cert item is not a map")
}
certs = append(certs, certMap)
}
return certs, nil
}
func (a Auth) bindCertToCdn(certID float64, domain string) (map[string]interface{}, error) {
params := map[string]interface{}{
"id": certID,
"domain": domain,
}
res, err := a.DogeCloudAPI("/cdn/cert/bind.json", params, true)
if err != nil {
return nil, fmt.Errorf("failed to call DogeCloud API: %w", err)
}
code, ok := res["code"].(float64)
if !ok {
return nil, fmt.Errorf("invalid response format: code not found")
}
if code != 200 {
return nil, fmt.Errorf("DogeCloud API error: %s", res["msg"])
}
return res, nil
}
// DogeCloudAPI 调用多吉云的 API 根据多吉云官网示例修改
func (a Auth) DogeCloudAPI(apiPath string, data map[string]interface{}, jsonMode bool) (map[string]interface{}, error) {
AccessKey := a.AccessKey
SecretKey := a.SecretKey
body := ""
mime := ""
if jsonMode {
_body, err := json.Marshal(data)
if err != nil {
return nil, err
}
body = string(_body)
mime = "application/json"
} else {
values := url.Values{}
for k, v := range data {
values.Set(k, v.(string))
}
body = values.Encode()
mime = "application/x-www-form-urlencoded"
}
signStr := apiPath + "\n" + body
hmacObj := hmac.New(sha1.New, []byte(SecretKey))
hmacObj.Write([]byte(signStr))
sign := hex.EncodeToString(hmacObj.Sum(nil))
Authorization := "TOKEN " + AccessKey + ":" + sign
req, err := http.NewRequest("POST", "https://api.dogecloud.com"+apiPath, strings.NewReader(body))
if err != nil {
return nil, err // 创建请求错误
}
req.Header.Add("Content-Type", mime)
req.Header.Add("Authorization", Authorization)
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, err
} // 网络错误
defer resp.Body.Close()
r, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err // 读取响应错误
}
var result map[string]interface{}
err = json.Unmarshal(r, &result)
if err != nil {
return nil, err
}
return result, nil
}