From 463b47a0abc36fb52514a9fefed17c6af387fdb8 Mon Sep 17 00:00:00 2001 From: Andrew Lytvynov Date: Tue, 8 Apr 2025 15:37:00 -0700 Subject: [PATCH] ipn/ipnlocal: include previous cert in new ACME orders (#15595) When we have an old cert that is being rotated, include it in the order. If we're in the ARI-recommended rotation window, LE should exclude us from rate limits. If we're not within that window, the order still succeeds, so there's no risk in including the old cert. Fixes #15542 Signed-off-by: Andrew Lytvynov --- ipn/ipnlocal/cert.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/ipn/ipnlocal/cert.go b/ipn/ipnlocal/cert.go index 111dc5a2d..86052eb8d 100644 --- a/ipn/ipnlocal/cert.go +++ b/ipn/ipnlocal/cert.go @@ -484,14 +484,15 @@ func getCertPEMCached(cs certStore, domain string, now time.Time) (p *TLSCertKey // In case this method was triggered multiple times in parallel (when // serving incoming requests), check whether one of the other goroutines // already renewed the cert before us. - if p, err := getCertPEMCached(cs, domain, now); err == nil { + previous, err := getCertPEMCached(cs, domain, now) + if err == nil { // shouldStartDomainRenewal caches its result so it's OK to call this // frequently. - shouldRenew, err := b.shouldStartDomainRenewal(cs, domain, now, p, minValidity) + shouldRenew, err := b.shouldStartDomainRenewal(cs, domain, now, previous, minValidity) if err != nil { logf("error checking for certificate renewal: %v", err) } else if !shouldRenew { - return p, nil + return previous, nil } } else if !errors.Is(err, ipn.ErrStateNotExist) && !errors.Is(err, errCertExpired) { return nil, err @@ -536,7 +537,17 @@ func getCertPEMCached(cs certStore, domain string, now time.Time) (p *TLSCertKey return nil, err } - order, err := ac.AuthorizeOrder(ctx, []acme.AuthzID{{Type: "dns", Value: domain}}) + // If we have a previous cert, include it in the order. Assuming we're + // within the ARI renewal window this should exclude us from LE rate + // limits. + var opts []acme.OrderOption + if previous != nil { + prevCrt, err := previous.parseCertificate() + if err == nil { + opts = append(opts, acme.WithOrderReplacesCert(prevCrt)) + } + } + order, err := ac.AuthorizeOrder(ctx, []acme.AuthzID{{Type: "dns", Value: domain}}, opts...) if err != nil { return nil, err }