diff --git a/certificate/authorization.go b/certificate/authorization.go index 9161d75499..452db0d99b 100644 --- a/certificate/authorization.go +++ b/certificate/authorization.go @@ -60,7 +60,7 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz return responses, nil } -func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) { +func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force bool) { for _, authzURL := range order.Authorizations { auth, err := c.core.Authorizations.Get(authzURL) if err != nil { @@ -68,7 +68,7 @@ func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) { continue } - if auth.Status == acme.StatusValid { + if auth.Status == acme.StatusValid && !force { log.Infof("Skipping deactivating of valid auth: %s", authzURL) continue } diff --git a/certificate/certificates.go b/certificate/certificates.go index 596f9512fa..93952b4c02 100644 --- a/certificate/certificates.go +++ b/certificate/certificates.go @@ -49,22 +49,30 @@ type Resource struct { // If you do not want that you can supply your own private key in the privateKey parameter. // If this parameter is non-nil it will be used instead of generating a new one. // -// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle. +// If `Bundle` is true, the `[]byte` contains both the issuer certificate and your issued certificate as a bundle. +// +// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful. +// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2. type ObtainRequest struct { - Domains []string - Bundle bool - PrivateKey crypto.PrivateKey - MustStaple bool - PreferredChain string + Domains []string + Bundle bool + PrivateKey crypto.PrivateKey + MustStaple bool + PreferredChain string + AlwaysDeactivateAuthorizations bool } // ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it. // -// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle. +// If `Bundle` is true, the `[]byte` contains both the issuer certificate and your issued certificate as a bundle. +// +// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful. +// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2. type ObtainForCSRRequest struct { - CSR *x509.CertificateRequest - Bundle bool - PreferredChain string + CSR *x509.CertificateRequest + Bundle bool + PreferredChain string + AlwaysDeactivateAuthorizations bool } type resolver interface { @@ -117,14 +125,14 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) { authz, err := c.getAuthorizations(order) if err != nil { // If any challenge fails, return. Do not generate partial SAN certificates. - c.deactivateAuthorizations(order) + c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations) return nil, err } err = c.resolver.Solve(authz) if err != nil { // If any challenge fails, return. Do not generate partial SAN certificates. - c.deactivateAuthorizations(order) + c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations) return nil, err } @@ -138,6 +146,10 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) { } } + if request.AlwaysDeactivateAuthorizations { + c.deactivateAuthorizations(order, true) + } + // Do not return an empty failures map, because // it would still be a non-nil error value if len(failures) > 0 { @@ -178,14 +190,14 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error) authz, err := c.getAuthorizations(order) if err != nil { // If any challenge fails, return. Do not generate partial SAN certificates. - c.deactivateAuthorizations(order) + c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations) return nil, err } err = c.resolver.Solve(authz) if err != nil { // If any challenge fails, return. Do not generate partial SAN certificates. - c.deactivateAuthorizations(order) + c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations) return nil, err } @@ -199,6 +211,10 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error) } } + if request.AlwaysDeactivateAuthorizations { + c.deactivateAuthorizations(order, true) + } + if cert != nil { // Add the CSR to the certificate so that it can be used for renewals. cert.CSR = certcrypto.PEMEncode(request.CSR) diff --git a/cmd/cmd_renew.go b/cmd/cmd_renew.go index 06d65be8ae..ac7897ff49 100644 --- a/cmd/cmd_renew.go +++ b/cmd/cmd_renew.go @@ -62,6 +62,10 @@ func createRenew() cli.Command { Name: "preferred-chain", Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.", }, + cli.StringFlag{ + Name: "always-deactivate-authorizations", + Usage: "Force the authorizations to be relinquished even if the certificate request was successful.", + }, }, } } @@ -127,11 +131,12 @@ func renewForDomains(ctx *cli.Context, client *lego.Client, certsStorage *Certif } request := certificate.ObtainRequest{ - Domains: merge(certDomains, domains), - Bundle: bundle, - PrivateKey: privateKey, - MustStaple: ctx.Bool("must-staple"), - PreferredChain: ctx.String("preferred-chain"), + Domains: merge(certDomains, domains), + Bundle: bundle, + PrivateKey: privateKey, + MustStaple: ctx.Bool("must-staple"), + PreferredChain: ctx.String("preferred-chain"), + AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"), } certRes, err := client.Certificate.Obtain(request) if err != nil { @@ -174,9 +179,10 @@ func renewForCSR(ctx *cli.Context, client *lego.Client, certsStorage *Certificat log.Infof("[%s] acme: Trying renewal with %d hours remaining", domain, int(timeLeft.Hours())) certRes, err := client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{ - CSR: csr, - Bundle: bundle, - PreferredChain: ctx.String("preferred-chain"), + CSR: csr, + Bundle: bundle, + PreferredChain: ctx.String("preferred-chain"), + AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"), }) if err != nil { log.Fatal(err) diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go index 342f909f35..db368cdf5a 100644 --- a/cmd/cmd_run.go +++ b/cmd/cmd_run.go @@ -47,6 +47,10 @@ func createRun() cli.Command { Name: "preferred-chain", Usage: "If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used.", }, + cli.StringFlag{ + Name: "always-deactivate-authorizations", + Usage: "Force the authorizations to be relinquished even if the certificate request was successful.", + }, }, } } @@ -163,10 +167,11 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso if len(domains) > 0 { // obtain a certificate, generating a new private key request := certificate.ObtainRequest{ - Domains: domains, - Bundle: bundle, - MustStaple: ctx.Bool("must-staple"), - PreferredChain: ctx.String("preferred-chain"), + Domains: domains, + Bundle: bundle, + MustStaple: ctx.Bool("must-staple"), + PreferredChain: ctx.String("preferred-chain"), + AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"), } return client.Certificate.Obtain(request) } @@ -179,8 +184,9 @@ func obtainCertificate(ctx *cli.Context, client *lego.Client) (*certificate.Reso // obtain a certificate for this CSR return client.Certificate.ObtainForCSR(certificate.ObtainForCSRRequest{ - CSR: csr, - Bundle: bundle, - PreferredChain: ctx.String("preferred-chain"), + CSR: csr, + Bundle: bundle, + PreferredChain: ctx.String("preferred-chain"), + AlwaysDeactivateAuthorizations: ctx.Bool("always-deactivate-authorizations"), }) }