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"
2024-09-09 15:24:04 +00:00
"net"
"net/http"
2018-07-31 09:50:03 +00:00
"net/url"
2024-09-09 15:24:04 +00:00
"os"
2018-03-05 19:54:04 +00:00
"reflect"
2022-09-13 18:34:08 +00:00
"sort"
2024-09-09 15:24:04 +00:00
"strconv"
2018-03-05 19:54:04 +00:00
"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"
2022-11-21 17:36:05 +00:00
"github.com/rs/zerolog/log"
2020-08-17 16:04:03 +00:00
ptypes "github.com/traefik/paerser/types"
2023-02-03 14:24:05 +00:00
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/logs"
httpmuxer "github.com/traefik/traefik/v3/pkg/muxer/http"
tcpmuxer "github.com/traefik/traefik/v3/pkg/muxer/tcp"
"github.com/traefik/traefik/v3/pkg/safe"
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
"github.com/traefik/traefik/v3/pkg/types"
"github.com/traefik/traefik/v3/pkg/version"
2018-03-05 19:54:04 +00:00
)
2023-11-17 00:50:06 +00:00
const resolverSuffix = ".acme"
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
2024-09-09 15:24:04 +00:00
CACertificates [ ] string ` description:"Specify the paths to PEM encoded CA Certificates that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list." json:"caCertificates,omitempty" toml:"caCertificates,omitempty" yaml:"caCertificates,omitempty" `
CASystemCertPool bool ` description:"Define if the certificates pool must use a copy of the system cert pool." json:"caSystemCertPool,omitempty" toml:"caSystemCertPool,omitempty" yaml:"caSystemCertPool,omitempty" export:"true" `
CAServerName string ` description:"Specify the CA server name that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list." json:"caServerName,omitempty" toml:"caServerName,omitempty" yaml:"caServerName,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 {
2023-08-16 15:50:06 +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
2022-09-13 18:34:08 +00:00
certificates [ ] * CertAndStore
certificatesMu sync . RWMutex
2018-11-14 09:18:03 +00:00
account * Account
2019-01-07 17:30:06 +00:00
client * lego . Client
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 {
2023-11-29 11:20:57 +00:00
logger := log . With ( ) . Str ( logs . ProviderName , p . ResolverName + resolverSuffix ) . Logger ( )
2018-11-14 09:18:03 +00:00
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
2022-11-21 17:36:05 +00:00
if p . account != nil && p . account . Registration != nil && ! isAccountMatchingCaServer ( logger . WithContext ( context . Background ( ) ) , p . account . Registration . URI , p . CAServer ) {
logger . Info ( ) . Msg ( "Account URI does not match the current CAServer. The account will be reset." )
2018-07-11 07:08:03 +00:00
p . account = nil
}
2022-09-13 18:34:08 +00:00
p . certificatesMu . Lock ( )
2019-07-19 09:52:04 +00:00
p . certificates , err = p . Store . GetCertificates ( p . ResolverName )
2022-09-13 18:34:08 +00:00
p . certificatesMu . Unlock ( )
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 {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2018-11-14 09:18:03 +00:00
2018-07-31 09:50:03 +00:00
aru , err := url . Parse ( accountURI )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Err ( err ) . Str ( "registrationURL" , accountURI ) . Msg ( "Unable to parse account.Registration URL" )
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 {
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Err ( err ) . Str ( "caServerURL" , serverURI ) . Msg ( "Unable to parse CAServer URL" )
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 {
2023-11-29 11:20:57 +00:00
logger := log . With ( ) . Str ( logs . ProviderName , p . ResolverName + resolverSuffix ) . Str ( "acmeCA" , p . Configuration . CAServer ) .
2022-11-21 17:36:05 +00:00
Logger ( )
ctx := logger . WithContext ( context . Background ( ) )
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 . watchNewDomains ( ctx )
2018-07-03 10:44:04 +00:00
p . configurationChan = configurationChan
2022-09-13 18:34:08 +00:00
p . certificatesMu . RLock ( )
msg := p . buildMessage ( )
p . certificatesMu . RUnlock ( )
p . configurationChan <- msg
2018-07-03 10:44:04 +00:00
2021-11-10 11:06:09 +00:00
renewPeriod , renewInterval := getCertificateRenewDurations ( p . CertificatesDuration )
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msgf ( "Attempt to renew certificates %q before expiry and check every %q" ,
2021-11-10 11:06:09 +00:00
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 ( )
2023-11-29 11:20:57 +00:00
logger := log . With ( ) . Str ( logs . ProviderName , p . ResolverName + resolverSuffix ) . Logger ( )
2022-11-21 17:36:05 +00:00
ctx := logger . WithContext ( context . Background ( ) )
2018-11-14 09:18:03 +00:00
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
}
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msg ( "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
}
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msg ( 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 )
2024-09-09 15:24:04 +00:00
config . HTTPClient , err = p . createHTTPClient ( )
if err != nil {
return nil , fmt . Errorf ( "creating HTTP client: %w" , err )
}
2019-01-07 17:30:06 +00:00
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 {
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msgf ( "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 ,
2024-10-09 14:04:04 +00:00
dns01 . CondOption ( len ( p . DNSChallenge . Resolvers ) > 0 ,
dns01 . AddRecursiveNameservers ( p . DNSChallenge . Resolvers ) ) ,
dns01 . PropagationWait ( time . Duration ( p . DNSChallenge . DelayBeforeCheck ) , p . DNSChallenge . DisablePropagationCheck ) ,
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 {
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msg ( "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 {
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msg ( "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
}
2024-09-09 15:24:04 +00:00
func ( p * Provider ) createHTTPClient ( ) ( * http . Client , error ) {
tlsConfig , err := p . createClientTLSConfig ( )
if err != nil {
return nil , fmt . Errorf ( "creating client TLS config: %w" , err )
}
return & http . Client {
Timeout : 2 * time . Minute ,
Transport : & http . Transport {
Proxy : http . ProxyFromEnvironment ,
DialContext : ( & net . Dialer {
Timeout : 30 * time . Second ,
KeepAlive : 30 * time . Second ,
} ) . DialContext ,
TLSHandshakeTimeout : 30 * time . Second ,
ResponseHeaderTimeout : 30 * time . Second ,
TLSClientConfig : tlsConfig ,
} ,
} , nil
}
func ( p * Provider ) createClientTLSConfig ( ) ( * tls . Config , error ) {
if len ( p . CACertificates ) > 0 || p . CAServerName != "" {
certPool , err := lego . CreateCertPool ( p . CACertificates , p . CASystemCertPool )
if err != nil {
return nil , fmt . Errorf ( "creating cert pool with custom certificates: %w" , err )
}
return & tls . Config {
ServerName : p . CAServerName ,
RootCAs : certPool ,
} , nil
}
// Compatibility layer with the lego.
// https://github.com/go-acme/lego/blob/834a9089f143e3407b3f5c8b93a0e285ba231fe2/lego/client_config.go#L24-L34
// https://github.com/go-acme/lego/blob/834a9089f143e3407b3f5c8b93a0e285ba231fe2/lego/client_config.go#L97-L113
serverName := os . Getenv ( "LEGO_CA_SERVER_NAME" )
customCACertsPath := os . Getenv ( "LEGO_CA_CERTIFICATES" )
if customCACertsPath == "" && serverName == "" {
return nil , nil
}
useSystemCertPool , _ := strconv . ParseBool ( os . Getenv ( "LEGO_CA_SYSTEM_CERT_POOL" ) )
certPool , err := lego . CreateCertPool ( strings . Split ( customCACertsPath , string ( os . PathListSeparator ) ) , useSystemCertPool )
if err != nil {
return nil , fmt . Errorf ( "creating cert pool: %w" , err )
}
return & tls . Config {
ServerName : serverName ,
RootCAs : certPool ,
} , 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 ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2020-12-01 09:40:05 +00:00
if p . EAB != nil {
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Msg ( "Register with external account binding..." )
2020-12-01 09:40:05 +00:00
eabOptions := registration . RegisterEABOptions { TermsOfServiceAgreed : true , Kid : p . EAB . Kid , HmacEncoded : p . EAB . HmacEncoded }
return client . Registration . RegisterWithExternalAccountBinding ( eabOptions )
}
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Msg ( "Register..." )
2020-12-01 09:40:05 +00:00
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 ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2022-09-13 18:34:08 +00:00
2019-03-14 08:30:04 +00:00
if len ( domains ) == 0 {
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msg ( "No domain parsed in provider ACME" )
2019-03-14 08:30:04 +00:00
return
}
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msgf ( "Trying 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 ( ) {
2022-09-13 18:34:08 +00:00
dom , cert , err := p . resolveCertificate ( ctx , domain , tlsStore )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Strs ( "domains" , domains ) . Msg ( "Unable to obtain ACME certificate for domains" )
2022-09-13 18:34:08 +00:00
return
}
2022-09-23 08:42:09 +00:00
err = p . addCertificateForDomain ( dom , cert , tlsStore )
2022-09-13 18:34:08 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Strs ( "domains" , dom . ToStrArray ( ) ) . Msg ( "Error adding certificate for domains" )
2019-03-14 08:30:04 +00:00
}
} )
}
}
2018-11-14 09:18:03 +00:00
func ( p * Provider ) watchNewDomains ( ctx context . Context ) {
2023-11-29 11:20:57 +00:00
rootLogger := log . Ctx ( ctx ) . With ( ) . Str ( logs . ProviderName , p . ResolverName + resolverSuffix ) . Str ( "ACME CA" , p . Configuration . CAServer ) . Logger ( )
2022-11-21 17:36:05 +00:00
ctx = rootLogger . WithContext ( ctx )
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
2022-11-21 17:36:05 +00:00
logger := rootLogger . With ( ) . Str ( logs . RouterName , routerName ) . Str ( logs . Rule , route . Rule ) . Logger ( )
ctxRouter := logger . WithContext ( ctx )
2019-03-14 08:30:04 +00:00
2019-07-19 09:52:04 +00:00
if len ( route . TLS . Domains ) > 0 {
2022-09-13 18:34:08 +00:00
domains := deleteUnnecessaryDomains ( ctxRouter , route . TLS . Domains )
2024-09-09 15:24:04 +00:00
for _ , domain := range domains {
2022-09-13 18:34:08 +00:00
safe . Go ( func ( ) {
dom , cert , err := p . resolveCertificate ( ctx , domain , traefiktls . DefaultTLSStoreName )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Strs ( "domains" , domain . ToStrArray ( ) ) . Msg ( "Unable to obtain ACME certificate for domains" )
2022-09-13 18:34:08 +00:00
return
}
2022-09-23 08:42:09 +00:00
err = p . addCertificateForDomain ( dom , cert , traefiktls . DefaultTLSStoreName )
2022-09-13 18:34:08 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Strs ( "domains" , dom . ToStrArray ( ) ) . Msg ( "Error adding certificate for domains" )
2019-07-19 09:52:04 +00:00
}
2022-09-13 18:34:08 +00:00
} )
}
} else {
domains , err := tcpmuxer . ParseHostSNI ( route . Rule )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Error parsing domains in provider ACME" )
2022-09-13 18:34:08 +00:00
continue
2019-07-19 09:52:04 +00:00
}
2022-09-13 18:34:08 +00:00
p . resolveDomains ( ctxRouter , domains , traefiktls . DefaultTLSStoreName )
}
}
}
if config . HTTP != nil {
for routerName , route := range config . HTTP . Routers {
if route . TLS == nil || route . TLS . CertResolver != p . ResolverName {
continue
}
2022-11-21 17:36:05 +00:00
logger := rootLogger . With ( ) . Str ( logs . RouterName , routerName ) . Str ( logs . Rule , route . Rule ) . Logger ( )
ctxRouter := logger . WithContext ( ctx )
2019-07-19 09:52:04 +00:00
2022-09-13 18:34:08 +00:00
if len ( route . TLS . Domains ) > 0 {
2019-07-19 09:52:04 +00:00
domains := deleteUnnecessaryDomains ( ctxRouter , route . TLS . Domains )
2024-09-09 15:24:04 +00:00
for _ , domain := range domains {
2019-07-19 09:52:04 +00:00
safe . Go ( func ( ) {
2022-09-13 18:34:08 +00:00
dom , cert , err := p . resolveCertificate ( ctx , domain , traefiktls . DefaultTLSStoreName )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Strs ( "domains" , domain . ToStrArray ( ) ) . Msg ( "Unable to obtain ACME certificate for domains" )
2022-09-13 18:34:08 +00:00
return
}
2022-09-23 08:42:09 +00:00
err = p . addCertificateForDomain ( dom , cert , traefiktls . DefaultTLSStoreName )
2022-09-13 18:34:08 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Strs ( "domains" , dom . ToStrArray ( ) ) . Msg ( "Error adding certificate for domain" )
2019-07-19 09:52:04 +00:00
}
} )
}
} else {
2022-09-13 18:34:08 +00:00
domains , err := httpmuxer . ParseDomains ( route . Rule )
2019-07-19 09:52:04 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Error parsing domains in provider ACME" )
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
}
}
}
2022-09-13 18:34:08 +00:00
if config . TLS == nil {
continue
}
for tlsStoreName , tlsStore := range config . TLS . Stores {
2022-11-21 17:36:05 +00:00
logger := rootLogger . With ( ) . Str ( logs . TLSStoreName , tlsStoreName ) . Logger ( )
2022-09-13 18:34:08 +00:00
if tlsStore . DefaultCertificate != nil && tlsStore . DefaultGeneratedCert != nil {
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Msg ( "defaultCertificate and defaultGeneratedCert cannot be defined at the same time." )
2022-09-13 18:34:08 +00:00
}
// Gives precedence to the user defined default certificate.
if tlsStore . DefaultCertificate != nil || tlsStore . DefaultGeneratedCert == nil {
2019-03-26 10:12:04 +00:00
continue
}
2021-02-11 15:32:03 +00:00
2022-09-13 18:34:08 +00:00
if tlsStore . DefaultGeneratedCert . Domain == nil || tlsStore . DefaultGeneratedCert . Resolver == "" {
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Msg ( "default generated certificate domain or resolver is missing." )
2022-09-13 18:34:08 +00:00
continue
}
if tlsStore . DefaultGeneratedCert . Resolver != p . ResolverName {
continue
}
validDomains , err := p . sanitizeDomains ( ctx , * tlsStore . DefaultGeneratedCert . Domain )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Strs ( "domains" , tlsStore . DefaultGeneratedCert . Domain . ToStrArray ( ) ) . Msg ( "domains validation" )
2022-09-13 18:34:08 +00:00
}
if p . certExists ( validDomains ) {
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msg ( "Default ACME certificate generation is not required." )
2022-09-13 18:34:08 +00:00
continue
}
safe . Go ( func ( ) {
cert , err := p . resolveDefaultCertificate ( ctx , validDomains )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Strs ( "domains" , validDomains ) . Msgf ( "Unable to obtain ACME certificate for domain" )
2022-09-13 18:34:08 +00:00
return
}
domain := types . Domain {
Main : validDomains [ 0 ] ,
2019-07-19 09:52:04 +00:00
}
2022-09-13 18:34:08 +00:00
if len ( validDomains ) > 0 {
domain . SANs = validDomains [ 1 : ]
}
2022-09-23 08:42:09 +00:00
err = p . addCertificateForDomain ( domain , cert , traefiktls . DefaultTLSStoreName )
2019-07-19 09:52:04 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Error adding certificate for domain" )
2019-07-19 09:52:04 +00:00
}
2022-09-13 18:34:08 +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
}
}
} )
}
2022-09-13 18:34:08 +00:00
func ( p * Provider ) resolveDefaultCertificate ( ctx context . Context , domains [ ] string ) ( * certificate . Resource , error ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2022-09-13 18:34:08 +00:00
p . resolvingDomainsMutex . Lock ( )
2024-09-30 10:10:05 +00:00
sortedDomains := make ( [ ] string , len ( domains ) )
copy ( sortedDomains , domains )
sort . Strings ( sortedDomains )
domainKey := strings . Join ( sortedDomains , "," )
2022-09-13 18:34:08 +00:00
if _ , ok := p . resolvingDomains [ domainKey ] ; ok {
p . resolvingDomainsMutex . Unlock ( )
return nil , nil
}
p . resolvingDomains [ domainKey ] = struct { } { }
for _ , certDomain := range domains {
p . resolvingDomains [ certDomain ] = struct { } { }
}
p . resolvingDomainsMutex . Unlock ( )
defer p . removeResolvingDomains ( append ( domains , domainKey ) )
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msgf ( "Loading ACME certificates %+v..." , domains )
2022-09-13 18:34:08 +00:00
client , err := p . getClient ( )
2018-03-26 12:12:03 +00:00
if err != nil {
2022-09-13 18:34:08 +00:00
return nil , fmt . Errorf ( "cannot get ACME client %w" , err )
}
request := certificate . ObtainRequest {
Domains : domains ,
Bundle : true ,
PreferredChain : p . PreferredChain ,
}
cert , err := client . Certificate . Obtain ( request )
if err != nil {
return nil , fmt . Errorf ( "unable to generate a certificate for the domains %v: %w" , domains , err )
}
if cert == nil {
return nil , fmt . Errorf ( "unable to generate a certificate for the domains %v" , domains )
}
if len ( cert . Certificate ) == 0 || len ( cert . PrivateKey ) == 0 {
return nil , fmt . Errorf ( "certificate for domains %v is empty: %v" , domains , cert )
}
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msgf ( "Default certificate obtained for domains %+v" , domains )
2022-09-13 18:34:08 +00:00
return cert , nil
}
func ( p * Provider ) resolveCertificate ( ctx context . Context , domain types . Domain , tlsStore string ) ( types . Domain , * certificate . Resource , error ) {
domains , err := p . sanitizeDomains ( ctx , domain )
if err != nil {
return types . Domain { } , 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 {
2022-09-13 18:34:08 +00:00
return types . Domain { } , nil , nil
2018-03-05 19:54:04 +00:00
}
2021-02-11 15:32:03 +00:00
2018-08-20 07:40:03 +00:00
defer p . removeResolvingDomains ( uncheckedDomains )
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
logger . Debug ( ) . Msgf ( "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 {
2022-09-13 18:34:08 +00:00
return types . Domain { } , 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 ,
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 {
2022-09-13 18:34:08 +00:00
return types . Domain { } , 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 {
2022-09-13 18:34:08 +00:00
return types . Domain { } , nil , fmt . Errorf ( "unable to generate a certificate for the domains %v" , 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 {
2022-09-13 18:34:08 +00:00
return types . Domain { } , nil , fmt . Errorf ( "certificate for domains %v is empty: %v" , uncheckedDomains , cert )
2018-04-16 17:34:04 +00:00
}
2018-06-15 14:42:03 +00:00
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msgf ( "Certificates obtained for domains %+v" , uncheckedDomains )
2018-04-16 17:34:04 +00:00
2022-09-13 18:34:08 +00:00
domain = types . Domain { Main : uncheckedDomains [ 0 ] }
2018-03-06 09:12:04 +00:00
if len ( uncheckedDomains ) > 1 {
2022-09-13 18:34:08 +00:00
domain . SANs = uncheckedDomains [ 1 : ]
2018-03-06 09:12:04 +00:00
}
2018-03-05 19:54:04 +00:00
2022-09-13 18:34:08 +00:00
return domain , 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 )
}
}
2022-09-23 08:42:09 +00:00
func ( p * Provider ) addCertificateForDomain ( domain types . Domain , crt * certificate . Resource , tlsStore string ) error {
if crt == nil {
return nil
}
2022-09-13 18:34:08 +00:00
p . certificatesMu . Lock ( )
defer p . certificatesMu . Unlock ( )
2022-09-23 08:42:09 +00:00
cert := Certificate { Certificate : crt . Certificate , Key : crt . PrivateKey , Domain : domain }
2022-09-13 18:34:08 +00:00
certUpdated := false
for _ , domainsCertificate := range p . certificates {
if reflect . DeepEqual ( domain , domainsCertificate . Certificate . Domain ) {
domainsCertificate . Certificate = cert
certUpdated = true
break
}
}
if ! certUpdated {
p . certificates = append ( p . certificates , & CertAndStore { Certificate : cert , Store : tlsStore } )
}
p . configurationChan <- p . buildMessage ( )
return p . Store . SaveCertificates ( p . ResolverName , p . certificates )
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 {
2022-04-26 12:36:08 +00:00
case certificatesDuration >= 365 * 24 : // >= 1 year
2021-11-10 11:06:09 +00:00
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
2024-08-08 08:22:05 +00:00
case certificatesDuration >= 30 * 24 : // >= 30 days
return 10 * 24 * time . Hour , 12 * time . Hour // 10 days, 12 hours
2021-11-10 11:06:09 +00:00
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
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2018-11-14 09:18:03 +00:00
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 {
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Msgf ( "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
2024-09-13 09:40:04 +00:00
// or cannot be checked by a wildcard
2018-07-03 10:44:04 +00:00
var newDomainsToCheck [ ] string
for _ , domainProcessed := range domainToCheck . ToStrArray ( ) {
if idxDomain < idxDomainToCheck && isDomainAlreadyChecked ( domainProcessed , domain . ToStrArray ( ) ) {
// The domain is duplicated in a CN
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Msgf ( "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
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Msgf ( "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
}
2022-09-13 18:34:08 +00:00
func ( p * Provider ) buildMessage ( ) dynamic . Message {
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 {
2024-01-11 16:06:06 +00:00
CertFile : types . FileOrContent ( cert . Certificate . Certificate ) ,
KeyFile : types . FileOrContent ( cert . Key ) ,
2019-06-27 21:58:03 +00:00
} ,
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
2022-09-13 18:34:08 +00:00
return 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 ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2018-11-14 09:18:03 +00:00
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Msg ( "Testing certificate renew..." )
2022-09-13 18:34:08 +00:00
p . certificatesMu . RLock ( )
var certificates [ ] * CertAndStore
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 ) ) {
2022-09-13 18:34:08 +00:00
certificates = append ( certificates , cert )
}
}
2018-04-16 17:34:04 +00:00
2022-09-13 18:34:08 +00:00
p . certificatesMu . RUnlock ( )
2018-04-16 17:34:04 +00:00
2022-09-13 18:34:08 +00:00
for _ , cert := range certificates {
client , err := p . getClient ( )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Err ( err ) . Msgf ( "Error renewing certificate from LE : %+v" , cert . Domain )
2022-09-13 18:34:08 +00:00
continue
}
2018-04-16 17:34:04 +00:00
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Msgf ( "Renewing certificate from LE : %+v" , cert . Domain )
2022-09-13 18:34:08 +00:00
2024-03-11 08:18:03 +00:00
res := certificate . Resource {
2022-09-13 18:34:08 +00:00
Domain : cert . Domain . Main ,
PrivateKey : cert . Key ,
Certificate : cert . Certificate . Certificate ,
2024-03-11 08:18:03 +00:00
}
opts := & certificate . RenewOptions {
Bundle : true ,
PreferredChain : p . PreferredChain ,
}
renewedCert , err := client . Certificate . RenewWithOptions ( res , opts )
2022-09-13 18:34:08 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msgf ( "Error renewing certificate from LE: %v" , cert . Domain )
2022-09-13 18:34:08 +00:00
continue
}
2018-04-16 17:34:04 +00:00
2022-09-13 18:34:08 +00:00
if len ( renewedCert . Certificate ) == 0 || len ( renewedCert . PrivateKey ) == 0 {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Msgf ( "domains %v renew certificate with no value: %v" , cert . Domain . ToStrArray ( ) , cert )
2022-09-13 18:34:08 +00:00
continue
}
2022-09-23 08:42:09 +00:00
err = p . addCertificateForDomain ( cert . Domain , renewedCert , cert . Store )
2022-09-13 18:34:08 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Error adding certificate for domain" )
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 {
2022-11-21 17:36:05 +00:00
log . Ctx ( ctx ) . Debug ( ) . Msgf ( "Looking for provided certificate(s) to validate %q..." , domainsToCheck )
2018-03-06 09:12:04 +00:00
2022-09-13 18:34:08 +00:00
var allDomains [ ] string
store := p . tlsManager . GetStore ( tlsStore )
if store != nil {
allDomains = append ( allDomains , store . GetAllDomains ( ) ... )
}
2018-03-26 12:12:03 +00:00
// Get ACME certificates
2022-09-13 18:34:08 +00:00
p . certificatesMu . RLock ( )
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
}
2022-09-13 18:34:08 +00:00
p . certificatesMu . RUnlock ( )
p . resolvingDomainsMutex . Lock ( )
defer p . resolvingDomainsMutex . Unlock ( )
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
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2018-03-06 09:12:04 +00:00
if len ( uncheckedDomains ) == 0 {
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Strs ( "domains" , domainsToCheck ) . Msg ( "No ACME certificate generation required for domains" )
2018-03-06 09:12:04 +00:00
} else {
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Strs ( "domains" , domainsToCheck ) . Msgf ( "Domains need ACME certificates generation for domains %q." , 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 ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2018-11-14 09:18:03 +00:00
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 {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) .
Str ( "domain" , cert . Domain . Main ) .
Strs ( "SANs" , cert . Domain . SANs ) .
Msg ( "Failed to load TLS key pair from ACME certificate for domain, certificate will be renewed" )
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 {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) .
Str ( "domain" , cert . Domain . Main ) .
Strs ( "SANs" , cert . Domain . SANs ) .
Msg ( "Failed to parse TLS key pair from ACME certificate for domain, certificate will be renewed" )
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
2022-09-13 18:34:08 +00:00
// sanitizeDomains checks if given domain is allowed to generate a ACME certificate and return it.
func ( p * Provider ) sanitizeDomains ( ctx context . Context , domain types . Domain ) ( [ ] string , error ) {
2018-03-26 12:12:03 +00:00
domains := domain . ToStrArray ( )
if len ( domains ) == 0 {
2022-09-13 18:34:08 +00:00
return nil , errors . New ( "no domain was given" )
2018-03-26 12:12:03 +00:00
}
2018-07-03 10:44:04 +00:00
2022-09-13 18:34:08 +00:00
var cleanDomains [ ] string
for _ , dom := range domains {
2023-05-16 14:00:06 +00:00
if strings . HasPrefix ( dom , "*.*" ) {
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-07-03 10:44:04 +00:00
2022-09-13 18:34:08 +00:00
canonicalDomain := types . CanonicalDomain ( dom )
2019-01-07 17:30:06 +00:00
cleanDomain := dns01 . UnFqdn ( canonicalDomain )
2018-10-11 08:50:03 +00:00
if canonicalDomain != cleanDomain {
2022-11-21 17:36:05 +00:00
log . Ctx ( ctx ) . Warn ( ) . Msgf ( "FQDN detected, please remove the trailing dot: %s" , canonicalDomain )
2018-10-11 08:50:03 +00:00
}
2022-09-13 18:34:08 +00:00
2018-10-11 08:50:03 +00:00
cleanDomains = append ( cleanDomains , cleanDomain )
}
return cleanDomains , nil
2018-03-26 12:12:03 +00:00
}
2022-09-13 18:34:08 +00:00
// certExists returns whether a certificate already exists for given domains.
func ( p * Provider ) certExists ( validDomains [ ] string ) bool {
p . certificatesMu . RLock ( )
defer p . certificatesMu . RUnlock ( )
2024-09-30 10:10:05 +00:00
sortedDomains := make ( [ ] string , len ( validDomains ) )
copy ( sortedDomains , validDomains )
sort . Strings ( sortedDomains )
2022-09-13 18:34:08 +00:00
for _ , cert := range p . certificates {
domains := cert . Certificate . Domain . ToStrArray ( )
sort . Strings ( domains )
2024-09-30 10:10:05 +00:00
if reflect . DeepEqual ( domains , sortedDomains ) {
2022-09-13 18:34:08 +00:00
return true
}
}
return false
}
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
}