mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-10 06:29:52 +00:00
feat: support replacing old certificate on deployment to aws acm
This commit is contained in:
parent
cd93a2d72c
commit
9e08cfd1d1
@ -306,6 +306,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer
|
|||||||
AccessKeyId: access.AccessKeyId,
|
AccessKeyId: access.AccessKeyId,
|
||||||
SecretAccessKey: access.SecretAccessKey,
|
SecretAccessKey: access.SecretAccessKey,
|
||||||
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
|
Region: maputil.GetString(options.ProviderExtendedConfig, "region"),
|
||||||
|
CertificateArn: maputil.GetString(options.ProviderExtendedConfig, "certificateArn"),
|
||||||
})
|
})
|
||||||
return deployer, err
|
return deployer, err
|
||||||
|
|
||||||
|
@ -5,9 +5,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"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/deployer"
|
||||||
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
"github.com/usual2970/certimate/internal/pkg/core/uploader"
|
||||||
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aws-acm"
|
uploadersp "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aws-acm"
|
||||||
|
certutil "github.com/usual2970/certimate/internal/pkg/utils/cert"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeployerConfig struct {
|
type DeployerConfig struct {
|
||||||
@ -17,11 +23,15 @@ type DeployerConfig struct {
|
|||||||
SecretAccessKey string `json:"secretAccessKey"`
|
SecretAccessKey string `json:"secretAccessKey"`
|
||||||
// AWS 区域。
|
// AWS 区域。
|
||||||
Region string `json:"region"`
|
Region string `json:"region"`
|
||||||
|
// ACM 证书 ARN。
|
||||||
|
// 选填。零值时表示新建证书;否则表示更新证书。
|
||||||
|
CertificateArn string `json:"certificateArn,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeployerProvider struct {
|
type DeployerProvider struct {
|
||||||
config *DeployerConfig
|
config *DeployerConfig
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
|
sdkClient *awsacm.Client
|
||||||
sslUploader uploader.Uploader
|
sslUploader uploader.Uploader
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +42,11 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
|||||||
panic("config is nil")
|
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{
|
uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{
|
||||||
AccessKeyId: config.AccessKeyId,
|
AccessKeyId: config.AccessKeyId,
|
||||||
SecretAccessKey: config.SecretAccessKey,
|
SecretAccessKey: config.SecretAccessKey,
|
||||||
@ -44,6 +59,7 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) {
|
|||||||
return &DeployerProvider{
|
return &DeployerProvider{
|
||||||
config: config,
|
config: config,
|
||||||
logger: slog.Default(),
|
logger: slog.Default(),
|
||||||
|
sdkClient: client,
|
||||||
sslUploader: uploader,
|
sslUploader: uploader,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -59,13 +75,48 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
||||||
// 上传证书到 ACM
|
if d.config.CertificateArn == "" {
|
||||||
upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM)
|
// 上传证书到 ACM
|
||||||
if err != nil {
|
upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM)
|
||||||
return nil, fmt.Errorf("failed to upload certificate file: %w", err)
|
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))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
d.logger.Info("ssl certificate uploaded", slog.Any("result", upres))
|
// 提取服务器证书
|
||||||
|
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
|
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
|
||||||
|
}
|
||||||
|
@ -32,7 +32,7 @@ type DeployerConfig struct {
|
|||||||
// Key Vault 名称。
|
// Key Vault 名称。
|
||||||
KeyVaultName string `json:"keyvaultName"`
|
KeyVaultName string `json:"keyvaultName"`
|
||||||
// Key Vault 证书名称。
|
// Key Vault 证书名称。
|
||||||
// 选填。
|
// 选填。零值时表示新建证书;否则表示更新证书。
|
||||||
CertificateName string `json:"certificateName,omitempty"`
|
CertificateName string `json:"certificateName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ type DeployerConfig struct {
|
|||||||
// 加速域名(支持泛域名)。
|
// 加速域名(支持泛域名)。
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
// 证书 ID。
|
// 证书 ID。
|
||||||
// 选填。
|
// 选填。零值时表示新建证书;否则表示更新证书。
|
||||||
CertificateId string `json:"certificateId,omitempty"`
|
CertificateId string `json:"certificateId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
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{
|
createCertificateReq := &cfsdk.CreateCertificateRequest{
|
||||||
Certificate: certPEM,
|
Certificate: certPEM,
|
||||||
CertificateKey: privkeyPEM,
|
CertificateKey: privkeyPEM,
|
||||||
|
@ -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) {
|
func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) {
|
||||||
// 提取 Edgio 所需的服务端证书和中间证书内容
|
// 提取服务器证书和中间证书
|
||||||
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to extract certs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上传 TLS 证书
|
// 上传 TLS 证书
|
||||||
|
@ -24,7 +24,7 @@ type DeployerConfig struct {
|
|||||||
// CDN 资源 ID。
|
// CDN 资源 ID。
|
||||||
ResourceId int64 `json:"resourceId"`
|
ResourceId int64 `json:"resourceId"`
|
||||||
// 证书 ID。
|
// 证书 ID。
|
||||||
// 选填。
|
// 选填。零值时表示新建证书;否则表示更新证书。
|
||||||
CertificateId int64 `json:"certificateId,omitempty"`
|
CertificateId int64 `json:"certificateId,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ type DeployerConfig struct {
|
|||||||
// 加速域名(支持泛域名)。
|
// 加速域名(支持泛域名)。
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
// 证书 ID。
|
// 证书 ID。
|
||||||
// 选填。
|
// 选填。零值时表示新建证书;否则表示更新证书。
|
||||||
CertificateId string `json:"certificateId,omitempty"`
|
CertificateId string `json:"certificateId,omitempty"`
|
||||||
// Webhook ID。
|
// Webhook ID。
|
||||||
// 选填。
|
// 选填。
|
||||||
|
@ -65,9 +65,11 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成 AWS 业务参数
|
// 提取服务器证书
|
||||||
scertPEM, _ := certutil.ConvertCertificateToPEM(certX509)
|
serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM)
|
||||||
bcertPEM := 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
|
// 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
|
// REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ImportCertificate.html
|
||||||
importCertificateReq := &awsacm.ImportCertificateInput{
|
importCertificateReq := &awsacm.ImportCertificateInput{
|
||||||
Certificate: ([]byte)(scertPEM),
|
Certificate: ([]byte)(serverCertPEM),
|
||||||
CertificateChain: ([]byte)(bcertPEM),
|
CertificateChain: ([]byte)(intermediaCertPEM),
|
||||||
PrivateKey: ([]byte)(privkeyPEM),
|
PrivateKey: ([]byte)(privkeyPEM),
|
||||||
}
|
}
|
||||||
importCertificateResp, err := u.sdkClient.ImportCertificate(context.TODO(), importCertificateReq)
|
importCertificateResp, err := u.sdkClient.ImportCertificate(context.TODO(), importCertificateReq)
|
||||||
|
@ -5,6 +5,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
type DeployNodeConfigFormAWSACMConfigFieldValues = Nullish<{
|
type DeployNodeConfigFormAWSACMConfigFieldValues = Nullish<{
|
||||||
region: string;
|
region: string;
|
||||||
|
certificateArn?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type DeployNodeConfigFormAWSACMConfigProps = {
|
export type DeployNodeConfigFormAWSACMConfigProps = {
|
||||||
@ -27,6 +28,7 @@ const DeployNodeConfigFormAWSACMConfig = ({ form: formInst, formName, disabled,
|
|||||||
.string({ message: t("workflow_node.deploy.form.aws_acm_region.placeholder") })
|
.string({ message: t("workflow_node.deploy.form.aws_acm_region.placeholder") })
|
||||||
.nonempty(t("workflow_node.deploy.form.aws_acm_region.placeholder"))
|
.nonempty(t("workflow_node.deploy.form.aws_acm_region.placeholder"))
|
||||||
.trim(),
|
.trim(),
|
||||||
|
certificateArn: z.string({ message: t("workflow_node.deploy.form.aws_acm_certificate_arn.placeholder") }).nullish(),
|
||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
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")} />
|
<Input placeholder={t("workflow_node.deploy.form.aws_acm_region.placeholder")} />
|
||||||
</Form.Item>
|
</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>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,7 @@ const DeployNodeConfigFormAzureKeyVaultConfig = ({
|
|||||||
certificateName: z
|
certificateName: z
|
||||||
.string({ message: t("workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder") })
|
.string({ message: t("workflow_node.deploy.form.azure_keyvault_certificate_name.placeholder") })
|
||||||
.nullish()
|
.nullish()
|
||||||
.refine((v) =>{
|
.refine((v) => {
|
||||||
if (!v) return true;
|
if (!v) return true;
|
||||||
return /^[a-zA-Z0-9-]{1,127}$/.test(v);
|
return /^[a-zA-Z0-9-]{1,127}$/.test(v);
|
||||||
}, t("workflow_node.deploy.form.azure_keyvault_certificate_name.errmsg.invalid")),
|
}, t("workflow_node.deploy.form.azure_keyvault_certificate_name.errmsg.invalid")),
|
||||||
|
@ -261,6 +261,9 @@
|
|||||||
"workflow_node.deploy.form.aws_acm_region.label": "AWS ACM Region",
|
"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.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_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.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.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>",
|
"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>",
|
||||||
|
@ -260,6 +260,9 @@
|
|||||||
"workflow_node.deploy.form.aws_acm_region.label": "AWS ACM 服务区域",
|
"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.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_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.label": "AWS CloudFront 服务区域",
|
||||||
"workflow_node.deploy.form.aws_cloudfront_region.placeholder": "请输入 AWS CloudFront 服务区域(例如:us-east-1)",
|
"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>",
|
"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>",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user