From 6c6bb78568d3237b081184ebfd19105763966992 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Thu, 15 May 2025 01:40:37 +0800 Subject: [PATCH] feat: deploy server certificate or intermedia certificate --- internal/deployer/providers.go | 56 ++++++++++--------- .../edgio-applications/edgio_applications.go | 6 +- .../core/deployer/providers/local/local.go | 26 +++++++++ .../pkg/core/deployer/providers/ssh/ssh.go | 26 +++++++++ .../deployer/providers/webhook/webhook.go | 8 +++ internal/pkg/utils/cert/extractor.go | 10 ++-- .../node/DeployNodeConfigFormLocalConfig.tsx | 46 ++++++++++++--- .../node/DeployNodeConfigFormSSHConfig.tsx | 48 ++++++++++++---- ui/src/i18n/locales/en/nls.access.json | 2 +- .../i18n/locales/en/nls.workflow.nodes.json | 24 +++++--- ui/src/i18n/locales/zh/nls.access.json | 2 +- .../i18n/locales/zh/nls.workflow.nodes.json | 26 ++++++--- 12 files changed, 210 insertions(+), 70 deletions(-) diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 30cf9d41..3ad6aa4c 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -693,16 +693,18 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer case domain.DeploymentProviderTypeLocal: { deployer, err := pLocal.NewDeployer(&pLocal.DeployerConfig{ - ShellEnv: pLocal.ShellEnvType(maputil.GetString(options.ProviderExtendedConfig, "shellEnv")), - PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), - OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), - OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), + ShellEnv: pLocal.ShellEnvType(maputil.GetString(options.ProviderExtendedConfig, "shellEnv")), + PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), + OutputFormat: pLocal.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pLocal.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), + OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"), + OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"), + OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), }) return deployer, err } @@ -819,22 +821,24 @@ func createDeployerProvider(options *deployerProviderOptions) (deployer.Deployer } deployer, err := pSSH.NewDeployer(&pSSH.DeployerConfig{ - SshHost: access.Host, - SshPort: access.Port, - SshUsername: access.Username, - SshPassword: access.Password, - SshKey: access.Key, - SshKeyPassphrase: access.KeyPassphrase, - UseSCP: maputil.GetBool(options.ProviderExtendedConfig, "useSCP"), - PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), - PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), - OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), - OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), - OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), - PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), - JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), - JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), - JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), + SshHost: access.Host, + SshPort: access.Port, + SshUsername: access.Username, + SshPassword: access.Password, + SshKey: access.Key, + SshKeyPassphrase: access.KeyPassphrase, + UseSCP: maputil.GetBool(options.ProviderExtendedConfig, "useSCP"), + PreCommand: maputil.GetString(options.ProviderExtendedConfig, "preCommand"), + PostCommand: maputil.GetString(options.ProviderExtendedConfig, "postCommand"), + OutputFormat: pSSH.OutputFormatType(maputil.GetOrDefaultString(options.ProviderExtendedConfig, "format", string(pSSH.OUTPUT_FORMAT_PEM))), + OutputCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPath"), + OutputServerCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForServerOnly"), + OutputIntermediaCertPath: maputil.GetString(options.ProviderExtendedConfig, "certPathForIntermediaOnly"), + OutputKeyPath: maputil.GetString(options.ProviderExtendedConfig, "keyPath"), + PfxPassword: maputil.GetString(options.ProviderExtendedConfig, "pfxPassword"), + JksAlias: maputil.GetString(options.ProviderExtendedConfig, "jksAlias"), + JksKeypass: maputil.GetString(options.ProviderExtendedConfig, "jksKeypass"), + JksStorepass: maputil.GetString(options.ProviderExtendedConfig, "jksStorepass"), }) return deployer, err } 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 3dd202a3..3425f05e 100644 --- a/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications.go +++ b/internal/pkg/core/deployer/providers/edgio-applications/edgio_applications.go @@ -57,7 +57,7 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { // 提取 Edgio 所需的服务端证书和中间证书内容 - privateCertPEM, intermediateCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM) + serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM) if err != nil { return nil, err } @@ -66,8 +66,8 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE // REF: https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts uploadTlsCertReq := edgiodtos.UploadTlsCertRequest{ EnvironmentID: d.config.EnvironmentId, - PrimaryCert: privateCertPEM, - IntermediateCert: intermediateCertPEM, + PrimaryCert: serverCertPEM, + IntermediateCert: intermediaCertPEM, PrivateKey: privkeyPEM, } uploadTlsCertResp, err := d.sdkClient.UploadTlsCert(uploadTlsCertReq) diff --git a/internal/pkg/core/deployer/providers/local/local.go b/internal/pkg/core/deployer/providers/local/local.go index 77f96543..a71ad9d3 100644 --- a/internal/pkg/core/deployer/providers/local/local.go +++ b/internal/pkg/core/deployer/providers/local/local.go @@ -25,6 +25,12 @@ type DeployerConfig struct { OutputFormat OutputFormatType `json:"outputFormat,omitempty"` // 输出证书文件路径。 OutputCertPath string `json:"outputCertPath,omitempty"` + // 输出服务器证书文件路径。 + // 选填。 + OutputServerCertPath string `json:"outputServerCertPath,omitempty"` + // 输出中间证书文件路径。 + // 选填。 + OutputIntermediaCertPath string `json:"outputIntermediaCertPath,omitempty"` // 输出私钥文件路径。 OutputKeyPath string `json:"outputKeyPath,omitempty"` // PFX 导出密码。 @@ -69,6 +75,12 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { } func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 提取服务器证书和中间证书 + serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM) + if err != nil { + return nil, fmt.Errorf("failed to extract certs: %w", err) + } + // 执行前置命令 if d.config.PreCommand != "" { stdout, stderr, err := execCommand(d.config.ShellEnv, d.config.PreCommand) @@ -86,6 +98,20 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } d.logger.Info("ssl certificate file saved", slog.String("path", d.config.OutputCertPath)) + if d.config.OutputServerCertPath != "" { + if err := fileutil.WriteString(d.config.OutputServerCertPath, serverCertPEM); err != nil { + return nil, fmt.Errorf("failed to save server certificate file: %w", err) + } + d.logger.Info("ssl server certificate file saved", slog.String("path", d.config.OutputServerCertPath)) + } + + if d.config.OutputIntermediaCertPath != "" { + if err := fileutil.WriteString(d.config.OutputIntermediaCertPath, intermediaCertPEM); err != nil { + return nil, fmt.Errorf("failed to save intermedia certificate file: %w", err) + } + d.logger.Info("ssl intermedia certificate file saved", slog.String("path", d.config.OutputIntermediaCertPath)) + } + if err := fileutil.WriteString(d.config.OutputKeyPath, privkeyPEM); err != nil { return nil, fmt.Errorf("failed to save private key file: %w", err) } diff --git a/internal/pkg/core/deployer/providers/ssh/ssh.go b/internal/pkg/core/deployer/providers/ssh/ssh.go index 4b8b433d..cf09214b 100644 --- a/internal/pkg/core/deployer/providers/ssh/ssh.go +++ b/internal/pkg/core/deployer/providers/ssh/ssh.go @@ -41,6 +41,12 @@ type DeployerConfig struct { OutputFormat OutputFormatType `json:"outputFormat,omitempty"` // 输出证书文件路径。 OutputCertPath string `json:"outputCertPath,omitempty"` + // 输出服务器证书文件路径。 + // 选填。 + OutputServerCertPath string `json:"outputServerCertPath,omitempty"` + // 输出中间证书文件路径。 + // 选填。 + OutputIntermediaCertPath string `json:"outputIntermediaCertPath,omitempty"` // 输出私钥文件路径。 OutputKeyPath string `json:"outputKeyPath,omitempty"` // PFX 导出密码。 @@ -85,6 +91,12 @@ func (d *DeployerProvider) WithLogger(logger *slog.Logger) deployer.Deployer { } func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*deployer.DeployResult, error) { + // 提取服务器证书和中间证书 + serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM) + if err != nil { + return nil, fmt.Errorf("failed to extract certs: %w", err) + } + // 连接 client, err := createSshClient( d.config.SshHost, @@ -118,6 +130,20 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE } d.logger.Info("ssl certificate file uploaded", slog.String("path", d.config.OutputCertPath)) + if d.config.OutputServerCertPath != "" { + if err := writeFileString(client, d.config.UseSCP, d.config.OutputServerCertPath, serverCertPEM); err != nil { + return nil, fmt.Errorf("failed to save server certificate file: %w", err) + } + d.logger.Info("ssl server certificate file uploaded", slog.String("path", d.config.OutputServerCertPath)) + } + + if d.config.OutputIntermediaCertPath != "" { + if err := writeFileString(client, d.config.UseSCP, d.config.OutputIntermediaCertPath, intermediaCertPEM); err != nil { + return nil, fmt.Errorf("failed to save intermedia certificate file: %w", err) + } + d.logger.Info("ssl intermedia certificate file uploaded", slog.String("path", d.config.OutputIntermediaCertPath)) + } + if err := writeFileString(client, d.config.UseSCP, d.config.OutputKeyPath, privkeyPEM); err != nil { return nil, fmt.Errorf("failed to upload private key file: %w", err) } diff --git a/internal/pkg/core/deployer/providers/webhook/webhook.go b/internal/pkg/core/deployer/providers/webhook/webhook.go index 07b2eaaa..418b2c1a 100644 --- a/internal/pkg/core/deployer/providers/webhook/webhook.go +++ b/internal/pkg/core/deployer/providers/webhook/webhook.go @@ -75,6 +75,12 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE return nil, fmt.Errorf("failed to parse x509: %w", err) } + // 提取服务器证书和中间证书 + serverCertPEM, intermediaCertPEM, err := certutil.ExtractCertificatesFromPEM(certPEM) + if err != nil { + return nil, fmt.Errorf("failed to extract certs: %w", err) + } + // 处理 Webhook URL webhookUrl, err := url.Parse(d.config.WebhookUrl) if err != nil { @@ -134,6 +140,8 @@ func (d *DeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPE replaceJsonValueRecursively(webhookData, "${DOMAIN}", certX509.Subject.CommonName) replaceJsonValueRecursively(webhookData, "${DOMAINS}", strings.Join(certX509.DNSNames, ";")) replaceJsonValueRecursively(webhookData, "${CERTIFICATE}", certPEM) + replaceJsonValueRecursively(webhookData, "${SERVER_CERTIFICATE}", serverCertPEM) + replaceJsonValueRecursively(webhookData, "${INTERMEDIA_CERTIFICATE}", intermediaCertPEM) replaceJsonValueRecursively(webhookData, "${PRIVATE_KEY}", privkeyPEM) if webhookMethod == http.MethodGet || webhookContentType == CONTENT_TYPE_FORM || webhookContentType == CONTENT_TYPE_MULTIPART { diff --git a/internal/pkg/utils/cert/extractor.go b/internal/pkg/utils/cert/extractor.go index 110f4772..94d0a8da 100644 --- a/internal/pkg/utils/cert/extractor.go +++ b/internal/pkg/utils/cert/extractor.go @@ -12,9 +12,9 @@ import ( // // 出参: // - serverCertPEM: 服务器证书的 PEM 内容。 -// - interCertPEM: 中间证书的 PEM 内容。 +// - intermediaCertPEM: 中间证书的 PEM 内容。 // - err: 错误。 -func ExtractCertificatesFromPEM(certPEM string) (serverCertPEM string, interCertPEM string, err error) { +func ExtractCertificatesFromPEM(certPEM string) (serverCertPEM string, intermediaCertPEM string, err error) { pemBlocks := make([]*pem.Block, 0) pemData := []byte(certPEM) for { @@ -28,7 +28,7 @@ func ExtractCertificatesFromPEM(certPEM string) (serverCertPEM string, interCert } serverCertPEM = "" - interCertPEM = "" + intermediaCertPEM = "" if len(pemBlocks) == 0 { return "", "", errors.New("failed to decode PEM block") @@ -40,9 +40,9 @@ func ExtractCertificatesFromPEM(certPEM string) (serverCertPEM string, interCert if len(pemBlocks) > 1 { for i := 1; i < len(pemBlocks); i++ { - interCertPEM += string(pem.EncodeToMemory(pemBlocks[i])) + intermediaCertPEM += string(pem.EncodeToMemory(pemBlocks[i])) } } - return serverCertPEM, interCertPEM, nil + return serverCertPEM, intermediaCertPEM, nil } diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx index e71cd639..700f0b09 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormLocalConfig.tsx @@ -11,14 +11,16 @@ import { CERTIFICATE_FORMATS } from "@/domain/certificate"; type DeployNodeConfigFormLocalConfigFieldValues = Nullish<{ format: string; certPath: string; - keyPath?: string | null; - pfxPassword?: string | null; - jksAlias?: string | null; - jksKeypass?: string | null; - jksStorepass?: string | null; - shellEnv?: string | null; - preCommand?: string | null; - postCommand?: string | null; + certPathForServerOnly?: string; + certPathForIntermediaOnly?: string; + keyPath?: string; + pfxPassword?: string; + jksAlias?: string; + jksKeypass?: string; + jksStorepass?: string; + shellEnv?: string; + preCommand?: string; + postCommand?: string; }>; export type DeployNodeConfigFormLocalConfigProps = { @@ -160,6 +162,16 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i .min(1, t("workflow_node.deploy.form.local_cert_path.tooltip")) .max(256, t("common.errmsg.string_max", { max: 256 })) .trim(), + certPathForServerOnly: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .trim() + .nullish(), + certPathForIntermediaOnly: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .trim() + .nullish(), keyPath: z .string() .max(256, t("common.errmsg.string_max", { max: 256 })) @@ -326,6 +338,24 @@ const DeployNodeConfigFormLocalConfig = ({ form: formInst, formName, disabled, i > + + } + > + + + + } + > + + diff --git a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx index 287baaeb..6a8d01c9 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigFormSSHConfig.tsx @@ -13,13 +13,15 @@ import { initPresetScript } from "./DeployNodeConfigFormLocalConfig"; type DeployNodeConfigFormSSHConfigFieldValues = Nullish<{ format: string; certPath: string; - keyPath?: string | null; - pfxPassword?: string | null; - jksAlias?: string | null; - jksKeypass?: string | null; - jksStorepass?: string | null; - preCommand?: string | null; - postCommand?: string | null; + certPathForServerOnly?: string; + certPathForIntermediaOnly?: string; + keyPath?: string; + pfxPassword?: string; + jksAlias?: string; + jksKeypass?: string; + jksStorepass?: string; + preCommand?: string; + postCommand?: string; useSCP?: boolean; }>; @@ -61,6 +63,16 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini .trim() .nullish() .refine((v) => fieldFormat !== FORMAT_PEM || !!v?.trim(), { message: t("workflow_node.deploy.form.ssh_key_path.tooltip") }), + certPathForServerOnly: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .trim() + .nullish(), + certPathForIntermediaOnly: z + .string() + .max(256, t("common.errmsg.string_max", { max: 256 })) + .trim() + .nullish(), pfxPassword: z .string() .max(64, t("common.errmsg.string_max", { max: 256 })) @@ -207,6 +219,24 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini > + + } + > + + + + } + > + + @@ -249,10 +279,6 @@ const DeployNodeConfigFormSSHConfig = ({ form: formInst, formName, disabled, ini - -