2018-03-05 19:54:04 +00:00
package acme
import (
2018-11-14 09:18:03 +00:00
"context"
2018-03-05 19:54:04 +00:00
"crypto/tls"
"crypto/x509"
2019-04-01 13:30:07 +00:00
"errors"
2018-03-05 19:54:04 +00:00
"fmt"
2018-07-31 09:50:03 +00:00
"net/url"
2018-03-05 19:54:04 +00:00
"reflect"
"strings"
"sync"
"time"
2020-09-04 08:52:03 +00:00
"github.com/go-acme/lego/v4/certificate"
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/challenge/dns01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns"
"github.com/go-acme/lego/v4/registration"
2020-08-17 16:04:03 +00:00
ptypes "github.com/traefik/paerser/types"
2020-09-16 13:46:04 +00:00
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/rules"
"github.com/traefik/traefik/v2/pkg/safe"
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
"github.com/traefik/traefik/v2/pkg/types"
"github.com/traefik/traefik/v2/pkg/version"
2018-03-05 19:54:04 +00:00
)
2020-07-07 12:42:03 +00:00
// oscpMustStaple enables OSCP stapling as from https://github.com/go-acme/lego/issues/270.
var oscpMustStaple = false
2018-03-05 19:54:04 +00:00
2020-05-11 10:06:07 +00:00
// Configuration holds ACME configuration provided by users.
2018-03-05 19:54:04 +00:00
type Configuration struct {
2021-11-10 11:06:09 +00:00
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" `
2020-12-01 09:40:05 +00:00
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" `
TLSChallenge * TLSChallenge ` description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true" `
2019-06-17 09:48:05 +00:00
}
// SetDefaults sets the default values.
func ( a * Configuration ) SetDefaults ( ) {
a . CAServer = lego . LEDirectoryProduction
a . Storage = "acme.json"
a . KeyType = "RSA4096"
2021-11-10 11:06:09 +00:00
a . CertificatesDuration = 3 * 30 * 24 // 90 Days
2018-03-05 19:54:04 +00:00
}
2019-07-19 09:52:04 +00:00
// CertAndStore allows mapping a TLS certificate to a TLS store.
type CertAndStore struct {
Certificate
Store string
}
2020-05-11 10:06:07 +00:00
// Certificate is a struct which contains all data needed from an ACME certificate.
2018-03-05 19:54:04 +00:00
type Certificate struct {
2019-07-01 09:30:05 +00:00
Domain types . Domain ` json:"domain,omitempty" toml:"domain,omitempty" yaml:"domain,omitempty" `
Certificate [ ] byte ` json:"certificate,omitempty" toml:"certificate,omitempty" yaml:"certificate,omitempty" `
Key [ ] byte ` json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" `
2018-03-05 19:54:04 +00:00
}
2020-12-01 09:40:05 +00:00
// EAB contains External Account Binding configuration.
type EAB struct {
2022-01-24 10:08:05 +00:00
Kid string ` description:"Key identifier from External CA." json:"kid,omitempty" toml:"kid,omitempty" yaml:"kid,omitempty" loggable:"false" `
HmacEncoded string ` description:"Base64 encoded HMAC key from External CA." json:"hmacEncoded,omitempty" toml:"hmacEncoded,omitempty" yaml:"hmacEncoded,omitempty" loggable:"false" `
2020-12-01 09:40:05 +00:00
}
// DNSChallenge contains DNS challenge configuration.
2018-03-05 19:54:04 +00:00
type DNSChallenge struct {
2020-10-30 11:44:05 +00:00
Provider string ` description:"Use a DNS-01 based challenge provider rather than HTTPS." json:"provider,omitempty" toml:"provider,omitempty" yaml:"provider,omitempty" export:"true" `
DelayBeforeCheck ptypes . Duration ` description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers." json:"delayBeforeCheck,omitempty" toml:"delayBeforeCheck,omitempty" yaml:"delayBeforeCheck,omitempty" export:"true" `
2020-08-17 16:04:03 +00:00
Resolvers [ ] string ` description:"Use following DNS servers to resolve the FQDN authority." json:"resolvers,omitempty" toml:"resolvers,omitempty" yaml:"resolvers,omitempty" `
2020-10-30 11:44:05 +00:00
DisablePropagationCheck bool ` description:"Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended]" json:"disablePropagationCheck,omitempty" toml:"disablePropagationCheck,omitempty" yaml:"disablePropagationCheck,omitempty" export:"true" `
2018-03-05 19:54:04 +00:00
}
2020-12-01 09:40:05 +00:00
// HTTPChallenge contains HTTP challenge configuration.
2018-03-05 19:54:04 +00:00
type HTTPChallenge struct {
2020-10-30 11:44:05 +00:00
EntryPoint string ` description:"HTTP challenge EntryPoint" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true" `
2018-03-05 19:54:04 +00:00
}
2020-12-01 09:40:05 +00:00
// TLSChallenge contains TLS challenge configuration.
2018-07-03 10:44:04 +00:00
type TLSChallenge struct { }
2018-11-14 09:18:03 +00:00
// Provider holds configurations of the provider.
type Provider struct {
* Configuration
2020-10-29 14:40:04 +00:00
ResolverName string
Store Store ` json:"store,omitempty" toml:"store,omitempty" yaml:"store,omitempty" `
TLSChallengeProvider challenge . Provider
HTTPChallengeProvider challenge . Provider
2019-07-19 09:52:04 +00:00
certificates [ ] * CertAndStore
2018-11-14 09:18:03 +00:00
account * Account
2019-01-07 17:30:06 +00:00
client * lego . Client
2019-07-19 09:52:04 +00:00
certsChan chan * CertAndStore
2019-07-10 07:26:04 +00:00
configurationChan chan <- dynamic . Message
2019-03-14 08:30:04 +00:00
tlsManager * traefiktls . Manager
2018-11-14 09:18:03 +00:00
clientMutex sync . Mutex
2019-07-10 07:26:04 +00:00
configFromListenerChan chan dynamic . Configuration
2018-11-14 09:18:03 +00:00
pool * safe . Pool
resolvingDomains map [ string ] struct { }
resolvingDomainsMutex sync . RWMutex
}
2020-05-11 10:06:07 +00:00
// SetTLSManager sets the tls manager to use.
2019-03-14 08:30:04 +00:00
func ( p * Provider ) SetTLSManager ( tlsManager * traefiktls . Manager ) {
p . tlsManager = tlsManager
}
2020-05-11 10:06:07 +00:00
// SetConfigListenerChan initializes the configFromListenerChan.
2019-07-10 07:26:04 +00:00
func ( p * Provider ) SetConfigListenerChan ( configFromListenerChan chan dynamic . Configuration ) {
2018-03-05 19:54:04 +00:00
p . configFromListenerChan = configFromListenerChan
}
2020-05-11 10:06:07 +00:00
// ListenConfiguration sets a new Configuration into the configFromListenerChan.
2019-07-10 07:26:04 +00:00
func ( p * Provider ) ListenConfiguration ( config dynamic . Configuration ) {
2018-07-03 10:44:04 +00:00
p . configFromListenerChan <- config
}
2020-05-11 10:06:07 +00:00
// Init for compatibility reason the BaseProvider implements an empty Init.
2018-11-27 16:42:04 +00:00
func ( p * Provider ) Init ( ) error {
2019-07-22 08:16:04 +00:00
ctx := log . With ( context . Background ( ) , log . Str ( log . ProviderName , p . ResolverName + ".acme" ) )
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
2019-03-14 08:30:04 +00:00
if len ( p . Configuration . Storage ) == 0 {
return errors . New ( "unable to initialize ACME provider with no storage location for the certificates" )
2018-07-11 07:08:03 +00:00
}
2021-11-10 11:06:09 +00:00
if p . CertificatesDuration < 1 {
return errors . New ( "cannot manage certificates with duration lower than 1 hour" )
}
2018-07-11 07:08:03 +00:00
var err error
2019-07-19 09:52:04 +00:00
p . account , err = p . Store . GetAccount ( p . ResolverName )
2018-07-11 07:08:03 +00:00
if err != nil {
2020-05-11 10:06:07 +00:00
return fmt . Errorf ( "unable to get ACME account: %w" , err )
2018-07-11 07:08:03 +00:00
}
// Reset Account if caServer changed, thus registration URI can be updated
2018-11-14 09:18:03 +00:00
if p . account != nil && p . account . Registration != nil && ! isAccountMatchingCaServer ( ctx , p . account . Registration . URI , p . CAServer ) {
logger . Info ( "Account URI does not match the current CAServer. The account will be reset." )
2018-07-11 07:08:03 +00:00
p . account = nil
}
2019-07-19 09:52:04 +00:00
p . certificates , err = p . Store . GetCertificates ( p . ResolverName )
2018-07-11 07:08:03 +00:00
if err != nil {
2020-05-11 10:06:07 +00:00
return fmt . Errorf ( "unable to get ACME certificates : %w" , err )
2018-07-11 07:08:03 +00:00
}
2018-08-20 07:40:03 +00:00
// Init the currently resolved domain map
p . resolvingDomains = make ( map [ string ] struct { } )
2018-07-11 07:08:03 +00:00
return nil
}
2020-07-07 12:42:03 +00:00
func isAccountMatchingCaServer ( ctx context . Context , accountURI , serverURI string ) bool {
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
2018-07-31 09:50:03 +00:00
aru , err := url . Parse ( accountURI )
if err != nil {
2018-11-14 09:18:03 +00:00
logger . Infof ( "Unable to parse account.Registration URL: %v" , err )
2018-07-31 09:50:03 +00:00
return false
}
2018-11-14 09:18:03 +00:00
2018-07-31 09:50:03 +00:00
cau , err := url . Parse ( serverURI )
if err != nil {
2018-11-14 09:18:03 +00:00
logger . Infof ( "Unable to parse CAServer URL: %v" , err )
2018-07-31 09:50:03 +00:00
return false
}
2018-11-14 09:18:03 +00:00
2018-07-31 09:50:03 +00:00
return cau . Hostname ( ) == aru . Hostname ( )
}
2022-02-07 10:58:04 +00:00
// ThrottleDuration returns the throttle duration.
func ( p * Provider ) ThrottleDuration ( ) time . Duration {
return 0
}
2018-07-03 10:44:04 +00:00
// Provide allows the file provider to provide configurations to traefik
// using the given Configuration channel.
2019-07-10 07:26:04 +00:00
func ( p * Provider ) Provide ( configurationChan chan <- dynamic . Message , pool * safe . Pool ) error {
2021-11-10 11:06:09 +00:00
ctx := log . With ( context . Background ( ) ,
log . Str ( log . ProviderName , p . ResolverName + ".acme" ) ,
log . Str ( "ACME CA" , p . Configuration . CAServer ) )
2018-11-14 09:18:03 +00:00
2018-07-03 10:44:04 +00:00
p . pool = pool
2018-07-11 07:08:03 +00:00
2018-11-14 09:18:03 +00:00
p . watchCertificate ( ctx )
p . watchNewDomains ( ctx )
2018-07-03 10:44:04 +00:00
p . configurationChan = configurationChan
p . refreshCertificates ( )
2021-11-10 11:06:09 +00:00
renewPeriod , renewInterval := getCertificateRenewDurations ( p . CertificatesDuration )
log . FromContext ( ctx ) . Debugf ( "Attempt to renew certificates %q before expiry and check every %q" ,
renewPeriod , renewInterval )
p . renewCertificates ( ctx , renewPeriod )
2018-07-03 10:44:04 +00:00
2021-11-10 11:06:09 +00:00
ticker := time . NewTicker ( renewInterval )
2020-02-03 16:56:04 +00:00
pool . GoCtx ( func ( ctxPool context . Context ) {
2018-07-03 10:44:04 +00:00
for {
select {
case <- ticker . C :
2021-11-10 11:06:09 +00:00
p . renewCertificates ( ctx , renewPeriod )
2020-02-03 16:56:04 +00:00
case <- ctxPool . Done ( ) :
2018-07-03 10:44:04 +00:00
ticker . Stop ( )
return
}
}
} )
return nil
}
2019-01-07 17:30:06 +00:00
func ( p * Provider ) getClient ( ) ( * lego . Client , error ) {
2018-07-03 10:44:04 +00:00
p . clientMutex . Lock ( )
defer p . clientMutex . Unlock ( )
2019-07-22 08:16:04 +00:00
ctx := log . With ( context . Background ( ) , log . Str ( log . ProviderName , p . ResolverName + ".acme" ) )
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
2018-07-03 10:44:04 +00:00
if p . client != nil {
return p . client , nil
}
2018-11-14 09:18:03 +00:00
account , err := p . initAccount ( ctx )
2018-07-03 10:44:04 +00:00
if err != nil {
return nil , err
}
2018-11-14 09:18:03 +00:00
logger . Debug ( "Building ACME client..." )
2018-07-03 10:44:04 +00:00
2019-06-17 09:48:05 +00:00
caServer := lego . LEDirectoryProduction
2018-07-03 10:44:04 +00:00
if len ( p . CAServer ) > 0 {
caServer = p . CAServer
}
2018-11-14 09:18:03 +00:00
logger . Debug ( caServer )
2018-07-03 10:44:04 +00:00
2019-01-07 17:30:06 +00:00
config := lego . NewConfig ( account )
config . CADirURL = caServer
2020-10-08 10:58:04 +00:00
config . Certificate . KeyType = GetKeyType ( ctx , p . KeyType )
2019-01-07 17:30:06 +00:00
config . UserAgent = fmt . Sprintf ( "containous-traefik/%s" , version . Version )
client , err := lego . NewClient ( config )
2018-07-03 10:44:04 +00:00
if err != nil {
return nil , err
}
// New users will need to register; be sure to save it
if account . GetRegistration ( ) == nil {
2020-12-01 09:40:05 +00:00
reg , errR := p . register ( ctx , client )
2018-11-14 09:18:03 +00:00
if errR != nil {
return nil , errR
2018-03-05 19:54:04 +00:00
}
2018-07-03 10:44:04 +00:00
account . Registration = reg
}
2018-03-05 19:54:04 +00:00
2018-07-03 10:44:04 +00:00
// Save the account once before all the certificates generation/storing
// No certificate can be generated if account is not initialized
2019-07-19 09:52:04 +00:00
err = p . Store . SaveAccount ( p . ResolverName , account )
2018-07-03 10:44:04 +00:00
if err != nil {
2018-03-05 19:54:04 +00:00
return nil , err
}
2019-07-19 09:52:04 +00:00
if ( p . DNSChallenge == nil || len ( p . DNSChallenge . Provider ) == 0 ) &&
( p . HTTPChallenge == nil || len ( p . HTTPChallenge . EntryPoint ) == 0 ) &&
p . TLSChallenge == nil {
return nil , errors . New ( "ACME challenge not specified, please select TLS or HTTP or DNS Challenge" )
}
if p . DNSChallenge != nil && len ( p . DNSChallenge . Provider ) > 0 {
2018-11-14 09:18:03 +00:00
logger . Debugf ( "Using DNS Challenge provider: %s" , p . DNSChallenge . Provider )
2018-03-05 19:54:04 +00:00
2019-01-07 17:30:06 +00:00
var provider challenge . Provider
2018-07-03 10:44:04 +00:00
provider , err = dns . NewDNSChallengeProviderByName ( p . DNSChallenge . Provider )
if err != nil {
return nil , err
}
2019-01-07 17:30:06 +00:00
err = client . Challenge . SetDNS01Provider ( provider ,
dns01 . CondOption ( len ( p . DNSChallenge . Resolvers ) > 0 , dns01 . AddRecursiveNameservers ( p . DNSChallenge . Resolvers ) ) ,
2020-09-04 08:52:03 +00:00
dns01 . WrapPreCheck ( func ( domain , fqdn , value string , check dns01 . PreCheckFunc ) ( bool , error ) {
if p . DNSChallenge . DelayBeforeCheck > 0 {
logger . Debugf ( "Delaying %d rather than validating DNS propagation now." , p . DNSChallenge . DelayBeforeCheck )
time . Sleep ( time . Duration ( p . DNSChallenge . DelayBeforeCheck ) )
}
2020-09-23 10:24:03 +00:00
if p . DNSChallenge . DisablePropagationCheck {
return true , nil
}
2020-09-04 08:52:03 +00:00
return check ( fqdn , value )
} ) ,
2019-01-07 17:30:06 +00:00
)
2018-07-03 10:44:04 +00:00
if err != nil {
return nil , err
}
2019-07-19 09:52:04 +00:00
}
2018-07-31 10:32:04 +00:00
2019-07-19 09:52:04 +00:00
if p . HTTPChallenge != nil && len ( p . HTTPChallenge . EntryPoint ) > 0 {
2018-11-14 09:18:03 +00:00
logger . Debug ( "Using HTTP Challenge provider." )
2018-07-03 10:44:04 +00:00
2020-10-29 14:40:04 +00:00
err = client . Challenge . SetHTTP01Provider ( p . HTTPChallengeProvider )
2018-07-03 10:44:04 +00:00
if err != nil {
return nil , err
}
2019-07-19 09:52:04 +00:00
}
2019-02-05 16:10:03 +00:00
2019-07-19 09:52:04 +00:00
if p . TLSChallenge != nil {
2018-11-14 09:18:03 +00:00
logger . Debug ( "Using TLS Challenge provider." )
2018-07-03 10:44:04 +00:00
2020-10-29 14:40:04 +00:00
err = client . Challenge . SetTLSALPN01Provider ( p . TLSChallengeProvider )
2018-07-03 10:44:04 +00:00
if err != nil {
return nil , err
}
}
p . client = client
return p . client , nil
}
2018-11-14 09:18:03 +00:00
func ( p * Provider ) initAccount ( ctx context . Context ) ( * Account , error ) {
2018-07-03 10:44:04 +00:00
if p . account == nil || len ( p . account . Email ) == 0 {
var err error
2018-11-14 09:18:03 +00:00
p . account , err = NewAccount ( ctx , p . Email , p . KeyType )
2018-07-03 10:44:04 +00:00
if err != nil {
return nil , err
}
}
2018-08-08 05:58:03 +00:00
// Set the KeyType if not already defined in the account
if len ( p . account . KeyType ) == 0 {
2018-11-14 09:18:03 +00:00
p . account . KeyType = GetKeyType ( ctx , p . KeyType )
2018-08-08 05:58:03 +00:00
}
2018-07-03 10:44:04 +00:00
return p . account , nil
2018-03-05 19:54:04 +00:00
}
2020-12-01 09:40:05 +00:00
func ( p * Provider ) register ( ctx context . Context , client * lego . Client ) ( * registration . Resource , error ) {
logger := log . FromContext ( ctx )
if p . EAB != nil {
logger . Info ( "Register with external account binding..." )
eabOptions := registration . RegisterEABOptions { TermsOfServiceAgreed : true , Kid : p . EAB . Kid , HmacEncoded : p . EAB . HmacEncoded }
return client . Registration . RegisterWithExternalAccountBinding ( eabOptions )
}
logger . Info ( "Register..." )
return client . Registration . Register ( registration . RegisterOptions { TermsOfServiceAgreed : true } )
}
2019-07-19 09:52:04 +00:00
func ( p * Provider ) resolveDomains ( ctx context . Context , domains [ ] string , tlsStore string ) {
2019-03-14 08:30:04 +00:00
if len ( domains ) == 0 {
log . FromContext ( ctx ) . Debug ( "No domain parsed in provider ACME" )
return
}
2019-10-18 08:54:05 +00:00
log . FromContext ( ctx ) . Debugf ( "Try to challenge certificate for domain %v found in HostSNI rule" , domains )
2019-03-14 08:30:04 +00:00
var domain types . Domain
if len ( domains ) > 0 {
domain = types . Domain { Main : domains [ 0 ] }
if len ( domains ) > 1 {
domain . SANs = domains [ 1 : ]
}
safe . Go ( func ( ) {
2019-07-19 09:52:04 +00:00
if _ , err := p . resolveCertificate ( ctx , domain , tlsStore ) ; err != nil {
2019-03-14 08:30:04 +00:00
log . FromContext ( ctx ) . Errorf ( "Unable to obtain ACME certificate for domains %q: %v" , strings . Join ( domains , "," ) , err )
}
} )
}
}
2018-11-14 09:18:03 +00:00
func ( p * Provider ) watchNewDomains ( ctx context . Context ) {
2020-02-03 16:56:04 +00:00
p . pool . GoCtx ( func ( ctxPool context . Context ) {
2018-03-05 19:54:04 +00:00
for {
select {
case config := <- p . configFromListenerChan :
2019-03-14 08:30:04 +00:00
if config . TCP != nil {
for routerName , route := range config . TCP . Routers {
2019-07-19 09:52:04 +00:00
if route . TLS == nil || route . TLS . CertResolver != p . ResolverName {
2019-03-26 10:12:04 +00:00
continue
}
2019-09-13 17:28:04 +00:00
2019-03-14 08:30:04 +00:00
ctxRouter := log . With ( ctx , log . Str ( log . RouterName , routerName ) , log . Str ( log . Rule , route . Rule ) )
2019-09-13 17:28:04 +00:00
logger := log . FromContext ( ctxRouter )
2019-03-14 08:30:04 +00:00
2019-07-19 09:52:04 +00:00
if len ( route . TLS . Domains ) > 0 {
for _ , domain := range route . TLS . Domains {
if domain . Main != dns01 . UnFqdn ( domain . Main ) {
2019-09-13 17:28:04 +00:00
logger . Warnf ( "FQDN detected, please remove the trailing dot: %s" , domain . Main )
2019-07-19 09:52:04 +00:00
}
for _ , san := range domain . SANs {
if san != dns01 . UnFqdn ( san ) {
2019-09-13 17:28:04 +00:00
logger . Warnf ( "FQDN detected, please remove the trailing dot: %s" , san )
2019-07-19 09:52:04 +00:00
}
}
}
domains := deleteUnnecessaryDomains ( ctxRouter , route . TLS . Domains )
for i := 0 ; i < len ( domains ) ; i ++ {
domain := domains [ i ]
safe . Go ( func ( ) {
2021-06-14 08:06:05 +00:00
if _ , err := p . resolveCertificate ( ctx , domain , traefiktls . DefaultTLSStoreName ) ; err != nil {
2019-07-22 08:16:04 +00:00
log . WithoutContext ( ) . WithField ( log . ProviderName , p . ResolverName + ".acme" ) .
2019-07-19 09:52:04 +00:00
Errorf ( "Unable to obtain ACME certificate for domains %q : %v" , strings . Join ( domain . ToStrArray ( ) , "," ) , err )
}
} )
}
} else {
domains , err := rules . ParseHostSNI ( route . Rule )
if err != nil {
2019-09-13 17:28:04 +00:00
logger . Errorf ( "Error parsing domains in provider ACME: %v" , err )
2019-07-19 09:52:04 +00:00
continue
}
2021-06-14 08:06:05 +00:00
p . resolveDomains ( ctxRouter , domains , traefiktls . DefaultTLSStoreName )
2019-03-14 08:30:04 +00:00
}
}
}
for routerName , route := range config . HTTP . Routers {
2019-07-19 09:52:04 +00:00
if route . TLS == nil || route . TLS . CertResolver != p . ResolverName {
2019-03-26 10:12:04 +00:00
continue
}
2021-02-11 15:32:03 +00:00
2019-03-14 08:30:04 +00:00
ctxRouter := log . With ( ctx , log . Str ( log . RouterName , routerName ) , log . Str ( log . Rule , route . Rule ) )
2018-11-14 09:18:03 +00:00
2019-07-19 09:52:04 +00:00
if len ( route . TLS . Domains ) > 0 {
domains := deleteUnnecessaryDomains ( ctxRouter , route . TLS . Domains )
for i := 0 ; i < len ( domains ) ; i ++ {
domain := domains [ i ]
safe . Go ( func ( ) {
2021-06-14 08:06:05 +00:00
if _ , err := p . resolveCertificate ( ctx , domain , traefiktls . DefaultTLSStoreName ) ; err != nil {
2019-07-22 08:16:04 +00:00
log . WithoutContext ( ) . WithField ( log . ProviderName , p . ResolverName + ".acme" ) .
2019-07-19 09:52:04 +00:00
Errorf ( "Unable to obtain ACME certificate for domains %q : %v" , strings . Join ( domain . ToStrArray ( ) , "," ) , err )
}
} )
}
} else {
domains , err := rules . ParseDomains ( route . Rule )
if err != nil {
log . FromContext ( ctxRouter ) . Errorf ( "Error parsing domains in provider ACME: %v" , err )
continue
}
2021-06-14 08:06:05 +00:00
p . resolveDomains ( ctxRouter , domains , traefiktls . DefaultTLSStoreName )
2018-11-14 09:18:03 +00:00
}
2018-03-05 19:54:04 +00:00
}
2020-02-03 16:56:04 +00:00
case <- ctxPool . Done ( ) :
2018-03-05 19:54:04 +00:00
return
}
}
} )
}
2019-07-19 09:52:04 +00:00
func ( p * Provider ) resolveCertificate ( ctx context . Context , domain types . Domain , tlsStore string ) ( * certificate . Resource , error ) {
domains , err := p . getValidDomains ( ctx , domain )
2018-03-26 12:12:03 +00:00
if err != nil {
return nil , err
2018-03-05 19:54:04 +00:00
}
2020-07-08 10:54:04 +00:00
// Check if provided certificates are not already in progress and lock them if needed
2019-07-19 09:52:04 +00:00
uncheckedDomains := p . getUncheckedDomains ( ctx , domains , tlsStore )
2018-03-06 09:12:04 +00:00
if len ( uncheckedDomains ) == 0 {
2018-03-05 19:54:04 +00:00
return nil , nil
}
2021-02-11 15:32:03 +00:00
2018-08-20 07:40:03 +00:00
defer p . removeResolvingDomains ( uncheckedDomains )
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
logger . Debugf ( "Loading ACME certificates %+v..." , uncheckedDomains )
2018-06-15 14:42:03 +00:00
2018-03-05 19:54:04 +00:00
client , err := p . getClient ( )
if err != nil {
2020-05-11 10:06:07 +00:00
return nil , fmt . Errorf ( "cannot get ACME client %w" , err )
2018-03-05 19:54:04 +00:00
}
2019-05-10 07:58:05 +00:00
request := certificate . ObtainRequest {
2021-05-20 13:08:12 +00:00
Domains : domains ,
Bundle : true ,
MustStaple : oscpMustStaple ,
PreferredChain : p . PreferredChain ,
2018-07-31 10:32:04 +00:00
}
2018-04-16 17:34:04 +00:00
2019-05-10 07:58:05 +00:00
cert , err := client . Certificate . Obtain ( request )
2018-05-15 15:28:02 +00:00
if err != nil {
2020-05-11 10:06:07 +00:00
return nil , fmt . Errorf ( "unable to generate a certificate for the domains %v: %w" , uncheckedDomains , err )
2018-07-31 10:32:04 +00:00
}
2019-01-07 17:30:06 +00:00
if cert == nil {
2018-07-31 10:32:04 +00:00
return nil , fmt . Errorf ( "domains %v do not generate a certificate" , uncheckedDomains )
2018-03-05 19:54:04 +00:00
}
2019-01-07 17:30:06 +00:00
if len ( cert . Certificate ) == 0 || len ( cert . PrivateKey ) == 0 {
return nil , fmt . Errorf ( "domains %v generate certificate with no value: %v" , uncheckedDomains , cert )
2018-04-16 17:34:04 +00:00
}
2018-06-15 14:42:03 +00:00
2018-11-14 09:18:03 +00:00
logger . Debugf ( "Certificates obtained for domains %+v" , uncheckedDomains )
2018-04-16 17:34:04 +00:00
2018-03-06 09:12:04 +00:00
if len ( uncheckedDomains ) > 1 {
domain = types . Domain { Main : uncheckedDomains [ 0 ] , SANs : uncheckedDomains [ 1 : ] }
} else {
domain = types . Domain { Main : uncheckedDomains [ 0 ] }
}
2019-07-19 09:52:04 +00:00
p . addCertificateForDomain ( domain , cert . Certificate , cert . PrivateKey , tlsStore )
2018-03-05 19:54:04 +00:00
2019-01-07 17:30:06 +00:00
return cert , nil
2018-03-05 19:54:04 +00:00
}
2018-08-20 07:40:03 +00:00
func ( p * Provider ) removeResolvingDomains ( resolvingDomains [ ] string ) {
p . resolvingDomainsMutex . Lock ( )
defer p . resolvingDomainsMutex . Unlock ( )
for _ , domain := range resolvingDomains {
delete ( p . resolvingDomains , domain )
}
}
2020-07-07 12:42:03 +00:00
func ( p * Provider ) addCertificateForDomain ( domain types . Domain , certificate , key [ ] byte , tlsStore string ) {
2019-07-19 09:52:04 +00:00
p . certsChan <- & CertAndStore { Certificate : Certificate { Certificate : certificate , Key : key , Domain : domain } , Store : tlsStore }
2018-03-05 19:54:04 +00:00
}
2021-11-10 11:06:09 +00:00
// 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
}
}
2018-07-03 10:44:04 +00:00
// deleteUnnecessaryDomains deletes from the configuration :
// - Duplicated domains
2020-05-11 10:06:07 +00:00
// - Domains which are checked by wildcard domain.
2019-07-19 09:52:04 +00:00
func deleteUnnecessaryDomains ( ctx context . Context , domains [ ] types . Domain ) [ ] types . Domain {
2018-07-03 10:44:04 +00:00
var newDomains [ ] types . Domain
2018-03-05 19:54:04 +00:00
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
2019-07-19 09:52:04 +00:00
for idxDomainToCheck , domainToCheck := range domains {
2018-07-03 10:44:04 +00:00
keepDomain := true
2018-03-05 19:54:04 +00:00
2019-07-19 09:52:04 +00:00
for idxDomain , domain := range domains {
2018-07-03 10:44:04 +00:00
if idxDomainToCheck == idxDomain {
continue
}
2018-03-05 19:54:04 +00:00
2018-07-03 10:44:04 +00:00
if reflect . DeepEqual ( domain , domainToCheck ) {
if idxDomainToCheck > idxDomain {
2018-11-14 09:18:03 +00:00
logger . Warnf ( "The domain %v is duplicated in the configuration but will be process by ACME provider only once." , domainToCheck )
2018-07-03 10:44:04 +00:00
keepDomain = false
}
break
2018-03-05 19:54:04 +00:00
}
2018-07-03 10:44:04 +00:00
// Check if CN or SANS to check already exists
// or can not be checked by a wildcard
var newDomainsToCheck [ ] string
for _ , domainProcessed := range domainToCheck . ToStrArray ( ) {
if idxDomain < idxDomainToCheck && isDomainAlreadyChecked ( domainProcessed , domain . ToStrArray ( ) ) {
// The domain is duplicated in a CN
2018-11-14 09:18:03 +00:00
logger . Warnf ( "Domain %q is duplicated in the configuration or validated by the domain %v. It will be processed once." , domainProcessed , domain )
2018-07-03 10:44:04 +00:00
continue
} else if domain . Main != domainProcessed && strings . HasPrefix ( domain . Main , "*" ) && isDomainAlreadyChecked ( domainProcessed , [ ] string { domain . Main } ) {
// Check if a wildcard can validate the domain
2018-11-14 09:18:03 +00:00
logger . Warnf ( "Domain %q will not be processed by ACME provider because it is validated by the wildcard %q" , domainProcessed , domain . Main )
2018-07-03 10:44:04 +00:00
continue
}
newDomainsToCheck = append ( newDomainsToCheck , domainProcessed )
}
2018-03-05 19:54:04 +00:00
2018-07-03 10:44:04 +00:00
// Delete the domain if both Main and SANs can be validated by the wildcard domain
// otherwise keep the unchecked values
if newDomainsToCheck == nil {
keepDomain = false
break
2018-03-05 19:54:04 +00:00
}
2018-07-03 10:44:04 +00:00
domainToCheck . Set ( newDomainsToCheck )
2018-03-05 19:54:04 +00:00
}
2018-07-03 10:44:04 +00:00
if keepDomain {
newDomains = append ( newDomains , domainToCheck )
}
}
2018-03-05 19:54:04 +00:00
2019-07-19 09:52:04 +00:00
return newDomains
2018-03-05 19:54:04 +00:00
}
2018-11-14 09:18:03 +00:00
func ( p * Provider ) watchCertificate ( ctx context . Context ) {
2019-07-19 09:52:04 +00:00
p . certsChan = make ( chan * CertAndStore )
2018-11-14 09:18:03 +00:00
2020-02-03 16:56:04 +00:00
p . pool . GoCtx ( func ( ctxPool context . Context ) {
2018-03-05 19:54:04 +00:00
for {
select {
case cert := <- p . certsChan :
certUpdated := false
for _ , domainsCertificate := range p . certificates {
2019-07-19 09:52:04 +00:00
if reflect . DeepEqual ( cert . Domain , domainsCertificate . Certificate . Domain ) {
2018-03-05 19:54:04 +00:00
domainsCertificate . Certificate = cert . Certificate
certUpdated = true
break
}
}
if ! certUpdated {
p . certificates = append ( p . certificates , cert )
}
2018-07-03 10:44:04 +00:00
err := p . saveCertificates ( )
if err != nil {
2018-11-14 09:18:03 +00:00
log . FromContext ( ctx ) . Error ( err )
2018-07-03 10:44:04 +00:00
}
2020-02-03 16:56:04 +00:00
case <- ctxPool . Done ( ) :
2018-03-05 19:54:04 +00:00
return
}
}
} )
}
2018-07-03 10:44:04 +00:00
func ( p * Provider ) saveCertificates ( ) error {
2019-07-19 09:52:04 +00:00
err := p . Store . SaveCertificates ( p . ResolverName , p . certificates )
2018-07-03 10:44:04 +00:00
2018-03-05 19:54:04 +00:00
p . refreshCertificates ( )
2018-07-03 10:44:04 +00:00
return err
2018-03-05 19:54:04 +00:00
}
func ( p * Provider ) refreshCertificates ( ) {
2019-07-10 07:26:04 +00:00
conf := dynamic . Message {
2019-07-22 08:16:04 +00:00
ProviderName : p . ResolverName + ".acme" ,
2019-07-10 07:26:04 +00:00
Configuration : & dynamic . Configuration {
HTTP : & dynamic . HTTPConfiguration {
Routers : map [ string ] * dynamic . Router { } ,
Middlewares : map [ string ] * dynamic . Middleware { } ,
Services : map [ string ] * dynamic . Service { } ,
2019-03-14 08:30:04 +00:00
} ,
2019-07-10 07:26:04 +00:00
TLS : & dynamic . TLSConfiguration { } ,
2018-03-05 19:54:04 +00:00
} ,
}
for _ , cert := range p . certificates {
2019-06-27 21:58:03 +00:00
certConf := & traefiktls . CertAndStores {
Certificate : traefiktls . Certificate {
2019-07-19 09:52:04 +00:00
CertFile : traefiktls . FileOrContent ( cert . Certificate . Certificate ) ,
2019-06-27 21:58:03 +00:00
KeyFile : traefiktls . FileOrContent ( cert . Key ) ,
} ,
2019-07-19 09:52:04 +00:00
Stores : [ ] string { cert . Store } ,
2019-06-27 21:58:03 +00:00
}
conf . Configuration . TLS . Certificates = append ( conf . Configuration . TLS . Certificates , certConf )
2018-03-05 19:54:04 +00:00
}
2019-06-27 21:58:03 +00:00
2018-11-14 09:18:03 +00:00
p . configurationChan <- conf
2018-03-05 19:54:04 +00:00
}
2021-11-10 11:06:09 +00:00
func ( p * Provider ) renewCertificates ( ctx context . Context , renewPeriod time . Duration ) {
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
logger . Info ( "Testing certificate renew..." )
2019-01-07 17:30:06 +00:00
for _ , cert := range p . certificates {
2019-07-19 09:52:04 +00:00
crt , err := getX509Certificate ( ctx , & cert . Certificate )
2018-03-05 19:54:04 +00:00
// If there's an error, we assume the cert is broken, and needs update
2021-11-10 11:06:09 +00:00
if err != nil || crt == nil || crt . NotAfter . Before ( time . Now ( ) . Add ( renewPeriod ) ) {
2018-03-05 19:54:04 +00:00
client , err := p . getClient ( )
if err != nil {
2019-01-07 17:30:06 +00:00
logger . Infof ( "Error renewing certificate from LE : %+v, %v" , cert . Domain , err )
2018-03-05 19:54:04 +00:00
continue
}
2018-04-16 17:34:04 +00:00
2019-01-07 17:30:06 +00:00
logger . Infof ( "Renewing certificate from LE : %+v" , cert . Domain )
2018-04-16 17:34:04 +00:00
2019-01-07 17:30:06 +00:00
renewedCert , err := client . Certificate . Renew ( certificate . Resource {
Domain : cert . Domain . Main ,
PrivateKey : cert . Key ,
2019-07-19 09:52:04 +00:00
Certificate : cert . Certificate . Certificate ,
2020-09-04 08:52:03 +00:00
} , true , oscpMustStaple , p . PreferredChain )
2018-03-05 19:54:04 +00:00
if err != nil {
2019-01-07 17:30:06 +00:00
logger . Errorf ( "Error renewing certificate from LE: %v, %v" , cert . Domain , err )
2018-03-05 19:54:04 +00:00
continue
}
2018-04-16 17:34:04 +00:00
if len ( renewedCert . Certificate ) == 0 || len ( renewedCert . PrivateKey ) == 0 {
2019-01-07 17:30:06 +00:00
logger . Errorf ( "domains %v renew certificate with no value: %v" , cert . Domain . ToStrArray ( ) , cert )
2018-04-16 17:34:04 +00:00
continue
}
2019-07-19 09:52:04 +00:00
p . addCertificateForDomain ( cert . Domain , renewedCert . Certificate , renewedCert . PrivateKey , cert . Store )
2018-03-05 19:54:04 +00:00
}
}
}
2018-03-06 09:12:04 +00:00
// Get provided certificate which check a domains list (Main and SANs)
2020-05-11 10:06:07 +00:00
// from static and dynamic provided certificates.
2019-07-19 09:52:04 +00:00
func ( p * Provider ) getUncheckedDomains ( ctx context . Context , domainsToCheck [ ] string , tlsStore string ) [ ] string {
2020-07-08 10:54:04 +00:00
p . resolvingDomainsMutex . Lock ( )
defer p . resolvingDomainsMutex . Unlock ( )
2018-08-20 07:40:03 +00:00
2018-11-14 09:18:03 +00:00
log . FromContext ( ctx ) . Debugf ( "Looking for provided certificate(s) to validate %q..." , domainsToCheck )
2018-03-06 09:12:04 +00:00
2019-07-19 09:52:04 +00:00
allDomains := p . tlsManager . GetStore ( tlsStore ) . GetAllDomains ( )
2018-03-26 12:12:03 +00:00
// Get ACME certificates
2019-01-07 17:30:06 +00:00
for _ , cert := range p . certificates {
allDomains = append ( allDomains , strings . Join ( cert . Domain . ToStrArray ( ) , "," ) )
2018-03-26 12:12:03 +00:00
}
2018-08-20 07:40:03 +00:00
// Get currently resolved domains
for domain := range p . resolvingDomains {
2018-08-21 09:43:34 +00:00
allDomains = append ( allDomains , domain )
2018-08-20 07:40:03 +00:00
}
2020-07-08 10:54:04 +00:00
uncheckedDomains := searchUncheckedDomains ( ctx , domainsToCheck , allDomains )
// Lock domains that will be resolved by this routine
for _ , domain := range uncheckedDomains {
p . resolvingDomains [ domain ] = struct { } { }
}
return uncheckedDomains
2018-03-06 09:12:04 +00:00
}
2020-07-07 12:42:03 +00:00
func searchUncheckedDomains ( ctx context . Context , domainsToCheck , existentDomains [ ] string ) [ ] string {
2018-03-27 14:18:03 +00:00
var uncheckedDomains [ ] string
2018-03-26 12:12:03 +00:00
for _ , domainToCheck := range domainsToCheck {
if ! isDomainAlreadyChecked ( domainToCheck , existentDomains ) {
2018-03-06 09:12:04 +00:00
uncheckedDomains = append ( uncheckedDomains , domainToCheck )
2018-03-05 19:54:04 +00:00
}
}
2018-07-03 10:44:04 +00:00
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
2018-03-06 09:12:04 +00:00
if len ( uncheckedDomains ) == 0 {
2018-11-14 09:18:03 +00:00
logger . Debugf ( "No ACME certificate generation required for domains %q." , domainsToCheck )
2018-03-06 09:12:04 +00:00
} else {
2018-11-14 09:18:03 +00:00
logger . Debugf ( "Domains %q need ACME certificates generation for domains %q." , domainsToCheck , strings . Join ( uncheckedDomains , "," ) )
2018-03-06 09:12:04 +00:00
}
return uncheckedDomains
2018-03-05 19:54:04 +00:00
}
2019-01-07 17:30:06 +00:00
func getX509Certificate ( ctx context . Context , cert * Certificate ) ( * x509 . Certificate , error ) {
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
2019-01-07 17:30:06 +00:00
tlsCert , err := tls . X509KeyPair ( cert . Certificate , cert . Key )
2018-03-05 19:54:04 +00:00
if err != nil {
2019-01-07 17:30:06 +00:00
logger . Errorf ( "Failed to load TLS key pair from ACME certificate for domain %q (SAN : %q), certificate will be renewed : %v" , cert . Domain . Main , strings . Join ( cert . Domain . SANs , "," ) , err )
2018-03-05 19:54:04 +00:00
return nil , err
}
2018-07-03 10:44:04 +00:00
crt := tlsCert . Leaf
2018-03-05 19:54:04 +00:00
if crt == nil {
crt , err = x509 . ParseCertificate ( tlsCert . Certificate [ 0 ] )
if err != nil {
2019-01-07 17:30:06 +00:00
logger . Errorf ( "Failed to parse TLS key pair from ACME certificate for domain %q (SAN : %q), certificate will be renewed : %v" , cert . Domain . Main , strings . Join ( cert . Domain . SANs , "," ) , err )
2018-03-05 19:54:04 +00:00
}
}
2018-07-03 10:44:04 +00:00
2018-03-05 19:54:04 +00:00
return crt , err
}
2018-03-26 12:12:03 +00:00
2020-05-11 10:06:07 +00:00
// getValidDomains checks if given domain is allowed to generate a ACME certificate and return it.
2019-07-19 09:52:04 +00:00
func ( p * Provider ) getValidDomains ( ctx context . Context , domain types . Domain ) ( [ ] string , error ) {
2018-03-26 12:12:03 +00:00
domains := domain . ToStrArray ( )
if len ( domains ) == 0 {
return nil , errors . New ( "unable to generate a certificate in ACME provider when no domain is given" )
}
2018-07-03 10:44:04 +00:00
2018-03-26 12:12:03 +00:00
if strings . HasPrefix ( domain . Main , "*" ) {
if p . DNSChallenge == nil {
return nil , fmt . Errorf ( "unable to generate a wildcard certificate in ACME provider for domain %q : ACME needs a DNSChallenge" , strings . Join ( domains , "," ) )
}
2018-07-03 10:44:04 +00:00
2018-04-11 15:16:07 +00:00
if strings . HasPrefix ( domain . Main , "*.*" ) {
return nil , fmt . Errorf ( "unable to generate a wildcard certificate in ACME provider for domain %q : ACME does not allow '*.*' wildcard domain" , strings . Join ( domains , "," ) )
2018-03-26 12:12:03 +00:00
}
2018-04-11 15:16:07 +00:00
}
2018-07-03 10:44:04 +00:00
2018-10-11 08:50:03 +00:00
var cleanDomains [ ] string
for _ , domain := range domains {
canonicalDomain := types . CanonicalDomain ( domain )
2019-01-07 17:30:06 +00:00
cleanDomain := dns01 . UnFqdn ( canonicalDomain )
2018-10-11 08:50:03 +00:00
if canonicalDomain != cleanDomain {
2018-11-14 09:18:03 +00:00
log . FromContext ( ctx ) . Warnf ( "FQDN detected, please remove the trailing dot: %s" , canonicalDomain )
2018-10-11 08:50:03 +00:00
}
cleanDomains = append ( cleanDomains , cleanDomain )
}
return cleanDomains , nil
2018-03-26 12:12:03 +00:00
}
func isDomainAlreadyChecked ( domainToCheck string , existentDomains [ ] string ) bool {
for _ , certDomains := range existentDomains {
for _ , certDomain := range strings . Split ( certDomains , "," ) {
2018-03-27 14:18:03 +00:00
if types . MatchDomain ( domainToCheck , certDomain ) {
2018-03-26 12:12:03 +00:00
return true
}
}
}
return false
}