2018-03-05 19:54:04 +00:00
package acme
import (
"crypto/tls"
"crypto/x509"
"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"
2018-07-31 10:32:04 +00:00
"github.com/cenk/backoff"
2018-03-05 19:54:04 +00:00
"github.com/containous/flaeg"
"github.com/containous/traefik/log"
"github.com/containous/traefik/rules"
"github.com/containous/traefik/safe"
2018-04-24 20:40:04 +00:00
traefiktls "github.com/containous/traefik/tls"
2018-03-05 19:54:04 +00:00
"github.com/containous/traefik/types"
2018-06-05 07:54:03 +00:00
"github.com/containous/traefik/version"
2018-03-05 19:54:04 +00:00
"github.com/pkg/errors"
2018-06-15 14:08:03 +00:00
"github.com/sirupsen/logrus"
2018-05-31 07:30:04 +00:00
"github.com/xenolf/lego/acme"
legolog "github.com/xenolf/lego/log"
2018-03-05 19:54:04 +00:00
"github.com/xenolf/lego/providers/dns"
)
var (
// OSCPMustStaple enables OSCP stapling as from https://github.com/xenolf/lego/issues/270
OSCPMustStaple = false
)
// 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-16 17:34:04 +00:00
OnDemand bool ` description:"Enable on demand certificate generation. This will request a certificate from Let's Encrypt during the first TLS handshake for a hostname that does not yet have a certificate." ` // Deprecated
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
}
// Provider holds configurations of the provider.
type Provider struct {
* Configuration
Store Store
certificates [ ] * Certificate
account * Account
client * acme . Client
certsChan chan * Certificate
configurationChan chan <- types . ConfigMessage
2018-04-27 08:36:04 +00:00
certificateStore * traefiktls . CertificateStore
2018-03-05 19:54:04 +00:00
clientMutex sync . Mutex
configFromListenerChan chan types . Configuration
pool * safe . Pool
2018-08-20 07:40:03 +00:00
resolvingDomains map [ string ] struct { }
resolvingDomainsMutex sync . RWMutex
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-04-11 15:16:07 +00:00
Provider string ` description:"Use a DNS-01 based challenge provider rather than HTTPS." `
2018-03-05 19:54:04 +00:00
DelayBeforeCheck flaeg . Duration ` description:"Assume DNS propagates after a delay in seconds rather than finding and querying nameservers." `
2018-07-31 10:32:04 +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-03-05 19:54:04 +00:00
// SetConfigListenerChan initializes the configFromListenerChan
func ( p * Provider ) SetConfigListenerChan ( configFromListenerChan chan types . Configuration ) {
p . configFromListenerChan = configFromListenerChan
}
2018-07-03 10:44:04 +00:00
// SetCertificateStore allow to initialize certificate store
func ( p * Provider ) SetCertificateStore ( certificateStore * traefiktls . CertificateStore ) {
p . certificateStore = certificateStore
}
// ListenConfiguration sets a new Configuration into the configFromListenerChan
func ( p * Provider ) ListenConfiguration ( config types . Configuration ) {
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 ) {
acmeCert , err := p . resolveCertificate ( types . Domain { Main : domain } , false )
if acmeCert == nil || err != nil {
return nil , err
}
certificate , err := tls . X509KeyPair ( acmeCert . Certificate , acmeCert . PrivateKey )
return & certificate , err
}
2018-07-11 07:08:03 +00:00
// Init for compatibility reason the BaseProvider implements an empty Init
func ( p * Provider ) Init ( _ types . Constraints ) error {
acme . UserAgent = fmt . Sprintf ( "containous-traefik/%s" , version . Version )
if p . ACMELogging {
legolog . Logger = fmtlog . New ( log . WriterLevel ( logrus . InfoLevel ) , "legolog: " , 0 )
} else {
legolog . Logger = fmtlog . New ( ioutil . Discard , "" , 0 )
}
if p . Store == nil {
return errors . New ( "no store found for the ACME provider" )
}
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-07-31 09:50:03 +00:00
if p . account != nil && p . account . Registration != nil && ! isAccountMatchingCaServer ( p . account . Registration . URI , p . CAServer ) {
log . 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-07-31 09:50:03 +00:00
func isAccountMatchingCaServer ( accountURI string , serverURI string ) bool {
aru , err := url . Parse ( accountURI )
if err != nil {
log . Infof ( "Unable to parse account.Registration URL : %v" , err )
return false
}
cau , err := url . Parse ( serverURI )
if err != nil {
log . Infof ( "Unable to parse CAServer URL : %v" , err )
return false
}
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-07-11 07:08:03 +00:00
func ( p * Provider ) Provide ( configurationChan chan <- types . ConfigMessage , pool * safe . Pool ) error {
2018-07-03 10:44:04 +00:00
p . pool = pool
2018-07-11 07:08:03 +00:00
p . watchCertificate ( )
p . watchNewDomains ( )
2018-07-03 10:44:04 +00:00
p . configurationChan = configurationChan
p . refreshCertificates ( )
p . deleteUnnecessaryDomains ( )
for i := 0 ; i < len ( p . Domains ) ; i ++ {
domain := p . Domains [ i ]
safe . Go ( func ( ) {
if _ , err := p . resolveCertificate ( domain , true ) ; err != nil {
log . Errorf ( "Unable to obtain ACME certificate for domains %q : %v" , strings . Join ( domain . ToStrArray ( ) , "," ) , err )
}
} )
}
p . renewCertificates ( )
ticker := time . NewTicker ( 24 * time . Hour )
pool . Go ( func ( stop chan bool ) {
for {
select {
case <- ticker . C :
p . renewCertificates ( )
case <- stop :
ticker . Stop ( )
return
}
}
} )
return nil
}
func ( p * Provider ) getClient ( ) ( * acme . Client , error ) {
p . clientMutex . Lock ( )
defer p . clientMutex . Unlock ( )
if p . client != nil {
return p . client , nil
}
account , err := p . initAccount ( )
if err != nil {
return nil , err
}
log . Debug ( "Building ACME client..." )
caServer := "https://acme-v02.api.letsencrypt.org/directory"
if len ( p . CAServer ) > 0 {
caServer = p . CAServer
}
log . Debug ( caServer )
client , err := acme . NewClient ( caServer , account , account . KeyType )
if err != nil {
return nil , err
}
// New users will need to register; be sure to save it
if account . GetRegistration ( ) == nil {
log . Info ( "Register..." )
reg , err := client . Register ( true )
2018-03-05 19:54:04 +00:00
if err != nil {
return nil , err
}
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
}
2018-07-03 10:44:04 +00:00
if p . DNSChallenge != nil && len ( p . DNSChallenge . Provider ) > 0 {
log . Debugf ( "Using DNS Challenge provider: %s" , p . DNSChallenge . Provider )
2018-03-05 19:54:04 +00:00
2018-07-03 10:44:04 +00:00
err = dnsOverrideDelay ( p . DNSChallenge . DelayBeforeCheck )
if err != nil {
return nil , err
}
var provider acme . ChallengeProvider
provider , err = dns . NewDNSChallengeProviderByName ( p . DNSChallenge . Provider )
if err != nil {
return nil , err
}
client . ExcludeChallenges ( [ ] acme . Challenge { acme . HTTP01 , acme . TLSALPN01 } )
err = client . SetChallengeProvider ( acme . DNS01 , provider )
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
if challengeProviderTimeout , ok := provider . ( acme . ChallengeProviderTimeout ) ; ok {
p . DNSChallenge . preCheckTimeout , p . DNSChallenge . preCheckInterval = challengeProviderTimeout . Timeout ( )
}
2018-07-03 10:44:04 +00:00
} else if p . HTTPChallenge != nil && len ( p . HTTPChallenge . EntryPoint ) > 0 {
log . Debug ( "Using HTTP Challenge provider." )
client . ExcludeChallenges ( [ ] acme . Challenge { acme . DNS01 , acme . TLSALPN01 } )
err = client . SetChallengeProvider ( acme . HTTP01 , & challengeHTTP { Store : p . Store } )
if err != nil {
return nil , err
}
} else if p . TLSChallenge != nil {
log . Debug ( "Using TLS Challenge provider." )
client . ExcludeChallenges ( [ ] acme . Challenge { acme . HTTP01 , acme . DNS01 } )
err = client . SetChallengeProvider ( acme . TLSALPN01 , & challengeTLSALPN { Store : p . Store } )
if err != nil {
return nil , err
}
} else {
return nil , errors . New ( "ACME challenge not specified, please select TLS or HTTP or DNS Challenge" )
}
p . client = client
return p . client , nil
}
func ( p * Provider ) initAccount ( ) ( * Account , error ) {
if p . account == nil || len ( p . account . Email ) == 0 {
var err error
p . account , err = NewAccount ( p . Email , p . KeyType )
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 {
p . account . KeyType = GetKeyType ( p . KeyType )
}
2018-07-03 10:44:04 +00:00
return p . account , nil
2018-03-05 19:54:04 +00:00
}
2018-09-27 16:04:03 +00:00
func contains ( entryPoints [ ] string , acmeEntryPoint string ) bool {
for _ , entryPoint := range entryPoints {
if entryPoint == acmeEntryPoint {
return true
}
}
return false
}
2018-03-05 19:54:04 +00:00
func ( p * Provider ) watchNewDomains ( ) {
p . pool . Go ( func ( stop chan bool ) {
for {
select {
case config := <- p . configFromListenerChan :
for _ , frontend := range config . Frontends {
2018-09-27 16:04:03 +00:00
if ! contains ( frontend . EntryPoints , p . EntryPoint ) {
continue
}
2018-03-05 19:54:04 +00:00
for _ , route := range frontend . Routes {
domainRules := rules . Rules { }
domains , err := domainRules . ParseDomains ( route . Rule )
if err != nil {
log . Errorf ( "Error parsing domains in provider ACME: %v" , err )
continue
}
if len ( domains ) == 0 {
2018-09-04 15:14:04 +00:00
log . Debugf ( "No domain parsed in rule %q in provider ACME" , route . Rule )
2018-03-05 19:54:04 +00:00
continue
}
log . Debugf ( "Try to challenge certificate for domain %v founded in Host 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 ( ) {
2018-03-26 12:12:03 +00:00
if _ , err := p . resolveCertificate ( domain , false ) ; err != nil {
2018-03-05 19:54:04 +00:00
log . Errorf ( "Unable to obtain ACME certificate for domains %q detected thanks to rule %q : %v" , strings . Join ( domains , "," ) , route . Rule , err )
}
} )
}
}
}
case <- stop :
return
}
}
} )
}
2018-03-26 12:12:03 +00:00
func ( p * Provider ) resolveCertificate ( domain types . Domain , domainFromConfigurationFile bool ) ( * acme . CertificateResource , error ) {
domains , err := p . getValidDomains ( domain , domainFromConfigurationFile )
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-03-26 12:12:03 +00:00
uncheckedDomains := p . getUncheckedDomains ( 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-03-06 09:12:04 +00:00
log . 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 )
}
2018-07-31 10:32:04 +00:00
var certificate * acme . CertificateResource
2018-03-05 19:54:04 +00:00
bundle := true
2018-07-31 10:32:04 +00:00
if p . useCertificateWithRetry ( uncheckedDomains ) {
certificate , err = obtainCertificateWithRetry ( domains , client , p . DNSChallenge . preCheckTimeout , p . DNSChallenge . preCheckInterval , bundle )
} else {
certificate , err = client . ObtainCertificate ( domains , bundle , nil , OSCPMustStaple )
}
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 )
}
if certificate == nil {
return nil , fmt . Errorf ( "domains %v do not generate a certificate" , uncheckedDomains )
2018-03-05 19:54:04 +00:00
}
2018-04-16 17:34:04 +00:00
if len ( certificate . Certificate ) == 0 || len ( certificate . PrivateKey ) == 0 {
return nil , fmt . Errorf ( "domains %v generate certificate with no value: %v" , uncheckedDomains , certificate )
}
2018-06-15 14:42:03 +00:00
2018-04-16 17:34:04 +00:00
log . Debugf ( "Certificates obtained for domains %+v" , uncheckedDomains )
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 ] }
}
2018-03-05 19:54:04 +00:00
p . addCertificateForDomain ( domain , certificate . Certificate , certificate . PrivateKey )
2018-05-31 07:30:04 +00:00
return certificate , 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
}
func obtainCertificateWithRetry ( domains [ ] string , client * acme . Client , timeout , interval time . Duration , bundle bool ) ( * acme . CertificateResource , error ) {
var certificate * acme . CertificateResource
var err error
operation := func ( ) error {
certificate , err = client . ObtainCertificate ( domains , bundle , nil , OSCPMustStaple )
return err
}
notify := func ( err error , time time . Duration ) {
log . Errorf ( "Error obtaining certificate retrying in %s" , time )
}
// 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 {
log . Errorf ( "Error obtaining certificate: %v" , err )
return nil , err
}
return certificate , nil
}
2018-07-03 10:44:04 +00:00
func dnsOverrideDelay ( delay flaeg . Duration ) error {
if delay == 0 {
return nil
2018-06-15 14:42:03 +00:00
}
2018-07-03 10:44:04 +00:00
if delay > 0 {
log . Debugf ( "Delaying %d rather than validating DNS propagation now." , delay )
2018-03-05 19:54:04 +00:00
2018-07-03 10:44:04 +00:00
acme . PreCheckDNS = func ( _ , _ string ) ( bool , error ) {
time . Sleep ( time . Duration ( delay ) )
return true , nil
2018-03-05 19:54:04 +00:00
}
2018-06-15 14:42:03 +00:00
} else {
2018-07-03 10:44:04 +00:00
return fmt . Errorf ( "delayBeforeCheck: %d cannot be less than 0" , delay )
2018-03-05 19:54:04 +00:00
}
2018-07-03 10:44:04 +00:00
return 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
func ( p * Provider ) deleteUnnecessaryDomains ( ) {
var newDomains [ ] types . Domain
2018-03-05 19:54:04 +00:00
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 {
log . Warnf ( "The domain %v is duplicated in the configuration but will be process by ACME provider only once." , domainToCheck )
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
log . Warnf ( "Domain %q is duplicated in the configuration or validated by the domain %v. It will be processed once." , domainProcessed , domain )
continue
} else if domain . Main != domainProcessed && strings . HasPrefix ( domain . Main , "*" ) && isDomainAlreadyChecked ( domainProcessed , [ ] string { domain . Main } ) {
// Check if a wildcard can validate the domain
log . Warnf ( "Domain %q will not be processed by ACME provider because it is validated by the wildcard %q" , domainProcessed , domain . Main )
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
}
func ( p * Provider ) watchCertificate ( ) {
p . certsChan = make ( chan * Certificate )
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 {
log . Error ( err )
}
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 ( ) {
config := types . ConfigMessage {
ProviderName : "ACME" ,
Configuration : & types . Configuration {
Backends : map [ string ] * types . Backend { } ,
Frontends : map [ string ] * types . Frontend { } ,
2018-04-24 20:40:04 +00:00
TLS : [ ] * traefiktls . Configuration { } ,
2018-03-05 19:54:04 +00:00
} ,
}
for _ , cert := range p . certificates {
2018-04-24 20:40:04 +00:00
certificate := & traefiktls . Certificate { CertFile : traefiktls . FileOrContent ( cert . Certificate ) , KeyFile : traefiktls . FileOrContent ( cert . Key ) }
config . Configuration . TLS = append ( config . Configuration . TLS , & traefiktls . Configuration { Certificate : certificate , EntryPoints : [ ] string { p . EntryPoint } } )
2018-03-05 19:54:04 +00:00
}
p . configurationChan <- config
}
func ( p * Provider ) renewCertificates ( ) {
log . Info ( "Testing certificate renew..." )
for _ , certificate := range p . certificates {
crt , err := getX509Certificate ( certificate )
// If there's an error, we assume the cert is broken, and needs update
// <= 30 days left, renew certificate
if err != nil || crt == nil || crt . NotAfter . Before ( time . Now ( ) . Add ( 24 * 30 * time . Hour ) ) {
client , err := p . getClient ( )
if err != nil {
log . Infof ( "Error renewing certificate from LE : %+v, %v" , certificate . Domain , err )
continue
}
2018-04-16 17:34:04 +00:00
2018-03-05 19:54:04 +00:00
log . Infof ( "Renewing certificate from LE : %+v" , certificate . Domain )
2018-04-16 17:34:04 +00:00
2018-03-05 19:54:04 +00:00
renewedCert , err := client . RenewCertificate ( acme . CertificateResource {
Domain : certificate . Domain . Main ,
PrivateKey : certificate . Key ,
Certificate : certificate . Certificate ,
} , true , OSCPMustStaple )
2018-04-16 17:34:04 +00:00
2018-03-05 19:54:04 +00:00
if err != nil {
log . Errorf ( "Error renewing certificate from LE: %v, %v" , certificate . Domain , err )
continue
}
2018-04-16 17:34:04 +00:00
if len ( renewedCert . Certificate ) == 0 || len ( renewedCert . PrivateKey ) == 0 {
log . Errorf ( "domains %v renew certificate with no value: %v" , certificate . Domain . ToStrArray ( ) , certificate )
continue
}
2018-03-05 19:54:04 +00:00
p . addCertificateForDomain ( certificate . Domain , renewedCert . Certificate , renewedCert . PrivateKey )
}
}
}
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-03-26 12:12:03 +00:00
func ( p * Provider ) getUncheckedDomains ( domainsToCheck [ ] string , checkConfigurationDomains bool ) [ ] string {
2018-08-20 07:40:03 +00:00
p . resolvingDomainsMutex . RLock ( )
defer p . resolvingDomainsMutex . RUnlock ( )
2018-03-26 12:12:03 +00:00
log . Debugf ( "Looking for provided certificate(s) to validate %q..." , domainsToCheck )
2018-03-06 09:12:04 +00:00
2018-07-03 10:44:04 +00:00
allDomains := p . certificateStore . GetAllDomains ( )
2018-03-26 12:12:03 +00:00
// Get ACME certificates
for _ , certificate := range p . certificates {
2018-04-24 20:40:04 +00:00
allDomains = append ( allDomains , strings . Join ( certificate . 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-04-24 20:40:04 +00:00
return searchUncheckedDomains ( domainsToCheck , allDomains )
2018-03-06 09:12:04 +00:00
}
2018-03-26 12:12:03 +00:00
func searchUncheckedDomains ( 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-03-06 09:12:04 +00:00
if len ( uncheckedDomains ) == 0 {
2018-08-20 07:40:03 +00:00
log . Debugf ( "No ACME certificate generation required for domains %q." , domainsToCheck )
2018-03-06 09:12:04 +00:00
} else {
2018-03-26 12:12:03 +00:00
log . 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
}
func getX509Certificate ( certificate * Certificate ) ( * x509 . Certificate , error ) {
tlsCert , err := tls . X509KeyPair ( certificate . Certificate , certificate . Key )
if err != nil {
log . Errorf ( "Failed to load TLS keypair from ACME certificate for domain %q (SAN : %q), certificate will be renewed : %v" , certificate . Domain . Main , strings . Join ( certificate . Domain . SANs , "," ) , err )
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 {
log . Errorf ( "Failed to parse TLS keypair from ACME certificate for domain %q (SAN : %q), certificate will be renewed : %v" , certificate . Domain . Main , strings . Join ( certificate . Domain . SANs , "," ) , err )
}
}
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
func ( p * Provider ) getValidDomains ( domain types . Domain , wildcardAllowed bool ) ( [ ] string , error ) {
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 )
cleanDomain := acme . UnFqdn ( canonicalDomain )
if canonicalDomain != cleanDomain {
log . Warnf ( "FQDN detected, please remove the trailing dot: %s" , canonicalDomain )
}
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
}