From 395eb44b0678b4e985e68e9f72fa428f855a0aa9 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 5 Sep 2025 22:31:48 +0800 Subject: [PATCH 1/6] feat(provider): new acme dns-01 provider: vultr --- go.mod | 1 + go.sum | 2 + internal/certapply/applicators/sp_vultr.go | 29 +++++++++++ internal/domain/access.go | 4 ++ internal/domain/provider.go | 2 + internal/workflow/service.go | 8 ++- .../acme-dns01/providers/vultr/vultr.go | 38 ++++++++++++++ ui/public/imgs/providers/vultr.svg | 1 + ui/src/components/access/AccessForm.tsx | 4 ++ .../forms/AccessConfigFieldsProviderVultr.tsx | 52 +++++++++++++++++++ ui/src/domain/provider.ts | 4 ++ ui/src/i18n/locales/en/nls.access.json | 3 ++ ui/src/i18n/locales/en/nls.provider.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 3 ++ ui/src/i18n/locales/zh/nls.provider.json | 1 + 15 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 internal/certapply/applicators/sp_vultr.go create mode 100644 pkg/core/ssl-applicator/acme-dns01/providers/vultr/vultr.go create mode 100644 ui/public/imgs/providers/vultr.svg create mode 100644 ui/src/components/access/forms/AccessConfigFieldsProviderVultr.tsx diff --git a/go.mod b/go.mod index 0140ddc8..861bfceb 100644 --- a/go.mod +++ b/go.mod @@ -140,6 +140,7 @@ require ( github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/vultr/govultr/v3 v3.21.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.mongodb.org/mongo-driver v1.17.2 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect diff --git a/go.sum b/go.sum index 74227562..959dfd1a 100644 --- a/go.sum +++ b/go.sum @@ -887,6 +887,8 @@ github.com/volcengine/volc-sdk-golang v1.0.219 h1:IqMCdpJ6uuqS2ZZQYUVHKVd+2H1au0 github.com/volcengine/volc-sdk-golang v1.0.219/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= github.com/volcengine/volcengine-go-sdk v1.1.30 h1:D85LL8euXgxwsZzwQ0azT/hannEsiKxKY4h/7/HU764= github.com/volcengine/volcengine-go-sdk v1.1.30/go.mod h1:EyKoi6t6eZxoPNGr2GdFCZti2Skd7MO3eUzx7TtSvNo= +github.com/vultr/govultr/v3 v3.21.1 h1:0cnA8fXiqayPGbAlNHaW+5oCQjpDNkkAm3Nt3LOHplM= +github.com/vultr/govultr/v3 v3.21.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= diff --git a/internal/certapply/applicators/sp_vultr.go b/internal/certapply/applicators/sp_vultr.go new file mode 100644 index 00000000..6101a023 --- /dev/null +++ b/internal/certapply/applicators/sp_vultr.go @@ -0,0 +1,29 @@ +package applicators + +import ( + "fmt" + + "github.com/go-acme/lego/v4/challenge" + + "github.com/certimate-go/certimate/internal/domain" + "github.com/certimate-go/certimate/pkg/core/ssl-applicator/acme-dns01/providers/vultr" + xmaps "github.com/certimate-go/certimate/pkg/utils/maps" +) + +func init() { + if err := ACMEDns01Registries.Register(domain.ACMEDns01ProviderTypeVultr, func(options *ProviderFactoryOptions) (challenge.Provider, error) { + credentials := domain.AccessConfigForVultr{} + if err := xmaps.Populate(options.ProviderAccessConfig, &credentials); err != nil { + return nil, fmt.Errorf("failed to populate provider access config: %w", err) + } + + provider, err := vultr.NewChallengeProvider(&vultr.ChallengeProviderConfig{ + ApiKey: credentials.ApiKey, + DnsPropagationTimeout: options.DnsPropagationTimeout, + DnsTTL: options.DnsTTL, + }) + return provider, err + }); err != nil { + panic(err) + } +} diff --git a/internal/domain/access.go b/internal/domain/access.go index d43462ed..5f745e3c 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -402,6 +402,10 @@ type AccessConfigForVolcEngine struct { SecretAccessKey string `json:"secretAccessKey"` } +type AccessConfigForVultr struct { + ApiKey string `json:"apiKey"` +} + type AccessConfigForWangsu struct { AccessKeyId string `json:"accessKeyId"` AccessKeySecret string `json:"accessKeySecret"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index e35919a7..379b6276 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -86,6 +86,7 @@ const ( AccessProviderTypeUpyun = AccessProviderType("upyun") AccessProviderTypeVercel = AccessProviderType("vercel") AccessProviderTypeVolcEngine = AccessProviderType("volcengine") + AccessProviderTypeVultr = AccessProviderType("vultr") AccessProviderTypeWangsu = AccessProviderType("wangsu") AccessProviderTypeWebhook = AccessProviderType("webhook") AccessProviderTypeWeComBot = AccessProviderType("wecombot") @@ -171,6 +172,7 @@ const ( ACMEDns01ProviderTypeVercel = ACMEDns01ProviderType(AccessProviderTypeVercel) ACMEDns01ProviderTypeVolcEngine = ACMEDns01ProviderType(AccessProviderTypeVolcEngine) // 兼容旧值,等同于 [ACMEDns01ProviderTypeVolcEngineDNS] ACMEDns01ProviderTypeVolcEngineDNS = ACMEDns01ProviderType(AccessProviderTypeVolcEngine + "-dns") + ACMEDns01ProviderTypeVultr = ACMEDns01ProviderType(AccessProviderTypeVultr) ACMEDns01ProviderTypeWestcn = ACMEDns01ProviderType(AccessProviderTypeWestcn) ) diff --git a/internal/workflow/service.go b/internal/workflow/service.go index adf2a39e..d869a1c0 100644 --- a/internal/workflow/service.go +++ b/internal/workflow/service.go @@ -111,7 +111,9 @@ func (s *WorkflowService) StartRun(ctx context.Context, req *dtos.WorkflowStartR workflowRun = resp } - s.dispatcher.Start(ctx, workflowRun.Id) + if err := s.dispatcher.Start(ctx, workflowRun.Id); err != nil { + return nil, err + } return &dtos.WorkflowStartRunResp{RunId: workflowRun.Id}, nil } @@ -131,7 +133,9 @@ func (s *WorkflowService) CancelRun(ctx context.Context, req *dtos.WorkflowCance return nil, errors.New("workflow run is not pending or processing") } - s.dispatcher.Cancel(ctx, workflowRun.Id) + if err := s.dispatcher.Cancel(ctx, workflowRun.Id); err != nil { + return nil, err + } return &dtos.WorkflowCancelRunResp{}, nil } diff --git a/pkg/core/ssl-applicator/acme-dns01/providers/vultr/vultr.go b/pkg/core/ssl-applicator/acme-dns01/providers/vultr/vultr.go new file mode 100644 index 00000000..c3badc34 --- /dev/null +++ b/pkg/core/ssl-applicator/acme-dns01/providers/vultr/vultr.go @@ -0,0 +1,38 @@ +package vultr + +import ( + "errors" + "time" + + "github.com/go-acme/lego/v4/providers/dns/vultr" + + "github.com/certimate-go/certimate/pkg/core" +) + +type ChallengeProviderConfig struct { + ApiKey string `json:"apiKey"` + DnsPropagationTimeout int32 `json:"dnsPropagationTimeout,omitempty"` + DnsTTL int32 `json:"dnsTTL,omitempty"` +} + +func NewChallengeProvider(config *ChallengeProviderConfig) (core.ACMEChallenger, error) { + if config == nil { + return nil, errors.New("the configuration of the acme challenge provider is nil") + } + + providerConfig := vultr.NewDefaultConfig() + providerConfig.APIKey = config.ApiKey + if config.DnsPropagationTimeout != 0 { + providerConfig.PropagationTimeout = time.Duration(config.DnsPropagationTimeout) * time.Second + } + if config.DnsTTL != 0 { + providerConfig.TTL = int(config.DnsTTL) + } + + provider, err := vultr.NewDNSProviderConfig(providerConfig) + if err != nil { + return nil, err + } + + return provider, nil +} diff --git a/ui/public/imgs/providers/vultr.svg b/ui/public/imgs/providers/vultr.svg new file mode 100644 index 00000000..cf49d15d --- /dev/null +++ b/ui/public/imgs/providers/vultr.svg @@ -0,0 +1 @@ + diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index 976434ae..ed643447 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -80,6 +80,7 @@ import AccessConfigFieldsProviderUniCloud from "./forms/AccessConfigFieldsProvid import AccessConfigFieldsProviderUpyun from "./forms/AccessConfigFieldsProviderUpyun"; import AccessConfigFieldsProviderVercel from "./forms/AccessConfigFieldsProviderVercel"; import AccessConfigFieldsProviderVolcEngine from "./forms/AccessConfigFieldsProviderVolcEngine"; +import AccessConfigFieldsProviderVultr from "./forms/AccessConfigFieldsProviderVultr"; import AccessConfigFieldsProviderWangsu from "./forms/AccessConfigFieldsProviderWangsu"; import AccessConfigFieldsProviderWebhook from "./forms/AccessConfigFieldsProviderWebhook"; import AccessConfigFieldsProviderWeComBot from "./forms/AccessConfigFieldsProviderWeComBot"; @@ -335,6 +336,9 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, .. case ACCESS_PROVIDERS.VOLCENGINE: { return ; } + case ACCESS_PROVIDERS.VULTR: { + return ; + } case ACCESS_PROVIDERS.WANGSU: { return ; } diff --git a/ui/src/components/access/forms/AccessConfigFieldsProviderVultr.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderVultr.tsx new file mode 100644 index 00000000..1ce3e4ea --- /dev/null +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderVultr.tsx @@ -0,0 +1,52 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const AccessConfigFormFieldsProviderVultr = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + apiKey: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + apiKey: z.string().nonempty(t("access.form.vultr_api_key.placeholder")), + }); +}; + +const _default = Object.assign(AccessConfigFormFieldsProviderVultr, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index aeb875dd..bbb87fd7 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -88,6 +88,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ UPYUN: "upyun", VERCEL: "vercel", VOLCENGINE: "volcengine", + VULTR: "vultr", WANGSU: "wangsu", WEBHOOK: "webhook", WECOMBOT: "wecombot", @@ -178,6 +179,7 @@ export const accessProvidersMap: Maphttps://www.volcengine.com/docs/6291/216571", + "access.form.vultr_api_key.label": "Vultr API key", + "access.form.vultr_api_key.placeholder": "Please enter Vultr API key", + "access.form.vultr_api_key.tooltip": "For more information, see https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key", "access.form.wangsu_access_key_id.label": "Wangsu Cloud AccessKeyId", "access.form.wangsu_access_key_id.placeholder": "Please enter Wangsu Cloud AccessKeyId", "access.form.wangsu_access_key_id.tooltip": "For more information, see https://en.wangsu.com/document/account-manage/15775", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index 593cda3d..b620226e 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -171,6 +171,7 @@ "provider.volcengine.imagex": "Volcengine - ImageX", "provider.volcengine.live": "Volcengine - Live", "provider.volcengine.tos": "Volcengine - TOS (Tinder Object Storage)", + "provider.vultr": "Vultr", "provider.wangsu": "Wangsu Cloud", "provider.wangsu.cdn": "Wangsu Cloud - CDN (Content Delivery Network)", "provider.wangsu.cdnpro": "Wangsu Cloud - CDN Pro (CDN 360)", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 9aef0305..5df865f4 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -491,6 +491,9 @@ "access.form.volcengine_secret_access_key.label": "火山引擎 SecretAccessKey", "access.form.volcengine_secret_access_key.placeholder": "请输入火山引擎 SecretAccessKey", "access.form.volcengine_secret_access_key.tooltip": "这是什么?请参阅 https://www.volcengine.com/docs/6291/216571", + "access.form.vultr_api_key.label": "Vultr API Key", + "access.form.vultr_api_key.placeholder": "请输入 Vultr API Key", + "access.form.vultr_api_key.tooltip": "这是什么?请参阅 https://docs.vultr.com/platform/other/users/manage-users/api-access/regenerate-user-api-key", "access.form.wangsu_access_key_id.label": "网宿云 AccessKeyId", "access.form.wangsu_access_key_id.placeholder": "请输入网宿云 AccessKeyId", "access.form.wangsu_access_key_id.tooltip": "这是什么?请参阅 https://www.wangsu.com/document/account-manage/15775", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 4c39a384..6d9f78d6 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -171,6 +171,7 @@ "provider.volcengine.imagex": "火山引擎 - 图片服务 ImageX", "provider.volcengine.live": "火山引擎 - 视频直播 Live", "provider.volcengine.tos": "火山引擎 - 对象存储 TOS", + "provider.vultr": "Vultr", "provider.wangsu": "网宿云", "provider.wangsu.cdn": "网宿云 - 内容分发网络 CDN", "provider.wangsu.cdnpro": "网宿云 - CDN Pro (CDN 360)", From 3d2f527d78a99df96e67659dcea3d38fafdf8efd Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 5 Sep 2025 22:47:45 +0800 Subject: [PATCH 2/6] feat(provider): new ca provider: actalisssl --- internal/certapply/config.go | 1 + internal/domain/access.go | 4 + internal/domain/provider.go | 2 + ui/public/imgs/providers/actalisssl.png | Bin 0 -> 4673 bytes ui/src/components/access/AccessForm.tsx | 4 + .../AccessConfigFieldsProviderActalisSSL.tsx | 70 +++++++++++++++++ ui/src/domain/provider.ts | 4 + ui/src/i18n/locales/en/nls.access.json | 6 ++ ui/src/i18n/locales/en/nls.provider.json | 1 + ui/src/i18n/locales/zh/nls.access.json | 6 ++ ui/src/i18n/locales/zh/nls.provider.json | 1 + ui/src/pages/settings/SettingsSSLProvider.tsx | 73 ++++++++++++++++++ 12 files changed, 172 insertions(+) create mode 100644 ui/public/imgs/providers/actalisssl.png create mode 100644 ui/src/components/access/forms/AccessConfigFieldsProviderActalisSSL.tsx diff --git a/internal/certapply/config.go b/internal/certapply/config.go index 8b36d778..d47753fb 100644 --- a/internal/certapply/config.go +++ b/internal/certapply/config.go @@ -15,6 +15,7 @@ import ( var acmeDirUrls = map[string]string{ string(domain.CAProviderTypeLetsEncrypt): "https://acme-v02.api.letsencrypt.org/directory", string(domain.CAProviderTypeLetsEncryptStaging): "https://acme-staging-v02.api.letsencrypt.org/directory", + string(domain.CAProviderTypeActalisSSL): "https://acme-api.actalis.com/acme/directory", string(domain.CAProviderTypeBuypass): "https://api.buypass.com/acme/directory", string(domain.CAProviderTypeGoogleTrustServices): "https://dv.acme-v02.api.pki.goog/directory", string(domain.CAProviderTypeSSLCom): "https://acme.ssl.com/sslcom-dv-rsa", diff --git a/internal/domain/access.go b/internal/domain/access.go index 5f745e3c..ceb569b8 100644 --- a/internal/domain/access.go +++ b/internal/domain/access.go @@ -44,6 +44,10 @@ type AccessConfigForACMEHttpReq struct { Password string `json:"password,omitempty"` } +type AccessConfigForActalisSSL struct { + AccessConfigForACMEExternalAccountBinding +} + type AccessConfigForAliyun struct { AccessKeyId string `json:"accessKeyId"` AccessKeySecret string `json:"accessKeySecret"` diff --git a/internal/domain/provider.go b/internal/domain/provider.go index 379b6276..fec6d3bf 100644 --- a/internal/domain/provider.go +++ b/internal/domain/provider.go @@ -13,6 +13,7 @@ const ( AccessProviderTypeACMECA = AccessProviderType("acmeca") AccessProviderTypeACMEDNS = AccessProviderType("acmedns") AccessProviderTypeACMEHttpReq = AccessProviderType("acmehttpreq") + AccessProviderTypeActalisSSL = AccessProviderType("actalisssl") AccessProviderTypeAkamai = AccessProviderType("akamai") // Akamai(预留) AccessProviderTypeAliyun = AccessProviderType("aliyun") AccessProviderTypeAPISIX = AccessProviderType("apisix") @@ -105,6 +106,7 @@ NOTICE: If you add new constant, please keep ASCII order. */ const ( CAProviderTypeACMECA = CAProviderType(AccessProviderTypeACMECA) + CAProviderTypeActalisSSL = CAProviderType(AccessProviderTypeActalisSSL) CAProviderTypeBuypass = CAProviderType(AccessProviderTypeBuypass) CAProviderTypeGoogleTrustServices = CAProviderType(AccessProviderTypeGoogleTrustServices) CAProviderTypeLetsEncrypt = CAProviderType(AccessProviderTypeLetsEncrypt) diff --git a/ui/public/imgs/providers/actalisssl.png b/ui/public/imgs/providers/actalisssl.png new file mode 100644 index 0000000000000000000000000000000000000000..3897c6b4e5defe0d6d5213c6b9c28a6e6047f002 GIT binary patch literal 4673 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 LR+cLLWEA)x07P%R literal 0 HcmV?d00001 diff --git a/ui/src/components/access/AccessForm.tsx b/ui/src/components/access/AccessForm.tsx index ed643447..69112aa5 100644 --- a/ui/src/components/access/AccessForm.tsx +++ b/ui/src/components/access/AccessForm.tsx @@ -15,6 +15,7 @@ import AccessConfigFieldsProvider1Panel from "./forms/AccessConfigFieldsProvider import AccessConfigFieldsProviderACMECA from "./forms/AccessConfigFieldsProviderACMECA"; import AccessConfigFieldsProviderACMEDNS from "./forms/AccessConfigFieldsProviderACMEDNS"; import AccessConfigFieldsProviderACMEHttpReq from "./forms/AccessConfigFieldsProviderACMEHttpReq"; +import AccessConfigFieldsProviderActalisSSL from "./forms/AccessConfigFieldsProviderActalisSSL"; import AccessConfigFieldsProviderAliyun from "./forms/AccessConfigFieldsProviderAliyun"; import AccessConfigFieldsProviderAPISIX from "./forms/AccessConfigFieldsProviderAPISIX"; import AccessConfigFieldsProviderAWS from "./forms/AccessConfigFieldsProviderAWS"; @@ -141,6 +142,9 @@ const AccessForm = ({ className, style, disabled, initialValues, mode, usage, .. case ACCESS_PROVIDERS.ACMEHTTPREQ: { return ; } + case ACCESS_PROVIDERS.ACTALISSSL: { + return ; + } case ACCESS_PROVIDERS.ALIYUN: { return ; } diff --git a/ui/src/components/access/forms/AccessConfigFieldsProviderActalisSSL.tsx b/ui/src/components/access/forms/AccessConfigFieldsProviderActalisSSL.tsx new file mode 100644 index 00000000..e0d95932 --- /dev/null +++ b/ui/src/components/access/forms/AccessConfigFieldsProviderActalisSSL.tsx @@ -0,0 +1,70 @@ +import { getI18n, useTranslation } from "react-i18next"; +import { Form, Input } from "antd"; +import { createSchemaFieldRule } from "antd-zod"; +import { z } from "zod"; + +import { useFormNestedFieldsContext } from "./_context"; + +const AccessConfigFormFieldsProviderActalisSSL = () => { + const { i18n, t } = useTranslation(); + + const { parentNamePath } = useFormNestedFieldsContext(); + const formSchema = z.object({ + [parentNamePath]: getSchema({ i18n }), + }); + const formRule = createSchemaFieldRule(formSchema); + const initialValues = getInitialValues(); + + return ( + <> + } + > + + + + } + > + + + + ); +}; + +const getInitialValues = (): Nullish>> => { + return { + eabKid: "", + eabHmacKey: "", + }; +}; + +const getSchema = ({ i18n = getI18n() }: { i18n: ReturnType }) => { + const { t } = i18n; + + return z.object({ + eabKid: z + .string() + .min(1, t("access.form.actalisssl_eab_kid.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + eabHmacKey: z + .string() + .min(1, t("access.form.actalisssl_eab_hmac_key.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + }); +}; + +const _default = Object.assign(AccessConfigFormFieldsProviderActalisSSL, { + getInitialValues, + getSchema, +}); + +export default _default; diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index bbb87fd7..8a934aa9 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -19,6 +19,7 @@ export const ACCESS_PROVIDERS = Object.freeze({ ACMECA: "acmeca", ACMEDNS: "acmedns", ACMEHTTPREQ: "acmehttpreq", + ACTALISSSL: "actalisssl", ALIYUN: "aliyun", APISIX: "apisix", AWS: "aws", @@ -188,6 +189,7 @@ export const accessProvidersMap: Map = new [ [CA_PROVIDERS.LETSENCRYPT, "builtin"], [CA_PROVIDERS.LETSENCRYPTSTAGING, "builtin"], + [CA_PROVIDERS.ACTALISSSL], [CA_PROVIDERS.BUYPASS], [CA_PROVIDERS.GOOGLETRUSTSERVICES], [CA_PROVIDERS.SSLCOM], diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 10522775..2603421f 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -78,6 +78,12 @@ "access.form.acmehttpreq_password.label": "HTTP Basic Auth password (Optional)", "access.form.acmehttpreq_password.placeholder": "Please enter HTTP Basic Auth password", "access.form.acmehttpreq_password.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", + "access.form.actalisssl_eab_kid.label": "ACME EAB KID", + "access.form.actalisssl_eab_kid.placeholder": "Please enter ACME EAB KID", + "access.form.actalisssl_eab_kid.tooltip": "For more information, see https://www.actalis.com/manage-with-acme", + "access.form.actalisssl_eab_hmac_key.label": "ACME EAB HMAC key", + "access.form.actalisssl_eab_hmac_key.placeholder": "Please enter ACME EAB HMAC key", + "access.form.actalisssl_eab_hmac_key.tooltip": "For more information, see https://www.actalis.com/manage-with-acme", "access.form.aliyun_access_key_id.label": "Aliyun AccessKeyId", "access.form.aliyun_access_key_id.placeholder": "Please enter Aliyun AccessKeyId", "access.form.aliyun_access_key_id.tooltip": "For more information, see https://www.alibabacloud.com/help/en/acr/create-and-obtain-an-accesskey-pair", diff --git a/ui/src/i18n/locales/en/nls.provider.json b/ui/src/i18n/locales/en/nls.provider.json index b620226e..49cf3367 100644 --- a/ui/src/i18n/locales/en/nls.provider.json +++ b/ui/src/i18n/locales/en/nls.provider.json @@ -5,6 +5,7 @@ "provider.acmeca": "ACME Custom CA Endpoint", "provider.acmedns": "ACME-DNS", "provider.acmehttpreq": "ACME Custom HTTP Endpoint", + "provider.actalisssl": "Actalis SSL", "provider.aliyun": "Alibaba Cloud", "provider.aliyun.alb": "Alibaba Cloud - ALB (Application Load Balancer)", "provider.aliyun.apigw": "Alibaba Cloud - API Gateway", diff --git a/ui/src/i18n/locales/zh/nls.access.json b/ui/src/i18n/locales/zh/nls.access.json index 5df865f4..1cda5e6b 100644 --- a/ui/src/i18n/locales/zh/nls.access.json +++ b/ui/src/i18n/locales/zh/nls.access.json @@ -77,6 +77,12 @@ "access.form.acmehttpreq_password.label": "HTTP 基本认证密码(可选)", "access.form.acmehttpreq_password.placeholder": "请输入 HTTP 基本认证密码", "access.form.acmehttpreq_password.tooltip": "这是什么?请参阅 https://go-acme.github.io/lego/dns/httpreq/", + "access.form.actalisssl_eab_kid.label": "ACME EAB KID", + "access.form.actalisssl_eab_kid.placeholder": "请输入 ACME EAB KID", + "access.form.actalisssl_eab_kid.tooltip": "这是什么?请参阅 https://www.actalis.com/manage-with-acme", + "access.form.actalisssl_eab_hmac_key.label": "ACME EAB HMAC Key", + "access.form.actalisssl_eab_hmac_key.placeholder": "请输入 ACME EAB HMAC Key", + "access.form.actalisssl_eab_hmac_key.tooltip": "这是什么?请参阅 https://www.actalis.com/manage-with-acme", "access.form.aliyun_access_key_id.label": "阿里云 AccessKeyId", "access.form.aliyun_access_key_id.placeholder": "请输入阿里云 AccessKeyId", "access.form.aliyun_access_key_id.tooltip": "这是什么?请参阅 https://help.aliyun.com/zh/ram/user-guide/create-an-accesskey-pair", diff --git a/ui/src/i18n/locales/zh/nls.provider.json b/ui/src/i18n/locales/zh/nls.provider.json index 6d9f78d6..85f1e578 100644 --- a/ui/src/i18n/locales/zh/nls.provider.json +++ b/ui/src/i18n/locales/zh/nls.provider.json @@ -5,6 +5,7 @@ "provider.acmeca": "ACME 自定义 CA 端点", "provider.acmedns": "ACME-DNS", "provider.acmehttpreq": "ACME 自定义 HTTP 端点", + "provider.actalisssl": "Actalis SSL", "provider.aliyun": "阿里云", "provider.aliyun.alb": "阿里云 - 应用型负载均衡 ALB", "provider.aliyun.apigw": "阿里云 - API 网关", diff --git a/ui/src/pages/settings/SettingsSSLProvider.tsx b/ui/src/pages/settings/SettingsSSLProvider.tsx index 4170af57..5d97aa49 100644 --- a/ui/src/pages/settings/SettingsSSLProvider.tsx +++ b/ui/src/pages/settings/SettingsSSLProvider.tsx @@ -108,6 +108,76 @@ const SSLProviderEditFormLetsEncryptStagingConfig = () => { ); }; +const SSLProviderEditFormActalisSSLConfig = () => { + const { t } = useTranslation(); + + const { pending, settings, updateSettings } = useContext(SSLProviderContext); + + const formSchema = z.object({ + eabKid: z + .string(t("access.form.actalisssl_eab_kid.placeholder")) + .min(1, t("access.form.actalisssl_eab_kid.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + eabHmacKey: z + .string(t("access.form.actalisssl_eab_hmac_key.placeholder")) + .min(1, t("access.form.actalisssl_eab_hmac_key.placeholder")) + .max(256, t("common.errmsg.string_max", { max: 256 })), + }); + const formRule = createSchemaFieldRule(formSchema); + const { form: formInst, formProps } = useAntdForm>({ + initialValues: settings?.content?.config?.[CA_PROVIDERS.ACTALISSSL], + onSubmit: async (values) => { + const newSettings = produce(settings, (draft) => { + draft.content ??= {} as SSLProviderSettingsContent; + draft.content.provider = CA_PROVIDERS.ACTALISSSL; + + draft.content.config ??= {} as SSLProviderSettingsContent["config"]; + draft.content.config[CA_PROVIDERS.ACTALISSSL] = values; + }); + await updateSettings(newSettings); + + setFormChanged(false); + }, + }); + + const [formChanged, setFormChanged] = useState(false); + useEffect(() => { + setFormChanged(settings?.content?.provider !== CA_PROVIDERS.ACTALISSSL); + }, [settings?.content?.provider]); + + const handleFormChange = () => { + setFormChanged(true); + }; + + return ( +
+ } + > + + + + } + > + + + + + + +
+ ); +}; + const SSLProviderEditFormBuypassConfig = () => { const { t } = useTranslation(); @@ -466,6 +536,7 @@ const SettingsSSLProvider = () => { const providers = [ [CA_PROVIDERS.LETSENCRYPT, "provider.letsencrypt", "letsencrypt.org", "/imgs/providers/letsencrypt.svg"], [CA_PROVIDERS.LETSENCRYPTSTAGING, "provider.letsencryptstaging", "letsencrypt.org", "/imgs/providers/letsencrypt.svg"], + [CA_PROVIDERS.ACTALISSSL, "provider.actalisssl", "actalis.com", "/imgs/providers/actalisssl.png"], [CA_PROVIDERS.BUYPASS, "provider.buypass", "buypass.com", "/imgs/providers/buypass.png"], [CA_PROVIDERS.GOOGLETRUSTSERVICES, "provider.googletrustservices", "pki.goog", "/imgs/providers/google.svg"], [CA_PROVIDERS.SSLCOM, "provider.sslcom", "ssl.com", "/imgs/providers/sslcom.svg"], @@ -486,6 +557,8 @@ const SettingsSSLProvider = () => { return ; case CA_PROVIDERS.LETSENCRYPTSTAGING: return ; + case CA_PROVIDERS.ACTALISSSL: + return ; case CA_PROVIDERS.BUYPASS: return ; case CA_PROVIDERS.GOOGLETRUSTSERVICES: From aaf48216af761d209eb204ab491d1cfd703d8188 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 5 Sep 2025 23:24:32 +0800 Subject: [PATCH 3/6] feat: upload certificate to cas first on deployment to aliyun cdn/dcdn --- .../certdeploy/deployers/sp_aliyun_cdn.go | 1 + .../{sp_aliyund_cdn.go => sp_aliyun_dcdn.go} | 1 + .../providers/aliyun-cdn/aliyun_cdn.go | 57 +++++++++++++++---- .../providers/aliyun-dcdn/aliyun_dcdn.go | 53 +++++++++++++---- .../providers/aliyun-ddos/aliyun_ddos.go | 2 +- .../providers/aliyun-vod/aliyun_vod.go | 52 +++++++++++++---- .../providers/rainyun-rcdn/rainyun_rcdn.go | 2 +- .../designer/forms/BizApplyNodeConfigForm.tsx | 2 +- ...eployNodeConfigFieldsProviderAliyunCDN.tsx | 18 +++++- ...ployNodeConfigFieldsProviderAliyunDCDN.tsx | 18 +++++- .../i18n/locales/en/nls.workflow.nodes.json | 6 ++ .../i18n/locales/zh/nls.workflow.nodes.json | 6 ++ 12 files changed, 177 insertions(+), 41 deletions(-) rename internal/certdeploy/deployers/{sp_aliyund_cdn.go => sp_aliyun_dcdn.go} (92%) diff --git a/internal/certdeploy/deployers/sp_aliyun_cdn.go b/internal/certdeploy/deployers/sp_aliyun_cdn.go index cd446f50..3301adaf 100644 --- a/internal/certdeploy/deployers/sp_aliyun_cdn.go +++ b/internal/certdeploy/deployers/sp_aliyun_cdn.go @@ -20,6 +20,7 @@ func init() { AccessKeyId: credentials.AccessKeyId, AccessKeySecret: credentials.AccessKeySecret, ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/internal/certdeploy/deployers/sp_aliyund_cdn.go b/internal/certdeploy/deployers/sp_aliyun_dcdn.go similarity index 92% rename from internal/certdeploy/deployers/sp_aliyund_cdn.go rename to internal/certdeploy/deployers/sp_aliyun_dcdn.go index 2a8279b7..8725d0ef 100644 --- a/internal/certdeploy/deployers/sp_aliyund_cdn.go +++ b/internal/certdeploy/deployers/sp_aliyun_dcdn.go @@ -20,6 +20,7 @@ func init() { AccessKeyId: credentials.AccessKeyId, AccessKeySecret: credentials.AccessKeySecret, ResourceGroupId: credentials.ResourceGroupId, + Region: xmaps.GetString(options.ProviderExtendedConfig, "region"), Domain: xmaps.GetString(options.ProviderExtendedConfig, "domain"), }) return provider, err diff --git a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go index a59924d7..00a541d7 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-cdn/aliyun_cdn.go @@ -5,13 +5,16 @@ import ( "errors" "fmt" "log/slog" + "strconv" "strings" - "time" alicdn "github.com/alibabacloud-go/cdn-20180510/v5/client" aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client" "github.com/alibabacloud-go/tea/tea" + "github.com/samber/lo" + "github.com/certimate-go/certimate/pkg/core" + sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" ) type SSLDeployerProviderConfig struct { @@ -21,14 +24,17 @@ type SSLDeployerProviderConfig struct { AccessKeySecret string `json:"accessKeySecret"` // 阿里云资源组 ID。 ResourceGroupId string `json:"resourceGroupId,omitempty"` + // 阿里云地域。 + Region string `json:"region"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } type SSLDeployerProvider struct { - config *SSLDeployerProviderConfig - logger *slog.Logger - sdkClient *alicdn.Client + config *SSLDeployerProviderConfig + logger *slog.Logger + sdkClient *alicdn.Client + sslManager core.SSLManager } var _ core.SSLDeployer = (*SSLDeployerProvider)(nil) @@ -43,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv return nil, fmt.Errorf("could not create sdk client: %w", err) } + sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + ResourceGroupId: config.ResourceGroupId, + Region: lo. + If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou"). + Else("ap-southeast-1"), + }) + if err != nil { + return nil, fmt.Errorf("could not create ssl manager: %w", err) + } + return &SSLDeployerProvider{ - config: config, - logger: slog.Default(), - sdkClient: client, + config: config, + logger: slog.Default(), + sdkClient: client, + sslManager: sslmgr, }, nil } @@ -59,18 +78,32 @@ func (d *SSLDeployerProvider) SetLogger(logger *slog.Logger) { } func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privkeyPEM string) (*core.SSLDeployResult, error) { + if d.config.Domain == "" { + return nil, errors.New("config `domain` is required") + } + + // 上传证书 + upres, err := d.sslManager.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)) + } + // "*.example.com" → ".example.com",适配阿里云 CDN 要求的泛域名格式 domain := strings.TrimPrefix(d.config.Domain, "*") // 设置 CDN 域名域名证书 // REF: https://help.aliyun.com/zh/cdn/developer-reference/api-cdn-2018-05-10-setcdndomainsslcertificate + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) setCdnDomainSSLCertificateReq := &alicdn.SetCdnDomainSSLCertificateRequest{ - DomainName: tea.String(domain), - CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), - CertType: tea.String("upload"), + DomainName: tea.String(domain), + CertType: tea.String("cas"), + CertId: tea.Int64(int64(certId)), + CertRegion: lo. + If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")). + Else(tea.String("ap-southeast-1")), SSLProtocol: tea.String("on"), - SSLPub: tea.String(certPEM), - SSLPri: tea.String(privkeyPEM), } setCdnDomainSSLCertificateResp, err := d.sdkClient.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateReq) d.logger.Debug("sdk request 'cdn.SetCdnDomainSSLCertificate'", slog.Any("request", setCdnDomainSSLCertificateReq), slog.Any("response", setCdnDomainSSLCertificateResp)) diff --git a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go index 6118828f..a599e397 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go +++ b/pkg/core/ssl-deployer/providers/aliyun-dcdn/aliyun_dcdn.go @@ -5,13 +5,16 @@ import ( "errors" "fmt" "log/slog" + "strconv" "strings" - "time" aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client" alidcdn "github.com/alibabacloud-go/dcdn-20180115/v3/client" "github.com/alibabacloud-go/tea/tea" + "github.com/samber/lo" + "github.com/certimate-go/certimate/pkg/core" + sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" ) type SSLDeployerProviderConfig struct { @@ -21,14 +24,17 @@ type SSLDeployerProviderConfig struct { AccessKeySecret string `json:"accessKeySecret"` // 阿里云资源组 ID。 ResourceGroupId string `json:"resourceGroupId,omitempty"` + // 阿里云地域。 + Region string `json:"region"` // 加速域名(支持泛域名)。 Domain string `json:"domain"` } type SSLDeployerProvider struct { - config *SSLDeployerProviderConfig - logger *slog.Logger - sdkClient *alidcdn.Client + config *SSLDeployerProviderConfig + logger *slog.Logger + sdkClient *alidcdn.Client + sslManager core.SSLManager } var _ core.SSLDeployer = (*SSLDeployerProvider)(nil) @@ -43,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv return nil, fmt.Errorf("could not create sdk client: %w", err) } + sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + ResourceGroupId: config.ResourceGroupId, + Region: lo. + If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou"). + Else("ap-southeast-1"), + }) + if err != nil { + return nil, fmt.Errorf("could not create ssl manager: %w", err) + } + return &SSLDeployerProvider{ - config: config, - logger: slog.Default(), - sdkClient: client, + config: config, + logger: slog.Default(), + sdkClient: client, + sslManager: sslmgr, }, nil } @@ -63,18 +82,28 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domain` is required") } + // 上传证书 + upres, err := d.sslManager.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)) + } + // "*.example.com" → ".example.com",适配阿里云 DCDN 要求的泛域名格式 domain := strings.TrimPrefix(d.config.Domain, "*") // 配置域名证书 // REF: https://help.aliyun.com/zh/edge-security-acceleration/dcdn/developer-reference/api-dcdn-2018-01-15-setdcdndomainsslcertificate + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) setDcdnDomainSSLCertificateReq := &alidcdn.SetDcdnDomainSSLCertificateRequest{ - DomainName: tea.String(domain), - CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), - CertType: tea.String("upload"), + DomainName: tea.String(domain), + CertType: tea.String("cas"), + CertId: tea.Int64(int64(certId)), + CertRegion: lo. + If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")). + Else(tea.String("ap-southeast-1")), SSLProtocol: tea.String("on"), - SSLPub: tea.String(certPEM), - SSLPri: tea.String(privkeyPEM), } setDcdnDomainSSLCertificateResp, err := d.sdkClient.SetDcdnDomainSSLCertificate(setDcdnDomainSSLCertificateReq) d.logger.Debug("sdk request 'dcdn.SetDcdnDomainSSLCertificate'", slog.Any("request", setDcdnDomainSSLCertificateReq), slog.Any("response", setDcdnDomainSSLCertificateResp)) diff --git a/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go b/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go index 12f8ceec..dec81ee7 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go +++ b/pkg/core/ssl-deployer/providers/aliyun-ddos/aliyun_ddos.go @@ -94,7 +94,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // 为网站业务转发规则关联 SSL 证书 // REF: https://help.aliyun.com/zh/anti-ddos/anti-ddos-pro-and-premium/developer-reference/api-ddoscoo-2020-01-01-associatewebcert - certId, _ := strconv.Atoi(upres.CertId) + certId, _ := strconv.ParseInt(upres.CertId, 10, 32) associateWebCertReq := &aliddos.AssociateWebCertRequest{ Domain: tea.String(d.config.Domain), CertId: tea.Int32(int32(certId)), diff --git a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go index 08173368..07ea9c10 100644 --- a/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go +++ b/pkg/core/ssl-deployer/providers/aliyun-vod/aliyun_vod.go @@ -5,12 +5,16 @@ import ( "errors" "fmt" "log/slog" - "time" + "strconv" + "strings" aliopen "github.com/alibabacloud-go/darabonba-openapi/v2/client" "github.com/alibabacloud-go/tea/tea" alivod "github.com/alibabacloud-go/vod-20170321/v4/client" + "github.com/samber/lo" + "github.com/certimate-go/certimate/pkg/core" + sslmgrsp "github.com/certimate-go/certimate/pkg/core/ssl-manager/providers/aliyun-cas" ) type SSLDeployerProviderConfig struct { @@ -27,9 +31,10 @@ type SSLDeployerProviderConfig struct { } type SSLDeployerProvider struct { - config *SSLDeployerProviderConfig - logger *slog.Logger - sdkClient *alivod.Client + config *SSLDeployerProviderConfig + logger *slog.Logger + sdkClient *alivod.Client + sslManager core.SSLManager } var _ core.SSLDeployer = (*SSLDeployerProvider)(nil) @@ -44,10 +49,23 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv return nil, fmt.Errorf("could not create sdk client: %w", err) } + sslmgr, err := sslmgrsp.NewSSLManagerProvider(&sslmgrsp.SSLManagerProviderConfig{ + AccessKeyId: config.AccessKeyId, + AccessKeySecret: config.AccessKeySecret, + ResourceGroupId: config.ResourceGroupId, + Region: lo. + If(config.Region == "" || strings.HasPrefix(config.Region, "cn-"), "cn-hangzhou"). + Else("ap-southeast-1"), + }) + if err != nil { + return nil, fmt.Errorf("could not create ssl manager: %w", err) + } + return &SSLDeployerProvider{ - config: config, - logger: slog.Default(), - sdkClient: client, + config: config, + logger: slog.Default(), + sdkClient: client, + sslManager: sslmgr, }, nil } @@ -64,15 +82,25 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke return nil, errors.New("config `domain` is required") } + // 上传证书 + upres, err := d.sslManager.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)) + } + // 设置域名证书 // REF: https://help.aliyun.com/zh/vod/developer-reference/api-vod-2017-03-21-setvoddomainsslcertificate + certId, _ := strconv.ParseInt(upres.CertId, 10, 64) setVodDomainSSLCertificateReq := &alivod.SetVodDomainSSLCertificateRequest{ - DomainName: tea.String(d.config.Domain), - CertName: tea.String(fmt.Sprintf("certimate-%d", time.Now().UnixMilli())), - CertType: tea.String("upload"), + DomainName: tea.String(d.config.Domain), + CertType: tea.String("cas"), + CertId: tea.Int64(int64(certId)), + CertRegion: lo. + If(d.config.Region == "" || strings.HasPrefix(d.config.Region, "cn-"), tea.String("cn-hangzhou")). + Else(tea.String("ap-southeast-1")), SSLProtocol: tea.String("on"), - SSLPub: tea.String(certPEM), - SSLPri: tea.String(privkeyPEM), } setVodDomainSSLCertificateResp, err := d.sdkClient.SetVodDomainSSLCertificate(setVodDomainSSLCertificateReq) d.logger.Debug("sdk request 'live.SetVodDomainSSLCertificate'", slog.Any("request", setVodDomainSSLCertificateReq), slog.Any("response", setVodDomainSSLCertificateResp)) diff --git a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go index 8af0fa06..1accc617 100644 --- a/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go +++ b/pkg/core/ssl-deployer/providers/rainyun-rcdn/rainyun_rcdn.go @@ -80,7 +80,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke // RCDN SSL 绑定域名 // REF: https://apifox.com/apidoc/shared/a4595cc8-44c5-4678-a2a3-eed7738dab03/api-184214120 - certId, _ := strconv.Atoi(upres.CertId) + certId, _ := strconv.ParseInt(upres.CertId, 10, 32) rcdnInstanceSslBindReq := &rainyunsdk.RcdnInstanceSslBindRequest{ CertId: int32(certId), Domains: []string{d.config.Domain}, diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx index 5ab9cb56..b5609cb9 100644 --- a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx @@ -509,7 +509,7 @@ const BizApplyNodeConfigForm = ({ node, ...props }: BizApplyNodeConfigFormProps) > ({ value }))} + options={["classic", "tlsserver", "shortlived"].map((s) => ({ value: s }))} placeholder={t("workflow_node.apply.form.acme_profile.placeholder")} filterOption={(inputValue, option) => option!.value.toLowerCase().includes(inputValue.toLowerCase())} /> diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx index bb8d68f1..92105b64 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunCDN.tsx @@ -1,5 +1,5 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form, Input } from "antd"; +import { AutoComplete, Form, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -19,6 +19,20 @@ const BizDeployNodeConfigFieldsProviderAliyunCDN = () => { return ( <> + } + > + ({ value: s }))} + placeholder={t("workflow_node.deploy.form.aliyun_cdn_region.placeholder")} + /> + + { const getInitialValues = (): Nullish>> => { return { + region: "", domain: "", }; }; @@ -42,6 +57,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) const { t } = i18n; return z.object({ + region: z.string().nullish(), domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), }); }; diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDCDN.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDCDN.tsx index 4a871f2b..0ec8512e 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDCDN.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderAliyunDCDN.tsx @@ -1,5 +1,5 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form, Input } from "antd"; +import { AutoComplete, Form, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -19,6 +19,20 @@ const BizDeployNodeConfigFieldsProviderAliyunDCDN = () => { return ( <> + } + > + ({ value: s }))} + placeholder={t("workflow_node.deploy.form.aliyun_dcdn_region.placeholder")} + /> + + { const getInitialValues = (): Nullish>> => { return { + region: "", domain: "", }; }; @@ -42,6 +57,7 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) const { t } = i18n; return z.object({ + region: z.string().nullish(), domain: z.string().refine((v) => validDomainName(v, { allowWildcard: true }), t("common.errmsg.domain_invalid")), }); }; diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 60ed9ca9..d62db857 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -256,9 +256,15 @@ "workflow_node.deploy.form.aliyun_clb_snidomain.placeholder": "Please enter Alibaba Cloud CLB SNI domain name", "workflow_node.deploy.form.aliyun_clb_snidomain.help": "", "workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "For more information, see https://slb.console.aliyun.com/clb", + "workflow_node.deploy.form.aliyun_cdn_region.label": "Alibaba Cloud CDN region", + "workflow_node.deploy.form.aliyun_cdn_region.placeholder": "Please enter Alibaba Cloud CDN region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_cdn_region.tooltip": "
  • ap-southeast-1 for Alibaba Cloud International
  • cn-hangzhou for Alibaba Cloud in China
", "workflow_node.deploy.form.aliyun_cdn_domain.label": "Alibaba Cloud CDN domain", "workflow_node.deploy.form.aliyun_cdn_domain.placeholder": "Please enter Alibaba Cloud CDN domain name", "workflow_node.deploy.form.aliyun_cdn_domain.tooltip": "For more information, see https://cdn.console.aliyun.com", + "workflow_node.deploy.form.aliyun_dcdn_region.label": "Alibaba Cloud DCDN region", + "workflow_node.deploy.form.aliyun_dcdn_region.placeholder": "Please enter Alibaba Cloud DCDN region (e.g. cn-hangzhou)", + "workflow_node.deploy.form.aliyun_dcdn_region.tooltip": "
  • ap-southeast-1 for Alibaba Cloud International
  • cn-hangzhou for Alibaba Cloud in China
", "workflow_node.deploy.form.aliyun_dcdn_domain.label": "Alibaba Cloud DCDN domain", "workflow_node.deploy.form.aliyun_dcdn_domain.placeholder": "Please enter Alibaba Cloud DCDN domain name", "workflow_node.deploy.form.aliyun_dcdn_domain.tooltip": "For more information, see https://dcdn.console.aliyun.com", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index e214f6e9..c9a30717 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -255,9 +255,15 @@ "workflow_node.deploy.form.aliyun_clb_snidomain.placeholder": "请输入阿里云 CLB 扩展域名(支持泛域名)", "workflow_node.deploy.form.aliyun_clb_snidomain.help": "提示:不填写时,将替换监听器的默认证书;否则,将替换扩展域名证书。", "workflow_node.deploy.form.aliyun_clb_snidomain.tooltip": "这是什么?请参阅 https://slb.console.aliyun.com/clb", + "workflow_node.deploy.form.aliyun_cdn_region.label": "阿里云 CDN 服务地域", + "workflow_node.deploy.form.aliyun_cdn_region.placeholder": "请输入阿里云 CDN 服务地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_cdn_region.tooltip": "中国站请填写 cn-hangzhou
国际站请填写 ap-southeast-1。", "workflow_node.deploy.form.aliyun_cdn_domain.label": "阿里云 CDN 加速域名", "workflow_node.deploy.form.aliyun_cdn_domain.placeholder": "请输入阿里云 CDN 加速域名(支持泛域名)", "workflow_node.deploy.form.aliyun_cdn_domain.tooltip": "这是什么?请参阅 https://cdn.console.aliyun.com", + "workflow_node.deploy.form.aliyun_dcdn_region.label": "阿里云 DCDN 服务地域", + "workflow_node.deploy.form.aliyun_dcdn_region.placeholder": "请输入阿里云 DCDN 服务地域(例如:cn-hangzhou)", + "workflow_node.deploy.form.aliyun_dcdn_region.tooltip": "中国站请填写 cn-hangzhou
国际站请填写 ap-southeast-1。", "workflow_node.deploy.form.aliyun_dcdn_domain.label": "阿里云 DCDN 加速域名", "workflow_node.deploy.form.aliyun_dcdn_domain.placeholder": "请输入阿里云 DCDN 加速域名(支持泛域名)", "workflow_node.deploy.form.aliyun_dcdn_domain.tooltip": "这是什么?请参阅 https://dcdn.console.aliyun.com", From 145eadad2f88da66e882c3d6d791af305dbea813 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 5 Sep 2025 23:37:44 +0800 Subject: [PATCH 4/6] feat: webhook timeout --- internal/certdeploy/deployers/sp_webhook.go | 1 + internal/notify/notifiers/sp_webhook.go | 1 + pkg/core/notifier/providers/webhook/webhook.go | 6 ++++++ .../ssl-deployer/providers/webhook/webhook.go | 6 ++++++ ...BizDeployNodeConfigFieldsProviderWebhook.tsx | 17 ++++++++++++++++- ...BizNotifyNodeConfigFieldsProviderWebhook.tsx | 17 ++++++++++++++++- ui/src/i18n/locales/en/nls.access.json | 4 ++-- ui/src/i18n/locales/en/nls.workflow.nodes.json | 14 ++++++++++---- ui/src/i18n/locales/zh/nls.workflow.nodes.json | 6 ++++++ 9 files changed, 64 insertions(+), 8 deletions(-) diff --git a/internal/certdeploy/deployers/sp_webhook.go b/internal/certdeploy/deployers/sp_webhook.go index 271fc744..c53ff730 100644 --- a/internal/certdeploy/deployers/sp_webhook.go +++ b/internal/certdeploy/deployers/sp_webhook.go @@ -43,6 +43,7 @@ func init() { WebhookData: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", credentials.DataString), Method: credentials.Method, Headers: mergedHeaders, + Timeout: xmaps.GetInt32(options.ProviderExtendedConfig, "timeout"), AllowInsecureConnections: credentials.AllowInsecureConnections, }) return provider, err diff --git a/internal/notify/notifiers/sp_webhook.go b/internal/notify/notifiers/sp_webhook.go index db0c4412..7cacd0a6 100644 --- a/internal/notify/notifiers/sp_webhook.go +++ b/internal/notify/notifiers/sp_webhook.go @@ -43,6 +43,7 @@ func init() { WebhookData: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "webhookData", credentials.DataString), Method: credentials.Method, Headers: mergedHeaders, + Timeout: xmaps.GetInt32(options.ProviderExtendedConfig, "timeout"), AllowInsecureConnections: credentials.AllowInsecureConnections, }) return provider, err diff --git a/pkg/core/notifier/providers/webhook/webhook.go b/pkg/core/notifier/providers/webhook/webhook.go index 09ae91e3..3f57da7d 100644 --- a/pkg/core/notifier/providers/webhook/webhook.go +++ b/pkg/core/notifier/providers/webhook/webhook.go @@ -27,6 +27,9 @@ type NotifierProviderConfig struct { Method string `json:"method,omitempty"` // 请求标头。 Headers map[string]string `json:"headers,omitempty"` + // 请求超时(单位:秒)。 + // 零值时默认值 30。 + Timeout int32 `json:"timeout,omitempty"` // 是否允许不安全的连接。 AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } @@ -48,6 +51,9 @@ func NewNotifierProvider(config *NotifierProviderConfig) (*NotifierProvider, err SetTimeout(30 * time.Second). SetRetryCount(3). SetRetryWaitTime(5 * time.Second) + if config.Timeout > 0 { + client.SetTimeout(time.Duration(config.Timeout) * time.Second) + } if config.AllowInsecureConnections { client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) } diff --git a/pkg/core/ssl-deployer/providers/webhook/webhook.go b/pkg/core/ssl-deployer/providers/webhook/webhook.go index 2bf1aa14..9de2d8fc 100644 --- a/pkg/core/ssl-deployer/providers/webhook/webhook.go +++ b/pkg/core/ssl-deployer/providers/webhook/webhook.go @@ -28,6 +28,9 @@ type SSLDeployerProviderConfig struct { Method string `json:"method,omitempty"` // 请求标头。 Headers map[string]string `json:"headers,omitempty"` + // 请求超时(单位:秒)。 + // 零值时默认值 30。 + Timeout int32 `json:"timeout,omitempty"` // 是否允许不安全的连接。 AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } @@ -49,6 +52,9 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv SetTimeout(30 * time.Second). SetRetryCount(3). SetRetryWaitTime(5 * time.Second) + if config.Timeout > 0 { + client.SetTimeout(time.Duration(config.Timeout) * time.Second) + } if config.AllowInsecureConnections { client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) } diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWebhook.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWebhook.tsx index d6d111b9..7c288dca 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWebhook.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderWebhook.tsx @@ -1,5 +1,5 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form } from "antd"; +import { Form, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -51,6 +51,17 @@ const BizDeployNodeConfigFieldsProviderWebhook = () => { } /> + + + + ); }; @@ -76,6 +87,10 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return false; } }, t("workflow_node.deploy.form.webhook_data.errmsg.json_invalid")), + timeout: z.preprocess( + (v) => (v == null || v === "" ? void 0 : Number(v)), + z.number().int(t("workflow_node.deploy.form.webhook_timeout.placeholder")).gte(1, t("workflow_node.deploy.form.webhook_timeout.placeholder")).nullish() + ), }); }; diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFieldsProviderWebhook.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFieldsProviderWebhook.tsx index 247f568f..0f88205a 100644 --- a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFieldsProviderWebhook.tsx +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFieldsProviderWebhook.tsx @@ -1,5 +1,5 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form } from "antd"; +import { Form, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -51,6 +51,17 @@ const BizNotifyNodeConfigFieldsProviderWebhook = () => { } /> + + + + ); }; @@ -76,6 +87,10 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) return false; } }, t("workflow_node.notify.form.webhook_data.errmsg.json_invalid")), + timeout: z.preprocess( + (v) => (v == null || v === "" ? void 0 : Number(v)), + z.number().int(t("workflow_node.notify.form.webhook_timeout.placeholder")).gte(1, t("workflow_node.notify.form.webhook_timeout.placeholder")).nullish() + ), }); }; diff --git a/ui/src/i18n/locales/en/nls.access.json b/ui/src/i18n/locales/en/nls.access.json index 2603421f..bbf44181 100644 --- a/ui/src/i18n/locales/en/nls.access.json +++ b/ui/src/i18n/locales/en/nls.access.json @@ -65,7 +65,7 @@ "access.form.acmedns_credentials.label": "ACME-DNS credentials", "access.form.acmedns_credentials.placeholder": "Please enter ACME-DNS credentials", "access.form.acmedns_credentials.tooltip": "For more information, see https://github.com/joohoi/acme-dns", - "access.form.acmedns_credentials.errmsg.json_invalid": "Please enter a valiod JSON string", + "access.form.acmedns_credentials.errmsg.json_invalid": "Please enter a valid JSON string", "access.form.acmehttpreq_endpoint.label": "Endpoint", "access.form.acmehttpreq_endpoint.placeholder": "Please enter endpoint", "access.form.acmehttpreq_endpoint.tooltip": "For more information, see https://go-acme.github.io/lego/dns/httpreq/", @@ -518,7 +518,7 @@ "access.form.webhook_headers.placeholder": "Please enter Webhook request headers", "access.form.webhook_headers.errmsg.invalid": "Please enter a valid request headers", "access.form.webhook_headers.tooltip": "Example:
Content-Type: application/json
User-Agent: certimate
", - "access.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string", + "access.form.webhook_data.errmsg.json_invalid": "Please enter a valid JSON string", "access.form.webhook_data.label": "Webhook data (Optional)", "access.form.webhook_data.placeholder": "Please enter the default Webhook data", "access.form.webhook_data.help": "Notes: It can be overrided in the workflows.", diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index d62db857..958569f6 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -92,8 +92,8 @@ "workflow_node.apply.form.validity_lifetime.placeholder": "Please enter certificate's validity lifetime", "workflow_node.apply.form.validity_lifetime.help": "Notes: Not all CAs support this feature.", "workflow_node.apply.form.validity_lifetime.tooltip": "It determines the NotAfter field of the certificate in the ACME protocol. If you don't understand this option, just keep it by default.", - "workflow_node.apply.form.validity_lifetime.units.h": "Hour(s)", - "workflow_node.apply.form.validity_lifetime.units.d": "Day(s)", + "workflow_node.apply.form.validity_lifetime.units.h": "hours", + "workflow_node.apply.form.validity_lifetime.units.d": "days", "workflow_node.apply.form.acme_profile.label": "Certificate ACME profile (Optional)", "workflow_node.apply.form.acme_profile.placeholder": "Please enter certificate's ACME profile", "workflow_node.apply.form.acme_profile.help": "Notes: Not all CAs support this feature.", @@ -1002,7 +1002,10 @@ "workflow_node.deploy.form.webhook_data.placeholder": "Please enter Webhook data", "workflow_node.deploy.form.webhook_data.help": "Notes: Leave it blank to use the default Webhook data provided by the credential.", "workflow_node.deploy.form.webhook_data.guide": "
Supported variables:
  1. ${DOMAIN}: The primary domain of the certificate (CommonName).
  2. ${DOMAINS}: The domain list of the certificate (SubjectAltNames).
  3. ${CERTIFICATE}: The PEM format content of the certificate file.
  4. ${SERVER_CERTIFICATE}: The PEM format content of the server certificate file.
  5. ${INTERMEDIA_CERTIFICATE}: The PEM format content of the intermediate CA certificate file.
  6. ${PRIVATE_KEY}: The PEM format content of the private key file.

Please visit the credentials page for addtional notes.", - "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string", + "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "Please enter a valid JSON string", + "workflow_node.deploy.form.webhook_timeout.label": "Webhook timeout (Optional)", + "workflow_node.deploy.form.webhook_timeout.placeholder": "Please enter Webhook timeout", + "workflow_node.deploy.form.webhook_timeout.unit": "seconds", "workflow_node.deploy.form.skip_on_last_succeeded.label": "Repeated deployment", "workflow_node.deploy.form.skip_on_last_succeeded.prefix": "If the last deployment was successful, ", "workflow_node.deploy.form.skip_on_last_succeeded.suffix": " to re-deploy.", @@ -1047,7 +1050,10 @@ "workflow_node.notify.form.webhook_data.placeholder": "Please enter Webhook data", "workflow_node.notify.form.webhook_data.help": "Notes: Leave it blank to use the default Webhook data provided by the credential.", "workflow_node.notify.form.webhook_data.guide": "
Supported variables:
  1. ${SUBJECT}: The subject of notification.
  2. ${MESSAGE}: The message of notification.

Please visit the credentials page for addtional notes.", - "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "Please enter a valiod JSON string", + "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "Please enter a valid JSON string", + "workflow_node.notify.form.webhook_timeout.label": "Webhook timeout (Optional)", + "workflow_node.notify.form.webhook_timeout.placeholder": "Please enter Webhook timeout", + "workflow_node.notify.form.webhook_timeout.unit": "seconds", "workflow_node.notify.form.skip_on_all_prev_skipped.label": "Silent behavior", "workflow_node.notify.form.skip_on_all_prev_skipped.prefix": "If all the previous nodes were skipped, ", "workflow_node.notify.form.skip_on_all_prev_skipped.suffix": " to notify.", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index c9a30717..feb992b7 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -1001,6 +1001,9 @@ "workflow_node.deploy.form.webhook_data.help": "提示:不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", "workflow_node.deploy.form.webhook_data.guide": "
支持的变量:
  1. ${DOMAIN}:证书的主域名(即 CommonName)。
  2. ${DOMAINS}:证书的多域名列表(即 SubjectAltNames)。
  3. ${CERTIFICATE}:证书文件 PEM 格式内容。
  4. ${SERVER_CERTIFICATE}:证书文件(仅含服务器证书)PEM 格式内容。
  5. ${INTERMEDIA_CERTIFICATE}:证书文件(仅含中间证书)PEM 格式内容。
  6. ${PRIVATE_KEY}:私钥文件 PEM 格式内容。

其他注意事项请前往授权凭据页面查看。", "workflow_node.deploy.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", + "workflow_node.deploy.form.webhook_timeout.label": "Webhook 超时时间(可选)", + "workflow_node.deploy.form.webhook_timeout.placeholder": "请输入 Webhook 超时时间", + "workflow_node.deploy.form.webhook_timeout.unit": "秒", "workflow_node.deploy.form.skip_on_last_succeeded.label": "重复部署", "workflow_node.deploy.form.skip_on_last_succeeded.prefix": "当上次部署相同证书成功时,再次运行工作流时", "workflow_node.deploy.form.skip_on_last_succeeded.suffix": "此部署节点。", @@ -1046,6 +1049,9 @@ "workflow_node.notify.form.webhook_data.help": "提示:不填写时,将使用所选部署目标授权的默认 Webhook 回调数据。", "workflow_node.notify.form.webhook_data.guide": "
支持的变量:
  1. ${SUBJECT}:通知主题。
  2. ${MESSAGE}:通知内容。

其他注意事项请前往授权凭据页面查看。", "workflow_node.notify.form.webhook_data.errmsg.json_invalid": "请输入有效的 JSON 格式字符串", + "workflow_node.notify.form.webhook_timeout.label": "Webhook 超时时间(可选)", + "workflow_node.notify.form.webhook_timeout.placeholder": "请输入 Webhook 超时时间", + "workflow_node.notify.form.webhook_timeout.unit": "秒", "workflow_node.notify.form.skip_on_all_prev_skipped.label": "静默行为", "workflow_node.notify.form.skip_on_all_prev_skipped.prefix": "当前序申请、上传、部署等节点均已跳过执行时,", "workflow_node.notify.form.skip_on_all_prev_skipped.suffix": "此通知节点。", From ed03acd5a23b20f948d1795a77b507fab5c4aab7 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 6 Sep 2025 00:12:01 +0800 Subject: [PATCH 5/6] feat: custom k8s secret annotations and labels --- .../deployers/sp_kubernetes_secret.go | 47 +++++++++++ .../providers/k8s-secret/k8s_secret.go | 23 ++++++ ...deConfigFieldsProviderKubernetesSecret.tsx | 79 +++++++++++++++++++ .../i18n/locales/en/nls.workflow.nodes.json | 10 +++ .../i18n/locales/zh/nls.workflow.nodes.json | 10 +++ 5 files changed, 169 insertions(+) diff --git a/internal/certdeploy/deployers/sp_kubernetes_secret.go b/internal/certdeploy/deployers/sp_kubernetes_secret.go index 85d6fc05..8fe56574 100644 --- a/internal/certdeploy/deployers/sp_kubernetes_secret.go +++ b/internal/certdeploy/deployers/sp_kubernetes_secret.go @@ -2,6 +2,7 @@ package deployers import ( "fmt" + "strings" "github.com/certimate-go/certimate/internal/domain" "github.com/certimate-go/certimate/pkg/core" @@ -16,6 +17,50 @@ func init() { return nil, fmt.Errorf("failed to populate provider access config: %w", err) } + parseKeyValueMap := func(s string) (map[string]string, error) { + result := make(map[string]string) + + lines := strings.Split(s, "\n") + for i, line := range lines { + if strings.TrimSpace(line) == "" { + continue + } + + pos := strings.Index(line, ":") + if pos == -1 { + return nil, fmt.Errorf("invalid line format at line %d", i+1) + } + + key := strings.TrimSpace(line[:pos]) + value := strings.TrimSpace(line[pos+1:]) + if key == "" { + return nil, fmt.Errorf("invalid key at line %d", i+1) + } + + result[key] = value + } + + return result, nil + } + + secretAnnotations := make(map[string]string) + if secretAnnotationsString := xmaps.GetString(options.ProviderExtendedConfig, "secretAnnotations"); secretAnnotationsString != "" { + temp, err := parseKeyValueMap(secretAnnotationsString) + if err != nil { + return nil, fmt.Errorf("failed to parse kubernetes secret annotations: %w", err) + } + secretAnnotations = temp + } + + secretLabels := make(map[string]string) + if secretLabelsString := xmaps.GetString(options.ProviderExtendedConfig, "secretLabels"); secretLabelsString != "" { + temp, err := parseKeyValueMap(secretLabelsString) + if err != nil { + return nil, fmt.Errorf("failed to parse kubernetes secret labels: %w", err) + } + secretLabels = temp + } + provider, err := k8ssecret.NewSSLDeployerProvider(&k8ssecret.SSLDeployerProviderConfig{ KubeConfig: credentials.KubeConfig, Namespace: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "namespace", "default"), @@ -23,6 +68,8 @@ func init() { SecretType: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretType", "kubernetes.io/tls"), SecretDataKeyForCrt: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForCrt", "tls.crt"), SecretDataKeyForKey: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "secretDataKeyForKey", "tls.key"), + SecretAnnotations: secretAnnotations, + SecretLabels: secretLabels, }) return provider, err }); err != nil { diff --git a/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go b/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go index e9a16683..b90db949 100644 --- a/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go +++ b/pkg/core/ssl-deployer/providers/k8s-secret/k8s_secret.go @@ -30,6 +30,10 @@ type SSLDeployerProviderConfig struct { SecretDataKeyForCrt string `json:"secretDataKeyForCrt,omitempty"` // Kubernetes Secret 中用于存放私钥的 Key。 SecretDataKeyForKey string `json:"secretDataKeyForKey,omitempty"` + // Kubernetes Secret 注解。 + SecretAnnotations map[string]string `json:"secretAnnotations,omitempty"` + // Kubernetes Secret 标签。 + SecretLabels map[string]string `json:"secretLabels,omitempty"` } type SSLDeployerProvider struct { @@ -94,6 +98,17 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke "certimate/issuer-sn": certX509.Issuer.SerialNumber, "certimate/issuer-org": strings.Join(certX509.Issuer.Organization, ","), } + secretLabels := map[string]string{} + if d.config.SecretAnnotations != nil { + for k, v := range d.config.SecretAnnotations { + secretAnnotations[k] = v + } + } + if d.config.SecretLabels != nil { + for k, v := range d.config.SecretLabels { + secretLabels[k] = v + } + } // 获取 Secret 实例,如果不存在则创建 secretPayload, err = client.CoreV1().Secrets(d.config.Namespace).Get(context.TODO(), d.config.SecretName, k8smeta.GetOptions{}) @@ -106,6 +121,7 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke ObjectMeta: k8smeta.ObjectMeta{ Name: d.config.SecretName, Annotations: secretAnnotations, + Labels: secretLabels, }, Type: k8score.SecretType(d.config.SecretType), } @@ -131,6 +147,13 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke secretPayload.ObjectMeta.Annotations[k] = v } } + if secretPayload.ObjectMeta.Labels == nil { + secretPayload.ObjectMeta.Labels = secretLabels + } else { + for k, v := range secretLabels { + secretPayload.ObjectMeta.Labels[k] = v + } + } if secretPayload.Data == nil { secretPayload.Data = make(map[string][]byte) } diff --git a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKubernetesSecret.tsx b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKubernetesSecret.tsx index 6730b4d8..c6859b1a 100644 --- a/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKubernetesSecret.tsx +++ b/ui/src/components/workflow/designer/forms/BizDeployNodeConfigFieldsProviderKubernetesSecret.tsx @@ -3,6 +3,8 @@ import { Form, Input } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; +import CodeInput from "@/components/CodeInput"; + import { useFormNestedFieldsContext } from "./_context"; const BizDeployNodeConfigFieldsProviderKubernetesSecret = () => { @@ -13,8 +15,23 @@ const BizDeployNodeConfigFieldsProviderKubernetesSecret = () => { [parentNamePath]: getSchema({ i18n }), }); const formRule = createSchemaFieldRule(formSchema); + const formInst = Form.useFormInstance(); const initialValues = getInitialValues(); + const handleSecretAnnotationsBlur = () => { + let value = formInst.getFieldValue([parentNamePath, "secretAnnotations"]); + value = value.trim(); + value = value.replace(/(? { + let value = formInst.getFieldValue([parentNamePath, "secretLabels"]); + value = value.trim(); + value = value.replace(/(? { > + + } + > + + + + } + > + + ); }; @@ -88,6 +139,34 @@ const getSchema = ({ i18n = getI18n() }: { i18n?: ReturnType }) secretType: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_type.placeholder")), secretDataKeyForCrt: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_data_key_for_crt.placeholder")), secretDataKeyForKey: z.string().nonempty(t("workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder")), + secretAnnotations: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + const lines = v.split(/\r?\n/); + for (const line of lines) { + if (line.split(":").length < 2) { + return false; + } + } + return true; + }, t("workflow_node.deploy.form.k8s_secret_annotations.errmsg.invalid")), + secretLabels: z + .string() + .nullish() + .refine((v) => { + if (!v) return true; + + const lines = v.split(/\r?\n/); + for (const line of lines) { + if (line.split(":").length < 2) { + return false; + } + } + return true; + }, t("workflow_node.deploy.form.k8s_secret_labels.errmsg.invalid")), }); }; diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 958569f6..07c523da 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -594,6 +594,16 @@ "workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret data key for private key", "workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "Please enter Kubernetes Secret data key for private key", "workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "For more information, see https://kubernetes.io/docs/concepts/configuration/secret/", + "workflow_node.deploy.form.k8s_secret_annotations.label": "Kubernetes Secret annotations (Optional)", + "workflow_node.deploy.form.k8s_secret_annotations.placeholder": "Please enter Kubernetes Secret annotations", + "workflow_node.deploy.form.k8s_secret_annotations.help": "Notes: One key value pair per line, separated by colon.", + "workflow_node.deploy.form.k8s_secret_annotations.errmsg.invalid": "Please enter a valid annotations", + "workflow_node.deploy.form.k8s_secret_annotations.tooltip": "Example:
environment: production
app: nginx
", + "workflow_node.deploy.form.k8s_secret_labels.label": "Kubernetes Secret labels (Optional)", + "workflow_node.deploy.form.k8s_secret_labels.placeholder": "Please enter Kubernetes Secret labels", + "workflow_node.deploy.form.k8s_secret_labels.help": "Notes: One key value pair per line, separated by colon.", + "workflow_node.deploy.form.k8s_secret_labels.errmsg.invalid": "Please enter a valid labels", + "workflow_node.deploy.form.k8s_secret_labels.tooltip": "Example:
environment: production
app: nginx
", "workflow_node.deploy.form.kong_resource_type.label": "Resource type", "workflow_node.deploy.form.kong_resource_type.placeholder": "Please select resource type", "workflow_node.deploy.form.kong_resource_type.option.certificate.label": "SSL certificate", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index feb992b7..19bdbcaf 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -592,6 +592,16 @@ "workflow_node.deploy.form.k8s_secret_data_key_for_key.label": "Kubernetes Secret 数据键(用于存放私钥的字段)", "workflow_node.deploy.form.k8s_secret_data_key_for_key.placeholder": "请输入 Kubernetes Secret 中用于存放私钥的数据键", "workflow_node.deploy.form.k8s_secret_data_key_for_key.tooltip": "这是什么?请参阅 https://kubernetes.io/zh-cn/docs/concepts/configuration/secret/", + "workflow_node.deploy.form.k8s_secret_annotations.label": "Kubernetes Secret 注解(可选)", + "workflow_node.deploy.form.k8s_secret_annotations.placeholder": "请输入 Kubernetes Secret 注解", + "workflow_node.deploy.form.k8s_secret_annotations.help": "提示:每行一个键值对,以分号分隔。", + "workflow_node.deploy.form.k8s_secret_annotations.errmsg.invalid": "请输入有效的注解键值对", + "workflow_node.deploy.form.k8s_secret_annotations.tooltip": "示例:
environment: production
app: nginx
", + "workflow_node.deploy.form.k8s_secret_labels.label": "Kubernetes Secret 标签(可选)", + "workflow_node.deploy.form.k8s_secret_labels.placeholder": "请输入 Kubernetes Secret 标签", + "workflow_node.deploy.form.k8s_secret_labels.help": "提示:每行一个键值对,以分号分隔。", + "workflow_node.deploy.form.k8s_secret_labels.errmsg.invalid": "请输入有效的标签键值对", + "workflow_node.deploy.form.k8s_secret_labels.tooltip": "示例:
environment: production
app: nginx
", "workflow_node.deploy.form.kong_resource_type.label": "证书部署方式", "workflow_node.deploy.form.kong_resource_type.placeholder": "请选择证书部署方式", "workflow_node.deploy.form.kong_resource_type.option.certificate.label": "替换指定证书", From 9dcaf05b88da1936690132b6818ada8ed57b1914 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 6 Sep 2025 00:25:33 +0800 Subject: [PATCH 6/6] fix: #951 --- .../ssl-deployer/providers/baotawaf-site/baotawaf_site.go | 4 ++-- pkg/sdk3rd/btwaf/api_modify_site.go | 6 +++--- pkg/sdk3rd/btwaf/types.go | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go b/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go index 6659c62d..d1408d9e 100644 --- a/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go +++ b/pkg/core/ssl-deployer/providers/baotawaf-site/baotawaf_site.go @@ -114,8 +114,8 @@ func (d *SSLDeployerProvider) Deploy(ctx context.Context, certPEM string, privke modifySiteReq := &btwafsdk.ModifySiteRequest{ SiteId: lo.ToPtr(siteId), Type: lo.ToPtr("openCert"), - Server: &btwafsdk.SiteServerInfo{ - ListenSSLPorts: lo.ToPtr([]int32{d.config.SitePort}), + Server: &btwafsdk.SiteServerInfoMod{ + ListenSSLPorts: lo.ToPtr([]string{fmt.Sprintf("%d", d.config.SitePort)}), SSL: &btwafsdk.SiteServerSSLInfo{ IsSSL: lo.ToPtr(int32(1)), FullChain: lo.ToPtr(certPEM), diff --git a/pkg/sdk3rd/btwaf/api_modify_site.go b/pkg/sdk3rd/btwaf/api_modify_site.go index 516277ec..a93f8ba8 100644 --- a/pkg/sdk3rd/btwaf/api_modify_site.go +++ b/pkg/sdk3rd/btwaf/api_modify_site.go @@ -6,9 +6,9 @@ import ( ) type ModifySiteRequest struct { - SiteId *string `json:"site_id,omitempty"` - Type *string `json:"types,omitempty"` - Server *SiteServerInfo `json:"server,omitempty"` + SiteId *string `json:"site_id,omitempty"` + Type *string `json:"types,omitempty"` + Server *SiteServerInfoMod `json:"server,omitempty"` } type ModifySiteResponse struct { diff --git a/pkg/sdk3rd/btwaf/types.go b/pkg/sdk3rd/btwaf/types.go index 8c75a44b..b1c26de3 100644 --- a/pkg/sdk3rd/btwaf/types.go +++ b/pkg/sdk3rd/btwaf/types.go @@ -32,6 +32,11 @@ type SiteServerInfo struct { SSL *SiteServerSSLInfo `json:"ssl,omitempty"` } +type SiteServerInfoMod struct { + ListenSSLPorts *[]string `json:"listen_ssl_port,omitempty"` + SSL *SiteServerSSLInfo `json:"ssl,omitempty"` +} + type SiteServerSSLInfo struct { IsSSL *int32 `json:"is_ssl,omitempty"` FullChain *string `json:"full_chain,omitempty"`