diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 825cfedf..032eb6f8 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -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 diff --git a/internal/pkg/core/deployer/providers/aws-acm/aws_acm.go b/internal/pkg/core/deployer/providers/aws-acm/aws_acm.go index 3e817dcf..a9e90b60 100644 --- a/internal/pkg/core/deployer/providers/aws-acm/aws_acm.go +++ b/internal/pkg/core/deployer/providers/aws-acm/aws_acm.go @@ -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,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) { - // 上传证书到 ACM - upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) - if err != nil { - return nil, fmt.Errorf("failed to upload certificate file: %w", err) + if d.config.CertificateArn == "" { + // 上传证书到 ACM + upres, err := d.sslUploader.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)) + } } 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 } + +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 +} diff --git a/internal/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go b/internal/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go index b1f21fdc..b8f8df99 100644 --- a/internal/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go +++ b/internal/pkg/core/deployer/providers/azure-keyvault/azure_keyvault.go @@ -32,7 +32,7 @@ type DeployerConfig struct { // Key Vault 名称。 KeyVaultName string `json:"keyvaultName"` // Key Vault 证书名称。 - // 选填。 + // 选填。零值时表示新建证书;否则表示更新证书。 CertificateName string `json:"certificateName,omitempty"` } diff --git a/internal/pkg/core/deployer/providers/baishan-cdn/baishan_cdn.go b/internal/pkg/core/deployer/providers/baishan-cdn/baishan_cdn.go index 2294ed03..e3efa6e4 100644 --- a/internal/pkg/core/deployer/providers/baishan-cdn/baishan_cdn.go +++ b/internal/pkg/core/deployer/providers/baishan-cdn/baishan_cdn.go @@ -20,7 +20,7 @@ type DeployerConfig struct { // 加速域名(支持泛域名)。 Domain string `json:"domain"` // 证书 ID。 - // 选填。 + // 选填。零值时表示新建证书;否则表示更新证书。 CertificateId string `json:"certificateId,omitempty"` } diff --git a/internal/pkg/core/deployer/providers/cachefly/cachefly.go b/internal/pkg/core/deployer/providers/cachefly/cachefly.go index e3e819d7..21cb4dd0 100644 --- a/internal/pkg/core/deployer/providers/cachefly/cachefly.go +++ b/internal/pkg/core/deployer/providers/cachefly/cachefly.go @@ -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, diff --git a/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications.go b/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications.go index 3425f05e..195c202e 100644 --- a/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications.go +++ b/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications.go @@ -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 证书 diff --git a/internal/pkg/core/deployer/providers/gcore-cdn/gcore_cdn.go b/internal/pkg/core/deployer/providers/gcore-cdn/gcore_cdn.go index d9671e12..780f91a7 100644 --- a/internal/pkg/core/deployer/providers/gcore-cdn/gcore_cdn.go +++ b/internal/pkg/core/deployer/providers/gcore-cdn/gcore_cdn.go @@ -24,7 +24,7 @@ type DeployerConfig struct { // CDN 资源 ID。 ResourceId int64 `json:"resourceId"` // 证书 ID。 - // 选填。 + // 选填。零值时表示新建证书;否则表示更新证书。 CertificateId int64 `json:"certificateId,omitempty"` } diff --git a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go index 2faf1b03..ee16b08a 100644 --- a/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go +++ b/internal/pkg/core/deployer/providers/wangsu-cdnpro/wangsu_cdnpro.go @@ -34,7 +34,7 @@ type DeployerConfig struct { // 加速域名(支持泛域名)。 Domain string `json:"domain"` // 证书 ID。 - // 选填。 + // 选填。零值时表示新建证书;否则表示更新证书。 CertificateId string `json:"certificateId,omitempty"` // Webhook ID。 // 选填。 diff --git a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go index f808083c..05cc70e3 100644 --- a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go +++ b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go @@ -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) diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx index 60b49f54..f0964493 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAWSACMConfig.tsx @@ -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, > + +