mirror of
https://github.com/certimate-go/certimate.git
synced 2026-09-01 15:39:35 +08:00
feat(provider): support multiple certificates in deployment to tencentcloud edgeone
This commit is contained in:
@@ -26,6 +26,7 @@ func init() {
|
||||
ZoneId: xmaps.GetString(options.ProviderExtendedConfig, "zoneId"),
|
||||
DomainMatchPattern: xmaps.GetString(options.ProviderExtendedConfig, "domainMatchPattern"),
|
||||
Domains: lo.Filter(strings.Split(xmaps.GetString(options.ProviderExtendedConfig, "domains"), ";"), func(s string, _ int) bool { return s != "" }),
|
||||
EnableMultipleSSL: xmaps.GetBool(options.ProviderExtendedConfig, "enableMultipleSSL"),
|
||||
})
|
||||
return provider, err
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ func init() {
|
||||
{
|
||||
if nodeCfg["providerConfig"] != nil {
|
||||
providerCfg := nodeCfg["providerConfig"].(map[string]any)
|
||||
if providerCfg["websiteId"] != nil && providerCfg["websiteId"] != "" {
|
||||
if providerCfg["websiteId"] != nil && providerCfg["websiteId"].(string) != "" {
|
||||
providerCfg["websiteMatchPattern"] = "specified"
|
||||
nodeCfg["providerConfig"] = providerCfg
|
||||
|
||||
@@ -76,7 +76,7 @@ func init() {
|
||||
{
|
||||
if nodeCfg["providerConfig"] != nil {
|
||||
providerCfg := nodeCfg["providerConfig"].(map[string]any)
|
||||
if providerCfg["siteName"] != nil && providerCfg["siteName"] != "" {
|
||||
if providerCfg["siteNames"] == nil || providerCfg["siteNames"].(string) == "" {
|
||||
providerCfg["siteType"] = "php"
|
||||
providerCfg["siteNames"] = providerCfg["siteName"]
|
||||
delete(providerCfg, "siteName")
|
||||
@@ -123,7 +123,9 @@ func init() {
|
||||
|
||||
case "safeline":
|
||||
{
|
||||
node.Data["provider"] = "safeline-site"
|
||||
nodeCfg["provider"] = "safeline-site"
|
||||
|
||||
node.Data["config"] = nodeCfg
|
||||
_changed = true
|
||||
return
|
||||
}
|
||||
@@ -2,10 +2,12 @@ package tencentcloudeo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
@@ -18,6 +20,7 @@ import (
|
||||
"github.com/certimate-go/certimate/pkg/core/deployer/providers/tencentcloud-eo/internal"
|
||||
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
|
||||
xcerthostname "github.com/certimate-go/certimate/pkg/utils/cert/hostname"
|
||||
xcryptokey "github.com/certimate-go/certimate/pkg/utils/crypto/key"
|
||||
)
|
||||
|
||||
type DeployerConfig struct {
|
||||
@@ -34,6 +37,8 @@ type DeployerConfig struct {
|
||||
DomainMatchPattern string `json:"domainMatchPattern,omitempty"`
|
||||
// 加速域名列表(支持泛域名)。
|
||||
Domains []string `json:"domains"`
|
||||
// 是否启用多证书模式。
|
||||
EnableMultipleSSL bool `json:"enableMultipleSSL,omitempty"`
|
||||
}
|
||||
|
||||
type Deployer struct {
|
||||
@@ -97,6 +102,12 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
||||
}
|
||||
|
||||
// 获取全部可部署的域名信息
|
||||
domainsInZone, err := d.getAllDomainsInZone(ctx, d.config.ZoneId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取待部署的域名列表
|
||||
var domains []string
|
||||
switch d.config.DomainMatchPattern {
|
||||
@@ -115,11 +126,9 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
return nil, errors.New("config `domains` is required")
|
||||
}
|
||||
|
||||
domainCandidates, err := d.getAllDomainsInZone(ctx, d.config.ZoneId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domainCandidates := lo.Map(domainsInZone, func(domainInfo *tcteo.AccelerationDomain, _ int) string {
|
||||
return lo.FromPtr(domainInfo.DomainName)
|
||||
})
|
||||
domains = lo.Filter(domainCandidates, func(domain string, _ int) bool {
|
||||
for _, configDomain := range d.config.Domains {
|
||||
if xcerthostname.IsMatch(configDomain, domain) {
|
||||
@@ -140,11 +149,9 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domainCandidates, err := d.getAllDomainsInZone(ctx, d.config.ZoneId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domainCandidates := lo.Map(domainsInZone, func(domainInfo *tcteo.AccelerationDomain, _ int) string {
|
||||
return lo.FromPtr(domainInfo.DomainName)
|
||||
})
|
||||
domains = lo.Filter(domainCandidates, func(domain string, _ int) bool {
|
||||
return certX509.VerifyHostname(domain) == nil
|
||||
})
|
||||
@@ -157,24 +164,110 @@ func (d *Deployer) Deploy(ctx context.Context, certPEM, privkeyPEM string) (*dep
|
||||
return nil, fmt.Errorf("unsupported domain match pattern: '%s'", d.config.DomainMatchPattern)
|
||||
}
|
||||
|
||||
// 配置域名证书
|
||||
// REF: https://cloud.tencent.com/document/api/1552/80764
|
||||
modifyHostsCertificateReq := tcteo.NewModifyHostsCertificateRequest()
|
||||
modifyHostsCertificateReq.ZoneId = common.StringPtr(d.config.ZoneId)
|
||||
modifyHostsCertificateReq.Mode = common.StringPtr("sslcert")
|
||||
modifyHostsCertificateReq.Hosts = common.StringPtrs(domains)
|
||||
modifyHostsCertificateReq.ServerCertInfo = []*tcteo.ServerCertInfo{{CertId: common.StringPtr(upres.CertId)}}
|
||||
modifyHostsCertificateResp, err := d.sdkClient.ModifyHostsCertificate(modifyHostsCertificateReq)
|
||||
d.logger.Debug("sdk request 'teo.ModifyHostsCertificate'", slog.Any("request", modifyHostsCertificateReq), slog.Any("response", modifyHostsCertificateResp))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'teo.ModifyHostsCertificate': %w", err)
|
||||
// 跳过已部署过的域名
|
||||
domains = lo.Filter(domains, func(domain string, _ int) bool {
|
||||
var deployed bool
|
||||
|
||||
domainInfo, _ := lo.Find(domainsInZone, func(domainInfo *tcteo.AccelerationDomain) bool {
|
||||
return domain == lo.FromPtr(domainInfo.DomainName)
|
||||
})
|
||||
if domainInfo != nil && domainInfo.Certificate != nil {
|
||||
deployed = lo.ContainsBy(domainInfo.Certificate.List, func(certInfo *tcteo.CertificateInfo) bool {
|
||||
return upres.CertId == lo.FromPtr(certInfo.CertId)
|
||||
})
|
||||
}
|
||||
|
||||
return !deployed
|
||||
})
|
||||
|
||||
// 批量更新域名证书
|
||||
if len(domains) == 0 {
|
||||
d.logger.Info("no edgeone domains to deploy")
|
||||
} else {
|
||||
d.logger.Info("found edgeone domains to deploy", slog.Any("domains", domains))
|
||||
|
||||
// 配置域名证书
|
||||
// REF: https://cloud.tencent.com/document/api/1552/80764
|
||||
modifyHostsCertificateReqs := make([]*tcteo.ModifyHostsCertificateRequest, 0)
|
||||
|
||||
if d.config.EnableMultipleSSL {
|
||||
const algRSA = "RSA"
|
||||
const algECC = "ECC"
|
||||
|
||||
privkeyAlg, _, _ := xcryptokey.GetPrivateKeyAlgorithm(privkeyPEM)
|
||||
privkeyAlgStr := ""
|
||||
switch privkeyAlg {
|
||||
case x509.RSA:
|
||||
privkeyAlgStr = algRSA
|
||||
case x509.ECDSA:
|
||||
privkeyAlgStr = algECC
|
||||
}
|
||||
|
||||
for _, domain := range domains {
|
||||
modifyHostsCertificateReq := tcteo.NewModifyHostsCertificateRequest()
|
||||
modifyHostsCertificateReq.ZoneId = common.StringPtr(d.config.ZoneId)
|
||||
modifyHostsCertificateReq.Mode = common.StringPtr("sslcert")
|
||||
modifyHostsCertificateReq.Hosts = common.StringPtrs([]string{domain})
|
||||
modifyHostsCertificateReq.ServerCertInfo = []*tcteo.ServerCertInfo{{CertId: common.StringPtr(upres.CertId)}}
|
||||
|
||||
domainInfo, _ := lo.Find(domainsInZone, func(domainInfo *tcteo.AccelerationDomain) bool {
|
||||
return domain == lo.FromPtr(domainInfo.DomainName)
|
||||
})
|
||||
if domainInfo != nil && domainInfo.Certificate != nil {
|
||||
for _, certInfo := range domainInfo.Certificate.List {
|
||||
if lo.FromPtr(certInfo.CertId) == upres.CertId {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Split(lo.FromPtr(certInfo.SignAlgo), " ")[0] == privkeyAlgStr {
|
||||
continue
|
||||
}
|
||||
|
||||
certExpireTime, _ := time.Parse("2006-01-02T15:04:05Z", lo.FromPtr(certInfo.ExpireTime))
|
||||
if certExpireTime.Before(time.Now()) {
|
||||
continue
|
||||
}
|
||||
|
||||
modifyHostsCertificateReq.ServerCertInfo = append(modifyHostsCertificateReq.ServerCertInfo, &tcteo.ServerCertInfo{CertId: certInfo.CertId})
|
||||
}
|
||||
}
|
||||
|
||||
modifyHostsCertificateReqs = append(modifyHostsCertificateReqs, modifyHostsCertificateReq)
|
||||
}
|
||||
} else {
|
||||
modifyHostsCertificateReq := tcteo.NewModifyHostsCertificateRequest()
|
||||
modifyHostsCertificateReq.ZoneId = common.StringPtr(d.config.ZoneId)
|
||||
modifyHostsCertificateReq.Mode = common.StringPtr("sslcert")
|
||||
modifyHostsCertificateReq.Hosts = common.StringPtrs(domains)
|
||||
modifyHostsCertificateReq.ServerCertInfo = []*tcteo.ServerCertInfo{{CertId: common.StringPtr(upres.CertId)}}
|
||||
|
||||
modifyHostsCertificateReqs = append(modifyHostsCertificateReqs, modifyHostsCertificateReq)
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for _, modifyHostsCertificateReq := range modifyHostsCertificateReqs {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
modifyHostsCertificateResp, err := d.sdkClient.ModifyHostsCertificate(modifyHostsCertificateReq)
|
||||
d.logger.Debug("sdk request 'teo.ModifyHostsCertificate'", slog.Any("request", modifyHostsCertificateReq), slog.Any("response", modifyHostsCertificateResp))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to execute sdk request 'teo.ModifyHostsCertificate': %w", err)
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
}
|
||||
|
||||
return &deployer.DeployResult{}, nil
|
||||
}
|
||||
|
||||
func (d *Deployer) getAllDomainsInZone(ctx context.Context, zoneId string) ([]string, error) {
|
||||
var domainsInZone []string
|
||||
func (d *Deployer) getAllDomainsInZone(ctx context.Context, zoneId string) ([]*tcteo.AccelerationDomain, error) {
|
||||
var domainsInZone []*tcteo.AccelerationDomain
|
||||
|
||||
const pageSize = 200
|
||||
for offset := 0; ; offset += pageSize {
|
||||
@@ -196,12 +289,13 @@ func (d *Deployer) getAllDomainsInZone(ctx context.Context, zoneId string) ([]st
|
||||
return nil, fmt.Errorf("failed to execute sdk request 'teo.DescribeAccelerationDomains': %w", err)
|
||||
}
|
||||
|
||||
ignoredStatuses := []string{"offline", "forbidden", "init"}
|
||||
for _, domainItem := range describeAccelerationDomainsResp.Response.AccelerationDomains {
|
||||
if domainItem == nil || domainItem.DomainName == nil {
|
||||
if lo.Contains(ignoredStatuses, lo.FromPtr(domainItem.DomainStatus)) {
|
||||
continue
|
||||
}
|
||||
|
||||
domainsInZone = append(domainsInZone, *domainItem.DomainName)
|
||||
domainsInZone = append(domainsInZone, domainItem)
|
||||
}
|
||||
|
||||
if len(describeAccelerationDomainsResp.Response.AccelerationDomains) < pageSize {
|
||||
|
||||
@@ -62,6 +62,7 @@ func TestDeploy(t *testing.T) {
|
||||
ZoneId: fZoneId,
|
||||
DomainMatchPattern: provider.DOMAIN_MATCH_PATTERN_EXACT,
|
||||
Domains: strings.Split(fDomains, ";"),
|
||||
EnableMultipleSSL: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("err: %+v", err)
|
||||
|
||||
+17
-1
@@ -1,5 +1,5 @@
|
||||
import { getI18n, useTranslation } from "react-i18next";
|
||||
import { Form, Input, Radio } from "antd";
|
||||
import { Flex, Form, Input, Radio, Switch } from "antd";
|
||||
import { createSchemaFieldRule } from "antd-zod";
|
||||
import { z } from "zod";
|
||||
|
||||
@@ -88,6 +88,21 @@ const BizDeployNodeConfigFieldsProviderTencentCloudEO = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
</Show>
|
||||
|
||||
<Form.Item
|
||||
label={t("workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.label")}
|
||||
extra={t("workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.help")}
|
||||
>
|
||||
<Flex align="center" gap={8} wrap="wrap">
|
||||
<Form.Item name={[parentNamePath, "enableMultipleSSL"]} initialValue={initialValues.enableMultipleSSL} noStyle rules={[formRule]}>
|
||||
<Switch
|
||||
checkedChildren={t("workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.switch.on")}
|
||||
unCheckedChildren={t("workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.switch.off")}
|
||||
/>
|
||||
</Form.Item>
|
||||
<div>{t("workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.switch.suffix")}</div>
|
||||
</Flex>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -109,6 +124,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
|
||||
zoneId: z.string().nonempty(t("workflow_node.deploy.form.tencentcloud_eo_zone_id.placeholder")),
|
||||
domainMatchPattern: z.string().nonempty(t("workflow_node.deploy.form.shared_domain_match_pattern.placeholder")).default(DOMAIN_MATCH_PATTERN_EXACT),
|
||||
domains: z.string().nullish(),
|
||||
enableMultipleSSL: z.boolean().nullish(),
|
||||
})
|
||||
.superRefine((values, ctx) => {
|
||||
if (values.domainMatchPattern) {
|
||||
|
||||
@@ -817,6 +817,11 @@
|
||||
"workflow_node.deploy.form.tencentcloud_eo_domains.help": "Notes: Multi-domains should be separated by semicolons.",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.title": "Change Tencent Cloud EdgeOne domain",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.placeholder": "Please enter Tencent Cloud EdgeOne domain name",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.label": "Multiple SSL certificates",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.help": "Notes: Each domain name supports one RSA certificate and one ECC certificate at most.",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.switch.suffix": "to retain other certificates with different algorithms.",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.switch.on": "Allow",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.switch.off": "Disallow",
|
||||
"workflow_node.deploy.form.tencentcloud_gaap_endpoint.label": "Tencent Cloud API endpoint (Optional)",
|
||||
"workflow_node.deploy.form.tencentcloud_gaap_endpoint.placeholder": "Please enter Tencent Cloud GAAP API endpoint (e.g. gaap.intl.tencentcloudapi.com)",
|
||||
"workflow_node.deploy.form.tencentcloud_gaap_endpoint.tooltip": "<ul style=\"list-style: disc;\"><li><strong>gaap.intl.tencentcloudapi.com</strong> for Tencent Cloud International</li><li><strong>gaap.tencentcloudapi.com</strong> for Tencent Cloud in China</li></ul>",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"access.form.shared_acme_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key",
|
||||
"access.form.shared_allow_insecure_conns.label": "忽略 SSL/TLS 证书错误",
|
||||
"access.form.shared_allow_insecure_conns.switch.on": "允许",
|
||||
"access.form.shared_allow_insecure_conns.switch.off": "不允许",
|
||||
"access.form.shared_allow_insecure_conns.switch.off": "禁止",
|
||||
"access.form.1panel_server_url.label": "1Panel 服务地址",
|
||||
"access.form.1panel_server_url.placeholder": "请输入 1Panel 服务地址",
|
||||
"access.form.1panel_server_url.help": "提示:请勿包含安全入口后缀。",
|
||||
|
||||
@@ -815,6 +815,11 @@
|
||||
"workflow_node.deploy.form.tencentcloud_eo_domains.help": "提示:支持多个域名,以半角分号隔开。",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.title": "修改腾讯云 EdgeOne 加速域名",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_domains.multiple_input_modal.placeholder": "请输入腾讯云 EdgeOne 加速域名",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.label": "多证书模式",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.help": "提示:每个域名最多支持一个 RSA 证书、一个 ECC 证书。",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.switch.suffix": "保留与待部署证书算法不一致的其他有效证书。",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.switch.on": "",
|
||||
"workflow_node.deploy.form.tencentcloud_eo_enable_multiple_ssl.switch.off": "不",
|
||||
"workflow_node.deploy.form.tencentcloud_gaap_endpoint.label": "腾讯云接口端点(可选)",
|
||||
"workflow_node.deploy.form.tencentcloud_gaap_endpoint.placeholder": "请输入腾讯云 GAAP 接口端点(例如:gaap.tencentcloudapi.com)",
|
||||
"workflow_node.deploy.form.tencentcloud_gaap_endpoint.tooltip": "这是什么?请参阅 <a href=\"https://cloud.tencent.com/document/product/608/36934\" target=\"_blank\">https://cloud.tencent.com/document/product/608/36934</a><br>国际站用户请填写 <em>gaap.intl.tencentcloudapi.com</em>。",
|
||||
|
||||
Reference in New Issue
Block a user