Add config option to disable ARI

This may be temporary until ARI is more mature
This commit is contained in:
Matthew Holt 2024-08-08 08:08:29 -06:00
parent 1a2275d54c
commit 5ee48a3108
No known key found for this signature in database
GPG Key ID: 2A349DD577D586A5
5 changed files with 63 additions and 54 deletions

View File

@ -461,7 +461,7 @@ func (am *ACMEIssuer) doIssue(ctx context.Context, csr *x509.CertificateRequest,
// between client and server or some sort of bookkeeping error with regards to the certID // between client and server or some sort of bookkeeping error with regards to the certID
// and the server is rejecting the ARI certID. In any case, an invalid certID may cause // and the server is rejecting the ARI certID. In any case, an invalid certID may cause
// orders to fail. So try once without setting it. // orders to fail. So try once without setting it.
if !usingTestCA && attempts != 2 { if !am.config.DisableARI && !usingTestCA && attempts != 2 {
if replacing, ok := ctx.Value(ctxKeyARIReplaces).(*x509.Certificate); ok { if replacing, ok := ctx.Value(ctxKeyARIReplaces).(*x509.Certificate); ok {
params.Replaces = replacing params.Replaces = replacing
} }

View File

@ -103,53 +103,54 @@ func (cfg *Config) certNeedsRenewal(leaf *x509.Certificate, ari acme.RenewalInfo
logger = zap.NewNop() logger = zap.NewNop()
} }
// first check ARI: if it says it's time to renew, it's time to renew if !cfg.DisableARI {
// (notice that we don't strictly require an ARI window to also exist; we presume // first check ARI: if it says it's time to renew, it's time to renew
// that if a time has been selected, a window does or did exist, even if it didn't // (notice that we don't strictly require an ARI window to also exist; we presume
// get stored/encoded for some reason - but also: this allows administrators to // that if a time has been selected, a window does or did exist, even if it didn't
// manually or explicitly schedule a renewal time indepedently of ARI which could // get stored/encoded for some reason - but also: this allows administrators to
// be useful) // manually or explicitly schedule a renewal time indepedently of ARI which could
selectedTime := ari.SelectedTime // be useful)
selectedTime := ari.SelectedTime
// if, for some reason a random time in the window hasn't been selected yet, but an ARI // if, for some reason a random time in the window hasn't been selected yet, but an ARI
// window does exist, we can always improvise one... even if this is called repeatedly, // window does exist, we can always improvise one... even if this is called repeatedly,
// a random time is a random time, whether you generate it once or more :D // a random time is a random time, whether you generate it once or more :D
// (code borrowed from our acme package) // (code borrowed from our acme package)
if selectedTime.IsZero() && if selectedTime.IsZero() &&
(!ari.SuggestedWindow.Start.IsZero() && !ari.SuggestedWindow.End.IsZero()) { (!ari.SuggestedWindow.Start.IsZero() && !ari.SuggestedWindow.End.IsZero()) {
start, end := ari.SuggestedWindow.Start.Unix()+1, ari.SuggestedWindow.End.Unix() start, end := ari.SuggestedWindow.Start.Unix()+1, ari.SuggestedWindow.End.Unix()
selectedTime = time.Unix(rand.Int63n(end-start)+start, 0).UTC() selectedTime = time.Unix(rand.Int63n(end-start)+start, 0).UTC()
logger.Warn("no renewal time had been selected with ARI; chose an ephemeral one for now", logger.Warn("no renewal time had been selected with ARI; chose an ephemeral one for now",
zap.Time("ephemeral_selected_time", selectedTime)) zap.Time("ephemeral_selected_time", selectedTime))
}
// if a renewal time has been selected, start with that
if !selectedTime.IsZero() {
// ARI spec recommends an algorithm that renews after the randomly-selected
// time OR just before it if the next waking time would be after it; this
// cutoff can actually be before the start of the renewal window, but the spec
// author says that's OK: https://github.com/aarongable/draft-acme-ari/issues/71
cutoff := ari.SelectedTime.Add(-cfg.certCache.options.RenewCheckInterval)
if time.Now().After(cutoff) {
logger.Info("certificate needs renewal based on ARI window",
zap.Time("selected_time", selectedTime),
zap.Time("renewal_cutoff", cutoff))
return true
} }
// according to ARI, we are not ready to renew; however, we do not rely solely on // if a renewal time has been selected, start with that
// ARI calculations... what if there is a bug in our implementation, or in the if !selectedTime.IsZero() {
// server's, or the stored metadata? for redundancy, give credence to the expiration // ARI spec recommends an algorithm that renews after the randomly-selected
// date; ignore ARI if we are past a "dangerously close" limit, to avoid any // time OR just before it if the next waking time would be after it; this
// possibility of a bug in ARI compromising a site's uptime: we should always always // cutoff can actually be before the start of the renewal window, but the spec
// always give heed to actual validity period // author says that's OK: https://github.com/aarongable/draft-acme-ari/issues/71
if currentlyInRenewalWindow(leaf.NotBefore, expiration, 1.0/20.0) { cutoff := ari.SelectedTime.Add(-cfg.certCache.options.RenewCheckInterval)
logger.Warn("certificate is in emergency renewal window; superceding ARI", if time.Now().After(cutoff) {
zap.Duration("remaining", time.Until(expiration)), logger.Info("certificate needs renewal based on ARI window",
zap.Time("renewal_cutoff", cutoff)) zap.Time("selected_time", selectedTime),
return true zap.Time("renewal_cutoff", cutoff))
} return true
}
// according to ARI, we are not ready to renew; however, we do not rely solely on
// ARI calculations... what if there is a bug in our implementation, or in the
// server's, or the stored metadata? for redundancy, give credence to the expiration
// date; ignore ARI if we are past a "dangerously close" limit, to avoid any
// possibility of a bug in ARI compromising a site's uptime: we should always always
// always give heed to actual validity period
if currentlyInRenewalWindow(leaf.NotBefore, expiration, 1.0/20.0) {
logger.Warn("certificate is in emergency renewal window; superceding ARI",
zap.Duration("remaining", time.Until(expiration)),
zap.Time("renewal_cutoff", cutoff))
return true
}
}
} }
// the normal check, in the absence of ARI, is to determine if we're near enough (or past) // the normal check, in the absence of ARI, is to determine if we're near enough (or past)

View File

@ -149,6 +149,10 @@ type Config struct {
// EXPERIMENTAL: Subject to change or removal. // EXPERIMENTAL: Subject to change or removal.
SubjectTransformer func(ctx context.Context, domain string) string SubjectTransformer func(ctx context.Context, domain string) string
// Disables both ARI fetching and the use of ARI for renewal decisions.
// TEMPORARY: Will likely be removed in the future.
DisableARI bool
// Set a logger to enable logging. If not set, // Set a logger to enable logging. If not set,
// a default logger will be created. // a default logger will be created.
Logger *zap.Logger Logger *zap.Logger
@ -451,7 +455,7 @@ func (cfg *Config) manageOne(ctx context.Context, domainName string, async bool)
// ensure ARI is updated before we check whether the cert needs renewing // ensure ARI is updated before we check whether the cert needs renewing
// (we ignore the second return value because we already check if needs renewing anyway) // (we ignore the second return value because we already check if needs renewing anyway)
if cert.ari.NeedsRefresh() { if !cfg.DisableARI && cert.ari.NeedsRefresh() {
cert, _, err = cfg.updateARI(ctx, cert, cfg.Logger) cert, _, err = cfg.updateARI(ctx, cert, cfg.Logger)
if err != nil { if err != nil {
cfg.Logger.Error("updating ARI upon managing", zap.Error(err)) cfg.Logger.Error("updating ARI upon managing", zap.Error(err))
@ -888,11 +892,13 @@ func (cfg *Config) renewCert(ctx context.Context, name string, force, interactiv
// if we're renewing with the same ACME CA as before, have the ACME // if we're renewing with the same ACME CA as before, have the ACME
// client tell the server we are replacing a certificate (but doing // client tell the server we are replacing a certificate (but doing
// this on the wrong CA, or when the CA doesn't recognize the certID, // this on the wrong CA, or when the CA doesn't recognize the certID,
// can fail the order) // can fail the order) -- TODO: change this check to whether we're using the same ACME account, not CA
if acmeData, err := certRes.getACMEData(); err == nil && acmeData.CA != "" { if !cfg.DisableARI {
if acmeIss, ok := issuer.(*ACMEIssuer); ok { if acmeData, err := certRes.getACMEData(); err == nil && acmeData.CA != "" {
if acmeIss.CA == acmeData.CA { if acmeIss, ok := issuer.(*ACMEIssuer); ok {
ctx = context.WithValue(ctx, ctxKeyARIReplaces, leaf) if acmeIss.CA == acmeData.CA {
ctx = context.WithValue(ctx, ctxKeyARIReplaces, leaf)
}
} }
} }
} }
@ -1246,8 +1252,10 @@ func (cfg *Config) managedCertNeedsRenewal(certRes CertificateResource, emitLogs
return 0, nil, true return 0, nil, true
} }
var ari acme.RenewalInfo var ari acme.RenewalInfo
if ariPtr, err := certRes.getARI(); err == nil && ariPtr != nil { if !cfg.DisableARI {
ari = *ariPtr if ariPtr, err := certRes.getARI(); err == nil && ariPtr != nil {
ari = *ariPtr
}
} }
remaining := time.Until(expiresAt(certChain[0])) remaining := time.Until(expiresAt(certChain[0]))
return remaining, certChain[0], cfg.certNeedsRenewal(certChain[0], ari, emitLogs) return remaining, certChain[0], cfg.certNeedsRenewal(certChain[0], ari, emitLogs)

View File

@ -582,7 +582,7 @@ func (cfg *Config) handshakeMaintenance(ctx context.Context, hello *tls.ClientHe
} }
// Check ARI status // Check ARI status
if cert.ari.NeedsRefresh() { if !cfg.DisableARI && cert.ari.NeedsRefresh() {
// we ignore the second return value here because we go on to check renewal status below regardless // we ignore the second return value here because we go on to check renewal status below regardless
var err error var err error
cert, _, err = cfg.updateARI(ctx, cert, logger) cert, _, err = cfg.updateARI(ctx, cert, logger)

View File

@ -136,7 +136,7 @@ func (certCache *Cache) RenewManagedCertificates(ctx context.Context) error {
} }
// ACME-specific: see if if ACME Renewal Info (ARI) window needs refreshing // ACME-specific: see if if ACME Renewal Info (ARI) window needs refreshing
if cert.ari.NeedsRefresh() { if !cfg.DisableARI && cert.ari.NeedsRefresh() {
configs[cert.hash] = cfg configs[cert.hash] = cfg
ariQueue = append(ariQueue, cert) ariQueue = append(ariQueue, cert)
} }