mirror of
https://github.com/usual2970/certimate.git
synced 2025-10-04 13:34:52 +00:00
feat: support ARI
This commit is contained in:
@@ -14,10 +14,11 @@ import (
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
"github.com/usual2970/certimate/internal/domain"
|
||||
"github.com/usual2970/certimate/internal/pkg/utils/slices"
|
||||
uslices "github.com/usual2970/certimate/internal/pkg/utils/slices"
|
||||
"github.com/usual2970/certimate/internal/repository"
|
||||
)
|
||||
|
||||
@@ -45,7 +46,9 @@ type applicantOptions struct {
|
||||
DnsPropagationTimeout int32
|
||||
DnsTTL int32
|
||||
DisableFollowCNAME bool
|
||||
DisableARI bool
|
||||
SkipBeforeExpiryDays int32
|
||||
ReplacedARICertId string
|
||||
}
|
||||
|
||||
func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
||||
@@ -54,32 +57,49 @@ func NewWithApplyNode(node *domain.WorkflowNode) (Applicant, error) {
|
||||
}
|
||||
|
||||
nodeConfig := node.GetConfigForApply()
|
||||
|
||||
accessRepo := repository.NewAccessRepository()
|
||||
access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
|
||||
}
|
||||
|
||||
accessConfig, err := access.UnmarshalConfigToMap()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||
}
|
||||
|
||||
options := &applicantOptions{
|
||||
Domains: slices.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
|
||||
Domains: uslices.Filter(strings.Split(nodeConfig.Domains, ";"), func(s string) bool { return s != "" }),
|
||||
ContactEmail: nodeConfig.ContactEmail,
|
||||
Provider: domain.ApplyDNSProviderType(nodeConfig.Provider),
|
||||
ProviderAccessConfig: accessConfig,
|
||||
ProviderApplyConfig: nodeConfig.ProviderConfig,
|
||||
KeyAlgorithm: nodeConfig.KeyAlgorithm,
|
||||
Nameservers: slices.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }),
|
||||
Nameservers: uslices.Filter(strings.Split(nodeConfig.Nameservers, ";"), func(s string) bool { return s != "" }),
|
||||
DnsPropagationTimeout: nodeConfig.DnsPropagationTimeout,
|
||||
DnsTTL: nodeConfig.DnsTTL,
|
||||
DisableFollowCNAME: nodeConfig.DisableFollowCNAME,
|
||||
DisableARI: nodeConfig.DisableARI,
|
||||
SkipBeforeExpiryDays: nodeConfig.SkipBeforeExpiryDays,
|
||||
}
|
||||
|
||||
accessRepo := repository.NewAccessRepository()
|
||||
if access, err := accessRepo.GetById(context.Background(), nodeConfig.ProviderAccessId); err != nil {
|
||||
return nil, fmt.Errorf("failed to get access #%s record: %w", nodeConfig.ProviderAccessId, err)
|
||||
} else {
|
||||
accessConfig, err := access.UnmarshalConfigToMap()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal access config: %w", err)
|
||||
}
|
||||
|
||||
options.ProviderAccessConfig = accessConfig
|
||||
}
|
||||
|
||||
certRepo := repository.NewCertificateRepository()
|
||||
lastCertificate, _ := certRepo.GetByWorkflowNodeId(context.Background(), node.Id)
|
||||
if lastCertificate != nil {
|
||||
newCertSan := slices.Clone(options.Domains)
|
||||
oldCertSan := strings.Split(lastCertificate.SubjectAltNames, ";")
|
||||
slices.Sort(newCertSan)
|
||||
slices.Sort(oldCertSan)
|
||||
|
||||
if slices.Equal(newCertSan, oldCertSan) {
|
||||
lastCertX509, _ := certcrypto.ParsePEMCertificate([]byte(lastCertificate.Certificate))
|
||||
if lastCertX509 != nil {
|
||||
replacedARICertId, _ := certificate.MakeARICertID(lastCertX509)
|
||||
options.ReplacedARICertId = replacedARICertId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applicant, err := createApplicant(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -109,7 +129,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
||||
sslProviderConfig.Provider = defaultSSLProvider
|
||||
}
|
||||
|
||||
myUser, err := newAcmeUser(sslProviderConfig.Provider, options.ContactEmail)
|
||||
acmeUser, err := newAcmeUser(sslProviderConfig.Provider, options.ContactEmail)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -118,39 +138,42 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
||||
// link: https://github.com/go-acme/lego/issues/1867
|
||||
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(options.DisableFollowCNAME))
|
||||
|
||||
config := lego.NewConfig(myUser)
|
||||
|
||||
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
|
||||
// Create an ACME client config
|
||||
config := lego.NewConfig(acmeUser)
|
||||
config.CADirURL = sslProviderUrls[sslProviderConfig.Provider]
|
||||
config.Certificate.KeyType = parseKeyAlgorithm(options.KeyAlgorithm)
|
||||
|
||||
// A client facilitates communication with the CA server.
|
||||
// Create an ACME client
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the DNS01 challenge provider
|
||||
challengeOptions := make([]dns01.ChallengeOption, 0)
|
||||
if len(options.Nameservers) > 0 {
|
||||
challengeOptions = append(challengeOptions, dns01.AddRecursiveNameservers(dns01.ParseNameservers(options.Nameservers)))
|
||||
challengeOptions = append(challengeOptions, dns01.DisableAuthoritativeNssPropagationRequirement())
|
||||
}
|
||||
|
||||
client.Challenge.SetDNS01Provider(challengeProvider, challengeOptions...)
|
||||
|
||||
// New users will need to register
|
||||
if !myUser.hasRegistration() {
|
||||
reg, err := registerAcmeUser(client, sslProviderConfig, myUser)
|
||||
// New users need to register first
|
||||
if !acmeUser.hasRegistration() {
|
||||
reg, err := registerAcmeUser(client, sslProviderConfig, acmeUser)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to register: %w", err)
|
||||
}
|
||||
myUser.Registration = reg
|
||||
acmeUser.Registration = reg
|
||||
}
|
||||
|
||||
// Obtain a certificate
|
||||
certRequest := certificate.ObtainRequest{
|
||||
Domains: options.Domains,
|
||||
Bundle: true,
|
||||
}
|
||||
if !options.DisableARI {
|
||||
certRequest.ReplacesCertID = options.ReplacedARICertId
|
||||
}
|
||||
certResource, err := client.Certificate.Obtain(certRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -162,7 +185,7 @@ func apply(challengeProvider challenge.Provider, options *applicantOptions) (*Ap
|
||||
PrivateKey: strings.TrimSpace(string(certResource.PrivateKey)),
|
||||
ACMECertUrl: certResource.CertURL,
|
||||
ACMECertStableUrl: certResource.CertStableURL,
|
||||
CSR: string(certResource.CSR),
|
||||
CSR: strings.TrimSpace(string(certResource.CSR)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@@ -68,6 +68,7 @@ type WorkflowNodeConfigForApply struct {
|
||||
DnsPropagationTimeout int32 `json:"dnsPropagationTimeout"` // DNS 传播超时时间(默认取决于提供商)
|
||||
DnsTTL int32 `json:"dnsTTL"` // DNS TTL(默认取决于提供商)
|
||||
DisableFollowCNAME bool `json:"disableFollowCNAME"` // 是否禁用 CNAME 跟随
|
||||
DisableARI bool `json:"disableARI"` // 是否禁用 ARI
|
||||
SkipBeforeExpiryDays int32 `json:"skipBeforeExpiryDays"` // 证书到期前多少天前跳过续期(默认值:30)
|
||||
}
|
||||
|
||||
@@ -124,6 +125,7 @@ func (n *WorkflowNode) GetConfigForApply() WorkflowNodeConfigForApply {
|
||||
DnsPropagationTimeout: n.getConfigValueAsInt32("dnsPropagationTimeout"),
|
||||
DnsTTL: n.getConfigValueAsInt32("dnsTTL"),
|
||||
DisableFollowCNAME: n.getConfigValueAsBool("disableFollowCNAME"),
|
||||
DisableARI: n.getConfigValueAsBool("disableARI"),
|
||||
SkipBeforeExpiryDays: skipBeforeExpiryDays,
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package nodeprocessor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -131,8 +132,9 @@ func (a *applyNode) checkCanSkip(ctx context.Context, lastOutput *domain.Workflo
|
||||
|
||||
lastCertificate, _ := a.certRepo.GetByWorkflowNodeId(ctx, a.node.Id)
|
||||
renewalInterval := time.Duration(currentNodeConfig.SkipBeforeExpiryDays) * time.Hour * 24
|
||||
if lastCertificate != nil && time.Until(lastCertificate.ExpireAt) > renewalInterval {
|
||||
return true, "已申请过证书,且证书尚未临近过期"
|
||||
expirationTime := time.Until(lastCertificate.ExpireAt)
|
||||
if lastCertificate != nil && expirationTime > renewalInterval {
|
||||
return true, fmt.Sprintf("已申请过证书,且证书尚未临近过期(到期尚余 %d 天,距 %d 天时续期)", int(expirationTime.Hours()/24), currentNodeConfig.SkipBeforeExpiryDays)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user