fix: ari double renewal error

This commit is contained in:
Fu Diwei 2025-05-26 11:29:04 +08:00
parent 970a1f0f79
commit 46b4ff73c9
8 changed files with 86 additions and 11 deletions

View File

@ -26,13 +26,14 @@ import (
) )
type ApplyResult struct { type ApplyResult struct {
CSR string
FullChainCertificate string FullChainCertificate string
IssuerCertificate string IssuerCertificate string
PrivateKey string PrivateKey string
ACMEAccountUrl string ACMEAccountUrl string
ACMECertUrl string ACMECertUrl string
ACMECertStableUrl string ACMECertStableUrl string
CSR string ARIReplaced bool
} }
type Applicant interface { type Applicant interface {
@ -109,7 +110,7 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err
certRepo := repository.NewCertificateRepository() certRepo := repository.NewCertificateRepository()
lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), config.Node.Id) lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), config.Node.Id)
if lastCertificate != nil { if lastCertificate != nil && !lastCertificate.ACMERenewed {
newCertSan := slices.Clone(options.Domains) newCertSan := slices.Clone(options.Domains)
oldCertSan := strings.Split(lastCertificate.SubjectAltNames, ";") oldCertSan := strings.Split(lastCertificate.SubjectAltNames, ";")
slices.Sort(newCertSan) slices.Sort(newCertSan)
@ -119,8 +120,8 @@ func NewWithWorkflowNode(config ApplicantWithWorkflowNodeConfig) (Applicant, err
lastCertX509, _ := certcrypto.ParsePEMCertificate([]byte(lastCertificate.Certificate)) lastCertX509, _ := certcrypto.ParsePEMCertificate([]byte(lastCertificate.Certificate))
if lastCertX509 != nil { if lastCertX509 != nil {
replacedARICertId, _ := certificate.MakeARICertID(lastCertX509) replacedARICertId, _ := certificate.MakeARICertID(lastCertX509)
options.ReplacedARIAcct = lastCertificate.ACMEAccountUrl options.ARIReplaceAcct = lastCertificate.ACMEAccountUrl
options.ReplacedARICert = replacedARICertId options.ARIReplaceCert = replacedARICertId
} }
} }
} }
@ -235,22 +236,24 @@ func applyUseLego(legoProvider challenge.Provider, options *applicantProviderOpt
Domains: options.Domains, Domains: options.Domains,
Bundle: true, Bundle: true,
} }
if options.ReplacedARIAcct == user.Registration.URI { if options.ARIReplaceAcct == user.Registration.URI {
certRequest.ReplacesCertID = options.ReplacedARICert certRequest.ReplacesCertID = options.ARIReplaceCert
} }
certResource, err := client.Certificate.Obtain(certRequest) certResource, err := client.Certificate.Obtain(certRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &ApplyResult{ return &ApplyResult{
CSR: strings.TrimSpace(string(certResource.CSR)),
FullChainCertificate: strings.TrimSpace(string(certResource.Certificate)), FullChainCertificate: strings.TrimSpace(string(certResource.Certificate)),
IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)), IssuerCertificate: strings.TrimSpace(string(certResource.IssuerCertificate)),
PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)), PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)),
ACMEAccountUrl: user.Registration.URI, ACMEAccountUrl: user.Registration.URI,
ACMECertUrl: certResource.CertURL, ACMECertUrl: certResource.CertURL,
ACMECertStableUrl: certResource.CertStableURL, ACMECertStableUrl: certResource.CertStableURL,
CSR: strings.TrimSpace(string(certResource.CSR)), ARIReplaced: certRequest.ReplacesCertID != "",
}, nil }, nil
} }

View File

@ -57,8 +57,8 @@ type applicantProviderOptions struct {
DnsPropagationTimeout int32 DnsPropagationTimeout int32
DnsTTL int32 DnsTTL int32
DisableFollowCNAME bool DisableFollowCNAME bool
ReplacedARIAcct string ARIReplaceAcct string
ReplacedARICert string ARIReplaceCert string
} }
func createApplicantProvider(options *applicantProviderOptions) (challenge.Provider, error) { func createApplicantProvider(options *applicantProviderOptions) (challenge.Provider, error) {

View File

@ -28,6 +28,7 @@ type Certificate struct {
ACMEAccountUrl string `json:"acmeAccountUrl" db:"acmeAccountUrl"` ACMEAccountUrl string `json:"acmeAccountUrl" db:"acmeAccountUrl"`
ACMECertUrl string `json:"acmeCertUrl" db:"acmeCertUrl"` ACMECertUrl string `json:"acmeCertUrl" db:"acmeCertUrl"`
ACMECertStableUrl string `json:"acmeCertStableUrl" db:"acmeCertStableUrl"` ACMECertStableUrl string `json:"acmeCertStableUrl" db:"acmeCertStableUrl"`
ACMERenewed bool `json:"acmeRenewed" db:"acmeRenewed"`
WorkflowId string `json:"workflowId" db:"workflowId"` WorkflowId string `json:"workflowId" db:"workflowId"`
WorkflowNodeId string `json:"workflowNodeId" db:"workflowNodeId"` WorkflowNodeId string `json:"workflowNodeId" db:"workflowNodeId"`
WorkflowRunId string `json:"workflowRunId" db:"workflowRunId"` WorkflowRunId string `json:"workflowRunId" db:"workflowRunId"`

View File

@ -77,6 +77,25 @@ func (r *CertificateRepository) GetByWorkflowNodeId(ctx context.Context, workflo
return r.castRecordToModel(records[0]) return r.castRecordToModel(records[0])
} }
func (r *CertificateRepository) GetByWorkflowRunId(ctx context.Context, workflowRunId string) (*domain.Certificate, error) {
records, err := app.GetApp().FindRecordsByFilter(
domain.CollectionNameCertificate,
"workflowRunId={:workflowRunId} && deleted=null",
"-created",
1, 0,
dbx.Params{"workflowRunId": workflowRunId},
)
if err != nil {
return nil, err
}
if len(records) == 0 {
return nil, domain.ErrRecordNotFound
}
return r.castRecordToModel(records[0])
}
func (r *CertificateRepository) Save(ctx context.Context, certificate *domain.Certificate) (*domain.Certificate, error) { func (r *CertificateRepository) Save(ctx context.Context, certificate *domain.Certificate) (*domain.Certificate, error) {
collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameCertificate) collection, err := app.GetApp().FindCollectionByNameOrId(domain.CollectionNameCertificate)
if err != nil { if err != nil {
@ -109,6 +128,7 @@ func (r *CertificateRepository) Save(ctx context.Context, certificate *domain.Ce
record.Set("acmeAccountUrl", certificate.ACMEAccountUrl) record.Set("acmeAccountUrl", certificate.ACMEAccountUrl)
record.Set("acmeCertUrl", certificate.ACMECertUrl) record.Set("acmeCertUrl", certificate.ACMECertUrl)
record.Set("acmeCertStableUrl", certificate.ACMECertStableUrl) record.Set("acmeCertStableUrl", certificate.ACMECertStableUrl)
record.Set("acmeRenewed", certificate.ACMERenewed)
record.Set("workflowId", certificate.WorkflowId) record.Set("workflowId", certificate.WorkflowId)
record.Set("workflowRunId", certificate.WorkflowRunId) record.Set("workflowRunId", certificate.WorkflowRunId)
record.Set("workflowNodeId", certificate.WorkflowNodeId) record.Set("workflowNodeId", certificate.WorkflowNodeId)
@ -170,6 +190,7 @@ func (r *CertificateRepository) castRecordToModel(record *core.Record) (*domain.
ACMEAccountUrl: record.GetString("acmeAccountUrl"), ACMEAccountUrl: record.GetString("acmeAccountUrl"),
ACMECertUrl: record.GetString("acmeCertUrl"), ACMECertUrl: record.GetString("acmeCertUrl"),
ACMECertStableUrl: record.GetString("acmeCertStableUrl"), ACMECertStableUrl: record.GetString("acmeCertStableUrl"),
ACMERenewed: record.GetBool("acmeRenewed"),
WorkflowId: record.GetString("workflowId"), WorkflowId: record.GetString("workflowId"),
WorkflowRunId: record.GetString("workflowRunId"), WorkflowRunId: record.GetString("workflowRunId"),
WorkflowNodeId: record.GetString("workflowNodeId"), WorkflowNodeId: record.GetString("workflowNodeId"),

View File

@ -96,6 +96,15 @@ func (n *applyNode) Process(ctx context.Context) error {
return err return err
} }
// 保存 ARI 记录
if applyResult.ARIReplaced {
lastCertificate, _ := n.certRepo.GetByWorkflowRunId(ctx, lastOutput.RunId)
if lastCertificate != nil {
lastCertificate.ACMERenewed = true
n.certRepo.Save(ctx, lastCertificate)
}
}
n.logger.Info("apply completed") n.logger.Info("apply completed")
return nil return nil
@ -134,7 +143,7 @@ func (n *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workflo
return false, "the configuration item 'KeyAlgorithm' changed" return false, "the configuration item 'KeyAlgorithm' changed"
} }
lastCertificate, _ := n.certRepo.GetByWorkflowNodeId(ctx, n.node.Id) lastCertificate, _ := n.certRepo.GetByWorkflowRunId(ctx, lastOutput.RunId)
if lastCertificate != nil { if lastCertificate != nil {
renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24 renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24
expirationTime := time.Until(lastCertificate.ExpireAt) expirationTime := time.Until(lastCertificate.ExpireAt)

View File

@ -34,6 +34,8 @@ func (n *nodeProcessor) SetLogger(logger *slog.Logger) {
type certificateRepository interface { type certificateRepository interface {
GetByWorkflowNodeId(ctx context.Context, workflowNodeId string) (*domain.Certificate, error) GetByWorkflowNodeId(ctx context.Context, workflowNodeId string) (*domain.Certificate, error)
GetByWorkflowRunId(ctx context.Context, workflowRunId string) (*domain.Certificate, error)
Save(ctx context.Context, certificate *domain.Certificate) (*domain.Certificate, error)
} }
type workflowOutputRepository interface { type workflowOutputRepository interface {

View File

@ -83,7 +83,7 @@ func (n *uploadNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workfl
return false, "the configuration item 'PrivateKey' changed" return false, "the configuration item 'PrivateKey' changed"
} }
lastCertificate, _ := n.certRepo.GetByWorkflowNodeId(ctx, n.node.Id) lastCertificate, _ := n.certRepo.GetByWorkflowRunId(ctx, lastOutput.RunId)
if lastCertificate != nil { if lastCertificate != nil {
return true, "the certificate has already been uploaded" return true, "the certificate has already been uploaded"
} }

View File

@ -0,0 +1,39 @@
package migrations
import (
"github.com/pocketbase/pocketbase/core"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(app core.App) error {
// update collection `certificate`
{
collection, err := app.FindCollectionByNameOrId("4szxr9x43tpj6np")
if err != nil {
return err
}
// add field
if err := collection.Fields.AddMarshaledJSONAt(14, []byte(`{
"hidden": false,
"id": "bool810050391",
"name": "acmeRenewed",
"presentable": false,
"required": false,
"system": false,
"type": "bool"
}`)); err != nil {
return err
}
if err := app.Save(collection); err != nil {
return err
}
}
return nil
}, func(app core.App) error {
return nil
})
}