feat: allow configuration of ACME certificates duration
This commit is contained in:
parent
1f17731369
commit
0a5c9095ac
12 changed files with 183 additions and 19 deletions
|
@ -140,7 +140,11 @@ Please check the [configuration examples below](#configuration-examples) for mor
|
||||||
|
|
||||||
Traefik automatically tracks the expiry date of ACME certificates it generates.
|
Traefik automatically tracks the expiry date of ACME certificates it generates.
|
||||||
|
|
||||||
If there are less than 30 days remaining before the certificate expires, Traefik will attempt to renew it automatically.
|
By default, Traefik manages 90 days certificates,
|
||||||
|
and starts to renew certificates 30 days before their expiry.
|
||||||
|
|
||||||
|
When using a certificates resolver that issues certificates with custom durations,
|
||||||
|
one can configure the certificates' duration with the [`certificatesDuration`](#certificatesduration) option.
|
||||||
|
|
||||||
!!! info ""
|
!!! info ""
|
||||||
Certificates that are no longer used may still be renewed, as Traefik does not currently check if the certificate is being used before renewing.
|
Certificates that are no longer used may still be renewed, as Traefik does not currently check if the certificate is being used before renewing.
|
||||||
|
@ -533,6 +537,50 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik
|
||||||
!!! warning
|
!!! warning
|
||||||
For concurrency reasons, this file cannot be shared across multiple instances of Traefik.
|
For concurrency reasons, this file cannot be shared across multiple instances of Traefik.
|
||||||
|
|
||||||
|
### `certificatesDuration`
|
||||||
|
|
||||||
|
_Optional, Default=2160_
|
||||||
|
|
||||||
|
The `certificatesDuration` option defines the certificates' duration in hours.
|
||||||
|
It defaults to `2160` (90 days) to follow Let's Encrypt certificates' duration.
|
||||||
|
|
||||||
|
!!! warning "Traefik cannot manage certificates with a duration lower than 1 hour."
|
||||||
|
|
||||||
|
```yaml tab="File (YAML)"
|
||||||
|
certificatesResolvers:
|
||||||
|
myresolver:
|
||||||
|
acme:
|
||||||
|
# ...
|
||||||
|
certificatesDuration: 72
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```toml tab="File (TOML)"
|
||||||
|
[certificatesResolvers.myresolver.acme]
|
||||||
|
# ...
|
||||||
|
certificatesDuration=72
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash tab="CLI"
|
||||||
|
# ...
|
||||||
|
--certificatesresolvers.myresolver.acme.certificatesduration=72
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`certificatesDuration` is used to calculate two durations:
|
||||||
|
|
||||||
|
- `Renew Period`: the period before the end of the certificate duration, during which the certificate should be renewed.
|
||||||
|
- `Renew Interval`: the interval between renew attempts.
|
||||||
|
|
||||||
|
| Certificate Duration | Renew Period | Renew Interval |
|
||||||
|
|----------------------|-------------------|-------------------------|
|
||||||
|
| >= 1 year | 4 months | 1 week |
|
||||||
|
| >= 90 days | 30 days | 1 day |
|
||||||
|
| >= 7 days | 1 day | 1 hour |
|
||||||
|
| >= 24 hours | 6 hours | 10 min |
|
||||||
|
| < 24 hours | 20 min | 1 min |
|
||||||
|
|
||||||
### `preferredChain`
|
### `preferredChain`
|
||||||
|
|
||||||
_Optional, Default=""_
|
_Optional, Default=""_
|
||||||
|
|
|
@ -22,6 +22,14 @@
|
||||||
#
|
#
|
||||||
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
# caServer = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
|
# The certificates' duration in hours.
|
||||||
|
# It defaults to 2160 (90 days) to follow Let's Encrypt certificates' duration.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 2160
|
||||||
|
#
|
||||||
|
# certificatesDuration=2160
|
||||||
|
|
||||||
# Preferred chain to use.
|
# Preferred chain to use.
|
||||||
#
|
#
|
||||||
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
|
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
|
||||||
|
|
|
@ -21,6 +21,14 @@
|
||||||
#
|
#
|
||||||
--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
|
--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
|
||||||
|
|
||||||
|
# The certificates' duration in hours.
|
||||||
|
# It defaults to 2160 (90 days) to follow Let's Encrypt certificates' duration.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 2160
|
||||||
|
#
|
||||||
|
--certificatesresolvers.myresolver.acme.certificatesDuration=2160
|
||||||
|
|
||||||
# Preferred chain to use.
|
# Preferred chain to use.
|
||||||
#
|
#
|
||||||
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
|
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
|
||||||
|
|
|
@ -24,6 +24,14 @@ certificatesResolvers:
|
||||||
#
|
#
|
||||||
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
|
# caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||||
|
|
||||||
|
# The certificates' duration in hours.
|
||||||
|
# It defaults to 2160 (90 days) to follow Let's Encrypt certificates' duration.
|
||||||
|
#
|
||||||
|
# Optional
|
||||||
|
# Default: 2160
|
||||||
|
#
|
||||||
|
# certificatesDuration: 2160
|
||||||
|
|
||||||
# Preferred chain to use.
|
# Preferred chain to use.
|
||||||
#
|
#
|
||||||
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
|
# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name.
|
||||||
|
|
|
@ -54,6 +54,9 @@ Certificates resolvers configuration. (Default: ```false```)
|
||||||
`--certificatesresolvers.<name>.acme.caserver`:
|
`--certificatesresolvers.<name>.acme.caserver`:
|
||||||
CA server to use. (Default: ```https://acme-v02.api.letsencrypt.org/directory```)
|
CA server to use. (Default: ```https://acme-v02.api.letsencrypt.org/directory```)
|
||||||
|
|
||||||
|
`--certificatesresolvers.<name>.acme.certificatesduration`:
|
||||||
|
Certificates' duration in hours. (Default: ```2160```)
|
||||||
|
|
||||||
`--certificatesresolvers.<name>.acme.dnschallenge`:
|
`--certificatesresolvers.<name>.acme.dnschallenge`:
|
||||||
Activate DNS-01 Challenge. (Default: ```false```)
|
Activate DNS-01 Challenge. (Default: ```false```)
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,9 @@ Certificates resolvers configuration. (Default: ```false```)
|
||||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_CASERVER`:
|
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_CASERVER`:
|
||||||
CA server to use. (Default: ```https://acme-v02.api.letsencrypt.org/directory```)
|
CA server to use. (Default: ```https://acme-v02.api.letsencrypt.org/directory```)
|
||||||
|
|
||||||
|
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_CERTIFICATESDURATION`:
|
||||||
|
Certificates' duration in hours. (Default: ```2160```)
|
||||||
|
|
||||||
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_DNSCHALLENGE`:
|
`TRAEFIK_CERTIFICATESRESOLVERS_<NAME>_ACME_DNSCHALLENGE`:
|
||||||
Activate DNS-01 Challenge. (Default: ```false```)
|
Activate DNS-01 Challenge. (Default: ```false```)
|
||||||
|
|
||||||
|
|
|
@ -358,6 +358,7 @@
|
||||||
[certificatesResolvers.CertificateResolver0.acme]
|
[certificatesResolvers.CertificateResolver0.acme]
|
||||||
email = "foobar"
|
email = "foobar"
|
||||||
caServer = "foobar"
|
caServer = "foobar"
|
||||||
|
certificatesDuration = 2160
|
||||||
preferredChain = "foobar"
|
preferredChain = "foobar"
|
||||||
storage = "foobar"
|
storage = "foobar"
|
||||||
keyType = "foobar"
|
keyType = "foobar"
|
||||||
|
@ -376,6 +377,7 @@
|
||||||
[certificatesResolvers.CertificateResolver1.acme]
|
[certificatesResolvers.CertificateResolver1.acme]
|
||||||
email = "foobar"
|
email = "foobar"
|
||||||
caServer = "foobar"
|
caServer = "foobar"
|
||||||
|
certificatesDuration = 2160
|
||||||
preferredChain = "foobar"
|
preferredChain = "foobar"
|
||||||
storage = "foobar"
|
storage = "foobar"
|
||||||
keyType = "foobar"
|
keyType = "foobar"
|
||||||
|
|
|
@ -376,6 +376,7 @@ certificatesResolvers:
|
||||||
acme:
|
acme:
|
||||||
email: foobar
|
email: foobar
|
||||||
caServer: foobar
|
caServer: foobar
|
||||||
|
certificatesDuration: 2160
|
||||||
preferredChain: foobar
|
preferredChain: foobar
|
||||||
storage: foobar
|
storage: foobar
|
||||||
keyType: foobar
|
keyType: foobar
|
||||||
|
@ -396,6 +397,7 @@ certificatesResolvers:
|
||||||
acme:
|
acme:
|
||||||
email: foobar
|
email: foobar
|
||||||
caServer: foobar
|
caServer: foobar
|
||||||
|
certificatesDuration: 2160
|
||||||
preferredChain: foobar
|
preferredChain: foobar
|
||||||
storage: foobar
|
storage: foobar
|
||||||
keyType: foobar
|
keyType: foobar
|
||||||
|
|
|
@ -916,6 +916,7 @@ func TestDo_staticConfiguration(t *testing.T) {
|
||||||
ACME: &acme.Configuration{
|
ACME: &acme.Configuration{
|
||||||
Email: "acme Email",
|
Email: "acme Email",
|
||||||
CAServer: "CAServer",
|
CAServer: "CAServer",
|
||||||
|
CertificatesDuration: 42,
|
||||||
PreferredChain: "foobar",
|
PreferredChain: "foobar",
|
||||||
Storage: "Storage",
|
Storage: "Storage",
|
||||||
KeyType: "MyKeyType",
|
KeyType: "MyKeyType",
|
||||||
|
|
|
@ -426,6 +426,7 @@
|
||||||
"preferredChain": "foobar",
|
"preferredChain": "foobar",
|
||||||
"storage": "Storage",
|
"storage": "Storage",
|
||||||
"keyType": "MyKeyType",
|
"keyType": "MyKeyType",
|
||||||
|
"certificatesDuration": 42,
|
||||||
"dnsChallenge": {
|
"dnsChallenge": {
|
||||||
"provider": "DNSProvider",
|
"provider": "DNSProvider",
|
||||||
"delayBeforeCheck": "42ns",
|
"delayBeforeCheck": "42ns",
|
||||||
|
|
|
@ -39,6 +39,7 @@ type Configuration struct {
|
||||||
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,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"`
|
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"`
|
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"`
|
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"`
|
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.CAServer = lego.LEDirectoryProduction
|
||||||
a.Storage = "acme.json"
|
a.Storage = "acme.json"
|
||||||
a.KeyType = "RSA4096"
|
a.KeyType = "RSA4096"
|
||||||
|
a.CertificatesDuration = 3 * 30 * 24 // 90 Days
|
||||||
}
|
}
|
||||||
|
|
||||||
// CertAndStore allows mapping a TLS certificate to a TLS store.
|
// 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")
|
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
|
var err error
|
||||||
p.account, err = p.Store.GetAccount(p.ResolverName)
|
p.account, err = p.Store.GetAccount(p.ResolverName)
|
||||||
if err != nil {
|
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
|
// Provide allows the file provider to provide configurations to traefik
|
||||||
// using the given Configuration channel.
|
// using the given Configuration channel.
|
||||||
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
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
|
p.pool = pool
|
||||||
|
|
||||||
|
@ -187,14 +195,18 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||||
p.configurationChan = configurationChan
|
p.configurationChan = configurationChan
|
||||||
p.refreshCertificates()
|
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) {
|
pool.GoCtx(func(ctxPool context.Context) {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
p.renewCertificates(ctx)
|
p.renewCertificates(ctx, renewPeriod)
|
||||||
case <-ctxPool.Done():
|
case <-ctxPool.Done():
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
return
|
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}
|
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 :
|
// deleteUnnecessaryDomains deletes from the configuration :
|
||||||
// - Duplicated domains
|
// - Duplicated domains
|
||||||
// - Domains which are checked by wildcard domain.
|
// - Domains which are checked by wildcard domain.
|
||||||
|
@ -637,15 +667,14 @@ func (p *Provider) refreshCertificates() {
|
||||||
p.configurationChan <- conf
|
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 := log.FromContext(ctx)
|
||||||
|
|
||||||
logger.Info("Testing certificate renew...")
|
logger.Info("Testing certificate renew...")
|
||||||
for _, cert := range p.certificates {
|
for _, cert := range p.certificates {
|
||||||
crt, err := getX509Certificate(ctx, &cert.Certificate)
|
crt, err := getX509Certificate(ctx, &cert.Certificate)
|
||||||
// If there's an error, we assume the cert is broken, and needs update
|
// 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(renewPeriod)) {
|
||||||
if err != nil || crt == nil || crt.NotAfter.Before(time.Now().Add(24*30*time.Hour)) {
|
|
||||||
client, err := p.getClient()
|
client, err := p.getClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("Error renewing certificate from LE : %+v, %v", cert.Domain, err)
|
logger.Infof("Error renewing certificate from LE : %+v, %v", cert.Domain, err)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -592,3 +593,53 @@ func TestInitAccount(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_getCertificateRenewDurations(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
certificatesDurations int
|
||||||
|
expectRenewPeriod time.Duration
|
||||||
|
expectRenewInterval time.Duration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Less than 24 Hours certificates: 20 minutes renew period, 1 minutes renew interval",
|
||||||
|
certificatesDurations: 1,
|
||||||
|
expectRenewPeriod: time.Minute * 20,
|
||||||
|
expectRenewInterval: time.Minute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "1 Year certificates: 2 months renew period, 1 week renew interval",
|
||||||
|
certificatesDurations: 24 * 365,
|
||||||
|
expectRenewPeriod: time.Hour * 24 * 30 * 4,
|
||||||
|
expectRenewInterval: time.Hour * 24 * 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "90 Days certificates: 30 days renew period, 1 day renew interval",
|
||||||
|
certificatesDurations: 24 * 90,
|
||||||
|
expectRenewPeriod: time.Hour * 24 * 30,
|
||||||
|
expectRenewInterval: time.Hour * 24,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "7 Days certificates: 1 days renew period, 1 hour renew interval",
|
||||||
|
certificatesDurations: 24 * 7,
|
||||||
|
expectRenewPeriod: time.Hour * 24,
|
||||||
|
expectRenewInterval: time.Hour,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "24 Hours certificates: 6 hours renew period, 10 minutes renew interval",
|
||||||
|
certificatesDurations: 24,
|
||||||
|
expectRenewPeriod: time.Hour * 6,
|
||||||
|
expectRenewInterval: time.Minute * 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
renewPeriod, renewInterval := getCertificateRenewDurations(test.certificatesDurations)
|
||||||
|
assert.Equal(t, test.expectRenewPeriod, renewPeriod)
|
||||||
|
assert.Equal(t, test.expectRenewInterval, renewInterval)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue