diff --git a/go.mod b/go.mod index 0f12bd45..bca64be5 100644 --- a/go.mod +++ b/go.mod @@ -85,6 +85,7 @@ require ( github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect + github.com/aws/aws-sdk-go-v2/service/iam v1.42.0 // indirect github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 // indirect github.com/buger/goterm v1.0.4 // indirect github.com/diskfs/go-diskfs v1.5.0 // indirect diff --git a/go.sum b/go.sum index 7bbf5848..404e21e4 100644 --- a/go.sum +++ b/go.sum @@ -235,6 +235,8 @@ github.com/aws/aws-sdk-go-v2/service/acm v1.32.0/go.mod h1:3sKYAgRbuBa2QMYGh/WEc github.com/aws/aws-sdk-go-v2/service/cloudfront v1.46.1 h1:6xZNYtuVwzBs8k+TmraERt0vL68Ppg9aUi+aTQmPaVM= github.com/aws/aws-sdk-go-v2/service/cloudfront v1.46.1/go.mod h1:FIBJ48TS+qJb+Ne4qJ+0NeIhtPTVXItXooTeNeVI4Po= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= +github.com/aws/aws-sdk-go-v2/service/iam v1.42.0 h1:G6+UzGvubaet9QOh0664E9JeT+b6Zvop3AChozRqkrA= +github.com/aws/aws-sdk-go-v2/service/iam v1.42.0/go.mod h1:mPJkGQzeCoPs82ElNILor2JzZgYENr4UaSKUT8K27+c= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index e67c29e0..6f02c97a 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -27,6 +27,7 @@ import ( pAliyunWAF "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aliyun-waf" pAWSACM "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-acm" pAWSCloudFront "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-cloudfront" + pAWSIAM "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/aws-iam" pAzureKeyVault "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/azure-keyvault" pBaiduCloudAppBLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-appblb" pBaiduCloudBLB "github.com/usual2970/certimate/internal/pkg/core/deployer/providers/baiducloud-blb" @@ -331,7 +332,7 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } } - case domain.DeploymentProviderTypeAWSACM, domain.DeploymentProviderTypeAWSCloudFront: + case domain.DeploymentProviderTypeAWSACM, domain.DeploymentProviderTypeAWSCloudFront, domain.DeploymentProviderTypeAWSIAM: { access := domain.AccessConfigForAWS{} if err := maputil.Populate(options.ProviderAccessConfig, &access); err != nil { @@ -350,10 +351,20 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeAWSCloudFront: deployer, err := pAWSCloudFront.NewDeployer(&pAWSCloudFront.DeployerConfig{ + AccessKeyId: access.AccessKeyId, + SecretAccessKey: access.SecretAccessKey, + Region: maputil.GetString(options.ProviderServiceConfig, "region"), + DistributionId: maputil.GetString(options.ProviderServiceConfig, "distributionId"), + CertificateSource: maputil.GetOrDefaultString(options.ProviderServiceConfig, "certificateSource", "ACM"), + }) + return deployer, err + + case domain.DeploymentProviderTypeAWSIAM: + deployer, err := pAWSIAM.NewDeployer(&pAWSIAM.DeployerConfig{ AccessKeyId: access.AccessKeyId, SecretAccessKey: access.SecretAccessKey, Region: maputil.GetString(options.ProviderServiceConfig, "region"), - DistributionId: maputil.GetString(options.ProviderServiceConfig, "distributionId"), + CertificatePath: maputil.GetOrDefaultString(options.ProviderServiceConfig, "certificatePath", "/"), }) return deployer, err diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 1a30bdad..560b08da 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -195,6 +195,7 @@ const ( DeploymentProviderTypeAliyunWAF = DeploymentProviderType(AccessProviderTypeAliyun + "-waf") DeploymentProviderTypeAWSACM = DeploymentProviderType(AccessProviderTypeAWS + "-acm") DeploymentProviderTypeAWSCloudFront = DeploymentProviderType(AccessProviderTypeAWS + "-cloudfront") + DeploymentProviderTypeAWSIAM = DeploymentProviderType(AccessProviderTypeAWS + "-iam") DeploymentProviderTypeAzureKeyVault = DeploymentProviderType(AccessProviderTypeAzure + "-keyvault") DeploymentProviderTypeBaiduCloudAppBLB = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-appblb") DeploymentProviderTypeBaiduCloudBLB = DeploymentProviderType(AccessProviderTypeBaiduCloud + "-blb") diff --git a/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go b/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go index 7ec17044..e5a3f0b2 100644 --- a/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go +++ b/internal/pkg/core/deployer/providers/aws-cloudfront/aws_cloudfront.go @@ -14,7 +14,8 @@ import ( "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" + uploaderspacm "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aws-acm" + uploaderspiam "github.com/usual2970/certimate/internal/pkg/core/uploader/providers/aws-iam" ) type DeployerConfig struct { @@ -26,6 +27,9 @@ type DeployerConfig struct { Region string `json:"region"` // AWS CloudFront 分配 ID。 DistributionId string `json:"distributionId"` + // AWS CloudFront 证书来源。 + // 可取值 "ACM"、"IAM"。 + CertificateSource string `json:"certificateSource"` } type DeployerProvider struct { @@ -47,13 +51,28 @@ func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { return nil, fmt.Errorf("failed to create sdk client: %w", err) } - uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ - AccessKeyId: config.AccessKeyId, - SecretAccessKey: config.SecretAccessKey, - Region: config.Region, - }) - if err != nil { - return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + var uploader uploader.Uploader + if config.CertificateSource == "ACM" { + uploader, err = uploaderspacm.NewUploader(&uploaderspacm.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + SecretAccessKey: config.SecretAccessKey, + Region: config.Region, + }) + if err != nil { + return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + } + } else if config.CertificateSource == "IAM" { + uploader, err = uploaderspiam.NewUploader(&uploaderspiam.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + SecretAccessKey: config.SecretAccessKey, + Region: config.Region, + CertificatePath: "/cloudfront/", + }) + if err != nil { + return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + } + } else { + return nil, fmt.Errorf("unsupported certificate source: '%s'", config.CertificateSource) } return &DeployerProvider{ @@ -79,7 +98,7 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return nil, errors.New("config `distribuitionId` is required") } - // 上传证书到 ACM + // 上传证书到 ACM/IAM upres, err := d.sslUploader.Upload(ctx, certPEM, privkeyPEM) if err != nil { return nil, fmt.Errorf("failed to upload certificate file: %w", err) @@ -109,7 +128,19 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE updateDistributionReq.DistributionConfig.ViewerCertificate = &types.ViewerCertificate{} } updateDistributionReq.DistributionConfig.ViewerCertificate.CloudFrontDefaultCertificate = aws.Bool(false) - updateDistributionReq.DistributionConfig.ViewerCertificate.ACMCertificateArn = aws.String(upres.CertId) + if d.config.CertificateSource == "ACM" { + updateDistributionReq.DistributionConfig.ViewerCertificate.ACMCertificateArn = aws.String(upres.CertId) + updateDistributionReq.DistributionConfig.ViewerCertificate.IAMCertificateId = nil + } else if d.config.CertificateSource == "IAM" { + updateDistributionReq.DistributionConfig.ViewerCertificate.ACMCertificateArn = nil + updateDistributionReq.DistributionConfig.ViewerCertificate.IAMCertificateId = aws.String(upres.CertId) + if updateDistributionReq.DistributionConfig.ViewerCertificate.MinimumProtocolVersion == "" { + updateDistributionReq.DistributionConfig.ViewerCertificate.MinimumProtocolVersion = types.MinimumProtocolVersionTLSv1 + } + if updateDistributionReq.DistributionConfig.ViewerCertificate.SSLSupportMethod == "" { + updateDistributionReq.DistributionConfig.ViewerCertificate.SSLSupportMethod = types.SSLSupportMethodSniOnly + } + } updateDistributionResp, err := d.sdkClient.UpdateDistribution(context.TODO(), updateDistributionReq) d.logger.Debug("sdk request 'cloudfront.UpdateDistribution'", slog.Any("request", updateDistributionReq), slog.Any("response", updateDistributionResp)) if err != nil { diff --git a/internal/pkg/core/deployer/providers/aws-iam/aws_iam.go b/internal/pkg/core/deployer/providers/aws-iam/aws_iam.go new file mode 100644 index 00000000..ef6440d3 --- /dev/null +++ b/internal/pkg/core/deployer/providers/aws-iam/aws_iam.go @@ -0,0 +1,75 @@ +package awsiam + +import ( + "context" + "fmt" + "log/slog" + + "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-iam" +) + +type DeployerConfig struct { + // AWS AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // AWS SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // AWS 区域。 + Region string `json:"region"` + // IAM 证书路径。 + // 选填。 + CertificatePath string `json:"certificatePath,omitempty"` +} + +type DeployerProvider struct { + config *DeployerConfig + logger *slog.Logger + sslUploader uploader.Uploader +} + +var _ deployer.Deployer = (*DeployerProvider)(nil) + +func NewDeployer(config *DeployerConfig) (*DeployerProvider, error) { + if config == nil { + panic("config is nil") + } + + uploader, err := uploadersp.NewUploader(&uploadersp.UploaderConfig{ + AccessKeyId: config.AccessKeyId, + SecretAccessKey: config.SecretAccessKey, + Region: config.Region, + CertificatePath: config.CertificatePath, + }) + if err != nil { + return nil, fmt.Errorf("failed to create ssl uploader: %w", err) + } + + return &DeployerProvider{ + config: config, + logger: slog.Default(), + sslUploader: uploader, + }, nil +} + +func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { + if logger == nil { + d.logger = slog.New(slog.DiscardHandler) + } else { + d.logger = logger + } + d.sslUploader.WithLogger(logger) + return d +} + +func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 上传证书到 IAM + 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)) + } + + return &deployer.DeployResult{}, nil +} 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 f68ebadc..4f215266 100644 --- a/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go +++ b/internal/pkg/core/uploader/providers/aws-acm/aws_acm.go @@ -74,7 +74,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE // 获取证书列表,避免重复上传 // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListCertificates.html var listCertificatesNextToken *string = nil - listCertificatesMaxItems := int32(1000) + var listCertificatesMaxItems int32 = 1000 for { select { case <-ctx.Done(): @@ -107,7 +107,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE } // 最后对比证书内容 - // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_ListTagsForCertificate.html + // REF: https://docs.aws.amazon.com/en_us/acm/latest/APIReference/API_GetCertificate.html getCertificateReq := &awsacm.GetCertificateInput{ CertificateArn: certSummary.CertificateArn, } @@ -115,11 +115,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE if err != nil { return nil, fmt.Errorf("failed to execute sdk request 'acm.GetCertificate': %w", err) } else { - oldCertPEM := aws.ToString(getCertificateResp.CertificateChain) - if oldCertPEM == "" { - oldCertPEM = aws.ToString(getCertificateResp.Certificate) - } - + oldCertPEM := aws.ToString(getCertificateResp.Certificate) oldCertX509, err := certutil.ParseCertificateFromPEM(oldCertPEM) if err != nil { continue @@ -158,7 +154,7 @@ func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPE } return &uploader.UploadResult{ - CertId: *importCertificateResp.CertificateArn, + CertId: aws.ToString(importCertificateResp.CertificateArn), }, nil } diff --git a/internal/pkg/core/uploader/providers/aws-iam/aws_iam.go b/internal/pkg/core/uploader/providers/aws-iam/aws_iam.go new file mode 100644 index 00000000..10f1a174 --- /dev/null +++ b/internal/pkg/core/uploader/providers/aws-iam/aws_iam.go @@ -0,0 +1,185 @@ +package awsiam + +import ( + "context" + "fmt" + "log/slog" + "time" + + 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" + awsiam "github.com/aws/aws-sdk-go-v2/service/iam" + + "github.com/usual2970/certimate/internal/pkg/core/uploader" + certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" +) + +type UploaderConfig struct { + // AWS AccessKeyId。 + AccessKeyId string `json:"accessKeyId"` + // AWS SecretAccessKey。 + SecretAccessKey string `json:"secretAccessKey"` + // AWS 区域。 + Region string `json:"region"` + // IAM 证书路径。 + // 选填。 + CertificatePath string `json:"certificatePath,omitempty"` +} + +type UploaderProvider struct { + config *UploaderConfig + logger *slog.Logger + sdkClient *awsiam.Client +} + +var _ uploader.Uploader = (*UploaderProvider)(nil) + +func NewUploader(config *UploaderConfig) (*UploaderProvider, error) { + if config == 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) + } + + return &UploaderProvider{ + config: config, + logger: slog.Default(), + sdkClient: client, + }, nil +} + +func (u *UploaderProvider) WithLogger(logger *slog.Logger) uploader.Uploader { + if logger == nil { + u.logger = slog.New(slog.DiscardHandler) + } else { + u.logger = logger + } + return u +} + +func (u *UploaderProvider) Upload(ctx context.Context, certPEM string, privkeyPEM string) (*uploader.UploadResult, error) { + // 解析证书内容 + certX509, err := certutil.ParseCertificateFromPEM(certPEM) + if err != nil { + return nil, err + } + + // 提取服务器证书 + 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/IAM/latest/APIReference/API_ListServerCertificates.html + var listServerCertificatesMarker *string = nil + var listServerCertificatesMaxItems int32 = 1000 + for { + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + listServerCertificatesReq := &awsiam.ListServerCertificatesInput{ + Marker: listServerCertificatesMarker, + MaxItems: aws.Int32(listServerCertificatesMaxItems), + } + if u.config.CertificatePath != "" { + listServerCertificatesReq.PathPrefix = aws.String(u.config.CertificatePath) + } + listServerCertificatesResp, err := u.sdkClient.ListServerCertificates(context.TODO(), listServerCertificatesReq) + u.logger.Debug("sdk request 'iam.ListServerCertificates'", slog.Any("request", listServerCertificatesReq), slog.Any("response", listServerCertificatesResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'iam.ListServerCertificates': %w", err) + } + + for _, certMeta := range listServerCertificatesResp.ServerCertificateMetadataList { + // 先对比证书路径 + if u.config.CertificatePath != "" && aws.ToString(certMeta.Path) != u.config.CertificatePath { + continue + } + + // 先对比证书有效期 + if certMeta.Expiration == nil || !certMeta.Expiration.Equal(certX509.NotAfter) { + continue + } + + // 最后对比证书内容 + // REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_GetServerCertificate.html + getServerCertificateReq := &awsiam.GetServerCertificateInput{ + ServerCertificateName: certMeta.ServerCertificateName, + } + getServerCertificateResp, err := u.sdkClient.GetServerCertificate(context.TODO(), getServerCertificateReq) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'iam.GetServerCertificate': %w", err) + } else { + oldCertPEM := aws.ToString(getServerCertificateResp.ServerCertificate.CertificateBody) + oldCertX509, err := certutil.ParseCertificateFromPEM(oldCertPEM) + if err != nil { + continue + } + + if !certutil.EqualCertificate(certX509, oldCertX509) { + continue + } + } + + // 如果以上信息都一致,则视为已存在相同证书,直接返回 + u.logger.Info("ssl certificate already exists") + return &uploader.UploadResult{ + CertId: aws.ToString(certMeta.ServerCertificateId), + CertName: aws.ToString(certMeta.ServerCertificateName), + }, nil + } + + if listServerCertificatesResp.Marker == nil || len(listServerCertificatesResp.ServerCertificateMetadataList) < int(listServerCertificatesMaxItems) { + break + } else { + listServerCertificatesMarker = listServerCertificatesResp.Marker + } + } + + // 生成新证书名(需符合 AWS IAM 命名规则) + certName := fmt.Sprintf("certimate-%d", time.Now().UnixMilli()) + + // 导入证书 + // REF: https://docs.aws.amazon.com/en_us/IAM/latest/APIReference/API_UploadServerCertificate.html + uploadServerCertificateReq := &awsiam.UploadServerCertificateInput{ + ServerCertificateName: aws.String(certName), + Path: aws.String(u.config.CertificatePath), + CertificateBody: aws.String(serverCertPEM), + CertificateChain: aws.String(intermediaCertPEM), + PrivateKey: aws.String(privkeyPEM), + } + if u.config.CertificatePath == "" { + uploadServerCertificateReq.Path = aws.String("/") + } + uploadServerCertificateResp, err := u.sdkClient.UploadServerCertificate(context.TODO(), uploadServerCertificateReq) + u.logger.Debug("sdk request 'iam.UploadServerCertificate'", slog.Any("request", uploadServerCertificateReq), slog.Any("response", uploadServerCertificateResp)) + if err != nil { + return nil, fmt.Errorf("failed to execute sdk request 'iam.UploadServerCertificate': %w", err) + } + + return &uploader.UploadResult{ + CertId: aws.ToString(uploadServerCertificateResp.ServerCertificateMetadata.ServerCertificateId), + CertName: certName, + }, nil +} + +func createSdkClient(accessKeyId, secretAccessKey, region string) (*awsiam.Client, error) { + cfg, err := awscfg.LoadDefaultConfig(context.TODO()) + if err != nil { + return nil, err + } + + client := awsiam.NewFromConfig(cfg, func(o *awsiam.Options) { + o.Region = region + o.Credentials = aws.NewCredentialsCache(awscred.NewStaticCredentialsProvider(accessKeyId, secretAccessKey, "")) + }) + return client, nil +} diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx index 33fefcf0..c0083298 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm.tsx @@ -35,6 +35,7 @@ import DeployNodeConfigFormAliyunVODConfig from "./DeployNodeConfigFormAliyunVOD import DeployNodeConfigFormAliyunWAFConfig from "./DeployNodeConfigFormAliyunWAFConfig"; import DeployNodeConfigFormAWSACMConfig from "./DeployNodeConfigFormAWSACMConfig"; import DeployNodeConfigFormAWSCloudFrontConfig from "./DeployNodeConfigFormAWSCloudFrontConfig"; +import DeployNodeConfigFormAWSIAMConfig from "./DeployNodeConfigFormAWSIAMConfig"; import DeployNodeConfigFormAzureKeyVaultConfig from "./DeployNodeConfigFormAzureKeyVaultConfig"; import DeployNodeConfigFormBaiduCloudAppBLBConfig from "./DeployNodeConfigFormBaiduCloudAppBLBConfig"; import DeployNodeConfigFormBaiduCloudBLBConfig from "./DeployNodeConfigFormBaiduCloudBLBConfig"; @@ -238,6 +239,8 @@ const DeployNodeConfigForm = forwardRef; case DEPLOYMENT_PROVIDERS.AWS_CLOUDFRONT: return ; + case DEPLOYMENT_PROVIDERS.AWS_IAM: + return ; case DEPLOYMENT_PROVIDERS.AZURE_KEYVAULT: return ; case DEPLOYMENT_PROVIDERS.BAIDUCLOUD_APPBLB: diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx index f1689ced..a34f2fbb 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAWSCloudFrontConfig.tsx @@ -1,11 +1,12 @@ import { useTranslation } from "react-i18next"; -import { Form, type FormInstance, Input } from "antd"; +import { Form, type FormInstance, Input, Select } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; type DeployNodeConfigFormAWSCloudFrontConfigFieldValues = Nullish<{ region: string; distributionId: string; + certificateSource: string; }>; export type DeployNodeConfigFormAWSCloudFrontConfigProps = { @@ -17,7 +18,9 @@ export type DeployNodeConfigFormAWSCloudFrontConfigProps = { }; const initFormModel = (): DeployNodeConfigFormAWSCloudFrontConfigFieldValues => { - return {}; + return { + certificateSource: "ACM", + }; }; const DeployNodeConfigFormAWSCloudFrontConfig = ({ @@ -30,15 +33,9 @@ const DeployNodeConfigFormAWSCloudFrontConfig = ({ const { t } = useTranslation(); const formSchema = z.object({ - region: z - .string({ message: t("workflow_node.deploy.form.aws_cloudfront_region.placeholder") }) - .nonempty(t("workflow_node.deploy.form.aws_cloudfront_region.placeholder")) - .trim(), - distributionId: z - .string({ message: t("workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder") }) - .nonempty(t("workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })) - .trim(), + region: z.string().trim().nonempty(t("workflow_node.deploy.form.aws_cloudfront_region.placeholder")), + distributionId: z.string().trim().nonempty(t("workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder")), + certificateSource: z.string().trim().nonempty(t("workflow_node.deploy.form.aws_cloudfront_certificate_source.placeholder")), }); const formRule = createSchemaFieldRule(formSchema); @@ -72,6 +69,17 @@ const DeployNodeConfigFormAWSCloudFrontConfig = ({ > + + + + ); }; diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormAWSIAMConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormAWSIAMConfig.tsx new file mode 100644 index 00000000..1013153a --- /dev/null +++ b/ui/src/components/workflow/node/DeployNodeConfigFormAWSIAMConfig.tsx @@ -0,0 +1,77 @@ +import { useTranslation } from "react-i18next"; +import { Form, type FormInstance, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +type DeployNodeConfigFormAWSIAMConfigFieldValues = Nullish<{ + region: string; + certificatePath?: string; +}>; + +export type DeployNodeConfigFormAWSIAMConfigProps = { + form: FormInstance; + formName: string; + disabled?: boolean; + initialValues?: DeployNodeConfigFormAWSIAMConfigFieldValues; + onValuesChange?: (values: DeployNodeConfigFormAWSIAMConfigFieldValues) => void; +}; + +const initFormModel = (): DeployNodeConfigFormAWSIAMConfigFieldValues => { + return { + certificatePath: "/", + }; +}; + +const DeployNodeConfigFormAWSIAMConfig = ({ form: formInst, formName, disabled, initialValues, onValuesChange }: DeployNodeConfigFormAWSIAMConfigProps) => { + const { t } = useTranslation(); + + const formSchema = z.object({ + region: z + .string({ message: t("workflow_node.deploy.form.aws_iam_region.placeholder") }) + .nonempty(t("workflow_node.deploy.form.aws_iam_region.placeholder")) + .trim(), + certificatePath: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + return v.startsWith("/") && v.endsWith("/"); + }, t("workflow_node.deploy.form.aws_iam_certificate_path.errmsg.invalid")), + }); + const formRule = createSchemaFieldRule(formSchema); + + const handleFormChange = (_: unknown, values: z.infer) => { + onValuesChange?.(values); + }; + + return ( +
+ } + > + + + + } + > + + +
+ ); +}; + +export default DeployNodeConfigFormAWSIAMConfig; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index 64a52723..c74a13ba 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -389,6 +389,7 @@ export const DEPLOYMENT_PROVIDERS = Object.freeze({ ALIYUN_WAF: `${ACCESS_PROVIDERS.ALIYUN}-waf`, AWS_ACM: `${ACCESS_PROVIDERS.AWS}-acm`, AWS_CLOUDFRONT: `${ACCESS_PROVIDERS.AWS}-cloudfront`, + AWS_IAM: `${ACCESS_PROVIDERS.AWS}-iam`, AZURE_KEYVAULT: `${ACCESS_PROVIDERS.AZURE}-keyvault`, BAIDUCLOUD_APPBLB: `${ACCESS_PROVIDERS.BAIDUCLOUD}-appblb`, BAIDUCLOUD_BLB: `${ACCESS_PROVIDERS.BAIDUCLOUD}-blb`, @@ -561,6 +562,7 @@ export const deploymentProvidersMap: Maphttps://docs.aws.amazon.com/en_us/AmazonCloudFront/latest/DeveloperGuide/distribution-working-with.html", + "workflow_node.deploy.form.aws_cloudfront_certificate_source.label": "AWS CloudFront certificate source", + "workflow_node.deploy.form.aws_cloudfront_certificate_source.placeholder": "Please select AWS CloudFront certificate source", + "workflow_node.deploy.form.aws_iam_region.label": "AWS IAM Region", + "workflow_node.deploy.form.aws_iam_region.placeholder": "Please enter AWS IAM region (e.g. us-east-1)", + "workflow_node.deploy.form.aws_iam_region.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/general/latest/gr/rande.html#regional-endpoints", + "workflow_node.deploy.form.aws_iam_certificate_path.label": "AWS IAM certificate path (Optional)", + "workflow_node.deploy.form.aws_iam_certificate_path.placeholder": "Please enter AWS IAM certificate path", + "workflow_node.deploy.form.aws_iam_certificate_path.errmsg.invalid": "Please enter a valid AWS IAM certificate path", + "workflow_node.deploy.form.aws_iam_certificate_path.tooltip": "For more information, see https://docs.aws.amazon.com/en_us/IAM/latest/UserGuide/reference_identifiers.html", "workflow_node.deploy.form.azure_keyvault_name.label": "Azure KeyVault name", "workflow_node.deploy.form.azure_keyvault_name.placeholder": "Please enter Azure KeyVault name", "workflow_node.deploy.form.azure_keyvault_name.tooltip": "For more information, see https://learn.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 8a9a5ec4..79af14fc 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -27,6 +27,7 @@ "provider.aws": "AWS", "provider.aws.acm": "AWS - ACM (Amazon Certificate Manager)", "provider.aws.cloudfront": "AWS - CloudFront", + "provider.aws.iam": "AWS - IAM (Identity and Access Management)", "provider.aws.route53": "AWS - Route53", "provider.azure": "Azure", "provider.azure.dns": "Azure - DNS", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index 9f244ef2..9a8be8af 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -296,6 +296,15 @@ "workflow_node.deploy.form.aws_cloudfront_distribution_id.label": "AWS CloudFront 分配 ID", "workflow_node.deploy.form.aws_cloudfront_distribution_id.placeholder": "请输入 AWS CloudFront 分配 ID", "workflow_node.deploy.form.aws_cloudfront_distribution_id.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/distribution-working-with.html", + "workflow_node.deploy.form.aws_cloudfront_certificate_source.label": "AWS CloudFront 证书来源", + "workflow_node.deploy.form.aws_cloudfront_certificate_source.placeholder": "请选择 AWS CloudFront 证书来源", + "workflow_node.deploy.form.aws_iam_region.label": "AWS IAM 服务区域", + "workflow_node.deploy.form.aws_iam_region.placeholder": "请输入 AWS IAM 服务区域(例如:us-east-1)", + "workflow_node.deploy.form.aws_iam_region.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/general/latest/gr/rande.html#regional-endpoints", + "workflow_node.deploy.form.aws_iam_certificate_path.label": "AWS IAM 证书路径(可选)", + "workflow_node.deploy.form.aws_iam_certificate_path.placeholder": "请输入 AWS IAM 证书路径", + "workflow_node.deploy.form.aws_iam_certificate_path.errmsg.invalid": "请输入正确的 AWS IAM 证书路径", + "workflow_node.deploy.form.aws_iam_certificate_path.tooltip": "这是什么?请参阅 https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/reference_identifiers.html", "workflow_node.deploy.form.azure_keyvault_name.label": "Azure KeyVault 名称", "workflow_node.deploy.form.azure_keyvault_name.placeholder": "请输入 Azure KeyVault 名称", "workflow_node.deploy.form.azure_keyvault_name.tooltip": "这是什么?请参阅 https://learn.microsoft.com/zh-cn/azure/key-vault/general/about-keys-secrets-certificates",