From f15d05b22ffb752c5d9feeaed81346896cb2f29a Mon Sep 17 00:00:00 2001 From: Richard Kojedzinszky Date: Mon, 14 Jun 2021 10:06:05 +0200 Subject: [PATCH] tls Manager: do not build a default certificate for ACME challenges store Co-authored-by: Mathieu Lonjaret Co-authored-by: Romain --- pkg/provider/acme/provider.go | 10 ++- pkg/provider/kubernetes/crd/kubernetes.go | 8 +-- pkg/server/aggregator.go | 8 +-- pkg/server/router/tcp/router.go | 23 +++---- pkg/tls/certificate_store.go | 5 +- pkg/tls/tlsmanager.go | 83 +++++++++++++++++------ 6 files changed, 86 insertions(+), 51 deletions(-) diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index 61bc68f1d..26e61faa4 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -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(): diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 7b1299aff..108f7d87b 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -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) } diff --git a/pkg/server/aggregator.go b/pkg/server/aggregator.go index 2a7dfc3d3..d2bf19025 100644 --- a/pkg/server/aggregator.go +++ b/pkg/server/aggregator.go @@ -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 diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index 0621775dc..abe31d180 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -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 } diff --git a/pkg/tls/certificate_store.go b/pkg/tls/certificate_store.go index 91bd00ef5..36922f1ce 100644 --- a/pkg/tls/certificate_store.go +++ b/pkg/tls/certificate_store.go @@ -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 diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index 88fae8357..2ac87a161 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -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 }