mirror of
https://github.com/usual2970/certimate.git
synced 2025-07-20 09:57:59 +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
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user