1
0
Fork 0

feat: allow configuration of ACME certificates duration

This commit is contained in:
Pablo Montepagano 2021-11-10 08:06:09 -03:00 committed by GitHub
parent 1f17731369
commit 0a5c9095ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 183 additions and 19 deletions

View file

@ -33,12 +33,13 @@ var oscpMustStaple = false
// Configuration holds ACME configuration provided by users.
type Configuration struct {
Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"`
CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"`
PreferredChain string `description:"Preferred chain to use." json:"preferredChain,omitempty" toml:"preferredChain,omitempty" yaml:"preferredChain,omitempty" export:"true"`
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty" export:"true"`
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty" export:"true"`
EAB *EAB `description:"External Account Binding to use." json:"eab,omitempty" toml:"eab,omitempty" yaml:"eab,omitempty"`
Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"`
CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"`
PreferredChain string `description:"Preferred chain to use." json:"preferredChain,omitempty" toml:"preferredChain,omitempty" yaml:"preferredChain,omitempty" export:"true"`
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty" export:"true"`
KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty" export:"true"`
EAB *EAB `description:"External Account Binding to use." json:"eab,omitempty" toml:"eab,omitempty" yaml:"eab,omitempty"`
CertificatesDuration int `description:"Certificates' duration in hours." json:"certificatesDuration,omitempty" toml:"certificatesDuration,omitempty" yaml:"certificatesDuration,omitempty" export:"true"`
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
@ -50,6 +51,7 @@ func (a *Configuration) SetDefaults() {
a.CAServer = lego.LEDirectoryProduction
a.Storage = "acme.json"
a.KeyType = "RSA4096"
a.CertificatesDuration = 3 * 30 * 24 // 90 Days
}
// CertAndStore allows mapping a TLS certificate to a TLS store.
@ -133,6 +135,10 @@ func (p *Provider) Init() error {
return errors.New("unable to initialize ACME provider with no storage location for the certificates")
}
if p.CertificatesDuration < 1 {
return errors.New("cannot manage certificates with duration lower than 1 hour")
}
var err error
p.account, err = p.Store.GetAccount(p.ResolverName)
if err != nil {
@ -177,7 +183,9 @@ func isAccountMatchingCaServer(ctx context.Context, accountURI, serverURI string
// Provide allows the file provider to provide configurations to traefik
// using the given Configuration channel.
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, p.ResolverName+".acme"))
ctx := log.With(context.Background(),
log.Str(log.ProviderName, p.ResolverName+".acme"),
log.Str("ACME CA", p.Configuration.CAServer))
p.pool = pool
@ -187,14 +195,18 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
p.configurationChan = configurationChan
p.refreshCertificates()
p.renewCertificates(ctx)
renewPeriod, renewInterval := getCertificateRenewDurations(p.CertificatesDuration)
log.FromContext(ctx).Debugf("Attempt to renew certificates %q before expiry and check every %q",
renewPeriod, renewInterval)
ticker := time.NewTicker(24 * time.Hour)
p.renewCertificates(ctx, renewPeriod)
ticker := time.NewTicker(renewInterval)
pool.GoCtx(func(ctxPool context.Context) {
for {
select {
case <-ticker.C:
p.renewCertificates(ctx)
p.renewCertificates(ctx, renewPeriod)
case <-ctxPool.Done():
ticker.Stop()
return
@ -515,6 +527,24 @@ func (p *Provider) addCertificateForDomain(domain types.Domain, certificate, key
p.certsChan <- &CertAndStore{Certificate: Certificate{Certificate: certificate, Key: key, Domain: domain}, Store: tlsStore}
}
// getCertificateRenewDurations returns renew durations calculated from the given certificatesDuration in hours.
// The first (RenewPeriod) is the period before the end of the certificate duration, during which the certificate should be renewed.
// The second (RenewInterval) is the interval between renew attempts.
func getCertificateRenewDurations(certificatesDuration int) (time.Duration, time.Duration) {
switch {
case certificatesDuration >= 265*24: // >= 1 year
return 4 * 30 * 24 * time.Hour, 7 * 24 * time.Hour // 4 month, 1 week
case certificatesDuration >= 3*30*24: // >= 90 days
return 30 * 24 * time.Hour, 24 * time.Hour // 30 days, 1 day
case certificatesDuration >= 7*24: // >= 7 days
return 24 * time.Hour, time.Hour // 1 days, 1 hour
case certificatesDuration >= 24: // >= 1 days
return 6 * time.Hour, 10 * time.Minute // 6 hours, 10 minutes
default:
return 20 * time.Minute, time.Minute
}
}
// deleteUnnecessaryDomains deletes from the configuration :
// - Duplicated domains
// - Domains which are checked by wildcard domain.
@ -637,15 +667,14 @@ func (p *Provider) refreshCertificates() {
p.configurationChan <- conf
}
func (p *Provider) renewCertificates(ctx context.Context) {
func (p *Provider) renewCertificates(ctx context.Context, renewPeriod time.Duration) {
logger := log.FromContext(ctx)
logger.Info("Testing certificate renew...")
for _, cert := range p.certificates {
crt, err := getX509Certificate(ctx, &cert.Certificate)
// If there's an error, we assume the cert is broken, and needs update
// <= 30 days left, renew certificate
if err != nil || crt == nil || crt.NotAfter.Before(time.Now().Add(24*30*time.Hour)) {
if err != nil || crt == nil || crt.NotAfter.Before(time.Now().Add(renewPeriod)) {
client, err := p.getClient()
if err != nil {
logger.Infof("Error renewing certificate from LE : %+v, %v", cert.Domain, err)