@ -101,11 +101,13 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
}
if pair , err := getCertPEMCached ( cs , domain , now ) ; err == nil {
future := now . AddDate ( 0 , 0 , 14 )
if b . shouldStartDomainRenewal ( cs , domain , future ) {
shouldRenew , err := shouldStartDomainRenewal ( domain , now , pair )
if err != nil {
logf ( "error checking for certificate renewal: %v" , err )
} else if shouldRenew {
logf ( "starting async renewal" )
// Start renewal in the background.
go b . getCertPEM ( context . Background ( ) , cs , logf , traceACME , domain , future )
go b . getCertPEM ( context . Background ( ) , cs , logf , traceACME , domain , now )
}
return pair , nil
}
@ -118,18 +120,41 @@ func (b *LocalBackend) GetCertPEM(ctx context.Context, domain string) (*TLSCertK
return pair , nil
}
func ( b * LocalBackend ) shouldStartDomainRenewal ( cs certStore , domain string , future time . Time ) bool {
func shouldStartDomainRenewal ( domain string , now time . Time , pair * TLSCertKeyPair ) ( bool , error ) {
renewMu . Lock ( )
defer renewMu . Unlock ( )
now := time . Now ( )
if last , ok := lastRenewCheck [ domain ] ; ok && now . Sub ( last ) < time . Minute {
// We checked very recently. Don't bother reparsing &
// validating the x509 cert.
return false
return false , nil
}
lastRenewCheck [ domain ] = now
_ , err := getCertPEMCached ( cs , domain , future )
return errors . Is ( err , errCertExpired )
block , _ := pem . Decode ( pair . CertPEM )
if block == nil {
return false , fmt . Errorf ( "parsing certificate PEM" )
}
cert , err := x509 . ParseCertificate ( block . Bytes )
if err != nil {
return false , fmt . Errorf ( "parsing certificate: %w" , err )
}
certLifetime := cert . NotAfter . Sub ( cert . NotBefore )
if certLifetime < 0 {
return false , fmt . Errorf ( "negative certificate lifetime %v" , certLifetime )
}
// Per https://github.com/tailscale/tailscale/issues/8204, check
// whether we're more than 2/3 of the way through the certificate's
// lifetime, which is the officially-recommended best practice by Let's
// Encrypt.
renewalDuration := certLifetime * 2 / 3
renewAt := cert . NotBefore . Add ( renewalDuration )
if now . After ( renewAt ) {
return true , nil
}
return false , nil
}
// certStore provides a way to perist and retrieve TLS certificates.