mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-07 23:31:10 +08:00
【调整】插件支持动态参数和参数类型
【调整】获取证书列表支持状态过滤 【新增】dns提供商腾讯云eo
This commit is contained in:
52
plugins/alicloud/action.go
Normal file
52
plugins/alicloud/action.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
casPkg "ALLinSSL/plugins/alicloud/cas"
|
||||
cdnPkg "ALLinSSL/plugins/alicloud/cdn"
|
||||
dcdnPkg "ALLinSSL/plugins/alicloud/dcdn"
|
||||
esaPkg "ALLinSSL/plugins/alicloud/esa"
|
||||
ossPkg "ALLinSSL/plugins/alicloud/oss"
|
||||
wafPkg "ALLinSSL/plugins/alicloud/waf"
|
||||
)
|
||||
|
||||
func Cdn(cfg map[string]any) (*Response, error) {
|
||||
if err := cdnPkg.Deploy(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Response{Status: "success", Message: "OK", Result: nil}, nil
|
||||
}
|
||||
|
||||
func Dcdn(cfg map[string]any) (*Response, error) {
|
||||
if err := dcdnPkg.Deploy(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Response{Status: "success", Message: "OK", Result: nil}, nil
|
||||
}
|
||||
|
||||
func Oss(cfg map[string]any) (*Response, error) {
|
||||
if err := ossPkg.Deploy(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Response{Status: "success", Message: "OK", Result: nil}, nil
|
||||
}
|
||||
|
||||
func Esa(cfg map[string]any) (*Response, error) {
|
||||
if err := esaPkg.Deploy(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Response{Status: "success", Message: "OK", Result: nil}, nil
|
||||
}
|
||||
|
||||
func Cas(cfg map[string]any) (*Response, error) {
|
||||
if err := casPkg.Deploy(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Response{Status: "success", Message: "OK", Result: nil}, nil
|
||||
}
|
||||
|
||||
func Waf(cfg map[string]any) (*Response, error) {
|
||||
if err := wafPkg.Deploy(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Response{Status: "success", Message: "OK", Result: nil}, nil
|
||||
}
|
||||
65
plugins/alicloud/cas/action.go
Normal file
65
plugins/alicloud/cas/action.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package cas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
cas "github.com/alibabacloud-go/cas-20200407/v4/client"
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
func CreateClient(accessKey, accessSecret, endpoint string) (*cas.Client, error) {
|
||||
if endpoint == "" {
|
||||
endpoint = "cas.ap-southeast-1.aliyuncs.com"
|
||||
}
|
||||
config := &openapi.Config{
|
||||
AccessKeyId: tea.String(accessKey),
|
||||
AccessKeySecret: tea.String(accessSecret),
|
||||
Endpoint: tea.String(endpoint),
|
||||
}
|
||||
return cas.NewClient(config)
|
||||
}
|
||||
|
||||
func Upload(client *cas.Client, cert, key, name string) error {
|
||||
req := &cas.UploadUserCertificateRequest{
|
||||
Name: tea.String(name),
|
||||
Cert: tea.String(cert),
|
||||
Key: tea.String(key),
|
||||
}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
_, err := client.UploadUserCertificateWithOptions(req, runtime)
|
||||
return err
|
||||
}
|
||||
|
||||
func Deploy(cfg map[string]any) error {
|
||||
certPEM, ok := cfg["cert"].(string)
|
||||
if !ok || certPEM == "" {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
keyPEM, ok := cfg["key"].(string)
|
||||
if !ok || keyPEM == "" {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
accessKey, ok := cfg["access_key_id"].(string)
|
||||
if !ok || accessKey == "" {
|
||||
return fmt.Errorf("参数错误:access_key_id")
|
||||
}
|
||||
accessSecret, ok := cfg["access_key_secret"].(string)
|
||||
if !ok || accessSecret == "" {
|
||||
return fmt.Errorf("参数错误:access_key_secret")
|
||||
}
|
||||
name, _ := cfg["name"].(string)
|
||||
if name == "" {
|
||||
name = "allinssl-certificate"
|
||||
}
|
||||
endpoint, _ := cfg["endpoint"].(string)
|
||||
client, err := CreateClient(accessKey, accessSecret, endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Upload(client, certPEM, keyPEM, name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
58
plugins/alicloud/cdn/action.go
Normal file
58
plugins/alicloud/cdn/action.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package cdn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
aliyuncdn "github.com/alibabacloud-go/cdn-20180510/v6/client"
|
||||
aliyunopenapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
func createClient(accessKey, accessSecret string) (*aliyuncdn.Client, error) {
|
||||
config := &aliyunopenapi.Config{
|
||||
AccessKeyId: tea.String(accessKey),
|
||||
AccessKeySecret: tea.String(accessSecret),
|
||||
Endpoint: tea.String("cdn.aliyuncs.com"),
|
||||
}
|
||||
client, err := aliyuncdn.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func Deploy(cfg map[string]any) error {
|
||||
certPEM, ok := cfg["cert"].(string)
|
||||
if !ok || certPEM == "" {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
keyPEM, ok := cfg["key"].(string)
|
||||
if !ok || keyPEM == "" {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
accessKey, ok := cfg["access_key_id"].(string)
|
||||
if !ok || accessKey == "" {
|
||||
return fmt.Errorf("参数错误:access_key_id")
|
||||
}
|
||||
accessSecret, ok := cfg["access_key_secret"].(string)
|
||||
if !ok || accessSecret == "" {
|
||||
return fmt.Errorf("参数错误:access_key_secret")
|
||||
}
|
||||
domain, ok := cfg["domain"].(string)
|
||||
if !ok || domain == "" {
|
||||
return fmt.Errorf("参数错误:domain")
|
||||
}
|
||||
client, err := createClient(accessKey, accessSecret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := &aliyuncdn.SetCdnDomainSSLCertificateRequest{
|
||||
DomainName: tea.String(domain),
|
||||
SSLProtocol: tea.String("on"),
|
||||
SSLPub: tea.String(strings.TrimSpace(certPEM)),
|
||||
SSLPri: tea.String(strings.TrimSpace(keyPEM)),
|
||||
}
|
||||
_, err = client.SetCdnDomainSSLCertificate(req)
|
||||
return err
|
||||
}
|
||||
56
plugins/alicloud/dcdn/action.go
Normal file
56
plugins/alicloud/dcdn/action.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package dcdn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
aliyunopenapi "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"
|
||||
)
|
||||
|
||||
func createClient(accessKey, accessSecret string) (*dcdn.Client, error) {
|
||||
config := &aliyunopenapi.Config{
|
||||
AccessKeyId: tea.String(accessKey),
|
||||
AccessKeySecret: tea.String(accessSecret),
|
||||
RegionId: tea.String("cn-hangzhou"),
|
||||
}
|
||||
return dcdn.NewClient(config)
|
||||
}
|
||||
|
||||
func Deploy(cfg map[string]any) error {
|
||||
certPEM, ok := cfg["cert"].(string)
|
||||
if !ok || certPEM == "" {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
keyPEM, ok := cfg["key"].(string)
|
||||
if !ok || keyPEM == "" {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
accessKey, ok := cfg["access_key_id"].(string)
|
||||
if !ok || accessKey == "" {
|
||||
return fmt.Errorf("参数错误:access_key_id")
|
||||
}
|
||||
accessSecret, ok := cfg["access_key_secret"].(string)
|
||||
if !ok || accessSecret == "" {
|
||||
return fmt.Errorf("参数错误:access_key_secret")
|
||||
}
|
||||
domain, ok := cfg["domain"].(string)
|
||||
if !ok || domain == "" {
|
||||
return fmt.Errorf("参数错误:domain")
|
||||
}
|
||||
client, err := createClient(accessKey, accessSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建 DCDN 客户端失败: %w", err)
|
||||
}
|
||||
req := &dcdn.SetDcdnDomainSSLCertificateRequest{
|
||||
DomainName: tea.String(domain),
|
||||
SSLPri: tea.String(keyPEM),
|
||||
SSLPub: tea.String(certPEM),
|
||||
SSLProtocol: tea.String("on"),
|
||||
CertType: tea.String("upload"),
|
||||
}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
_, err = client.SetDcdnDomainSSLCertificateWithOptions(req, runtime)
|
||||
return err
|
||||
}
|
||||
74
plugins/alicloud/esa/action.go
Normal file
74
plugins/alicloud/esa/action.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package esa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
esa "github.com/alibabacloud-go/esa-20240910/v2/client"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
func CreateClient(accessKey, accessSecret string) (*esa.Client, error) {
|
||||
config := &openapi.Config{
|
||||
AccessKeyId: tea.String(accessKey),
|
||||
AccessKeySecret: tea.String(accessSecret),
|
||||
Endpoint: tea.String("esa.ap-southeast-1.aliyuncs.com"),
|
||||
}
|
||||
return esa.NewClient(config)
|
||||
}
|
||||
|
||||
func UploadCert(client *esa.Client, siteID int64, certPEM, keyPEM string) error {
|
||||
req := esa.SetCertificateRequest{
|
||||
SiteId: tea.Int64(siteID),
|
||||
Type: tea.String("upload"),
|
||||
Certificate: tea.String(certPEM),
|
||||
PrivateKey: tea.String(keyPEM),
|
||||
}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
_, err := client.SetCertificateWithOptions(&req, runtime)
|
||||
return err
|
||||
}
|
||||
|
||||
func Deploy(cfg map[string]any) error {
|
||||
certPEM, ok := cfg["cert"].(string)
|
||||
if !ok || certPEM == "" {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
keyPEM, ok := cfg["key"].(string)
|
||||
if !ok || keyPEM == "" {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
accessKey, ok := cfg["access_key_id"].(string)
|
||||
if !ok || accessKey == "" {
|
||||
return fmt.Errorf("参数错误:access_key_id")
|
||||
}
|
||||
accessSecret, ok := cfg["access_key_secret"].(string)
|
||||
if !ok || accessSecret == "" {
|
||||
return fmt.Errorf("参数错误:access_key_secret")
|
||||
}
|
||||
var siteID int64
|
||||
switch v := cfg["site_id"].(type) {
|
||||
case float64:
|
||||
siteID = int64(v)
|
||||
case string:
|
||||
var err error
|
||||
siteID, err = strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("site_id 格式错误: %w", err)
|
||||
}
|
||||
case int:
|
||||
siteID = int64(v)
|
||||
default:
|
||||
return fmt.Errorf("site_id 格式错误")
|
||||
}
|
||||
client, err := CreateClient(accessKey, accessSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建 ESA 客户端失败: %w", err)
|
||||
}
|
||||
if err := UploadCert(client, siteID, certPEM, keyPEM); err != nil {
|
||||
return fmt.Errorf("上传证书到 ESA 失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
105
plugins/alicloud/main.go
Normal file
105
plugins/alicloud/main.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
//go:embed metadata.json
|
||||
var metadataJSON []byte
|
||||
|
||||
var pluginMeta map[string]interface{}
|
||||
|
||||
func init() {
|
||||
if err := json.Unmarshal(metadataJSON, &pluginMeta); err != nil {
|
||||
panic(fmt.Sprintf("解析元数据失败: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Action string `json:"action"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Result map[string]interface{} `json:"result"`
|
||||
}
|
||||
|
||||
func outputJSON(resp *Response) {
|
||||
_ = json.NewEncoder(os.Stdout).Encode(resp)
|
||||
}
|
||||
|
||||
func outputError(msg string, err error) {
|
||||
outputJSON(&Response{
|
||||
Status: "error",
|
||||
Message: fmt.Sprintf("%s: %v", msg, err),
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
var req Request
|
||||
input, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
outputError("读取输入失败", err)
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(input, &req); err != nil {
|
||||
outputError("解析请求失败", err)
|
||||
return
|
||||
}
|
||||
switch req.Action {
|
||||
case "get_metadata":
|
||||
outputJSON(&Response{Status: "success", Message: "插件信息", Result: pluginMeta})
|
||||
case "list_actions":
|
||||
outputJSON(&Response{Status: "success", Message: "支持的动作", Result: map[string]interface{}{"actions": pluginMeta["actions"]}})
|
||||
case "cdn":
|
||||
rep, err := Cdn(req.Params)
|
||||
if err != nil {
|
||||
outputError("CDN 部署失败", err)
|
||||
return
|
||||
}
|
||||
outputJSON(rep)
|
||||
case "dcdn":
|
||||
rep, err := Dcdn(req.Params)
|
||||
if err != nil {
|
||||
outputError("DCDN 部署失败", err)
|
||||
return
|
||||
}
|
||||
outputJSON(rep)
|
||||
case "oss":
|
||||
rep, err := Oss(req.Params)
|
||||
if err != nil {
|
||||
outputError("OSS 部署失败", err)
|
||||
return
|
||||
}
|
||||
outputJSON(rep)
|
||||
case "waf":
|
||||
rep, err := Waf(req.Params)
|
||||
if err != nil {
|
||||
outputError("WAF 部署失败", err)
|
||||
return
|
||||
}
|
||||
outputJSON(rep)
|
||||
case "esa":
|
||||
rep, err := Esa(req.Params)
|
||||
if err != nil {
|
||||
outputError("ESA 部署失败", err)
|
||||
return
|
||||
}
|
||||
outputJSON(rep)
|
||||
case "cas":
|
||||
rep, err := Cas(req.Params)
|
||||
if err != nil {
|
||||
outputError("CAS 上传失败", err)
|
||||
return
|
||||
}
|
||||
outputJSON(rep)
|
||||
default:
|
||||
outputJSON(&Response{Status: "error", Message: "未知 action: " + req.Action})
|
||||
}
|
||||
}
|
||||
118
plugins/alicloud/metadata.json
Normal file
118
plugins/alicloud/metadata.json
Normal file
@@ -0,0 +1,118 @@
|
||||
{
|
||||
"name": "aliyun",
|
||||
"description": "阿里云",
|
||||
"version": "1.0.0",
|
||||
"author": "主包",
|
||||
"config": [
|
||||
{
|
||||
"name": "access_key_id",
|
||||
"type": "string",
|
||||
"description": "阿里云 AccessKeyId",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "access_key_secret",
|
||||
"type": "string",
|
||||
"description": "阿里云 AccessKeySecret",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "cas",
|
||||
"description": "上传到阿里云 CAS",
|
||||
"params": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"description": "证书名称",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "endpoint",
|
||||
"type": "string",
|
||||
"description": "CAS 端点",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "cdn",
|
||||
"description": "部署到阿里云 CDN",
|
||||
"params": [
|
||||
{
|
||||
"name": "domain",
|
||||
"type": "string",
|
||||
"description": "域名",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "dcdn",
|
||||
"description": "部署到阿里云 DCDN",
|
||||
"params": [
|
||||
{
|
||||
"name": "domain",
|
||||
"type": "string",
|
||||
"description": "域名",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "oss",
|
||||
"description": "部署到阿里云 OSS",
|
||||
"params": [
|
||||
{
|
||||
"name": "region",
|
||||
"type": "string",
|
||||
"description": "区域",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "bucket",
|
||||
"type": "string",
|
||||
"description": "存储桶",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "domain",
|
||||
"type": "string",
|
||||
"description": "域名",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "waf",
|
||||
"description": "部署到阿里云 WAF",
|
||||
"params": [
|
||||
{
|
||||
"name": "region",
|
||||
"type": "string",
|
||||
"description": "区域",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "domain",
|
||||
"type": "string",
|
||||
"description": "域名",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "esa",
|
||||
"description": "部署到阿里云 ESA",
|
||||
"params": [
|
||||
{
|
||||
"name": "site_id",
|
||||
"type": "string",
|
||||
"description": "站点 ID",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
62
plugins/alicloud/oss/action.go
Normal file
62
plugins/alicloud/oss/action.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package osswrap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
)
|
||||
|
||||
func createClient(accessKeyId, accessKeySecret, region string) (*oss.Client, error) {
|
||||
var endpoint string
|
||||
switch region {
|
||||
case "":
|
||||
endpoint = "oss.aliyuncs.com"
|
||||
case "cn-hzjbp", "cn-hzjbp-a", "cn-hzjbp-b":
|
||||
endpoint = "oss-cn-hzjbp-a-internal.aliyuncs.com"
|
||||
case "cn-shanghai-finance-1", "cn-shenzhen-finance-1", "cn-beijing-finance-1", "cn-north-2-gov-1":
|
||||
endpoint = fmt.Sprintf("oss-%s-internal.aliyuncs.com", region)
|
||||
default:
|
||||
endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region)
|
||||
}
|
||||
return oss.New(endpoint, accessKeyId, accessKeySecret)
|
||||
}
|
||||
|
||||
func Deploy(cfg map[string]any) error {
|
||||
certPEM, ok := cfg["cert"].(string)
|
||||
if !ok || certPEM == "" {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
keyPEM, ok := cfg["key"].(string)
|
||||
if !ok || keyPEM == "" {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
accessKey, ok := cfg["access_key_id"].(string)
|
||||
if !ok || accessKey == "" {
|
||||
return fmt.Errorf("参数错误:access_key_id")
|
||||
}
|
||||
accessSecret, ok := cfg["access_key_secret"].(string)
|
||||
if !ok || accessSecret == "" {
|
||||
return fmt.Errorf("参数错误:access_key_secret")
|
||||
}
|
||||
region, ok := cfg["region"].(string)
|
||||
if !ok || region == "" {
|
||||
return fmt.Errorf("参数错误:region")
|
||||
}
|
||||
bucket, ok := cfg["bucket"].(string)
|
||||
if !ok || bucket == "" {
|
||||
return fmt.Errorf("参数错误:bucket")
|
||||
}
|
||||
domain, ok := cfg["domain"].(string)
|
||||
if !ok || domain == "" {
|
||||
return fmt.Errorf("参数错误:domain")
|
||||
}
|
||||
client, err := createClient(accessKey, accessSecret, region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
putReq := oss.PutBucketCname{
|
||||
Cname: domain,
|
||||
CertificateConfiguration: &oss.CertificateConfiguration{Certificate: certPEM, PrivateKey: keyPEM, Force: true},
|
||||
}
|
||||
return client.PutBucketCnameWithCertificate(bucket, putReq)
|
||||
}
|
||||
54
plugins/alicloud/waf/action.go
Normal file
54
plugins/alicloud/waf/action.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Deploy(cfg map[string]any) error {
|
||||
certPEM, ok := cfg["cert"].(string)
|
||||
if !ok || certPEM == "" {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
keyPEM, ok := cfg["key"].(string)
|
||||
if !ok || keyPEM == "" {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
accessKey, ok := cfg["access_key_id"].(string)
|
||||
if !ok || accessKey == "" {
|
||||
return fmt.Errorf("参数错误:access_key_id")
|
||||
}
|
||||
accessSecret, ok := cfg["access_key_secret"].(string)
|
||||
if !ok || accessSecret == "" {
|
||||
return fmt.Errorf("参数错误:access_key_secret")
|
||||
}
|
||||
region, ok := cfg["region"].(string)
|
||||
if !ok || region == "" {
|
||||
return fmt.Errorf("参数错误:region")
|
||||
}
|
||||
domain, ok := cfg["domain"].(string)
|
||||
if !ok || domain == "" {
|
||||
return fmt.Errorf("参数错误:domain")
|
||||
}
|
||||
client, err := ClientAliWaf(accessKey, accessSecret, region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
instanceId, err := client.IGetInstanceId()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取地区实例ID失败: %v", err)
|
||||
}
|
||||
domainDesc, err := client.IDescribeDomainDetail(*instanceId, domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取域名配置详情失败: %v", err)
|
||||
}
|
||||
certName := fmt.Sprintf("%s_allinssl_%d", domain, time.Now().UnixMilli())
|
||||
certId, err := client.ICreateCerts(certName, certPEM, keyPEM, *instanceId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建证书失败: %v", err)
|
||||
}
|
||||
if err := client.IUpdateDomain(domainDesc, *instanceId, *certId); err != nil {
|
||||
return fmt.Errorf("更新证书失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
246
plugins/alicloud/waf/client.go
Normal file
246
plugins/alicloud/waf/client.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package waf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
openapiutil "github.com/alibabacloud-go/openapi-util/service"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
aliyunwaf "github.com/alibabacloud-go/waf-openapi-20211001/v5/client"
|
||||
)
|
||||
|
||||
type AliyunWafClient struct {
|
||||
aliyunwaf.Client
|
||||
accessKey string
|
||||
accessSecret string
|
||||
region string
|
||||
}
|
||||
|
||||
func ClientAliWaf(accessKey, accessSecret, region string) (_result *AliyunWafClient, err error) {
|
||||
config := &openapi.Config{
|
||||
AccessKeyId: tea.String(accessKey),
|
||||
AccessKeySecret: tea.String(accessSecret),
|
||||
Endpoint: tea.String(fmt.Sprintf("wafopenapi.%s.aliyuncs.com", region)),
|
||||
}
|
||||
client, err := aliyunwaf.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aliyunwafClient := &AliyunWafClient{
|
||||
Client: *client,
|
||||
accessKey: accessKey,
|
||||
accessSecret: accessSecret,
|
||||
region: region,
|
||||
}
|
||||
return aliyunwafClient, nil
|
||||
}
|
||||
|
||||
type CreateCertsResponseBody struct {
|
||||
CertIdentifier *string `json:"CertIdentifier,omitempty" xml:"DomainInfo,omitempty"`
|
||||
RequestId *string `json:"RequestId,omitempty" xml:"RequestId,omitempty"`
|
||||
}
|
||||
|
||||
type CreateCertsResponse struct {
|
||||
Headers map[string]*string `json:"headers,omitempty" xml:"headers,omitempty"`
|
||||
StatusCode *int32 `json:"statusCode,omitempty" xml:"statusCode,omitempty"`
|
||||
Body *CreateCertsResponseBody `json:"body,omitempty" xml:"body,omitempty"`
|
||||
}
|
||||
|
||||
func (client *AliyunWafClient) ICreateCerts(certName, certContent, certKey, instanceId string) (certId *string, _err error) {
|
||||
query := map[string]interface{}{
|
||||
"CertName": certName,
|
||||
"CertContent": certContent,
|
||||
"CertKey": certKey,
|
||||
"InstanceId": instanceId,
|
||||
}
|
||||
req := &openapi.OpenApiRequest{Query: openapiutil.Query(query)}
|
||||
params := &openapi.Params{
|
||||
Action: tea.String("CreateCerts"),
|
||||
Version: tea.String("2021-10-01"),
|
||||
Protocol: tea.String("HTTPS"),
|
||||
Pathname: tea.String("/"),
|
||||
Method: tea.String("POST"),
|
||||
AuthType: tea.String("AK"),
|
||||
Style: tea.String("RPC"),
|
||||
ReqBodyType: tea.String("formData"),
|
||||
BodyType: tea.String("json"),
|
||||
}
|
||||
createCertsResponse := &CreateCertsResponse{}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
_body, _err := client.CallApi(params, req, runtime)
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
_err = tea.Convert(_body, &createCertsResponse)
|
||||
certId = createCertsResponse.Body.CertIdentifier
|
||||
return certId, _err
|
||||
}
|
||||
|
||||
func (client *AliyunWafClient) IGetInstanceId() (instanceId *string, _err error) {
|
||||
req := &aliyunwaf.DescribeInstanceRequest{RegionId: tea.String(client.region)}
|
||||
response, _err := client.DescribeInstance(req)
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
instanceId = response.Body.InstanceId
|
||||
if instanceId == nil || *instanceId == "" {
|
||||
_err = fmt.Errorf("未找到WAF实例ID,请检查是否已创建WAF实例")
|
||||
return nil, _err
|
||||
}
|
||||
return instanceId, _err
|
||||
}
|
||||
|
||||
func (client *AliyunWafClient) IDescribeDomainDetail(instanceId, domain string) (describeDomainDetailResponseBody *aliyunwaf.DescribeDomainDetailResponseBody, _err error) {
|
||||
req := &aliyunwaf.DescribeDomainDetailRequest{
|
||||
InstanceId: tea.String(instanceId),
|
||||
RegionId: tea.String(client.region),
|
||||
Domain: tea.String(domain),
|
||||
}
|
||||
response, _err := client.DescribeDomainDetail(req)
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
describeDomainDetailResponseBody = response.Body
|
||||
return describeDomainDetailResponseBody, _err
|
||||
}
|
||||
|
||||
func (client *AliyunWafClient) IUpdateDomain(domainDesc *aliyunwaf.DescribeDomainDetailResponseBody, instanceId, certId string) error {
|
||||
modifyDomainReq := &aliyunwaf.ModifyDomainRequest{
|
||||
InstanceId: tea.String(instanceId),
|
||||
RegionId: tea.String(client.region),
|
||||
Domain: domainDesc.Domain,
|
||||
Listen: &aliyunwaf.ModifyDomainRequestListen{CertId: tea.String(certId)},
|
||||
}
|
||||
assignDomain(domainDesc, modifyDomainReq)
|
||||
_, err := client.ModifyDomain(modifyDomainReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func assignDomain(from *aliyunwaf.DescribeDomainDetailResponseBody, to *aliyunwaf.ModifyDomainRequest) *aliyunwaf.ModifyDomainRequest {
|
||||
if from == nil {
|
||||
return to
|
||||
}
|
||||
if from.Listen != nil {
|
||||
if to.Listen == nil {
|
||||
to.Listen = &aliyunwaf.ModifyDomainRequestListen{}
|
||||
}
|
||||
if from.Listen.CipherSuite != nil {
|
||||
to.Listen.CipherSuite = tea.Int32(int32(*from.Listen.CipherSuite))
|
||||
}
|
||||
if from.Listen.CustomCiphers != nil {
|
||||
to.Listen.CustomCiphers = from.Listen.CustomCiphers
|
||||
}
|
||||
if from.Listen.EnableTLSv3 != nil {
|
||||
to.Listen.EnableTLSv3 = from.Listen.EnableTLSv3
|
||||
}
|
||||
if from.Listen.ExclusiveIp != nil {
|
||||
to.Listen.ExclusiveIp = from.Listen.ExclusiveIp
|
||||
}
|
||||
if from.Listen.FocusHttps != nil {
|
||||
to.Listen.FocusHttps = from.Listen.FocusHttps
|
||||
}
|
||||
if from.Listen.Http2Enabled != nil {
|
||||
to.Listen.Http2Enabled = from.Listen.Http2Enabled
|
||||
}
|
||||
if from.Listen.IPv6Enabled != nil {
|
||||
to.Listen.IPv6Enabled = from.Listen.IPv6Enabled
|
||||
}
|
||||
if from.Listen.ProtectionResource != nil {
|
||||
to.Listen.ProtectionResource = from.Listen.ProtectionResource
|
||||
}
|
||||
if from.Listen.TLSVersion != nil {
|
||||
to.Listen.TLSVersion = from.Listen.TLSVersion
|
||||
}
|
||||
if from.Listen.XffHeaderMode != nil {
|
||||
to.Listen.XffHeaderMode = tea.Int32(int32(*from.Listen.XffHeaderMode))
|
||||
}
|
||||
if from.Listen.XffHeaders != nil {
|
||||
to.Listen.XffHeaders = from.Listen.XffHeaders
|
||||
}
|
||||
if from.Listen.HttpPorts != nil {
|
||||
to.Listen.HttpPorts = make([]*int32, len(from.Listen.HttpPorts))
|
||||
for i, port := range from.Listen.HttpPorts {
|
||||
if port != nil {
|
||||
to.Listen.HttpPorts[i] = tea.Int32(int32(*port))
|
||||
}
|
||||
}
|
||||
}
|
||||
if from.Listen.HttpsPorts != nil {
|
||||
to.Listen.HttpsPorts = make([]*int32, len(from.Listen.HttpsPorts))
|
||||
for i, port := range from.Listen.HttpsPorts {
|
||||
if port != nil {
|
||||
to.Listen.HttpsPorts[i] = tea.Int32(int32(*port))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if from.Redirect != nil {
|
||||
if to.Redirect == nil {
|
||||
to.Redirect = &aliyunwaf.ModifyDomainRequestRedirect{}
|
||||
}
|
||||
if from.Redirect.ConnectTimeout != nil {
|
||||
to.Redirect.ConnectTimeout = from.Redirect.ConnectTimeout
|
||||
}
|
||||
if from.Redirect.FocusHttpBackend != nil {
|
||||
to.Redirect.FocusHttpBackend = from.Redirect.FocusHttpBackend
|
||||
}
|
||||
if from.Redirect.Keepalive != nil {
|
||||
to.Redirect.Keepalive = from.Redirect.Keepalive
|
||||
}
|
||||
if from.Redirect.KeepaliveRequests != nil {
|
||||
to.Redirect.KeepaliveRequests = from.Redirect.KeepaliveRequests
|
||||
}
|
||||
if from.Redirect.KeepaliveTimeout != nil {
|
||||
to.Redirect.KeepaliveTimeout = from.Redirect.KeepaliveTimeout
|
||||
}
|
||||
if from.Redirect.Loadbalance != nil {
|
||||
to.Redirect.Loadbalance = from.Redirect.Loadbalance
|
||||
}
|
||||
if from.Redirect.ReadTimeout != nil {
|
||||
to.Redirect.ReadTimeout = from.Redirect.ReadTimeout
|
||||
}
|
||||
if from.Redirect.Retry != nil {
|
||||
to.Redirect.Retry = from.Redirect.Retry
|
||||
}
|
||||
if from.Redirect.SniEnabled != nil {
|
||||
to.Redirect.SniEnabled = from.Redirect.SniEnabled
|
||||
}
|
||||
if from.Redirect.SniHost != nil {
|
||||
to.Redirect.SniHost = from.Redirect.SniHost
|
||||
}
|
||||
if from.Redirect.WriteTimeout != nil {
|
||||
to.Redirect.WriteTimeout = from.Redirect.WriteTimeout
|
||||
}
|
||||
if from.Redirect.XffProto != nil {
|
||||
to.Redirect.XffProto = from.Redirect.XffProto
|
||||
}
|
||||
if from.Redirect.Backends != nil {
|
||||
to.Redirect.Backends = make([]*string, len(from.Redirect.Backends))
|
||||
for i, backend := range from.Redirect.Backends {
|
||||
if backend != nil {
|
||||
to.Redirect.Backends[i] = backend.Backend
|
||||
}
|
||||
}
|
||||
}
|
||||
if from.Redirect.BackupBackends != nil {
|
||||
to.Redirect.BackupBackends = make([]*string, len(from.Redirect.BackupBackends))
|
||||
for i, backend := range from.Redirect.BackupBackends {
|
||||
if backend != nil {
|
||||
to.Redirect.BackupBackends[i] = backend.Backend
|
||||
}
|
||||
}
|
||||
}
|
||||
if from.Redirect.RequestHeaders != nil {
|
||||
to.Redirect.RequestHeaders = make([]*aliyunwaf.ModifyDomainRequestRedirectRequestHeaders, len(from.Redirect.RequestHeaders))
|
||||
for i, header := range from.Redirect.RequestHeaders {
|
||||
if header != nil {
|
||||
to.Redirect.RequestHeaders[i] = &aliyunwaf.ModifyDomainRequestRedirectRequestHeaders{Key: header.Key, Value: header.Value}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return to
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"ALLinSSL/plugins/aliyun/cas"
|
||||
"ALLinSSL/plugins/aliyun/esa"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func uploadToCAS(cfg map[string]any) (*Response, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("config cannot be nil")
|
||||
}
|
||||
certStr, ok := cfg["cert"].(string)
|
||||
if !ok || certStr == "" {
|
||||
return nil, fmt.Errorf("cert is required and must be a string")
|
||||
}
|
||||
keyStr, ok := cfg["key"].(string)
|
||||
if !ok || keyStr == "" {
|
||||
return nil, fmt.Errorf("key is required and must be a string")
|
||||
}
|
||||
accessKey, ok := cfg["access_key"].(string)
|
||||
if !ok || accessKey == "" {
|
||||
return nil, fmt.Errorf("access_key is required and must be a string")
|
||||
}
|
||||
secretKey, ok := cfg["secret_key"].(string)
|
||||
if !ok || secretKey == "" {
|
||||
return nil, fmt.Errorf("secret_key is required and must be a string")
|
||||
}
|
||||
endpoint, ok := cfg["endpoint"].(string)
|
||||
if !ok || endpoint == "" {
|
||||
endpoint = "cas.ap-southeast-1.aliyuncs.com" // 默认值
|
||||
}
|
||||
name, ok := cfg["name"].(string)
|
||||
if !ok || name == "" {
|
||||
name = "allinssl-certificate" // 默认名称
|
||||
}
|
||||
|
||||
client, err := cas.CreateClient(accessKey, secretKey, endpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create CAS client: %w", err)
|
||||
}
|
||||
// 上传证书到 CAS
|
||||
err = cas.UploadToCas(client, certStr, keyStr, name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to upload certificate to CAS: %w", err)
|
||||
}
|
||||
|
||||
return &Response{
|
||||
Status: "success",
|
||||
Message: "CAS upload successful",
|
||||
Result: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func deployToESA(cfg map[string]any) (*Response, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("config cannot be nil")
|
||||
}
|
||||
certPEM, ok := cfg["cert"].(string)
|
||||
if !ok || certPEM == "" {
|
||||
return nil, fmt.Errorf("cert is required and must be a string")
|
||||
}
|
||||
privkeyPEM, ok := cfg["key"].(string)
|
||||
if !ok || privkeyPEM == "" {
|
||||
return nil, fmt.Errorf("key is required and must be a string")
|
||||
}
|
||||
accessKey, ok := cfg["access_key"].(string)
|
||||
if !ok || accessKey == "" {
|
||||
return nil, fmt.Errorf("access_key is required and must be a string")
|
||||
}
|
||||
secretKey, ok := cfg["secret_key"].(string)
|
||||
if !ok || secretKey == "" {
|
||||
return nil, fmt.Errorf("secret_key is required and must be a string")
|
||||
}
|
||||
var siteID int64
|
||||
switch v := cfg["site_id"].(type) {
|
||||
case float64:
|
||||
siteID = int64(v)
|
||||
case string:
|
||||
var err error
|
||||
siteID, err = strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("site_id format error: %w", err)
|
||||
}
|
||||
case int:
|
||||
siteID = int64(v)
|
||||
default:
|
||||
return nil, fmt.Errorf("site_id format error")
|
||||
}
|
||||
var delRepeatDomainCert bool
|
||||
switch v := cfg["del_repeat_domain_cert"].(type) {
|
||||
case bool:
|
||||
delRepeatDomainCert = v
|
||||
case string:
|
||||
if v == "true" {
|
||||
delRepeatDomainCert = true
|
||||
}
|
||||
case nil:
|
||||
delRepeatDomainCert = false
|
||||
}
|
||||
|
||||
client, err := esa.CreateEsaClient(accessKey, secretKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create ESA client: %w", err)
|
||||
}
|
||||
|
||||
// 检查是否需要删除重复的域名证书
|
||||
if delRepeatDomainCert {
|
||||
// 解析现有证书的域名
|
||||
certObj, err := ParseCertificate([]byte(certPEM))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse certificate: %w", err)
|
||||
}
|
||||
domainSet := make(map[string]bool)
|
||||
|
||||
if certObj.Subject.CommonName != "" {
|
||||
domainSet[certObj.Subject.CommonName] = true
|
||||
}
|
||||
for _, dns := range certObj.DNSNames {
|
||||
domainSet[dns] = true
|
||||
}
|
||||
|
||||
// 转成切片并拼接成逗号分隔的字符串
|
||||
var domains []string
|
||||
for domain := range domainSet {
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
domainList := strings.Join(domains, ",")
|
||||
|
||||
certList, err := esa.ListCertFromESA(client, siteID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list certificates from ESA: %w", err)
|
||||
}
|
||||
for _, cert := range certList {
|
||||
if *cert.SAN == domainList {
|
||||
err = esa.DeleteEsaCert(client, siteID, *cert.Id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to delete existing certificate: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = esa.UploadCertToESA(client, siteID, certPEM, privkeyPEM)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to upload certificate to ESA: %w", err)
|
||||
}
|
||||
|
||||
return &Response{
|
||||
Status: "success",
|
||||
Message: "ESA deployment successful",
|
||||
Result: nil,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package cas
|
||||
|
||||
import (
|
||||
cas "github.com/alibabacloud-go/cas-20200407/v4/client"
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
func CreateClient(accessKey, accessSecret, endpoint string) (*cas.Client, error) {
|
||||
if endpoint == "" {
|
||||
endpoint = "cas.ap-southeast-1.aliyuncs.com"
|
||||
}
|
||||
config := &openapi.Config{
|
||||
AccessKeyId: tea.String(accessKey),
|
||||
AccessKeySecret: tea.String(accessSecret),
|
||||
Endpoint: tea.String(endpoint),
|
||||
}
|
||||
return cas.NewClient(config)
|
||||
}
|
||||
|
||||
func UploadToCas(client *cas.Client, cert, key, name string) error {
|
||||
uploadUserCertificateRequest := &cas.UploadUserCertificateRequest{
|
||||
Name: tea.String(name),
|
||||
Cert: tea.String(cert),
|
||||
Key: tea.String(key),
|
||||
}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
_, err := client.UploadUserCertificateWithOptions(uploadUserCertificateRequest, runtime)
|
||||
return err
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package esa
|
||||
|
||||
import (
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
esa "github.com/alibabacloud-go/esa-20240910/v2/client"
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
// CreateEsaClient creates a new ESA client with the provided access key and secret.
|
||||
func CreateEsaClient(accessKey, accessSecret string) (*esa.Client, error) {
|
||||
config := &openapi.Config{
|
||||
AccessKeyId: tea.String(accessKey),
|
||||
AccessKeySecret: tea.String(accessSecret),
|
||||
Endpoint: tea.String("esa.ap-southeast-1.aliyuncs.com"),
|
||||
}
|
||||
return esa.NewClient(config)
|
||||
}
|
||||
|
||||
// UploadCertToESA uploads the certificate and private key to Alibaba Cloud ESA.
|
||||
func UploadCertToESA(client *esa.Client, id int64, certPEM, privkeyPEM string) error {
|
||||
req := esa.SetCertificateRequest{
|
||||
SiteId: tea.Int64(id),
|
||||
Type: tea.String("upload"),
|
||||
Certificate: tea.String(certPEM),
|
||||
PrivateKey: tea.String(privkeyPEM),
|
||||
}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
|
||||
_, err := client.SetCertificateWithOptions(&req, runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListCertFromESA retrieves the list of certificates from Alibaba Cloud ESA for a given site ID.
|
||||
func ListCertFromESA(client *esa.Client, id int64) ([]*esa.ListCertificatesResponseBodyResult, error) {
|
||||
req := esa.ListCertificatesRequest{
|
||||
SiteId: tea.Int64(id),
|
||||
}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
resp, err := client.ListCertificatesWithOptions(&req, runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Body.Result, nil
|
||||
}
|
||||
|
||||
// DeleteEsaCert deletes a certificate from Alibaba Cloud ESA by its ID.
|
||||
func DeleteEsaCert(client *esa.Client, id int64, certID string) error {
|
||||
req := esa.DeleteCertificateRequest{
|
||||
SiteId: tea.Int64(id),
|
||||
Id: tea.String(certID),
|
||||
}
|
||||
runtime := &util.RuntimeOptions{}
|
||||
|
||||
_, err := client.DeleteCertificateWithOptions(&req, runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type ActionInfo struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Params map[string]any `json:"params,omitempty"` // 可选参数
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Action string `json:"action"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Result map[string]interface{} `json:"result"`
|
||||
}
|
||||
|
||||
var pluginMeta = map[string]interface{}{
|
||||
"name": "aliyun",
|
||||
"description": "部署到阿里云",
|
||||
"version": "1.0.0",
|
||||
"author": "主包",
|
||||
"config": map[string]interface{}{
|
||||
"access_key": "阿里云 AccessKey",
|
||||
"secret_key": "阿里云 SecretKey",
|
||||
},
|
||||
"actions": []ActionInfo{
|
||||
{
|
||||
Name: "deployToESA",
|
||||
Description: "部署到阿里云esa",
|
||||
Params: map[string]any{
|
||||
"site_id": "站点 ID",
|
||||
"del_repeat_domain_cert": "是否删除重复的域名证书,默认 false",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "uploadToCAS",
|
||||
Description: "上传到阿里云cas",
|
||||
Params: map[string]any{
|
||||
"name": "证书名称",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// **解析 PEM 格式的证书**
|
||||
func ParseCertificate(certPEM []byte) (*x509.Certificate, error) {
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("无法解析证书 PEM")
|
||||
}
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
func outputJSON(resp *Response) {
|
||||
_ = json.NewEncoder(os.Stdout).Encode(resp)
|
||||
}
|
||||
|
||||
func outputError(msg string, err error) {
|
||||
outputJSON(&Response{
|
||||
Status: "error",
|
||||
Message: fmt.Sprintf("%s: %v", msg, err),
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
var req Request
|
||||
input, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
outputError("读取输入失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(input, &req); err != nil {
|
||||
outputError("解析请求失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch req.Action {
|
||||
case "get_metadata":
|
||||
outputJSON(&Response{
|
||||
Status: "success",
|
||||
Message: "插件信息",
|
||||
Result: pluginMeta,
|
||||
})
|
||||
case "list_actions":
|
||||
outputJSON(&Response{
|
||||
Status: "success",
|
||||
Message: "支持的动作",
|
||||
Result: map[string]interface{}{"actions": pluginMeta["actions"]},
|
||||
})
|
||||
case "deployToESA":
|
||||
rep, err := deployToESA(req.Params)
|
||||
if err != nil {
|
||||
outputError("ESA 部署失败", err)
|
||||
return
|
||||
}
|
||||
outputJSON(rep)
|
||||
case "uploadToCAS":
|
||||
rep, err := uploadToCAS(req.Params)
|
||||
if err != nil {
|
||||
outputError("CAS 上传失败", err)
|
||||
return
|
||||
}
|
||||
outputJSON(rep)
|
||||
default:
|
||||
outputJSON(&Response{
|
||||
Status: "error",
|
||||
Message: "未知 action: " + req.Action,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"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 Cdn(cfg map[string]any) (*Response, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("config cannot be nil")
|
||||
}
|
||||
certStr, ok := cfg["cert"].(string)
|
||||
if !ok || certStr == "" {
|
||||
return nil, fmt.Errorf("cert is required and must be a string")
|
||||
}
|
||||
keyStr, ok := cfg["key"].(string)
|
||||
if !ok || keyStr == "" {
|
||||
return nil, fmt.Errorf("key is required and must be a string")
|
||||
}
|
||||
accessKey, ok := cfg["access_key"].(string)
|
||||
if !ok || accessKey == "" {
|
||||
return nil, fmt.Errorf("access_key is required and must be a string")
|
||||
}
|
||||
secretKey, ok := cfg["secret_key"].(string)
|
||||
if !ok || secretKey == "" {
|
||||
return nil, fmt.Errorf("secret_key is required and must be a string")
|
||||
}
|
||||
domain, ok := cfg["domain"].(string)
|
||||
if !ok || domain == "" {
|
||||
return nil, fmt.Errorf("domain is required and must be a string")
|
||||
}
|
||||
sha256, err := GetSHA256(certStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get SHA256 of cert: %w", err)
|
||||
}
|
||||
note := fmt.Sprintf("allinssl-%s", sha256)
|
||||
|
||||
a := NewAuth(accessKey, secretKey)
|
||||
// 检查证书是否已存在于 CDN
|
||||
// 只根据证书名称检查是否存在,格式为 "allinssl-<sha256>"
|
||||
certList, err := a.listCertFromCdn()
|
||||
if err != nil {
|
||||
return nil, 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 nil, fmt.Errorf("failed to upload to CDN: %w", err)
|
||||
}
|
||||
}
|
||||
// 绑定证书到域名
|
||||
bindRes, err := a.bindCertToCdn(certID, domain)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to bind cert to CDN: %w", err)
|
||||
}
|
||||
|
||||
return &Response{
|
||||
Status: "success",
|
||||
Message: "Certificate uploaded and bound successfully",
|
||||
Result: bindRes,
|
||||
}, 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
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type ActionInfo struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Params map[string]any `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Action string `json:"action"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Result map[string]interface{} `json:"result"`
|
||||
}
|
||||
|
||||
var pluginMeta = map[string]interface{}{
|
||||
"name": "doge",
|
||||
"description": "部署到多吉云",
|
||||
"version": "1.0.0",
|
||||
"author": "主包",
|
||||
"config": map[string]interface{}{
|
||||
"access_key": "多吉云 AccessKey",
|
||||
"secret_key": "多吉云 SecretKey",
|
||||
},
|
||||
"actions": []ActionInfo{
|
||||
{
|
||||
Name: "cdn",
|
||||
Description: "部署到多吉云cdn",
|
||||
Params: map[string]any{
|
||||
"domain": "CDN 域名",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func GetSHA256(certStr string) (string, error) {
|
||||
certPEM := []byte(certStr)
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return "", fmt.Errorf("无法解析证书 PEM")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("解析证书失败: %v", err)
|
||||
}
|
||||
|
||||
sha256Hash := sha256.Sum256(cert.Raw)
|
||||
return hex.EncodeToString(sha256Hash[:]), nil
|
||||
}
|
||||
|
||||
func outputJSON(resp *Response) {
|
||||
_ = json.NewEncoder(os.Stdout).Encode(resp)
|
||||
}
|
||||
|
||||
func outputError(msg string, err error) {
|
||||
outputJSON(&Response{
|
||||
Status: "error",
|
||||
Message: fmt.Sprintf("%s: %v", msg, err),
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
var req Request
|
||||
input, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
outputError("读取输入失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(input, &req); err != nil {
|
||||
outputError("解析请求失败", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch req.Action {
|
||||
case "get_metadata":
|
||||
outputJSON(&Response{
|
||||
Status: "success",
|
||||
Message: "插件信息",
|
||||
Result: pluginMeta,
|
||||
})
|
||||
case "list_actions":
|
||||
outputJSON(&Response{
|
||||
Status: "success",
|
||||
Message: "支持的动作",
|
||||
Result: map[string]interface{}{"actions": pluginMeta["actions"]},
|
||||
})
|
||||
case "cdn":
|
||||
rep, err := Cdn(req.Params)
|
||||
if err != nil {
|
||||
outputError("CDN 部署失败", err)
|
||||
return
|
||||
}
|
||||
outputJSON(rep)
|
||||
|
||||
default:
|
||||
outputJSON(&Response{
|
||||
Status: "error",
|
||||
Message: "未知 action: " + req.Action,
|
||||
})
|
||||
}
|
||||
}
|
||||
34
plugins/doge/metadata.json
Normal file
34
plugins/doge/metadata.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "doge",
|
||||
"description": "多吉云",
|
||||
"version": "1.0.0",
|
||||
"author": "主包",
|
||||
"config": [
|
||||
{
|
||||
"name": "access_key",
|
||||
"type": "string",
|
||||
"description": "多吉云 AccessKey",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "secret_key",
|
||||
"type": "string",
|
||||
"description": "多吉云 SecretKey",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "cdn",
|
||||
"description": "部署到多吉云cdn",
|
||||
"params": [
|
||||
{
|
||||
"name": "domain",
|
||||
"type": "string",
|
||||
"description": "CDN 域名",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user