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"
"io/ioutil"
fmtlog "log"
2018-07-31 09:50:03 +00:00
"net/url"
2018-03-05 19:54:04 +00:00
"reflect"
"strings"
"sync"
"time"
2019-02-04 15:38:08 +00:00
"github.com/cenkalti/backoff"
2018-07-31 17:28:03 +00:00
"github.com/containous/flaeg/parse"
2019-03-15 08:42:03 +00:00
"github.com/containous/traefik/pkg/config"
"github.com/containous/traefik/pkg/log"
"github.com/containous/traefik/pkg/rules"
"github.com/containous/traefik/pkg/safe"
traefiktls "github.com/containous/traefik/pkg/tls"
"github.com/containous/traefik/pkg/types"
"github.com/containous/traefik/pkg/version"
2019-03-14 10:04:04 +00:00
"github.com/go-acme/lego/certificate"
"github.com/go-acme/lego/challenge"
"github.com/go-acme/lego/challenge/dns01"
"github.com/go-acme/lego/lego"
legolog "github.com/go-acme/lego/log"
"github.com/go-acme/lego/providers/dns"
"github.com/go-acme/lego/registration"
2018-06-15 14:08:03 +00:00
"github.com/sirupsen/logrus"
2018-03-05 19:54:04 +00:00
)
var (
2019-03-14 10:04:04 +00:00
// oscpMustStaple enables OSCP stapling as from https://github.com/go-acme/lego/issues/270
2018-11-14 09:18:03 +00:00
oscpMustStaple = false
2018-03-05 19:54:04 +00:00
)
// Configuration holds ACME configuration provided by users
type Configuration struct {
Email string ` description:"Email address used for registration" `
ACMELogging bool ` description:"Enable debug logging of ACME actions." `
CAServer string ` description:"CA server to use." `
Storage string ` description:"Storage to use." `
EntryPoint string ` description:"EntryPoint to use." `
2018-05-16 09:44:03 +00:00
KeyType string ` description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. Default to 'RSA4096'" `
2018-03-05 19:54:04 +00:00
OnHostRule bool ` description:"Enable certificate generation on frontends Host rules." `
2018-04-11 15:16:07 +00:00
DNSChallenge * DNSChallenge ` description:"Activate DNS-01 Challenge" `
2018-03-05 19:54:04 +00:00
HTTPChallenge * HTTPChallenge ` description:"Activate HTTP-01 Challenge" `
2018-07-03 10:44:04 +00:00
TLSChallenge * TLSChallenge ` description:"Activate TLS-ALPN-01 Challenge" `
2018-03-26 12:12:03 +00:00
Domains [ ] types . Domain ` description:"CN and SANs (alternative domains) to each main domain using format: --acme.domains='main.com,san1.com,san2.com' --acme.domains='*.main.net'. No SANs for wildcards domain. Wildcard domains only accepted with DNSChallenge" `
2018-03-05 19:54:04 +00:00
}
// Certificate is a struct which contains all data needed from an ACME certificate
type Certificate struct {
Domain types . Domain
Certificate [ ] byte
Key [ ] byte
}
// DNSChallenge contains DNS challenge Configuration
type DNSChallenge struct {
2018-10-25 15:38:04 +00:00
Provider string ` description:"Use a DNS-01 based challenge provider rather than HTTPS." `
2018-10-30 11:34:00 +00:00
DelayBeforeCheck parse . Duration ` description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers." `
2018-10-25 15:38:04 +00:00
Resolvers types . DNSResolvers ` description:"Use following DNS servers to resolve the FQDN authority." `
DisablePropagationCheck bool ` description:"Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended]" `
2018-11-14 09:18:03 +00:00
preCheckTimeout time . Duration
preCheckInterval time . Duration
2018-03-05 19:54:04 +00:00
}
// HTTPChallenge contains HTTP challenge Configuration
type HTTPChallenge struct {
EntryPoint string ` description:"HTTP challenge EntryPoint" `
}
2018-07-03 10:44:04 +00:00
// TLSChallenge contains TLS challenge Configuration
type TLSChallenge struct { }
2018-11-14 09:18:03 +00:00
// Provider holds configurations of the provider.
type Provider struct {
* Configuration
Store Store
certificates [ ] * Certificate
account * Account
2019-01-07 17:30:06 +00:00
client * lego . Client
2018-11-14 09:18:03 +00:00
certsChan chan * Certificate
configurationChan chan <- config . Message
2019-03-14 08:30:04 +00:00
tlsManager * traefiktls . Manager
2018-11-14 09:18:03 +00:00
clientMutex sync . Mutex
configFromListenerChan chan config . Configuration
pool * safe . Pool
resolvingDomains map [ string ] struct { }
resolvingDomainsMutex sync . RWMutex
}
2019-03-14 08:30:04 +00:00
// SetTLSManager sets the tls manager to use
func ( p * Provider ) SetTLSManager ( tlsManager * traefiktls . Manager ) {
p . tlsManager = tlsManager
}
2018-03-05 19:54:04 +00:00
// SetConfigListenerChan initializes the configFromListenerChan
2018-11-14 09:18:03 +00:00
func ( p * Provider ) SetConfigListenerChan ( configFromListenerChan chan config . Configuration ) {
2018-03-05 19:54:04 +00:00
p . configFromListenerChan = configFromListenerChan
}
2018-07-03 10:44:04 +00:00
// ListenConfiguration sets a new Configuration into the configFromListenerChan
2018-11-14 09:18:03 +00:00
func ( p * Provider ) ListenConfiguration ( config config . Configuration ) {
2018-07-03 10:44:04 +00:00
p . configFromListenerChan <- config
}
// ListenRequest resolves new certificates for a domain from an incoming request and return a valid Certificate to serve (onDemand option)
func ( p * Provider ) ListenRequest ( domain string ) ( * tls . Certificate , error ) {
2018-11-14 09:18:03 +00:00
ctx := log . With ( context . Background ( ) , log . Str ( log . ProviderName , "acme" ) )
acmeCert , err := p . resolveCertificate ( ctx , types . Domain { Main : domain } , false )
2018-07-03 10:44:04 +00:00
if acmeCert == nil || err != nil {
return nil , err
}
2019-01-07 17:30:06 +00:00
cert , err := tls . X509KeyPair ( acmeCert . Certificate , acmeCert . PrivateKey )
2018-07-03 10:44:04 +00:00
2019-01-07 17:30:06 +00:00
return & cert , err
2018-07-03 10:44:04 +00:00
}
2018-07-11 07:08:03 +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-03-14 08:30:04 +00:00
2018-11-14 09:18:03 +00:00
ctx := log . With ( context . Background ( ) , log . Str ( log . ProviderName , "acme" ) )
logger := log . FromContext ( ctx )
2018-07-11 07:08:03 +00:00
if p . ACMELogging {
2018-11-14 09:18:03 +00:00
legolog . Logger = fmtlog . New ( logger . WriterLevel ( logrus . InfoLevel ) , "legolog: " , 0 )
2018-07-11 07:08:03 +00:00
} else {
legolog . Logger = fmtlog . New ( ioutil . Discard , "" , 0 )
}
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
}
2019-03-14 08:30:04 +00:00
p . Store = NewLocalStore ( p . Configuration . Storage )
2018-07-11 07:08:03 +00:00
var err error
p . account , err = p . Store . GetAccount ( )
if err != nil {
return fmt . Errorf ( "unable to get ACME account : %v" , err )
}
// 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
}
p . certificates , err = p . Store . GetCertificates ( )
if err != nil {
return fmt . Errorf ( "unable to get ACME certificates : %v" , err )
}
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
}
2018-11-14 09:18:03 +00:00
func isAccountMatchingCaServer ( ctx context . Context , accountURI string , serverURI string ) bool {
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 ( )
}
2018-07-03 10:44:04 +00:00
// Provide allows the file provider to provide configurations to traefik
// using the given Configuration channel.
2018-11-14 09:18:03 +00:00
func ( p * Provider ) Provide ( configurationChan chan <- config . Message , pool * safe . Pool ) error {
ctx := log . With ( context . Background ( ) , log . Str ( log . ProviderName , "acme" ) )
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 ( )
2018-11-14 09:18:03 +00:00
p . deleteUnnecessaryDomains ( ctx )
2018-07-03 10:44:04 +00:00
for i := 0 ; i < len ( p . Domains ) ; i ++ {
domain := p . Domains [ i ]
safe . Go ( func ( ) {
2018-11-14 09:18:03 +00:00
if _ , err := p . resolveCertificate ( ctx , domain , true ) ; err != nil {
log . WithoutContext ( ) . WithField ( log . ProviderName , "acme" ) .
Errorf ( "Unable to obtain ACME certificate for domains %q : %v" , strings . Join ( domain . ToStrArray ( ) , "," ) , err )
2018-07-03 10:44:04 +00:00
}
} )
}
2018-11-14 09:18:03 +00:00
p . renewCertificates ( ctx )
2018-07-03 10:44:04 +00:00
ticker := time . NewTicker ( 24 * time . Hour )
pool . Go ( func ( stop chan bool ) {
for {
select {
case <- ticker . C :
2018-11-14 09:18:03 +00:00
p . renewCertificates ( ctx )
2018-07-03 10:44:04 +00:00
case <- stop :
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 ( )
2018-11-14 09:18:03 +00:00
ctx := log . With ( context . Background ( ) , log . Str ( log . ProviderName , "acme" ) )
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
caServer := "https://acme-v02.api.letsencrypt.org/directory"
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
2019-01-11 15:22:03 +00:00
config . Certificate . KeyType = account . 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 {
2018-11-14 09:18:03 +00:00
logger . Info ( "Register..." )
2018-07-03 10:44:04 +00:00
2019-01-07 17:30:06 +00:00
reg , errR := client . Registration . Register ( registration . RegisterOptions { TermsOfServiceAgreed : true } )
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
err = p . Store . SaveAccount ( account )
if err != nil {
2018-03-05 19:54:04 +00:00
return nil , err
}
2019-02-05 16:10:03 +00:00
switch {
case 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 ) ) ,
dns01 . CondOption ( p . DNSChallenge . DisablePropagationCheck || p . DNSChallenge . DelayBeforeCheck > 0 ,
dns01 . AddPreCheck ( func ( _ , _ string ) ( bool , error ) {
if p . DNSChallenge . DelayBeforeCheck > 0 {
log . Debugf ( "Delaying %d rather than validating DNS propagation now." , p . DNSChallenge . DelayBeforeCheck )
time . Sleep ( time . Duration ( p . DNSChallenge . DelayBeforeCheck ) )
}
return true , nil
} ) ) ,
)
2018-07-03 10:44:04 +00:00
if err != nil {
return nil , err
}
2018-07-31 10:32:04 +00:00
// Same default values than LEGO
p . DNSChallenge . preCheckTimeout = 60 * time . Second
p . DNSChallenge . preCheckInterval = 2 * time . Second
// Set the precheck timeout into the DNSChallenge provider
2019-01-07 17:30:06 +00:00
if challengeProviderTimeout , ok := provider . ( challenge . ProviderTimeout ) ; ok {
2018-07-31 10:32:04 +00:00
p . DNSChallenge . preCheckTimeout , p . DNSChallenge . preCheckInterval = challengeProviderTimeout . Timeout ( )
}
2019-02-05 16:10:03 +00:00
case 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
2019-01-07 17:30:06 +00:00
err = client . Challenge . SetHTTP01Provider ( & challengeHTTP { Store : p . Store } )
2018-07-03 10:44:04 +00:00
if err != nil {
return nil , err
}
2019-02-05 16:10:03 +00:00
case 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
2019-01-07 17:30:06 +00:00
err = client . Challenge . SetTLSALPN01Provider ( & challengeTLSALPN { Store : p . Store } )
2018-07-03 10:44:04 +00:00
if err != nil {
return nil , err
}
2019-02-05 16:10:03 +00:00
default :
2018-07-03 10:44:04 +00:00
return nil , errors . New ( "ACME challenge not specified, please select TLS or HTTP or DNS Challenge" )
}
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
}
2019-03-14 08:30:04 +00:00
func ( p * Provider ) resolveDomains ( ctx context . Context , domains [ ] string ) {
if len ( domains ) == 0 {
log . FromContext ( ctx ) . Debug ( "No domain parsed in provider ACME" )
return
}
log . FromContext ( ctx ) . Debugf ( "Try to challenge certificate for domain %v founded in HostSNI rule" , domains )
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 ( ) {
if _ , err := p . resolveCertificate ( ctx , domain , false ) ; err != nil {
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 ) {
2018-03-05 19:54:04 +00:00
p . pool . Go ( func ( stop chan bool ) {
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-03-26 10:12:04 +00:00
if route . TLS == nil {
continue
}
2019-03-14 08:30:04 +00:00
ctxRouter := log . With ( ctx , log . Str ( log . RouterName , routerName ) , log . Str ( log . Rule , route . Rule ) )
domains , err := rules . ParseHostSNI ( route . Rule )
if err != nil {
log . FromContext ( ctxRouter ) . Errorf ( "Error parsing domains in provider ACME: %v" , err )
continue
}
p . resolveDomains ( ctxRouter , domains )
}
}
for routerName , route := range config . HTTP . Routers {
2019-03-26 10:12:04 +00:00
if route . TLS == nil {
continue
}
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-01-30 15:24:07 +00:00
domains , err := rules . ParseDomains ( route . Rule )
2018-11-14 09:18:03 +00:00
if err != nil {
2019-03-14 08:30:04 +00:00
log . FromContext ( ctxRouter ) . Errorf ( "Error parsing domains in provider ACME: %v" , err )
2018-11-14 09:18:03 +00:00
continue
}
2019-03-14 08:30:04 +00:00
p . resolveDomains ( ctxRouter , domains )
2018-03-05 19:54:04 +00:00
}
case <- stop :
return
}
}
} )
}
2019-01-07 17:30:06 +00:00
func ( p * Provider ) resolveCertificate ( ctx context . Context , domain types . Domain , domainFromConfigurationFile bool ) ( * certificate . Resource , error ) {
2018-11-14 09:18:03 +00:00
domains , err := p . getValidDomains ( ctx , domain , domainFromConfigurationFile )
2018-03-26 12:12:03 +00:00
if err != nil {
return nil , err
2018-03-05 19:54:04 +00:00
}
2018-03-06 09:12:04 +00:00
// Check provided certificates
2018-11-14 09:18:03 +00:00
uncheckedDomains := p . getUncheckedDomains ( ctx , domains , ! domainFromConfigurationFile )
2018-03-06 09:12:04 +00:00
if len ( uncheckedDomains ) == 0 {
2018-03-05 19:54:04 +00:00
return nil , nil
}
2018-08-20 07:40:03 +00:00
p . addResolvingDomains ( uncheckedDomains )
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 {
return nil , fmt . Errorf ( "cannot get ACME client %v" , err )
}
2019-01-07 17:30:06 +00:00
var cert * certificate . Resource
2018-03-05 19:54:04 +00:00
bundle := true
2018-07-31 10:32:04 +00:00
if p . useCertificateWithRetry ( uncheckedDomains ) {
2019-01-07 17:30:06 +00:00
cert , err = obtainCertificateWithRetry ( ctx , domains , client , p . DNSChallenge . preCheckTimeout , p . DNSChallenge . preCheckInterval , bundle )
2018-07-31 10:32:04 +00:00
} else {
2019-01-07 17:30:06 +00:00
request := certificate . ObtainRequest {
Domains : domains ,
Bundle : bundle ,
MustStaple : oscpMustStaple ,
}
cert , err = client . Certificate . Obtain ( request )
2018-07-31 10:32:04 +00:00
}
2018-04-16 17:34:04 +00:00
2018-05-15 15:28:02 +00:00
if err != nil {
2018-07-31 10:32:04 +00:00
return nil , fmt . Errorf ( "unable to generate a certificate for the domains %v: %v" , uncheckedDomains , err )
}
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-01-07 17:30:06 +00:00
p . addCertificateForDomain ( domain , cert . Certificate , cert . PrivateKey )
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 )
}
}
func ( p * Provider ) addResolvingDomains ( resolvingDomains [ ] string ) {
p . resolvingDomainsMutex . Lock ( )
defer p . resolvingDomainsMutex . Unlock ( )
for _ , domain := range resolvingDomains {
p . resolvingDomains [ domain ] = struct { } { }
}
}
2018-07-31 10:32:04 +00:00
func ( p * Provider ) useCertificateWithRetry ( domains [ ] string ) bool {
// Check if we can use the retry mechanism only if we use the DNS Challenge and if is there are at least 2 domains to check
if p . DNSChallenge != nil && len ( domains ) > 1 {
rootDomain := ""
for _ , searchWildcardDomain := range domains {
// Search a wildcard domain if not already found
if len ( rootDomain ) == 0 && strings . HasPrefix ( searchWildcardDomain , "*." ) {
rootDomain = strings . TrimPrefix ( searchWildcardDomain , "*." )
if len ( rootDomain ) > 0 {
// Look for a root domain which matches the wildcard domain
for _ , searchRootDomain := range domains {
if rootDomain == searchRootDomain {
// If the domains list contains a wildcard domain and its root domain, we can use the retry mechanism to obtain the certificate
return true
}
}
}
// There is only one wildcard domain in the slice, if its root domain has not been found, the retry mechanism does not have to be used
return false
}
}
}
return false
}
2019-01-07 17:30:06 +00:00
func obtainCertificateWithRetry ( ctx context . Context , domains [ ] string , client * lego . Client , timeout , interval time . Duration , bundle bool ) ( * certificate . Resource , error ) {
2018-11-14 09:18:03 +00:00
logger := log . FromContext ( ctx )
2019-01-07 17:30:06 +00:00
var cert * certificate . Resource
2018-07-31 10:32:04 +00:00
var err error
operation := func ( ) error {
2019-01-07 17:30:06 +00:00
request := certificate . ObtainRequest {
Domains : domains ,
Bundle : bundle ,
MustStaple : oscpMustStaple ,
}
cert , err = client . Certificate . Obtain ( request )
2018-07-31 10:32:04 +00:00
return err
}
notify := func ( err error , time time . Duration ) {
2018-11-14 09:18:03 +00:00
logger . Errorf ( "Error obtaining certificate retrying in %s" , time )
2018-07-31 10:32:04 +00:00
}
// Define a retry backOff to let LEGO tries twice to obtain a certificate for both wildcard and root domain
ebo := backoff . NewExponentialBackOff ( )
ebo . MaxElapsedTime = 2 * timeout
ebo . MaxInterval = interval
rbo := backoff . WithMaxRetries ( ebo , 2 )
err = backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , rbo , notify )
if err != nil {
2018-11-14 09:18:03 +00:00
logger . Errorf ( "Error obtaining certificate: %v" , err )
2018-07-31 10:32:04 +00:00
return nil , err
}
2019-01-07 17:30:06 +00:00
return cert , nil
2018-03-05 19:54:04 +00:00
}
2018-07-03 10:44:04 +00:00
func ( p * Provider ) addCertificateForDomain ( domain types . Domain , certificate [ ] byte , key [ ] byte ) {
p . certsChan <- & Certificate { Certificate : certificate , Key : key , Domain : domain }
2018-03-05 19:54:04 +00:00
}
2018-07-03 10:44:04 +00:00
// deleteUnnecessaryDomains deletes from the configuration :
// - Duplicated domains
// - Domains which are checked by wildcard domain
2018-11-14 09:18:03 +00:00
func ( p * Provider ) deleteUnnecessaryDomains ( ctx context . Context ) {
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 )
2018-07-03 10:44:04 +00:00
for idxDomainToCheck , domainToCheck := range p . Domains {
keepDomain := true
2018-03-05 19:54:04 +00:00
2018-07-03 10:44:04 +00:00
for idxDomain , domain := range p . Domains {
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
2018-07-03 10:44:04 +00:00
p . Domains = newDomains
2018-03-05 19:54:04 +00:00
}
2018-11-14 09:18:03 +00:00
func ( p * Provider ) watchCertificate ( ctx context . Context ) {
2018-03-05 19:54:04 +00:00
p . certsChan = make ( chan * Certificate )
2018-11-14 09:18:03 +00:00
2018-03-05 19:54:04 +00:00
p . pool . Go ( func ( stop chan bool ) {
for {
select {
case cert := <- p . certsChan :
certUpdated := false
for _ , domainsCertificate := range p . certificates {
if reflect . DeepEqual ( cert . Domain , domainsCertificate . Domain ) {
domainsCertificate . Certificate = cert . Certificate
domainsCertificate . Key = cert . Key
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
}
2018-03-05 19:54:04 +00:00
case <- stop :
return
}
}
} )
}
2018-07-03 10:44:04 +00:00
func ( p * Provider ) saveCertificates ( ) error {
2018-03-05 19:54:04 +00:00
err := p . Store . SaveCertificates ( 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 ( ) {
2018-11-14 09:18:03 +00:00
conf := config . Message {
2018-03-05 19:54:04 +00:00
ProviderName : "ACME" ,
2018-11-14 09:18:03 +00:00
Configuration : & config . Configuration {
2019-03-14 08:30:04 +00:00
HTTP : & config . HTTPConfiguration {
Routers : map [ string ] * config . Router { } ,
Middlewares : map [ string ] * config . Middleware { } ,
Services : map [ string ] * config . Service { } ,
} ,
TLS : [ ] * traefiktls . Configuration { } ,
2018-03-05 19:54:04 +00:00
} ,
}
for _ , cert := range p . certificates {
2019-01-07 17:30:06 +00:00
cert := & traefiktls . Certificate { CertFile : traefiktls . FileOrContent ( cert . Certificate ) , KeyFile : traefiktls . FileOrContent ( cert . Key ) }
2019-03-14 08:30:04 +00:00
conf . Configuration . TLS = append ( conf . Configuration . TLS , & traefiktls . Configuration { Certificate : cert } )
2018-03-05 19:54:04 +00:00
}
2018-11-14 09:18:03 +00:00
p . configurationChan <- conf
2018-03-05 19:54:04 +00:00
}
2018-11-14 09:18:03 +00:00
func ( p * Provider ) renewCertificates ( ctx context . Context ) {
logger := log . FromContext ( ctx )
logger . Info ( "Testing certificate renew..." )
2019-01-07 17:30:06 +00:00
for _ , cert := range p . certificates {
crt , err := getX509Certificate ( ctx , cert )
2018-03-05 19:54:04 +00:00
// 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 ) ) {
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 ,
Certificate : cert . Certificate ,
2018-11-14 09:18:03 +00:00
} , true , oscpMustStaple )
2018-04-16 17:34:04 +00:00
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-01-07 17:30:06 +00:00
p . addCertificateForDomain ( cert . Domain , renewedCert . Certificate , renewedCert . PrivateKey )
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)
// from static and dynamic provided certificates
2018-11-14 09:18:03 +00:00
func ( p * Provider ) getUncheckedDomains ( ctx context . Context , domainsToCheck [ ] string , checkConfigurationDomains bool ) [ ] string {
2018-08-20 07:40:03 +00:00
p . resolvingDomainsMutex . RLock ( )
defer p . resolvingDomainsMutex . RUnlock ( )
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-03-14 08:30:04 +00:00
allDomains := p . tlsManager . GetStore ( "default" ) . 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
}
2018-03-26 12:12:03 +00:00
// Get Configuration Domains
if checkConfigurationDomains {
for i := 0 ; i < len ( p . Domains ) ; i ++ {
2018-04-24 20:40:04 +00:00
allDomains = append ( allDomains , strings . Join ( p . Domains [ i ] . ToStrArray ( ) , "," ) )
2018-03-06 09:12:04 +00:00
}
}
2018-11-14 09:18:03 +00:00
return searchUncheckedDomains ( ctx , domainsToCheck , allDomains )
2018-03-06 09:12:04 +00:00
}
2018-11-14 09:18:03 +00:00
func searchUncheckedDomains ( ctx context . Context , domainsToCheck [ ] string , 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
// getValidDomains checks if given domain is allowed to generate a ACME certificate and return it
2018-11-14 09:18:03 +00:00
func ( p * Provider ) getValidDomains ( ctx context . Context , domain types . Domain , wildcardAllowed bool ) ( [ ] 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 ! wildcardAllowed {
return nil , fmt . Errorf ( "unable to generate a wildcard certificate in ACME provider for domain %q from a 'Host' rule" , strings . Join ( domains , "," ) )
}
2018-07-03 10:44:04 +00:00
2018-03-26 12:12:03 +00:00
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-04-11 15:16:07 +00:00
for _ , san := range domain . SANs {
if strings . HasPrefix ( san , "*" ) {
return nil , fmt . Errorf ( "unable to generate a certificate in ACME provider for domains %q: SAN %q can not be a wildcard domain" , strings . Join ( domains , "," ) , san )
2018-03-26 12:12:03 +00:00
}
}
2018-04-11 15:16:07 +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
}