feat(provider): support multiple certificates in deployment to tencentcloud edgeone

This commit is contained in:
Fu Diwei
2025-11-20 20:16:56 +08:00
parent e363ad24b1
commit 3fb8c2e7a8
8 changed files with 154 additions and 30 deletions
@@ -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)