feat: upload certificate to cas first on deployment to aliyun cdn/dcdn

This commit is contained in:
Fu Diwei
2025-09-06 00:16:32 +08:00
parent 3d2f527d78
commit aaf48216af
12 changed files with 177 additions and 41 deletions
@@ -20,6 +20,7 @@ func init() {
AccessKeyId: credentials.AccessKeyId,
AccessKeySecret: credentials.AccessKeySecret,
ResourceGroupId: credentials.ResourceGroupId,
Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
})
return provider, err
@@ -20,6 +20,7 @@ func init() {
AccessKeyId: credentials.AccessKeyId,
AccessKeySecret: credentials.AccessKeySecret,
ResourceGroupId: credentials.ResourceGroupId,
Region: xmaps.GetString(options.ProviderExtendedConfig, "region"),
Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"),
})
return provider, err
@@ -5,13 +5,16 @@ import (
"errors"
"fmt"
"log/slog"
"strconv"
"strings"
"time"
alicdn "github.com/alibabacloud-go/cdn-20180510/v5/client"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/tea"
"github.com/samber/lo"
"github.com/certimate-go/certimate/pkg/core"
sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas"
)
type SSLDeployerProviderConfig struct {
@@ -21,14 +24,17 @@ type SSLDeployerProviderConfig struct {
AccessKeySecret string `json:"accessKeySecret"`
// 阿里云资源组 ID。
ResourceGroupId string `json:"resourceGroupId,omitempty"`
// 阿里云地域。
Region string `json:"region"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
}
type SSLDeployerProvider struct {
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alicdn.Client
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alicdn.Client
sslManager core.SSLManager
}
var _ core.SSLDeployer = (*SSLDeployerProvider)(nil)
@@ -43,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
ResourceGroupId: config.ResourceGroupId,
Region: lo.
If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
Else("ap-southeast-1"),
})
if err != nil {
return nil, fmt.Errorf("could not create ssl manager: %w", err)
}
return &SSLDeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
config: config,
logger: slog.Default(),
sdkClient: client,
sslManager: sslmgr,
}, nil
}
@@ -59,18 +78,32 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) {
}
func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) {
if d.config.Domain == "" {
return nil, errors.New("config `domain` is required")
}
// 上传证书
upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// "*.example.com" → ".example.com",适配阿里云 CDN 要求的泛域名格式
domain := strings.TrimPrefix(d.config.Domain, "*")
// 设置 CDN 域名域名证书
// REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
setCdnDomainSSLCertificateReq := &alicdn.SetCdnDomainSSLCertificateRequest{
DomainName: tea.String(domain),
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
CertType: tea.String("upload"),
DomainName: tea.String(domain),
CertType: tea.String("cas"),
CertId: tea.Int64(int64(certId)),
CertRegion: lo.
If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")).
Else(tea.String("ap-southeast-1")),
SSLProtocol: tea.String("on"),
SSLPub: tea.String(certPEM),
SSLPri: tea.String(privkeyPEM),
}
setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateReq)
d.logger.Debug("sdk request 'cdn.SetCdnDomainSSLCertificate'", slog.Any("request", setCdnDomainSSLCertificateReq), slog.Any("response", setCdnDomainSSLCertificateResp))
@@ -5,13 +5,16 @@ import (
"errors"
"fmt"
"log/slog"
"strconv"
"strings"
"time"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
alidcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client"
"github.com/alibabacloud-go/tea/tea"
"github.com/samber/lo"
"github.com/certimate-go/certimate/pkg/core"
sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas"
)
type SSLDeployerProviderConfig struct {
@@ -21,14 +24,17 @@ type SSLDeployerProviderConfig struct {
AccessKeySecret string `json:"accessKeySecret"`
// 阿里云资源组 ID。
ResourceGroupId string `json:"resourceGroupId,omitempty"`
// 阿里云地域。
Region string `json:"region"`
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
}
type SSLDeployerProvider struct {
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alidcdn.Client
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alidcdn.Client
sslManager core.SSLManager
}
var _ core.SSLDeployer = (*SSLDeployerProvider)(nil)
@@ -43,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
ResourceGroupId: config.ResourceGroupId,
Region: lo.
If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
Else("ap-southeast-1"),
})
if err != nil {
return nil, fmt.Errorf("could not create ssl manager: %w", err)
}
return &SSLDeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
config: config,
logger: slog.Default(),
sdkClient: client,
sslManager: sslmgr,
}, nil
}
@@ -63,18 +82,28 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
return nil, errors.New("config `domain` is required")
}
// 上传证书
upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// "*.example.com" → ".example.com",适配阿里云 DCDN 要求的泛域名格式
domain := strings.TrimPrefix(d.config.Domain, "*")
// 配置域名证书
// REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-setdcdndomainsslcertificate
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
setDcdnDomainSSLCertificateReq := &alidcdn.SetDcdnDomainSSLCertificateRequest{
DomainName: tea.String(domain),
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
CertType: tea.String("upload"),
DomainName: tea.String(domain),
CertType: tea.String("cas"),
CertId: tea.Int64(int64(certId)),
CertRegion: lo.
If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")).
Else(tea.String("ap-southeast-1")),
SSLProtocol: tea.String("on"),
SSLPub: tea.String(certPEM),
SSLPri: tea.String(privkeyPEM),
}
setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificate(setDcdnDomainSSLCertificateReq)
d.logger.Debug("sdk request 'dcdn.SetDcdnDomainSSLCertificate'", slog.Any("request", setDcdnDomainSSLCertificateReq), slog.Any("response", setDcdnDomainSSLCertificateResp))
@@ -94,7 +94,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
// 为网站业务转发规则关联 SSL 证书
// REF: https://help.aliyun.com/zh/anti-ddos/anti-ddos-pro-and-premium/developer-reference/api-ddoscoo-2020-01-01-associatewebcert
certId, _ := strconv.Atoi(upres.CertId)
certId, _ := strconv.ParseInt(upres.CertId, 10, 32)
associateWebCertReq := &aliddos.AssociateWebCertRequest{
Domain: tea.String(d.config.Domain),
CertId: tea.Int32(int32(certId)),
@@ -5,12 +5,16 @@ import (
"errors"
"fmt"
"log/slog"
"time"
"strconv"
"strings"
aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client"
"github.com/alibabacloud-go/tea/tea"
alivod "github.com/alibabacloud-go/vod-20170321/v4/client"
"github.com/samber/lo"
"github.com/certimate-go/certimate/pkg/core"
sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas"
)
type SSLDeployerProviderConfig struct {
@@ -27,9 +31,10 @@ type SSLDeployerProviderConfig struct {
}
type SSLDeployerProvider struct {
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alivod.Client
config *SSLDeployerProviderConfig
logger *slog.Logger
sdkClient *alivod.Client
sslManager core.SSLManager
}
var _ core.SSLDeployer = (*SSLDeployerProvider)(nil)
@@ -44,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv
return nil, fmt.Errorf("could not create sdk client: %w", err)
}
sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{
AccessKeyId: config.AccessKeyId,
AccessKeySecret: config.AccessKeySecret,
ResourceGroupId: config.ResourceGroupId,
Region: lo.
If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou").
Else("ap-southeast-1"),
})
if err != nil {
return nil, fmt.Errorf("could not create ssl manager: %w", err)
}
return &SSLDeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
config: config,
logger: slog.Default(),
sdkClient: client,
sslManager: sslmgr,
}, nil
}
@@ -64,15 +82,25 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
return nil, errors.New("config `domain` is required")
}
// 上传证书
upres, err := d.sslManager.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
// 设置域名证书
// REF: https://help.aliyun.com/zh/vod/developer-reference/api-vod-2017-03-21-setvoddomainsslcertificate
certId, _ := strconv.ParseInt(upres.CertId, 10, 64)
setVodDomainSSLCertificateReq := &alivod.SetVodDomainSSLCertificateRequest{
DomainName: tea.String(d.config.Domain),
CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())),
CertType: tea.String("upload"),
DomainName: tea.String(d.config.Domain),
CertType: tea.String("cas"),
CertId: tea.Int64(int64(certId)),
CertRegion: lo.
If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")).
Else(tea.String("ap-southeast-1")),
SSLProtocol: tea.String("on"),
SSLPub: tea.String(certPEM),
SSLPri: tea.String(privkeyPEM),
}
setVodDomainSSLCertificateResp, err := d.sdkClient.SetVodDomainSSLCertificate(setVodDomainSSLCertificateReq)
d.logger.Debug("sdk request 'live.SetVodDomainSSLCertificate'", slog.Any("request", setVodDomainSSLCertificateReq), slog.Any("response", setVodDomainSSLCertificateResp))
@@ -80,7 +80,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke
// RCDN SSL 绑定域名
// REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-184214120
certId, _ := strconv.Atoi(upres.CertId)
certId, _ := strconv.ParseInt(upres.CertId, 10, 32)
rcdnInstanceSslBindReq := &rainyunsdk.RcdnInstanceSslBindRequest{
CertId: int32(certId),
Domains: []string{d.config.Domain},
@@ -509,7 +509,7 @@ const BizApplyNodeConfigForm = ({ node, ...props }: BizApplyNodeConfigFormProps)
>
<AutoComplete
allowClear
options={["classic", "tlsserver", "shortlived"].map((value) => ({ value }))}
options={["classic", "tlsserver", "shortlived"].map((s) => ({ value: s }))}
placeholder={t("workflow_node.apply.form.acme_profile.placeholder")}
filterOption={(inputValue, option) => option!.value.toLowerCase().includes(inputValue.toLowerCase())}
/>
@@ -1,5 +1,5 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { AutoComplete, Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
@@ -19,6 +19,20 @@ const BizDeployNodeConfigFieldsProviderAliyunCDN = () => {
return (
<>
<Form.Item
name={[parentNamePath, "region"]}
initialValue={initialValues.region}
label={t("workflow_node.deploy.form.aliyun_cdn_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_cdn_region.tooltip") }}></span>}
>
<AutoComplete
allowClear
options={["cn-hangzhou", "ap-southeast-1"].map((s) => ({ value: s }))}
placeholder={t("workflow_node.deploy.form.aliyun_cdn_region.placeholder")}
/>
</Form.Item>
<Form.Item
name={[parentNamePath, "domain"]}
initialValue={initialValues.domain}
@@ -34,6 +48,7 @@ const BizDeployNodeConfigFieldsProviderAliyunCDN = () => {
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
region: "",
domain: "",
};
};
@@ -42,6 +57,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
const { t } = i18n;
return z.object({
region: z.string().nullish(),
domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
});
};
@@ -1,5 +1,5 @@
import { getI18n, useTranslation } from "react-i18next";
import { Form, Input } from "antd";
import { AutoComplete, Form, Input } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import { z } from "zod";
@@ -19,6 +19,20 @@ const BizDeployNodeConfigFieldsProviderAliyunDCDN = () => {
return (
<>
<Form.Item
name={[parentNamePath, "region"]}
initialValue={initialValues.region}
label={t("workflow_node.deploy.form.aliyun_dcdn_region.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aliyun_dcdn_region.tooltip") }}></span>}
>
<AutoComplete
allowClear
options={["cn-hangzhou", "ap-southeast-1"].map((s) => ({ value: s }))}
placeholder={t("workflow_node.deploy.form.aliyun_dcdn_region.placeholder")}
/>
</Form.Item>
<Form.Item
name={[parentNamePath, "domain"]}
initialValue={initialValues.domain}
@@ -34,6 +48,7 @@ const BizDeployNodeConfigFieldsProviderAliyunDCDN = () => {
const getInitialValues = (): Nullish<z.infer<ReturnType<typeof getSchema>>> => {
return {
region: "",
domain: "",
};
};
@@ -42,6 +57,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType<typeof getI18n> })
const { t } = i18n;
return z.object({
region: z.string().nullish(),
domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")),
});
};
@@ -256,9 +256,15 @@
"workflow_node.deploy.form.aliyun_clb_snidomain.placeholder": "Please enter Alibaba Cloud CLB SNI domain name",
"workflow_node.deploy.form.aliyun_clb_snidomain.help": "",
"workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "For more information, see <a href=\"https://slb.console.aliyun.com/clb\" target=\"_blank\">https://slb.console.aliyun.com/clb</a>",
"workflow_node.deploy.form.aliyun_cdn_region.label": "Alibaba Cloud CDN region",
"workflow_node.deploy.form.aliyun_cdn_region.placeholder": "Please enter Alibaba Cloud CDN region (e.g. cn-hangzhou)",
"workflow_node.deploy.form.aliyun_cdn_region.tooltip": "<ul style=\"list-style: disc;\"><li><strong>ap-southeast-1</strong> for Alibaba Cloud International</li><li><strong>cn-hangzhou</strong> for Alibaba Cloud in China</li></ul>",
"workflow_node.deploy.form.aliyun_cdn_domain.label": "Alibaba Cloud CDN domain",
"workflow_node.deploy.form.aliyun_cdn_domain.placeholder": "Please enter Alibaba Cloud CDN domain name",
"workflow_node.deploy.form.aliyun_cdn_domain.tooltip": "For more information, see <a href=\"https://cdn.console.aliyun.com\" target=\"_blank\">https://cdn.console.aliyun.com</a>",
"workflow_node.deploy.form.aliyun_dcdn_region.label": "Alibaba Cloud DCDN region",
"workflow_node.deploy.form.aliyun_dcdn_region.placeholder": "Please enter Alibaba Cloud DCDN region (e.g. cn-hangzhou)",
"workflow_node.deploy.form.aliyun_dcdn_region.tooltip": "<ul style=\"list-style: disc;\"><li><strong>ap-southeast-1</strong> for Alibaba Cloud International</li><li><strong>cn-hangzhou</strong> for Alibaba Cloud in China</li></ul>",
"workflow_node.deploy.form.aliyun_dcdn_domain.label": "Alibaba Cloud DCDN domain",
"workflow_node.deploy.form.aliyun_dcdn_domain.placeholder": "Please enter Alibaba Cloud DCDN domain name",
"workflow_node.deploy.form.aliyun_dcdn_domain.tooltip": "For more information, see <a href=\"https://dcdn.console.aliyun.com\" target=\"_blank\">https://dcdn.console.aliyun.com</a>",
@@ -255,9 +255,15 @@
"workflow_node.deploy.form.aliyun_clb_snidomain.placeholder": "请输入阿里云 CLB 扩展域名(支持泛域名)",
"workflow_node.deploy.form.aliyun_clb_snidomain.help": "提示:不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。",
"workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "这是什么?请参阅 <a href=\"https://slb.console.aliyun.com/clb\" target=\"_blank\">https://slb.console.aliyun.com/clb</a>",
"workflow_node.deploy.form.aliyun_cdn_region.label": "阿里云 CDN 服务地域",
"workflow_node.deploy.form.aliyun_cdn_region.placeholder": "请输入阿里云 CDN 服务地域(例如:cn-hangzhou)",
"workflow_node.deploy.form.aliyun_cdn_region.tooltip": "中国站请填写 <strong>cn-hangzhou</strong>;<br>国际站请填写 <strong>ap-southeast-1</strong>。",
"workflow_node.deploy.form.aliyun_cdn_domain.label": "阿里云 CDN 加速域名",
"workflow_node.deploy.form.aliyun_cdn_domain.placeholder": "请输入阿里云 CDN 加速域名(支持泛域名)",
"workflow_node.deploy.form.aliyun_cdn_domain.tooltip": "这是什么?请参阅 <a href=\"https://cdn.console.aliyun.com\" target=\"_blank\">https://cdn.console.aliyun.com</a>",
"workflow_node.deploy.form.aliyun_dcdn_region.label": "阿里云 DCDN 服务地域",
"workflow_node.deploy.form.aliyun_dcdn_region.placeholder": "请输入阿里云 DCDN 服务地域(例如:cn-hangzhou)",
"workflow_node.deploy.form.aliyun_dcdn_region.tooltip": "中国站请填写 <strong>cn-hangzhou</strong>;<br>国际站请填写 <strong>ap-southeast-1</strong>。",
"workflow_node.deploy.form.aliyun_dcdn_domain.label": "阿里云 DCDN 加速域名",
"workflow_node.deploy.form.aliyun_dcdn_domain.placeholder": "请输入阿里云 DCDN 加速域名(支持泛域名)",
"workflow_node.deploy.form.aliyun_dcdn_domain.tooltip": "这是什么?请参阅 <a href=\"https://dcdn.console.aliyun.com\" target=\"_blank\">https://dcdn.console.aliyun.com</a>",