tls Manager: do not build a default certificate for ACME challenges store

Co-authored-by: Mathieu Lonjaret <mathieu.lonjaret@gmail.com>
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
Richard Kojedzinszky 2021-06-14 10:06:05 +02:00 committed by GitHub
parent fc9f41b955
commit f15d05b22f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 51 deletions

View file

@ -383,7 +383,6 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
ctxRouter := log.With(ctx, log.Str(log.RouterName, routerName), log.Str(log.Rule, route.Rule))
logger := log.FromContext(ctxRouter)
tlsStore := "default"
if len(route.TLS.Domains) > 0 {
for _, domain := range route.TLS.Domains {
if domain.Main != dns01.UnFqdn(domain.Main) {
@ -400,7 +399,7 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
for i := 0; i < len(domains); i++ {
domain := domains[i]
safe.Go(func() {
if _, err := p.resolveCertificate(ctx, domain, tlsStore); err != nil {
if _, err := p.resolveCertificate(ctx, domain, traefiktls.DefaultTLSStoreName); err != nil {
log.WithoutContext().WithField(log.ProviderName, p.ResolverName+".acme").
Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
}
@ -412,7 +411,7 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
logger.Errorf("Error parsing domains in provider ACME: %v", err)
continue
}
p.resolveDomains(ctxRouter, domains, tlsStore)
p.resolveDomains(ctxRouter, domains, traefiktls.DefaultTLSStoreName)
}
}
}
@ -424,13 +423,12 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
ctxRouter := log.With(ctx, log.Str(log.RouterName, routerName), log.Str(log.Rule, route.Rule))
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 {
if _, err := p.resolveCertificate(ctx, domain, traefiktls.DefaultTLSStoreName); err != nil {
log.WithoutContext().WithField(log.ProviderName, p.ResolverName+".acme").
Errorf("Unable to obtain ACME certificate for domains %q : %v", strings.Join(domain.ToStrArray(), ","), err)
}
@ -442,7 +440,7 @@ func (p *Provider) watchNewDomains(ctx context.Context) {
log.FromContext(ctxRouter).Errorf("Error parsing domains in provider ACME: %v", err)
continue
}
p.resolveDomains(ctxRouter, domains, tlsStore)
p.resolveDomains(ctxRouter, domains, traefiktls.DefaultTLSStoreName)
}
}
case <-ctxPool.Done():

View file

@ -691,7 +691,7 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options
id := makeID(tlsOption.Namespace, tlsOption.Name)
// If the name is default, we override the default config.
if tlsOption.Name == "default" {
if tlsOption.Name == tls.DefaultTLSConfigName {
id = tlsOption.Name
nsDefault = append(nsDefault, tlsOption.Namespace)
}
@ -710,7 +710,7 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options
}
if len(nsDefault) > 1 {
delete(tlsOptions, "default")
delete(tlsOptions, tls.DefaultTLSConfigName)
log.FromContext(ctx).Errorf("Default TLS Options defined in multiple namespaces: %v", nsDefault)
}
@ -750,7 +750,7 @@ func buildTLSStores(ctx context.Context, client Client) map[string]tls.Store {
id := makeID(tlsStore.Namespace, tlsStore.Name)
// If the name is default, we override the default config.
if tlsStore.Name == "default" {
if tlsStore.Name == tls.DefaultTLSStoreName {
id = tlsStore.Name
nsDefault = append(nsDefault, tlsStore.Namespace)
}
@ -763,7 +763,7 @@ func buildTLSStores(ctx context.Context, client Client) map[string]tls.Store {
}
if len(nsDefault) > 1 {
delete(tlsStores, "default")
delete(tlsStores, tls.DefaultTLSStoreName)
log.FromContext(ctx).Errorf("Default TLS Stores defined in multiple namespaces: %v", nsDefault)
}

View file

@ -91,7 +91,7 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
}
for key, store := range configuration.TLS.Stores {
if key != "default" {
if key != tls.DefaultTLSStoreName {
key = provider.MakeQualifiedName(pvd, key)
} else {
defaultTLSStoreProviders = append(defaultTLSStoreProviders, pvd)
@ -113,16 +113,16 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
if len(defaultTLSStoreProviders) > 1 {
log.WithoutContext().Errorf("Default TLS Stores defined multiple times in %v", defaultTLSOptionProviders)
delete(conf.TLS.Stores, "default")
delete(conf.TLS.Stores, tls.DefaultTLSStoreName)
}
if len(defaultTLSOptionProviders) == 0 {
conf.TLS.Options["default"] = tls.DefaultTLSOptions
conf.TLS.Options[tls.DefaultTLSConfigName] = tls.DefaultTLSOptions
} else if len(defaultTLSOptionProviders) > 1 {
log.WithoutContext().Errorf("Default TLS Options defined multiple times in %v", defaultTLSOptionProviders)
// We do not set an empty tls.TLS{} as above so that we actually get a "cascading failure" later on,
// i.e. routers depending on this missing TLS option will fail to initialize as well.
delete(conf.TLS.Options, "default")
delete(conf.TLS.Options, tls.DefaultTLSConfigName)
}
return conf

View file

@ -18,11 +18,6 @@ import (
traefiktls "github.com/traefik/traefik/v2/pkg/tls"
)
const (
defaultTLSConfigName = "default"
defaultTLSStoreName = "default"
)
type middlewareBuilder interface {
BuildChain(ctx context.Context, names []string) *tcp.Chain
}
@ -103,7 +98,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
router := &tcp.Router{}
router.HTTPHandler(handlerHTTP)
defaultTLSConf, err := m.tlsManager.Get(defaultTLSStoreName, defaultTLSConfigName)
defaultTLSConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, traefiktls.DefaultTLSConfigName)
if err != nil {
log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err)
}
@ -123,8 +118,8 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
ctxRouter := log.With(provider.AddInContext(ctx, routerHTTPName), log.Str(log.RouterName, routerHTTPName))
logger := log.FromContext(ctxRouter)
tlsOptionsName := defaultTLSConfigName
if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != defaultTLSConfigName {
tlsOptionsName := traefiktls.DefaultTLSConfigName
if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != traefiktls.DefaultTLSConfigName {
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
}
@ -141,7 +136,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
}
for _, domain := range domains {
tlsConf, err := m.tlsManager.Get(defaultTLSStoreName, tlsOptionsName)
tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
if err != nil {
routerHTTPConfig.AddError(err, true)
logger.Debug(err)
@ -159,7 +154,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
if name, ok := tlsOptionsForHost[domain]; ok && name != tlsOptionsName {
// Different tlsOptions on the same domain fallback to default
tlsOptionsForHost[domain] = defaultTLSConfigName
tlsOptionsForHost[domain] = traefiktls.DefaultTLSConfigName
} else {
tlsOptionsForHost[domain] = tlsOptionsName
}
@ -280,14 +275,14 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
tlsOptionsName := routerConfig.TLS.Options
if len(tlsOptionsName) == 0 {
tlsOptionsName = defaultTLSConfigName
tlsOptionsName = traefiktls.DefaultTLSConfigName
}
if tlsOptionsName != defaultTLSConfigName {
if tlsOptionsName != traefiktls.DefaultTLSConfigName {
tlsOptionsName = provider.GetQualifiedName(ctxRouter, tlsOptionsName)
}
tlsConf, err := m.tlsManager.Get(defaultTLSStoreName, tlsOptionsName)
tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
if err != nil {
routerConfig.AddError(err, true)
logger.Debug(err)
@ -338,5 +333,5 @@ func findTLSOptionName(tlsOptionsForHost map[string]string, host string) string
return tlsOptions
}
return defaultTLSConfigName
return traefiktls.DefaultTLSConfigName
}

View file

@ -69,7 +69,10 @@ func (c CertificateStore) GetAllDomains() []string {
}
// GetBestCertificate returns the best match certificate, and caches the response.
func (c CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) *tls.Certificate {
func (c *CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) *tls.Certificate {
if c == nil {
return nil
}
domainToCheck := strings.ToLower(strings.TrimSpace(clientHello.ServerName))
if len(domainToCheck) == 0 {
// If no ServerName is provided, Check for local IP address matches

View file

@ -15,16 +15,24 @@ import (
"github.com/traefik/traefik/v2/pkg/types"
)
const (
// DefaultTLSConfigName is the name of the default set of options for configuring TLS.
DefaultTLSConfigName = "default"
// DefaultTLSStoreName is the name of the default store of TLS certificates.
// Note that it actually is the only usable one for now.
DefaultTLSStoreName = "default"
)
// DefaultTLSOptions the default TLS options.
var DefaultTLSOptions = Options{}
// Manager is the TLS option/store/configuration factory.
type Manager struct {
lock sync.RWMutex
storesConfig map[string]Store
stores map[string]*CertificateStore
configs map[string]Options
certs []*CertAndStores
lock sync.RWMutex
}
// NewManager creates a new Manager.
@ -38,6 +46,7 @@ func NewManager() *Manager {
}
// UpdateConfigs updates the TLS* configuration options.
// It initializes the default TLS store, and the TLS store for the ACME challenges.
func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, configs map[string]Options, certs []*CertAndStores) {
m.lock.Lock()
defer m.lock.Unlock()
@ -46,10 +55,22 @@ func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, co
m.storesConfig = stores
m.certs = certs
if m.storesConfig == nil {
m.storesConfig = make(map[string]Store)
}
if _, ok := m.storesConfig[DefaultTLSStoreName]; !ok {
m.storesConfig[DefaultTLSStoreName] = Store{}
}
if _, ok := m.storesConfig[tlsalpn01.ACMETLS1Protocol]; !ok {
m.storesConfig[tlsalpn01.ACMETLS1Protocol] = Store{}
}
m.stores = make(map[string]*CertificateStore)
for storeName, storeConfig := range m.storesConfig {
ctxStore := log.With(ctx, log.Str(log.TLSStoreName, storeName))
store, err := buildCertificateStore(ctxStore, storeConfig)
store, err := buildCertificateStore(ctxStore, storeConfig, storeName)
if err != nil {
log.FromContext(ctxStore).Errorf("Error while creating certificate store: %v", err)
continue
@ -75,7 +96,12 @@ func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, co
}
for storeName, certs := range storesCertificates {
m.getStore(storeName).DynamicCerts.Set(certs)
st, ok := m.stores[storeName]
if !ok {
st, _ = buildCertificateStore(context.Background(), Store{}, storeName)
m.stores[storeName] = st
}
st.DynamicCerts.Set(certs)
}
}
@ -87,20 +113,25 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
var tlsConfig *tls.Config
var err error
sniStrict := false
config, ok := m.configs[configName]
if !ok {
if ok {
sniStrict = config.SniStrict
tlsConfig, err = buildTLSConfig(config)
} else {
err = fmt.Errorf("unknown TLS options: %s", configName)
}
if err != nil {
tlsConfig = &tls.Config{}
}
store := m.getStore(storeName)
if store == nil {
err = fmt.Errorf("TLS store %s not found", storeName)
}
acmeTLSStore := m.getStore(tlsalpn01.ACMETLS1Protocol)
if err == nil {
tlsConfig, err = buildTLSConfig(config)
if err != nil {
tlsConfig = &tls.Config{}
}
if acmeTLSStore == nil {
err = fmt.Errorf("ACME TLS store %s not found", tlsalpn01.ACMETLS1Protocol)
}
tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
@ -120,7 +151,7 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
return bestCertificate, nil
}
if m.configs[configName].SniStrict {
if sniStrict {
return nil, fmt.Errorf("strict SNI enabled - No certificate found for domain: %q, closing connection", domainToCheck)
}
@ -152,12 +183,13 @@ func (m *Manager) GetCertificates() []*x509.Certificate {
return certificates
}
// getStore returns the store found for storeName, or nil otherwise.
func (m *Manager) getStore(storeName string) *CertificateStore {
_, ok := m.stores[storeName]
st, ok := m.stores[storeName]
if !ok {
m.stores[storeName], _ = buildCertificateStore(context.Background(), Store{})
return nil
}
return m.stores[storeName]
return st
}
// GetStore gets the certificate store of a given name.
@ -168,7 +200,7 @@ func (m *Manager) GetStore(storeName string) *CertificateStore {
return m.getStore(storeName)
}
func buildCertificateStore(ctx context.Context, tlsStore Store) (*CertificateStore, error) {
func buildCertificateStore(ctx context.Context, tlsStore Store, storename string) (*CertificateStore, error) {
certificateStore := NewCertificateStore()
certificateStore.DynamicCerts.Set(make(map[string]*tls.Certificate))
@ -178,14 +210,21 @@ func buildCertificateStore(ctx context.Context, tlsStore Store) (*CertificateSto
return certificateStore, err
}
certificateStore.DefaultCertificate = cert
} else {
log.FromContext(ctx).Debug("No default certificate, generating one")
cert, err := generate.DefaultCertificate()
if err != nil {
return certificateStore, err
}
certificateStore.DefaultCertificate = cert
return certificateStore, nil
}
// a default cert for the ACME store does not make any sense, so generating one
// is a waste.
if storename == tlsalpn01.ACMETLS1Protocol {
return certificateStore, nil
}
log.FromContext(ctx).Debug("No default certificate, generating one")
cert, err := generate.DefaultCertificate()
if err != nil {
return certificateStore, err
}
certificateStore.DefaultCertificate = cert
return certificateStore, nil
}