feat: support custom certificate lifetime

This commit is contained in:
imlonghao 2025-07-05 14:07:24 +08:00
parent eee8a38a71
commit f79b028d0b
No known key found for this signature in database
GPG Key ID: BB80A757B3E37324
9 changed files with 39 additions and 0 deletions

1
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -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
}

View File

@ -63,6 +63,7 @@ type applicantProviderOptions struct {
DnsPropagationWait int32
DnsPropagationTimeout int32
DnsTTL int32
LifeTime string
ACMEProfile string
DisableFollowCNAME bool
ARIReplaceAcct string

View File

@ -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"),

View File

@ -113,6 +113,10 @@ const ApplyNodeConfigForm = forwardRef<ApplyNodeConfigFormInstance, ApplyNodeCon
(v) => (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<ApplyNodeConfigFormInstance, ApplyNodeCon
/>
</Form.Item>
<Form.Item
name="lifeTime"
label={t("workflow_node.apply.form.life_time.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.apply.form.life_time.tooltip") }}></span>}
>
<Input
type="string"
allowClear
placeholder={t("workflow_node.apply.form.life_time.placeholder")}
/>
</Form.Item>
<Form.Item
name="acmeProfile"
label={t("workflow_node.apply.form.acme_profile.label")}

View File

@ -151,6 +151,7 @@ export type WorkflowNodeConfigForApply = {
caProviderAccessId?: string;
caProviderConfig?: Record<string, unknown>;
keyAlgorithm: string;
lifeTime?: string;
acmeProfile?: string;
nameservers?: string;
dnsPropagationTimeout?: number;

View File

@ -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.<a href=\"https://letsencrypt.org/docs/profiles/\" target=\"_blank\">Learn more</a>.",

View File

@ -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 证书配置。如果你不了解该选项的用途,保持默认即可。<a href=\"https://letsencrypt.org/zh-cn/docs/profiles/\" target=\"_blank\">点此了解更多</a>。",