feat: support replacing old certificate on deployment to aws acm

This commit is contained in:
Fu Diwei 2025-05-15 22:09:32 +08:00
parent cd93a2d72c
commit 9e08cfd1d1
13 changed files with 89 additions and 17 deletions

View File

@ -306,6 +306,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
AccessKeyId: access.AccessKeyId,
SecretAccessKey: access.SecretAccessKey,
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
CertificateArn: maputil.GetString(options.ProviderExtendedConfig, "certificateArn"),
})
return deployer, err

View File

@ -5,9 +5,15 @@ import (
"fmt"
"log/slog"
aws "github.com/aws/aws-sdk-go-v2/aws"
awscfg "github.com/aws/aws-sdk-go-v2/config"
awscred "github.com/aws/aws-sdk-go-v2/credentials"
awsacm "github.com/aws/aws-sdk-go-v2/service/acm"
"github.com/usual2970/certimate/internal/pkg/core/deployer"
"github.com/usual2970/certimate/internal/pkg/core/uploader"
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aws-acm"
certutil "github.com/usual2970/certimate/internal/pkg/utils/cert"
)
type DeployerConfig struct {
@ -17,11 +23,15 @@ type DeployerConfig struct {
SecretAccessKey string `json:"secretAccessKey"`
// AWS 区域。
Region string `json:"region"`
// ACM 证书 ARN。
// 选填。零值时表示新建证书;否则表示更新证书。
CertificateArn string `json:"certificateArn,omitempty"`
}
type DeployerProvider struct {
config *DeployerConfig
logger *slog.Logger
sdkClient *awsacm.Client
sslUploader uploader.Uploader
}
@ -32,6 +42,11 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
panic("config is nil")
}
client, err := createSdkClient(config.AccessKeyId, config.SecretAccessKey, config.Region)
if err != nil {
return nil, fmt.Errorf("failed to create sdk client: %w", err)
}
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
AccessKeyId: config.AccessKeyId,
SecretAccessKey: config.SecretAccessKey,
@ -44,6 +59,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
return &DeployerProvider{
config: config,
logger: slog.Default(),
sdkClient: client,
sslUploader: uploader,
}, nil
}
@ -59,6 +75,7 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
if d.config.CertificateArn == "" {
// 上传证书到 ACM
upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM)
if err != nil {
@ -66,6 +83,40 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE
} else {
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
}
} else {
// 提取服务器证书
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
if err != nil {
return nil, fmt.Errorf("failed to extract certs: %w", err)
}
// 导入证书
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
importCertificateReq := &awsacm.ImportCertificateInput{
CertificateArn: aws.String(d.config.CertificateArn),
Certificate: ([]byte)(serverCertPEM),
CertificateChain: ([]byte)(intermediaCertPEM),
PrivateKey: ([]byte)(privkeyPEM),
}
importCertificateResp, err := d.sdkClient.ImportCertificate(context.TODO(), importCertificateReq)
d.logger.Debug("sdk request 'acm.ImportCertificate'", slog.Any("request", importCertificateReq), slog.Any("response", importCertificateResp))
if err != nil {
return nil, fmt.Errorf("failed to execute sdk request 'acm.ImportCertificate': %w", err)
}
}
return &deployer.DeployResult{}, nil
}
func createSdkClient(accessKeyId, secretAccessKey, region string) (*awsacm.Client, error) {
cfg, err := awscfg.LoadDefaultConfig(context.TODO())
if err != nil {
return nil, err
}
client := awsacm.NewFromConfig(cfg, func(o *awsacm.Options) {
o.Region = region
o.Credentials = aws.NewCredentialsCache(awscred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, ""))
})
return client, nil
}

View File

@ -32,7 +32,7 @@ type DeployerConfig struct {
// Key Vault 名称。
KeyVaultName string `json:"keyvaultName"`
// Key Vault 证书名称。
// 选填。
// 选填。零值时表示新建证书;否则表示更新证书。
CertificateName string `json:"certificateName,omitempty"`
}

View File

@ -20,7 +20,7 @@ type DeployerConfig struct {
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
// 证书 ID。
// 选填。
// 选填。零值时表示新建证书;否则表示更新证书。
CertificateId string `json:"certificateId,omitempty"`
}

View File

@ -51,6 +51,7 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
// 上传证书
// REF: https://api.cachefly.com/api/2.5/docs#tag/Certificates/paths/~1certificates/post
createCertificateReq := &cfsdk.CreateCertificateRequest{
Certificate: certPEM,
CertificateKey: privkeyPEM,

View File

@ -56,10 +56,10 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
}
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
// 提取 Edgio 所需的服务端证书和中间证书内容
// 提取服务器证书和中间证书
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to extract certs: %w", err)
}
// 上传 TLS 证书

View File

@ -24,7 +24,7 @@ type DeployerConfig struct {
// CDN 资源 ID。
ResourceId int64 `json:"resourceId"`
// 证书 ID。
// 选填。
// 选填。零值时表示新建证书;否则表示更新证书。
CertificateId int64 `json:"certificateId,omitempty"`
}

View File

@ -34,7 +34,7 @@ type DeployerConfig struct {
// 加速域名(支持泛域名)。
Domain string `json:"domain"`
// 证书 ID。
// 选填。
// 选填。零值时表示新建证书;否则表示更新证书。
CertificateId string `json:"certificateId,omitempty"`
// Webhook ID。
// 选填。

View File

@ -65,9 +65,11 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE
return nil, err
}
// 生成 AWS 业务参数
scertPEM, _ := certutil.ConvertCertificateToPEM(certX509)
bcertPEM := certPEM
// 提取服务器证书
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
if err != nil {
return nil, fmt.Errorf("failed to extract certs: %w", err)
}
// 获取证书列表,避免重复上传
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListCertificates.html
@ -145,8 +147,8 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE
// 导入证书
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
importCertificateReq := &awsacm.ImportCertificateInput{
Certificate: ([]byte)(scertPEM),
CertificateChain: ([]byte)(bcertPEM),
Certificate: ([]byte)(serverCertPEM),
CertificateChain: ([]byte)(intermediaCertPEM),
PrivateKey: ([]byte)(privkeyPEM),
}
importCertificateResp, err := u.sdkClient.ImportCertificate(context.TODO(), importCertificateReq)

View File

@ -5,6 +5,7 @@ import { z } from "zod";
type DeployNodeConfigFormAWSACMConfigFieldValues = Nullish<{
region: string;
certificateArn?: string;
}>;
export type DeployNodeConfigFormAWSACMConfigProps = {
@ -27,6 +28,7 @@ const DeployNodeConfigFormAWSACMConfig = ({ form: formInst, formName, disabled,
.string({ message: t("workflow_node.deploy.form.aws_acm_region.placeholder") })
.nonempty(t("workflow_node.deploy.form.aws_acm_region.placeholder"))
.trim(),
certificateArn: z.string({ message: t("workflow_node.deploy.form.aws_acm_certificate_arn.placeholder") }).nullish(),
});
const formRule = createSchemaFieldRule(formSchema);
@ -51,6 +53,15 @@ const DeployNodeConfigFormAWSACMConfig = ({ form: formInst, formName, disabled,
>
<Input placeholder={t("workflow_node.deploy.form.aws_acm_region.placeholder")} />
</Form.Item>
<Form.Item
name="certificateArn"
label={t("workflow_node.deploy.form.aws_acm_certificate_arn.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.deploy.form.aws_acm_certificate_arn.tooltip") }}></span>}
>
<Input placeholder={t("workflow_node.deploy.form.aws_acm_certificate_arn.placeholder")} />
</Form.Item>
</Form>
);
};

View File

@ -261,6 +261,9 @@
"workflow_node.deploy.form.aws_acm_region.label": "AWS ACM Region",
"workflow_node.deploy.form.aws_acm_region.placeholder": "Please enter AWS ACM region (e.g. us-east-1)",
"workflow_node.deploy.form.aws_acm_region.tooltip": "For more information, see <a href=\"https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints\" target=\"_blank\">https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints</a>",
"workflow_node.deploy.form.aws_acm_certificate_arn.label": "AWS ACM certificate ARN (Optional)",
"workflow_node.deploy.form.aws_acm_certificate_arn.placeholder": "Please enter AWS ACM certificate ARN",
"workflow_node.deploy.form.aws_acm_certificate_arn.tooltip": "Leave it blank to import a new certificate.",
"workflow_node.deploy.form.aws_cloudfront_region.label": "AWS CloudFront Region",
"workflow_node.deploy.form.aws_cloudfront_region.placeholder": "Please enter AWS CloudFront region (e.g. us-east-1)",
"workflow_node.deploy.form.aws_cloudfront_region.tooltip": "For more information, see <a href=\"https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints\" target=\"_blank\">https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints</a>",

View File

@ -260,6 +260,9 @@
"workflow_node.deploy.form.aws_acm_region.label": "AWS ACM 服务区域",
"workflow_node.deploy.form.aws_acm_region.placeholder": "请输入 AWS ACM 服务区域例如us-east-1",
"workflow_node.deploy.form.aws_acm_region.tooltip": "这是什么?请参阅 <a href=\"https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints\" target=\"_blank\">https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints</a>",
"workflow_node.deploy.form.aws_acm_certificate_arn.label": "AWS ACM 证书 ARN可选",
"workflow_node.deploy.form.aws_acm_certificate_arn.placeholder": "请输入 AWS ACM 证书 ARN",
"workflow_node.deploy.form.aws_acm_certificate_arn.tooltip": "不填写时,将导入为新证书。",
"workflow_node.deploy.form.aws_cloudfront_region.label": "AWS CloudFront 服务区域",
"workflow_node.deploy.form.aws_cloudfront_region.placeholder": "请输入 AWS CloudFront 服务区域例如us-east-1",
"workflow_node.deploy.form.aws_cloudfront_region.tooltip": "这是什么?请参阅 <a href=\"https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints\" target=\"_blank\">https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints</a>",