diff --git a/go.mod b/go.mod index 712a9904..f17c5aff 100644 --- a/go.mod +++ b/go.mod @@ -64,6 +64,7 @@ require ( github.com/volcengine/ve-tos-golang-sdk/v2 v2.7.17 github.com/volcengine/volc-sdk-golang v1.0.212 github.com/volcengine/volcengine-go-sdk v1.1.18 + github.com/xhit/go-str2duration/v2 v2.1.0 gitlab.ecloud.com/ecloud/ecloudsdkclouddns v1.0.1 gitlab.ecloud.com/ecloud/ecloudsdkcore v1.0.0 golang.org/x/crypto v0.39.0 diff --git a/go.sum b/go.sum index 149f9151..3dc87db1 100644 --- a/go.sum +++ b/go.sum @@ -901,6 +901,8 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/applicant/applicant.go b/internal/applicant/applicant.go index c6d5608d..6be0ca77 100644 --- a/internal/applicant/applicant.go +++ b/internal/applicant/applicant.go @@ -16,6 +16,7 @@ import ( "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/lego" + "github.com/xhit/go-str2duration/v2" "golang.org/x/exp/slices" "golang.org/x/time/rate" @@ -64,6 +65,7 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err CAProviderAccessConfig: make(map[string]any), CAProviderServiceConfig: nodeCfg.CAProviderConfig, KeyAlgorithm: nodeCfg.KeyAlgorithm, + LifeTime: nodeCfg.LifeTime, ACMEProfile: nodeCfg.ACMEProfile, Nameservers: xslices.Filter(strings.Split(nodeCfg.Nameservers, ";"), func(s string) bool { return s != "" }), DnsPropagationWait: nodeCfg.DnsPropagationWait, @@ -238,6 +240,13 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt Bundle: true, Profile: options.ACMEProfile, } + if options.LifeTime != "" { + lifeTime, err := str2duration.ParseDuration(options.LifeTime) + if err != nil { + return nil, fmt.Errorf("invalid lifetime: %w", err) + } + certRequest.NotAfter = time.Now().Add(lifeTime) + } if options.ARIReplaceAcct == user.Registration.URI { certRequest.ReplacesCertID = options.ARIReplaceCert } diff --git a/internal/applicant/providers.go b/internal/applicant/providers.go index 7b07997f..b1f147c1 100644 --- a/internal/applicant/providers.go +++ b/internal/applicant/providers.go @@ -63,6 +63,7 @@ type applicantProviderOptions struct { DnsPropagationWait int32 DnsPropagationTimeout int32 DnsTTL int32 + LifeTime string ACMEProfile string DisableFollowCNAME bool ARIReplaceAcct string diff --git a/internal/domain/workflow.go b/internal/domain/workflow.go index 16895229..c51f513a 100644 --- a/internal/domain/workflow.go +++ b/internal/domain/workflow.go @@ -75,6 +75,7 @@ type WorkflowNodeConfigForApply struct { CAProviderAccessId string `json:"caProviderAccessId,omitempty"` // CA 提供商授权记录 ID CAProviderConfig map[string]any `json:"caProviderConfig,omitempty"` // CA 提供商额外配置 KeyAlgorithm string `json:"keyAlgorithm,omitempty"` // 证书算法 + LifeTime string `json:"lifeTime,omitempty"` // 证书有效期 ACMEProfile string `json:"acmeProfile,omitempty"` // ACME Profiles Extension Nameservers string `json:"nameservers,omitempty"` // DNS 服务器列表,以半角分号分隔 DnsPropagationWait int32 `json:"dnsPropagationWait,omitempty"` // DNS 传播等待时间,等同于 lego 的 `--dns-propagation-wait` 参数 @@ -132,6 +133,7 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply { CAProviderAccessId: xmaps.GetString(n.Config, "caProviderAccessId"), CAProviderConfig: xmaps.GetKVMapAny(n.Config, "caProviderConfig"), KeyAlgorithm: xmaps.GetOrDefaultString(n.Config, "keyAlgorithm", string(CertificateKeyAlgorithmTypeRSA2048)), + LifeTime: xmaps.GetString(n.Config, "lifeTime"), ACMEProfile: xmaps.GetString(n.Config, "acmeProfile"), Nameservers: xmaps.GetString(n.Config, "nameservers"), DnsPropagationWait: xmaps.GetInt32(n.Config, "dnsPropagationWait"), diff --git a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx index cdcb689c..43df598d 100644 --- a/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/node/ApplyNodeConfigForm.tsx @@ -113,6 +113,10 @@ const ApplyNodeConfigForm = forwardRef (v == null || v === "" ? undefined : Number(v)), z.number().int(t("workflow_node.apply.form.dns_ttl.placeholder")).gte(1, t("workflow_node.apply.form.dns_ttl.placeholder")).nullish() ), + lifeTime: z.string().nullish().refine((v) => { + if (!v) return true; + return /^[+-]?(\d+[ns|us|ms|s|m|h|d|w])+$/.test(v); + }, t("workflow_node.apply.form.life_time.placeholder")), acmeProfile: z.string().nullish(), disableFollowCNAME: z.boolean().nullish(), disableARI: z.boolean().nullish(), @@ -456,6 +460,19 @@ const ApplyNodeConfigForm = forwardRef + } + > + + + ; keyAlgorithm: string; + lifeTime?: string; acmeProfile?: string; nameservers?: string; dnsPropagationTimeout?: number; diff --git a/ui/src/i18n/locales/en/nls.workflow.nodes.json b/ui/src/i18n/locales/en/nls.workflow.nodes.json index 12254221..7c6b10f5 100644 --- a/ui/src/i18n/locales/en/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/en/nls.workflow.nodes.json @@ -71,6 +71,9 @@ "workflow_node.apply.form.ca_provider_access.button": "Create", "workflow_node.apply.form.key_algorithm.label": "Certificate key algorithm", "workflow_node.apply.form.key_algorithm.placeholder": "Please select certificate key algorithm", + "workflow_node.apply.form.life_time.label": "Certificate lifetime (Optional)", + "workflow_node.apply.form.life_time.placeholder": "Please enter certificate lifetime, unit support: ns, us, ms, s, m, h, d, w. e.g. 30d", + "workflow_node.apply.form.life_time.tooltip": "It determines the lifetime of the certificate. If you don't understand this option, just keep it by default.", "workflow_node.apply.form.acme_profile.label": "ACME certificate profile (Optional)", "workflow_node.apply.form.acme_profile.placeholder": "Please enter ACME certificate profile", "workflow_node.apply.form.acme_profile.tooltip": "It determines the ACME profile which will be used to affect issuance of the certificate requested. If you don't understand this option, just keep it by default.Learn more.", diff --git a/ui/src/i18n/locales/zh/nls.workflow.nodes.json b/ui/src/i18n/locales/zh/nls.workflow.nodes.json index a3f4278e..ae112573 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.nodes.json +++ b/ui/src/i18n/locales/zh/nls.workflow.nodes.json @@ -70,6 +70,9 @@ "workflow_node.apply.form.ca_provider_access.button": "新建", "workflow_node.apply.form.key_algorithm.label": "证书算法", "workflow_node.apply.form.key_algorithm.placeholder": "请选择证书算法", + "workflow_node.apply.form.life_time.label": "证书有效期(可选)", + "workflow_node.apply.form.life_time.placeholder": "请输入证书有效期,单位支持:ns、us、ms、s、m、h、d、w,例如:30d", + "workflow_node.apply.form.life_time.tooltip": "表示证书的有效期。如果你不了解该选项的用途,保持默认即可。", "workflow_node.apply.form.acme_profile.label": "ACME 证书配置(可选)", "workflow_node.apply.form.acme_profile.placeholder": "请输入 ACME 证书配置", "workflow_node.apply.form.acme_profile.tooltip": "表示证书颁发时使用的 ACME 证书配置。如果你不了解该选项的用途,保持默认即可。点此了解更多。",