Certificate resolvers.
Co-authored-by: Julien Salleyron <julien.salleyron@gmail.com> Co-authored-by: Jean-Baptiste Doumenjou <jb.doumenjou@gmail.com>
This commit is contained in:
parent
e3627e9cba
commit
f75f73f3d2
47 changed files with 1573 additions and 1249 deletions
|
@ -17,7 +17,7 @@ import (
|
|||
var _ challenge.ProviderTimeout = (*challengeHTTP)(nil)
|
||||
|
||||
type challengeHTTP struct {
|
||||
Store Store
|
||||
Store ChallengeStore
|
||||
}
|
||||
|
||||
// Present presents a challenge to obtain new ACME certificate.
|
||||
|
@ -52,7 +52,7 @@ func (p *Provider) Append(router *mux.Router) {
|
|||
domain = req.Host
|
||||
}
|
||||
|
||||
tokenValue := getTokenValue(ctx, token, domain, p.Store)
|
||||
tokenValue := getTokenValue(ctx, token, domain, p.ChallengeStore)
|
||||
if len(tokenValue) > 0 {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, err = rw.Write(tokenValue)
|
||||
|
@ -66,7 +66,7 @@ func (p *Provider) Append(router *mux.Router) {
|
|||
}))
|
||||
}
|
||||
|
||||
func getTokenValue(ctx context.Context, token, domain string, store Store) []byte {
|
||||
func getTokenValue(ctx context.Context, token, domain string, store ChallengeStore) []byte {
|
||||
logger := log.FromContext(ctx)
|
||||
logger.Debugf("Retrieving the ACME challenge for token %v...", token)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
var _ challenge.Provider = (*challengeTLSALPN)(nil)
|
||||
|
||||
type challengeTLSALPN struct {
|
||||
Store Store
|
||||
Store ChallengeStore
|
||||
}
|
||||
|
||||
func (c *challengeTLSALPN) Present(domain, token, keyAuth string) error {
|
||||
|
@ -37,7 +37,7 @@ func (c *challengeTLSALPN) CleanUp(domain, token, keyAuth string) error {
|
|||
|
||||
// GetTLSALPNCertificate Get the temp certificate for ACME TLS-ALPN-O1 challenge.
|
||||
func (p *Provider) GetTLSALPNCertificate(domain string) (*tls.Certificate, error) {
|
||||
cert, err := p.Store.GetTLSChallenge(domain)
|
||||
cert, err := p.ChallengeStore.GetTLSChallenge(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"sync"
|
||||
|
||||
"github.com/containous/traefik/pkg/log"
|
||||
|
@ -16,25 +15,34 @@ var _ Store = (*LocalStore)(nil)
|
|||
|
||||
// LocalStore Stores implementation for local file
|
||||
type LocalStore struct {
|
||||
saveDataChan chan map[string]*StoredData
|
||||
filename string
|
||||
storedData *StoredData
|
||||
SaveDataChan chan *StoredData `json:"-"`
|
||||
lock sync.RWMutex
|
||||
|
||||
lock sync.RWMutex
|
||||
storedData map[string]*StoredData
|
||||
}
|
||||
|
||||
// NewLocalStore initializes a new LocalStore with a file name
|
||||
func NewLocalStore(filename string) *LocalStore {
|
||||
store := &LocalStore{filename: filename, SaveDataChan: make(chan *StoredData)}
|
||||
store := &LocalStore{filename: filename, saveDataChan: make(chan map[string]*StoredData)}
|
||||
store.listenSaveAction()
|
||||
return store
|
||||
}
|
||||
|
||||
func (s *LocalStore) get() (*StoredData, error) {
|
||||
func (s *LocalStore) save(resolverName string, storedData *StoredData) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
s.storedData[resolverName] = storedData
|
||||
s.saveDataChan <- s.storedData
|
||||
}
|
||||
|
||||
func (s *LocalStore) get(resolverName string) (*StoredData, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if s.storedData == nil {
|
||||
s.storedData = &StoredData{
|
||||
HTTPChallenges: make(map[string]map[string][]byte),
|
||||
TLSChallenges: make(map[string]*Certificate),
|
||||
}
|
||||
s.storedData = map[string]*StoredData{}
|
||||
|
||||
hasData, err := CheckFile(s.filename)
|
||||
if err != nil {
|
||||
|
@ -56,49 +64,40 @@ func (s *LocalStore) get() (*StoredData, error) {
|
|||
}
|
||||
|
||||
if len(file) > 0 {
|
||||
if err := json.Unmarshal(file, s.storedData); err != nil {
|
||||
if err := json.Unmarshal(file, &s.storedData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if ACME Account is in ACME V1 format
|
||||
if s.storedData.Account != nil && s.storedData.Account.Registration != nil {
|
||||
isOldRegistration, err := regexp.MatchString(RegistrationURLPathV1Regexp, s.storedData.Account.Registration.URI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isOldRegistration {
|
||||
logger.Debug("Reseting ACME account.")
|
||||
s.storedData.Account = nil
|
||||
s.SaveDataChan <- s.storedData
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all certificates with no value
|
||||
var certificates []*Certificate
|
||||
for _, certificate := range s.storedData.Certificates {
|
||||
if len(certificate.Certificate) == 0 || len(certificate.Key) == 0 {
|
||||
logger.Debugf("Deleting empty certificate %v for %v", certificate, certificate.Domain.ToStrArray())
|
||||
continue
|
||||
var certificates []*CertAndStore
|
||||
for _, storedData := range s.storedData {
|
||||
for _, certificate := range storedData.Certificates {
|
||||
if len(certificate.Certificate.Certificate) == 0 || len(certificate.Key) == 0 {
|
||||
logger.Debugf("Deleting empty certificate %v for %v", certificate, certificate.Domain.ToStrArray())
|
||||
continue
|
||||
}
|
||||
certificates = append(certificates, certificate)
|
||||
}
|
||||
if len(certificates) < len(storedData.Certificates) {
|
||||
storedData.Certificates = certificates
|
||||
s.saveDataChan <- s.storedData
|
||||
}
|
||||
certificates = append(certificates, certificate)
|
||||
}
|
||||
|
||||
if len(certificates) < len(s.storedData.Certificates) {
|
||||
s.storedData.Certificates = certificates
|
||||
s.SaveDataChan <- s.storedData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s.storedData, nil
|
||||
if s.storedData[resolverName] == nil {
|
||||
s.storedData[resolverName] = &StoredData{}
|
||||
}
|
||||
return s.storedData[resolverName], nil
|
||||
}
|
||||
|
||||
// listenSaveAction listens to a chan to store ACME data in json format into LocalStore.filename
|
||||
func (s *LocalStore) listenSaveAction() {
|
||||
safe.Go(func() {
|
||||
logger := log.WithoutContext().WithField(log.ProviderName, "acme")
|
||||
for object := range s.SaveDataChan {
|
||||
for object := range s.saveDataChan {
|
||||
data, err := json.MarshalIndent(object, "", " ")
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
|
@ -113,8 +112,8 @@ func (s *LocalStore) listenSaveAction() {
|
|||
}
|
||||
|
||||
// GetAccount returns ACME Account
|
||||
func (s *LocalStore) GetAccount() (*Account, error) {
|
||||
storedData, err := s.get()
|
||||
func (s *LocalStore) GetAccount(resolverName string) (*Account, error) {
|
||||
storedData, err := s.get(resolverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -123,21 +122,21 @@ func (s *LocalStore) GetAccount() (*Account, error) {
|
|||
}
|
||||
|
||||
// SaveAccount stores ACME Account
|
||||
func (s *LocalStore) SaveAccount(account *Account) error {
|
||||
storedData, err := s.get()
|
||||
func (s *LocalStore) SaveAccount(resolverName string, account *Account) error {
|
||||
storedData, err := s.get(resolverName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storedData.Account = account
|
||||
s.SaveDataChan <- storedData
|
||||
s.save(resolverName, storedData)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCertificates returns ACME Certificates list
|
||||
func (s *LocalStore) GetCertificates() ([]*Certificate, error) {
|
||||
storedData, err := s.get()
|
||||
func (s *LocalStore) GetCertificates(resolverName string) ([]*CertAndStore, error) {
|
||||
storedData, err := s.get(resolverName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -146,20 +145,37 @@ func (s *LocalStore) GetCertificates() ([]*Certificate, error) {
|
|||
}
|
||||
|
||||
// SaveCertificates stores ACME Certificates list
|
||||
func (s *LocalStore) SaveCertificates(certificates []*Certificate) error {
|
||||
storedData, err := s.get()
|
||||
func (s *LocalStore) SaveCertificates(resolverName string, certificates []*CertAndStore) error {
|
||||
storedData, err := s.get(resolverName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storedData.Certificates = certificates
|
||||
s.SaveDataChan <- storedData
|
||||
s.save(resolverName, storedData)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LocalChallengeStore is an implementation of the ChallengeStore in memory.
|
||||
type LocalChallengeStore struct {
|
||||
storedData *StoredChallengeData
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// NewLocalChallengeStore initializes a new LocalChallengeStore.
|
||||
func NewLocalChallengeStore() *LocalChallengeStore {
|
||||
return &LocalChallengeStore{
|
||||
storedData: &StoredChallengeData{
|
||||
HTTPChallenges: make(map[string]map[string][]byte),
|
||||
TLSChallenges: make(map[string]*Certificate),
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// GetHTTPChallengeToken Get the http challenge token from the store
|
||||
func (s *LocalStore) GetHTTPChallengeToken(token, domain string) ([]byte, error) {
|
||||
func (s *LocalChallengeStore) GetHTTPChallengeToken(token, domain string) ([]byte, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
|
@ -179,7 +195,7 @@ func (s *LocalStore) GetHTTPChallengeToken(token, domain string) ([]byte, error)
|
|||
}
|
||||
|
||||
// SetHTTPChallengeToken Set the http challenge token in the store
|
||||
func (s *LocalStore) SetHTTPChallengeToken(token, domain string, keyAuth []byte) error {
|
||||
func (s *LocalChallengeStore) SetHTTPChallengeToken(token, domain string, keyAuth []byte) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
|
@ -196,7 +212,7 @@ func (s *LocalStore) SetHTTPChallengeToken(token, domain string, keyAuth []byte)
|
|||
}
|
||||
|
||||
// RemoveHTTPChallengeToken Remove the http challenge token in the store
|
||||
func (s *LocalStore) RemoveHTTPChallengeToken(token, domain string) error {
|
||||
func (s *LocalChallengeStore) RemoveHTTPChallengeToken(token, domain string) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
|
@ -214,7 +230,7 @@ func (s *LocalStore) RemoveHTTPChallengeToken(token, domain string) error {
|
|||
}
|
||||
|
||||
// AddTLSChallenge Add a certificate to the ACME TLS-ALPN-01 certificates storage
|
||||
func (s *LocalStore) AddTLSChallenge(domain string, cert *Certificate) error {
|
||||
func (s *LocalChallengeStore) AddTLSChallenge(domain string, cert *Certificate) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
|
@ -227,7 +243,7 @@ func (s *LocalStore) AddTLSChallenge(domain string, cert *Certificate) error {
|
|||
}
|
||||
|
||||
// GetTLSChallenge Get a certificate from the ACME TLS-ALPN-01 certificates storage
|
||||
func (s *LocalStore) GetTLSChallenge(domain string) (*Certificate, error) {
|
||||
func (s *LocalChallengeStore) GetTLSChallenge(domain string) (*Certificate, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
|
@ -239,7 +255,7 @@ func (s *LocalStore) GetTLSChallenge(domain string) (*Certificate, error) {
|
|||
}
|
||||
|
||||
// RemoveTLSChallenge Remove a certificate from the ACME TLS-ALPN-01 certificates storage
|
||||
func (s *LocalStore) RemoveTLSChallenge(domain string) error {
|
||||
func (s *LocalChallengeStore) RemoveTLSChallenge(domain string) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
|
|
|
@ -6,8 +6,6 @@ import (
|
|||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
fmtlog "log"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
@ -25,10 +23,8 @@ import (
|
|||
"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"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -39,16 +35,12 @@ var (
|
|||
// Configuration holds ACME configuration provided by users
|
||||
type Configuration struct {
|
||||
Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"`
|
||||
ACMELogging bool `description:"Enable debug logging of ACME actions." json:"acmeLogging,omitempty" toml:"acmeLogging,omitempty" yaml:"acmeLogging,omitempty"`
|
||||
CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"`
|
||||
Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty"`
|
||||
EntryPoint string `description:"EntryPoint to use." json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty"`
|
||||
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"`
|
||||
OnHostRule bool `description:"Enable certificate generation on router Host rules." json:"onHostRule,omitempty" toml:"onHostRule,omitempty" yaml:"onHostRule,omitempty"`
|
||||
DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty"`
|
||||
HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty"`
|
||||
TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty"`
|
||||
Domains []types.Domain `description:"The list of domains for which certificates are generated on startup. Wildcard domains only accepted with DNSChallenge." json:"domains,omitempty" toml:"domains,omitempty" yaml:"domains,omitempty"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
|
@ -58,6 +50,12 @@ func (a *Configuration) SetDefaults() {
|
|||
a.KeyType = "RSA4096"
|
||||
}
|
||||
|
||||
// CertAndStore allows mapping a TLS certificate to a TLS store.
|
||||
type CertAndStore struct {
|
||||
Certificate
|
||||
Store string
|
||||
}
|
||||
|
||||
// Certificate is a struct which contains all data needed from an ACME certificate
|
||||
type Certificate struct {
|
||||
Domain types.Domain `json:"domain,omitempty" toml:"domain,omitempty" yaml:"domain,omitempty"`
|
||||
|
@ -84,11 +82,13 @@ type TLSChallenge struct{}
|
|||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
*Configuration
|
||||
ResolverName string
|
||||
Store Store `json:"store,omitempty" toml:"store,omitempty" yaml:"store,omitempty"`
|
||||
certificates []*Certificate
|
||||
ChallengeStore ChallengeStore
|
||||
certificates []*CertAndStore
|
||||
account *Account
|
||||
client *lego.Client
|
||||
certsChan chan *Certificate
|
||||
certsChan chan *CertAndStore
|
||||
configurationChan chan<- dynamic.Message
|
||||
tlsManager *traefiktls.Manager
|
||||
clientMutex sync.Mutex
|
||||
|
@ -113,41 +113,20 @@ func (p *Provider) ListenConfiguration(config dynamic.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) {
|
||||
ctx := log.With(context.Background(), log.Str(log.ProviderName, "acme"))
|
||||
|
||||
acmeCert, err := p.resolveCertificate(ctx, types.Domain{Main: domain}, false)
|
||||
if acmeCert == nil || err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cert, err := tls.X509KeyPair(acmeCert.Certificate, acmeCert.PrivateKey)
|
||||
|
||||
return &cert, err
|
||||
}
|
||||
|
||||
// Init for compatibility reason the BaseProvider implements an empty Init
|
||||
func (p *Provider) Init() error {
|
||||
|
||||
ctx := log.With(context.Background(), log.Str(log.ProviderName, "acme"))
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
if p.ACMELogging {
|
||||
legolog.Logger = fmtlog.New(logger.WriterLevel(logrus.InfoLevel), "legolog: ", 0)
|
||||
} else {
|
||||
legolog.Logger = fmtlog.New(ioutil.Discard, "", 0)
|
||||
}
|
||||
|
||||
if len(p.Configuration.Storage) == 0 {
|
||||
return errors.New("unable to initialize ACME provider with no storage location for the certificates")
|
||||
}
|
||||
p.Store = NewLocalStore(p.Configuration.Storage)
|
||||
|
||||
var err error
|
||||
p.account, err = p.Store.GetAccount()
|
||||
p.account, err = p.Store.GetAccount(p.ResolverName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get ACME account : %v", err)
|
||||
return fmt.Errorf("unable to get ACME account: %v", err)
|
||||
}
|
||||
|
||||
// Reset Account if caServer changed, thus registration URI can be updated
|
||||
|
@ -156,7 +135,7 @@ func (p *Provider) Init() error {
|
|||
p.account = nil
|
||||
}
|
||||
|
||||
p.certificates, err = p.Store.GetCertificates()
|
||||
p.certificates, err = p.Store.GetCertificates(p.ResolverName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get ACME certificates : %v", err)
|
||||
}
|
||||
|
@ -188,7 +167,7 @@ func isAccountMatchingCaServer(ctx context.Context, accountURI string, serverURI
|
|||
// Provide allows the file provider to provide configurations to traefik
|
||||
// using the given Configuration channel.
|
||||
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
ctx := log.With(context.Background(), log.Str(log.ProviderName, "acme"))
|
||||
ctx := log.With(context.Background(), log.Str(log.ProviderName, "acme."+p.ResolverName))
|
||||
|
||||
p.pool = pool
|
||||
|
||||
|
@ -198,17 +177,6 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
p.configurationChan = configurationChan
|
||||
p.refreshCertificates()
|
||||
|
||||
p.deleteUnnecessaryDomains(ctx)
|
||||
for i := 0; i < len(p.Domains); i++ {
|
||||
domain := p.Domains[i]
|
||||
safe.Go(func() {
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
p.renewCertificates(ctx)
|
||||
|
||||
ticker := time.NewTicker(24 * time.Hour)
|
||||
|
@ -275,13 +243,18 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
|||
|
||||
// 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)
|
||||
err = p.Store.SaveAccount(p.ResolverName, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.DNSChallenge != nil && len(p.DNSChallenge.Provider) > 0:
|
||||
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 {
|
||||
logger.Debugf("Using DNS Challenge provider: %s", p.DNSChallenge.Provider)
|
||||
|
||||
var provider challenge.Provider
|
||||
|
@ -304,25 +277,24 @@ func (p *Provider) getClient() (*lego.Client, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
case p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0:
|
||||
if p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0 {
|
||||
logger.Debug("Using HTTP Challenge provider.")
|
||||
|
||||
err = client.Challenge.SetHTTP01Provider(&challengeHTTP{Store: p.Store})
|
||||
err = client.Challenge.SetHTTP01Provider(&challengeHTTP{Store: p.ChallengeStore})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
case p.TLSChallenge != nil:
|
||||
if p.TLSChallenge != nil {
|
||||
logger.Debug("Using TLS Challenge provider.")
|
||||
|
||||
err = client.Challenge.SetTLSALPN01Provider(&challengeTLSALPN{Store: p.Store})
|
||||
err = client.Challenge.SetTLSALPN01Provider(&challengeTLSALPN{Store: p.ChallengeStore})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errors.New("ACME challenge not specified, please select TLS or HTTP or DNS Challenge")
|
||||
}
|
||||
|
||||
p.client = client
|
||||
|
@ -346,7 +318,7 @@ func (p *Provider) initAccount(ctx context.Context) (*Account, error) {
|
|||
return p.account, nil
|
||||
}
|
||||
|
||||
func (p *Provider) resolveDomains(ctx context.Context, domains []string) {
|
||||
func (p *Provider) resolveDomains(ctx context.Context, domains []string, tlsStore string) {
|
||||
if len(domains) == 0 {
|
||||
log.FromContext(ctx).Debug("No domain parsed in provider ACME")
|
||||
return
|
||||
|
@ -362,7 +334,7 @@ func (p *Provider) resolveDomains(ctx context.Context, domains []string) {
|
|||
}
|
||||
|
||||
safe.Go(func() {
|
||||
if _, err := p.resolveCertificate(ctx, domain, false); err != nil {
|
||||
if _, err := p.resolveCertificate(ctx, domain, tlsStore); err != nil {
|
||||
log.FromContext(ctx).Errorf("Unable to obtain ACME certificate for domains %q: %v", strings.Join(domains, ","), err)
|
||||
}
|
||||
})
|
||||
|
@ -376,32 +348,72 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
|
|||
case config := <-p.configFromListenerChan:
|
||||
if config.TCP != nil {
|
||||
for routerName, route := range config.TCP.Routers {
|
||||
if route.TLS == nil {
|
||||
if route.TLS == nil || route.TLS.CertResolver != p.ResolverName {
|
||||
continue
|
||||
}
|
||||
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
|
||||
tlsStore := "default"
|
||||
if len(route.TLS.Domains) > 0 {
|
||||
for _, domain := range route.TLS.Domains {
|
||||
if domain.Main != dns01.UnFqdn(domain.Main) {
|
||||
log.Warnf("FQDN detected, please remove the trailing dot: %s", domain.Main)
|
||||
}
|
||||
for _, san := range domain.SANs {
|
||||
if san != dns01.UnFqdn(san) {
|
||||
log.Warnf("FQDN detected, please remove the trailing dot: %s", san)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
domains := deleteUnnecessaryDomains(ctxRouter, route.TLS.Domains)
|
||||
for i := 0; i < len(domains); i++ {
|
||||
domain := domains[i]
|
||||
safe.Go(func() {
|
||||
if _, err := p.resolveCertificate(ctx, domain, tlsStore); err != nil {
|
||||
log.WithoutContext().WithField(log.ProviderName, "acme."+p.ResolverName).
|
||||
Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
domains, err := rules.ParseHostSNI(route.Rule)
|
||||
if err != nil {
|
||||
log.FromContext(ctxRouter).Errorf("Error parsing domains in provider ACME: %v", err)
|
||||
continue
|
||||
}
|
||||
p.resolveDomains(ctxRouter, domains, tlsStore)
|
||||
}
|
||||
p.resolveDomains(ctxRouter, domains)
|
||||
}
|
||||
}
|
||||
|
||||
for routerName, route := range config.HTTP.Routers {
|
||||
if route.TLS == nil {
|
||||
if route.TLS == nil || route.TLS.CertResolver != p.ResolverName {
|
||||
continue
|
||||
}
|
||||
ctxRouter := log.With(ctx, log.Str(log.RouterName, routerName), log.Str(log.Rule, route.Rule))
|
||||
|
||||
domains, err := rules.ParseDomains(route.Rule)
|
||||
if err != nil {
|
||||
log.FromContext(ctxRouter).Errorf("Error parsing domains in provider ACME: %v", err)
|
||||
continue
|
||||
tlsStore := "default"
|
||||
if len(route.TLS.Domains) > 0 {
|
||||
domains := deleteUnnecessaryDomains(ctxRouter, route.TLS.Domains)
|
||||
for i := 0; i < len(domains); i++ {
|
||||
domain := domains[i]
|
||||
safe.Go(func() {
|
||||
if _, err := p.resolveCertificate(ctx, domain, tlsStore); err != nil {
|
||||
log.WithoutContext().WithField(log.ProviderName, "acme."+p.ResolverName).
|
||||
Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
domains, err := rules.ParseDomains(route.Rule)
|
||||
if err != nil {
|
||||
log.FromContext(ctxRouter).Errorf("Error parsing domains in provider ACME: %v", err)
|
||||
continue
|
||||
}
|
||||
p.resolveDomains(ctxRouter, domains, tlsStore)
|
||||
}
|
||||
p.resolveDomains(ctxRouter, domains)
|
||||
|
||||
}
|
||||
case <-stop:
|
||||
return
|
||||
|
@ -410,14 +422,14 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
func (p *Provider) resolveCertificate(ctx context.Context, domain types.Domain, domainFromConfigurationFile bool) (*certificate.Resource, error) {
|
||||
domains, err := p.getValidDomains(ctx, domain, domainFromConfigurationFile)
|
||||
func (p *Provider) resolveCertificate(ctx context.Context, domain types.Domain, tlsStore string) (*certificate.Resource, error) {
|
||||
domains, err := p.getValidDomains(ctx, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check provided certificates
|
||||
uncheckedDomains := p.getUncheckedDomains(ctx, domains, !domainFromConfigurationFile)
|
||||
uncheckedDomains := p.getUncheckedDomains(ctx, domains, tlsStore)
|
||||
if len(uncheckedDomains) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
@ -457,7 +469,7 @@ func (p *Provider) resolveCertificate(ctx context.Context, domain types.Domain,
|
|||
} else {
|
||||
domain = types.Domain{Main: uncheckedDomains[0]}
|
||||
}
|
||||
p.addCertificateForDomain(domain, cert.Certificate, cert.PrivateKey)
|
||||
p.addCertificateForDomain(domain, cert.Certificate, cert.PrivateKey, tlsStore)
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
@ -480,22 +492,22 @@ func (p *Provider) addResolvingDomains(resolvingDomains []string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *Provider) addCertificateForDomain(domain types.Domain, certificate []byte, key []byte) {
|
||||
p.certsChan <- &Certificate{Certificate: certificate, Key: key, Domain: domain}
|
||||
func (p *Provider) addCertificateForDomain(domain types.Domain, certificate []byte, key []byte, tlsStore string) {
|
||||
p.certsChan <- &CertAndStore{Certificate: Certificate{Certificate: certificate, Key: key, Domain: domain}, Store: tlsStore}
|
||||
}
|
||||
|
||||
// deleteUnnecessaryDomains deletes from the configuration :
|
||||
// - Duplicated domains
|
||||
// - Domains which are checked by wildcard domain
|
||||
func (p *Provider) deleteUnnecessaryDomains(ctx context.Context) {
|
||||
func deleteUnnecessaryDomains(ctx context.Context, domains []types.Domain) []types.Domain {
|
||||
var newDomains []types.Domain
|
||||
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
for idxDomainToCheck, domainToCheck := range p.Domains {
|
||||
for idxDomainToCheck, domainToCheck := range domains {
|
||||
keepDomain := true
|
||||
|
||||
for idxDomain, domain := range p.Domains {
|
||||
for idxDomain, domain := range domains {
|
||||
if idxDomainToCheck == idxDomain {
|
||||
continue
|
||||
}
|
||||
|
@ -538,11 +550,11 @@ func (p *Provider) deleteUnnecessaryDomains(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
p.Domains = newDomains
|
||||
return newDomains
|
||||
}
|
||||
|
||||
func (p *Provider) watchCertificate(ctx context.Context) {
|
||||
p.certsChan = make(chan *Certificate)
|
||||
p.certsChan = make(chan *CertAndStore)
|
||||
|
||||
p.pool.Go(func(stop chan bool) {
|
||||
for {
|
||||
|
@ -550,9 +562,8 @@ func (p *Provider) watchCertificate(ctx context.Context) {
|
|||
case cert := <-p.certsChan:
|
||||
certUpdated := false
|
||||
for _, domainsCertificate := range p.certificates {
|
||||
if reflect.DeepEqual(cert.Domain, domainsCertificate.Domain) {
|
||||
if reflect.DeepEqual(cert.Domain, domainsCertificate.Certificate.Domain) {
|
||||
domainsCertificate.Certificate = cert.Certificate
|
||||
domainsCertificate.Key = cert.Key
|
||||
certUpdated = true
|
||||
break
|
||||
}
|
||||
|
@ -573,7 +584,7 @@ func (p *Provider) watchCertificate(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (p *Provider) saveCertificates() error {
|
||||
err := p.Store.SaveCertificates(p.certificates)
|
||||
err := p.Store.SaveCertificates(p.ResolverName, p.certificates)
|
||||
|
||||
p.refreshCertificates()
|
||||
|
||||
|
@ -582,7 +593,7 @@ func (p *Provider) saveCertificates() error {
|
|||
|
||||
func (p *Provider) refreshCertificates() {
|
||||
conf := dynamic.Message{
|
||||
ProviderName: "ACME",
|
||||
ProviderName: "acme." + p.ResolverName,
|
||||
Configuration: &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
|
@ -596,9 +607,10 @@ func (p *Provider) refreshCertificates() {
|
|||
for _, cert := range p.certificates {
|
||||
certConf := &traefiktls.CertAndStores{
|
||||
Certificate: traefiktls.Certificate{
|
||||
CertFile: traefiktls.FileOrContent(cert.Certificate),
|
||||
CertFile: traefiktls.FileOrContent(cert.Certificate.Certificate),
|
||||
KeyFile: traefiktls.FileOrContent(cert.Key),
|
||||
},
|
||||
Stores: []string{cert.Store},
|
||||
}
|
||||
conf.Configuration.TLS.Certificates = append(conf.Configuration.TLS.Certificates, certConf)
|
||||
}
|
||||
|
@ -611,7 +623,7 @@ func (p *Provider) renewCertificates(ctx context.Context) {
|
|||
|
||||
logger.Info("Testing certificate renew...")
|
||||
for _, cert := range p.certificates {
|
||||
crt, err := getX509Certificate(ctx, cert)
|
||||
crt, err := getX509Certificate(ctx, &cert.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)) {
|
||||
|
@ -626,7 +638,7 @@ func (p *Provider) renewCertificates(ctx context.Context) {
|
|||
renewedCert, err := client.Certificate.Renew(certificate.Resource{
|
||||
Domain: cert.Domain.Main,
|
||||
PrivateKey: cert.Key,
|
||||
Certificate: cert.Certificate,
|
||||
Certificate: cert.Certificate.Certificate,
|
||||
}, true, oscpMustStaple)
|
||||
|
||||
if err != nil {
|
||||
|
@ -639,20 +651,20 @@ func (p *Provider) renewCertificates(ctx context.Context) {
|
|||
continue
|
||||
}
|
||||
|
||||
p.addCertificateForDomain(cert.Domain, renewedCert.Certificate, renewedCert.PrivateKey)
|
||||
p.addCertificateForDomain(cert.Domain, renewedCert.Certificate, renewedCert.PrivateKey, cert.Store)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get provided certificate which check a domains list (Main and SANs)
|
||||
// from static and dynamic provided certificates
|
||||
func (p *Provider) getUncheckedDomains(ctx context.Context, domainsToCheck []string, checkConfigurationDomains bool) []string {
|
||||
func (p *Provider) getUncheckedDomains(ctx context.Context, domainsToCheck []string, tlsStore string) []string {
|
||||
p.resolvingDomainsMutex.RLock()
|
||||
defer p.resolvingDomainsMutex.RUnlock()
|
||||
|
||||
log.FromContext(ctx).Debugf("Looking for provided certificate(s) to validate %q...", domainsToCheck)
|
||||
|
||||
allDomains := p.tlsManager.GetStore("default").GetAllDomains()
|
||||
allDomains := p.tlsManager.GetStore(tlsStore).GetAllDomains()
|
||||
|
||||
// Get ACME certificates
|
||||
for _, cert := range p.certificates {
|
||||
|
@ -664,13 +676,6 @@ func (p *Provider) getUncheckedDomains(ctx context.Context, domainsToCheck []str
|
|||
allDomains = append(allDomains, domain)
|
||||
}
|
||||
|
||||
// Get Configuration Domains
|
||||
if checkConfigurationDomains {
|
||||
for i := 0; i < len(p.Domains); i++ {
|
||||
allDomains = append(allDomains, strings.Join(p.Domains[i].ToStrArray(), ","))
|
||||
}
|
||||
}
|
||||
|
||||
return searchUncheckedDomains(ctx, domainsToCheck, allDomains)
|
||||
}
|
||||
|
||||
|
@ -712,17 +717,13 @@ func getX509Certificate(ctx context.Context, cert *Certificate) (*x509.Certifica
|
|||
}
|
||||
|
||||
// getValidDomains checks if given domain is allowed to generate a ACME certificate and return it
|
||||
func (p *Provider) getValidDomains(ctx context.Context, domain types.Domain, wildcardAllowed bool) ([]string, error) {
|
||||
func (p *Provider) getValidDomains(ctx context.Context, domain types.Domain) ([]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")
|
||||
}
|
||||
|
||||
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, ","))
|
||||
}
|
||||
|
||||
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, ","))
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
|||
desc string
|
||||
dynamicCerts *safe.Safe
|
||||
resolvingDomains map[string]struct{}
|
||||
acmeCertificates []*Certificate
|
||||
acmeCertificates []*CertAndStore
|
||||
domains []string
|
||||
expectedDomains []string
|
||||
}{
|
||||
|
@ -48,9 +48,11 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
|||
{
|
||||
desc: "wildcard already exists in ACME certificates",
|
||||
domains: []string{"*.traefik.wtf"},
|
||||
acmeCertificates: []*Certificate{
|
||||
acmeCertificates: []*CertAndStore{
|
||||
{
|
||||
Domain: types.Domain{Main: "*.traefik.wtf"},
|
||||
Certificate: Certificate{
|
||||
Domain: types.Domain{Main: "*.traefik.wtf"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDomains: nil,
|
||||
|
@ -69,9 +71,11 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
|||
{
|
||||
desc: "domain CN already exists in ACME certificates and SANs to generate",
|
||||
domains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||
acmeCertificates: []*Certificate{
|
||||
acmeCertificates: []*CertAndStore{
|
||||
{
|
||||
Domain: types.Domain{Main: "traefik.wtf"},
|
||||
Certificate: Certificate{
|
||||
Domain: types.Domain{Main: "traefik.wtf"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDomains: []string{"foo.traefik.wtf"},
|
||||
|
@ -85,9 +89,11 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
|||
{
|
||||
desc: "domain already exists in ACME certificates",
|
||||
domains: []string{"traefik.wtf"},
|
||||
acmeCertificates: []*Certificate{
|
||||
acmeCertificates: []*CertAndStore{
|
||||
{
|
||||
Domain: types.Domain{Main: "traefik.wtf"},
|
||||
Certificate: Certificate{
|
||||
Domain: types.Domain{Main: "traefik.wtf"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDomains: nil,
|
||||
|
@ -101,9 +107,11 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
|||
{
|
||||
desc: "domain matched by wildcard in ACME certificates",
|
||||
domains: []string{"who.traefik.wtf", "foo.traefik.wtf"},
|
||||
acmeCertificates: []*Certificate{
|
||||
acmeCertificates: []*CertAndStore{
|
||||
{
|
||||
Domain: types.Domain{Main: "*.traefik.wtf"},
|
||||
Certificate: Certificate{
|
||||
Domain: types.Domain{Main: "*.traefik.wtf"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDomains: nil,
|
||||
|
@ -111,9 +119,11 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
|||
{
|
||||
desc: "root domain with wildcard in ACME certificates",
|
||||
domains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||
acmeCertificates: []*Certificate{
|
||||
acmeCertificates: []*CertAndStore{
|
||||
{
|
||||
Domain: types.Domain{Main: "*.traefik.wtf"},
|
||||
Certificate: Certificate{
|
||||
Domain: types.Domain{Main: "*.traefik.wtf"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedDomains: []string{"traefik.wtf"},
|
||||
|
@ -171,7 +181,7 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
|||
resolvingDomains: test.resolvingDomains,
|
||||
}
|
||||
|
||||
domains := acmeProvider.getUncheckedDomains(context.Background(), test.domains, false)
|
||||
domains := acmeProvider.getUncheckedDomains(context.Background(), test.domains, "default")
|
||||
assert.Equal(t, len(test.expectedDomains), len(domains), "Unexpected domains.")
|
||||
})
|
||||
}
|
||||
|
@ -181,7 +191,6 @@ func TestGetValidDomain(t *testing.T) {
|
|||
testCases := []struct {
|
||||
desc string
|
||||
domains types.Domain
|
||||
wildcardAllowed bool
|
||||
dnsChallenge *DNSChallenge
|
||||
expectedErr string
|
||||
expectedDomains []string
|
||||
|
@ -190,7 +199,6 @@ func TestGetValidDomain(t *testing.T) {
|
|||
desc: "valid wildcard",
|
||||
domains: types.Domain{Main: "*.traefik.wtf"},
|
||||
dnsChallenge: &DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "",
|
||||
expectedDomains: []string{"*.traefik.wtf"},
|
||||
},
|
||||
|
@ -199,22 +207,12 @@ func TestGetValidDomain(t *testing.T) {
|
|||
domains: types.Domain{Main: "traefik.wtf", SANs: []string{"foo.traefik.wtf"}},
|
||||
dnsChallenge: &DNSChallenge{},
|
||||
expectedErr: "",
|
||||
wildcardAllowed: true,
|
||||
expectedDomains: []string{"traefik.wtf", "foo.traefik.wtf"},
|
||||
},
|
||||
{
|
||||
desc: "unauthorized wildcard",
|
||||
domains: types.Domain{Main: "*.traefik.wtf"},
|
||||
dnsChallenge: &DNSChallenge{},
|
||||
wildcardAllowed: false,
|
||||
expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.traefik.wtf\" from a 'Host' rule",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
{
|
||||
desc: "no domain",
|
||||
domains: types.Domain{},
|
||||
dnsChallenge: nil,
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a certificate in ACME provider when no domain is given",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
|
@ -222,7 +220,6 @@ func TestGetValidDomain(t *testing.T) {
|
|||
desc: "no DNSChallenge",
|
||||
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"foo.traefik.wtf"}},
|
||||
dnsChallenge: nil,
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.traefik.wtf,foo.traefik.wtf\" : ACME needs a DNSChallenge",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
|
@ -230,7 +227,6 @@ func TestGetValidDomain(t *testing.T) {
|
|||
desc: "unauthorized wildcard with SAN",
|
||||
domains: types.Domain{Main: "*.*.traefik.wtf", SANs: []string{"foo.traefik.wtf"}},
|
||||
dnsChallenge: &DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "unable to generate a wildcard certificate in ACME provider for domain \"*.*.traefik.wtf,foo.traefik.wtf\" : ACME does not allow '*.*' wildcard domain",
|
||||
expectedDomains: nil,
|
||||
},
|
||||
|
@ -238,7 +234,6 @@ func TestGetValidDomain(t *testing.T) {
|
|||
desc: "wildcard and SANs",
|
||||
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"traefik.wtf"}},
|
||||
dnsChallenge: &DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "",
|
||||
expectedDomains: []string{"*.traefik.wtf", "traefik.wtf"},
|
||||
},
|
||||
|
@ -246,7 +241,6 @@ func TestGetValidDomain(t *testing.T) {
|
|||
desc: "wildcard SANs",
|
||||
domains: types.Domain{Main: "*.traefik.wtf", SANs: []string{"*.acme.wtf"}},
|
||||
dnsChallenge: &DNSChallenge{},
|
||||
wildcardAllowed: true,
|
||||
expectedErr: "",
|
||||
expectedDomains: []string{"*.traefik.wtf", "*.acme.wtf"},
|
||||
},
|
||||
|
@ -259,7 +253,7 @@ func TestGetValidDomain(t *testing.T) {
|
|||
|
||||
acmeProvider := Provider{Configuration: &Configuration{DNSChallenge: test.dnsChallenge}}
|
||||
|
||||
domains, err := acmeProvider.getValidDomains(context.Background(), test.domains, test.wildcardAllowed)
|
||||
domains, err := acmeProvider.getValidDomains(context.Background(), test.domains)
|
||||
|
||||
if len(test.expectedErr) > 0 {
|
||||
assert.EqualError(t, err, test.expectedErr, "Unexpected error.")
|
||||
|
@ -439,10 +433,8 @@ func TestDeleteUnnecessaryDomains(t *testing.T) {
|
|||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
acmeProvider := Provider{Configuration: &Configuration{Domains: test.domains}}
|
||||
|
||||
acmeProvider.deleteUnnecessaryDomains(context.Background())
|
||||
assert.Equal(t, test.expectedDomains, acmeProvider.Domains, "unexpected domain")
|
||||
domains := deleteUnnecessaryDomains(context.Background(), test.domains)
|
||||
assert.Equal(t, test.expectedDomains, domains, "unexpected domain")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,27 @@
|
|||
package acme
|
||||
|
||||
// StoredData represents the data managed by Store
|
||||
// StoredData represents the data managed by Store.
|
||||
type StoredData struct {
|
||||
Account *Account
|
||||
Certificates []*Certificate
|
||||
Account *Account
|
||||
Certificates []*CertAndStore
|
||||
}
|
||||
|
||||
// StoredChallengeData represents the data managed by ChallengeStore.
|
||||
type StoredChallengeData struct {
|
||||
HTTPChallenges map[string]map[string][]byte
|
||||
TLSChallenges map[string]*Certificate
|
||||
}
|
||||
|
||||
// Store is a generic interface that represents a storage
|
||||
// Store is a generic interface that represents a storage.
|
||||
type Store interface {
|
||||
GetAccount() (*Account, error)
|
||||
SaveAccount(*Account) error
|
||||
GetCertificates() ([]*Certificate, error)
|
||||
SaveCertificates([]*Certificate) error
|
||||
GetAccount(string) (*Account, error)
|
||||
SaveAccount(string, *Account) error
|
||||
GetCertificates(string) ([]*CertAndStore, error)
|
||||
SaveCertificates(string, []*CertAndStore) error
|
||||
}
|
||||
|
||||
// ChallengeStore is a generic interface that represents a store for challenge data.
|
||||
type ChallengeStore interface {
|
||||
GetHTTPChallengeToken(token, domain string) ([]byte, error)
|
||||
SetHTTPChallengeToken(token, domain string, keyAuth []byte) error
|
||||
RemoveHTTPChallengeToken(token, domain string) error
|
||||
|
|
|
@ -438,7 +438,10 @@ func (p *Provider) loadIngressRouteConfiguration(ctx context.Context, client Cli
|
|||
}
|
||||
|
||||
if ingressRoute.Spec.TLS != nil {
|
||||
tlsConf := &dynamic.RouterTLSConfig{}
|
||||
tlsConf := &dynamic.RouterTLSConfig{
|
||||
CertResolver: ingressRoute.Spec.TLS.CertResolver,
|
||||
}
|
||||
|
||||
if ingressRoute.Spec.TLS.Options != nil && len(ingressRoute.Spec.TLS.Options.Name) > 0 {
|
||||
tlsOptionsName := ingressRoute.Spec.TLS.Options.Name
|
||||
// Is a Kubernetes CRD reference, (i.e. not a cross-provider reference)
|
||||
|
@ -537,7 +540,8 @@ func (p *Provider) loadIngressRouteTCPConfiguration(ctx context.Context, client
|
|||
|
||||
if ingressRouteTCP.Spec.TLS != nil {
|
||||
conf.Routers[serviceName].TLS = &dynamic.RouterTCPTLSConfig{
|
||||
Passthrough: ingressRouteTCP.Spec.TLS.Passthrough,
|
||||
Passthrough: ingressRouteTCP.Spec.TLS.Passthrough,
|
||||
CertResolver: ingressRouteTCP.Spec.TLS.CertResolver,
|
||||
}
|
||||
|
||||
if ingressRouteTCP.Spec.TLS.Options != nil && len(ingressRouteTCP.Spec.TLS.Options.Name) > 0 {
|
||||
|
|
|
@ -32,7 +32,8 @@ type TLS struct {
|
|||
// certificate details.
|
||||
SecretName string `json:"secretName"`
|
||||
// Options is a reference to a TLSOption, that specifies the parameters of the TLS connection.
|
||||
Options *TLSOptionRef `json:"options"`
|
||||
Options *TLSOptionRef `json:"options"`
|
||||
CertResolver string `json:"certResolver"`
|
||||
}
|
||||
|
||||
// TLSOptionRef is a ref to the TLSOption resources.
|
||||
|
|
|
@ -30,7 +30,8 @@ type TLSTCP struct {
|
|||
SecretName string `json:"secretName"`
|
||||
Passthrough bool `json:"passthrough"`
|
||||
// Options is a reference to a TLSOption, that specifies the parameters of the TLS connection.
|
||||
Options *TLSOptionTCPRef `json:"options"`
|
||||
Options *TLSOptionTCPRef `json:"options"`
|
||||
CertResolver string `json:"certResolver"`
|
||||
}
|
||||
|
||||
// TLSOptionTCPRef is a ref to the TLSOption resources.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue