diff --git a/acme/acme.go b/acme/acme.go index 837824792..cbf570151 100644 --- a/acme/acme.go +++ b/acme/acme.go @@ -23,7 +23,6 @@ import ( "github.com/containous/traefik/log" acmeprovider "github.com/containous/traefik/provider/acme" "github.com/containous/traefik/safe" - traefikTls "github.com/containous/traefik/tls" "github.com/containous/traefik/tls/generate" "github.com/containous/traefik/types" "github.com/eapache/channels" @@ -582,7 +581,7 @@ func (a *ACME) getUncheckedDomains(domains []string, account *Account) []string // Get dynamic certificates if a.dynamicCerts != nil && a.dynamicCerts.Get() != nil { - for domains, certificate := range a.dynamicCerts.Get().(*traefikTls.DomainsCertificates).Get().(map[string]*tls.Certificate) { + for domains, certificate := range a.dynamicCerts.Get().(map[string]*tls.Certificate) { allCerts[domains] = certificate } } diff --git a/anonymize/anonymize_config_test.go b/anonymize/anonymize_config_test.go index 837b8390f..b92cfd82c 100644 --- a/anonymize/anonymize_config_test.go +++ b/anonymize/anonymize_config_test.go @@ -25,7 +25,7 @@ import ( "github.com/containous/traefik/provider/mesos" "github.com/containous/traefik/provider/rancher" "github.com/containous/traefik/provider/zk" - traefikTls "github.com/containous/traefik/tls" + traefiktls "github.com/containous/traefik/tls" "github.com/containous/traefik/types" ) @@ -46,14 +46,14 @@ func TestDo_globalConfiguration(t *testing.T) { config.EntryPoints = configuration.EntryPoints{ "foo": { Address: "foo Address", - TLS: &traefikTls.TLS{ + TLS: &traefiktls.TLS{ MinVersion: "foo MinVersion", CipherSuites: []string{"foo CipherSuites 1", "foo CipherSuites 2", "foo CipherSuites 3"}, - Certificates: traefikTls.Certificates{ + Certificates: traefiktls.Certificates{ {CertFile: "CertFile 1", KeyFile: "KeyFile 1"}, {CertFile: "CertFile 2", KeyFile: "KeyFile 2"}, }, - ClientCA: traefikTls.ClientCA{ + ClientCA: traefiktls.ClientCA{ Files: []string{"foo ClientCAFiles 1", "foo ClientCAFiles 2", "foo ClientCAFiles 3"}, Optional: false, }, @@ -91,14 +91,14 @@ func TestDo_globalConfiguration(t *testing.T) { }, "fii": { Address: "fii Address", - TLS: &traefikTls.TLS{ + TLS: &traefiktls.TLS{ MinVersion: "fii MinVersion", CipherSuites: []string{"fii CipherSuites 1", "fii CipherSuites 2", "fii CipherSuites 3"}, - Certificates: traefikTls.Certificates{ + Certificates: traefiktls.Certificates{ {CertFile: "CertFile 1", KeyFile: "KeyFile 1"}, {CertFile: "CertFile 2", KeyFile: "KeyFile 2"}, }, - ClientCA: traefikTls.ClientCA{ + ClientCA: traefiktls.ClientCA{ Files: []string{"fii ClientCAFiles 1", "fii ClientCAFiles 2", "fii ClientCAFiles 3"}, Optional: false, }, @@ -181,7 +181,7 @@ func TestDo_globalConfiguration(t *testing.T) { config.MaxIdleConnsPerHost = 666 config.IdleTimeout = flaeg.Duration(666 * time.Second) config.InsecureSkipVerify = true - config.RootCAs = traefikTls.RootCAs{"RootCAs 1", "RootCAs 2", "RootCAs 3"} + config.RootCAs = traefiktls.RootCAs{"RootCAs 1", "RootCAs 2", "RootCAs 3"} config.Retry = &configuration.Retry{ Attempts: 666, } diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index ce783d10a..4e6fe83e5 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -28,7 +28,7 @@ import ( "github.com/containous/traefik/safe" "github.com/containous/traefik/server" "github.com/containous/traefik/server/uuid" - traefikTls "github.com/containous/traefik/tls" + traefiktls "github.com/containous/traefik/tls" "github.com/containous/traefik/types" "github.com/containous/traefik/version" "github.com/coreos/go-systemd/daemon" @@ -62,7 +62,7 @@ Complete documentation is available at https://traefik.io`, // add custom parsers f.AddParser(reflect.TypeOf(configuration.EntryPoints{}), &configuration.EntryPoints{}) f.AddParser(reflect.TypeOf(configuration.DefaultEntryPoints{}), &configuration.DefaultEntryPoints{}) - f.AddParser(reflect.TypeOf(traefikTls.RootCAs{}), &traefikTls.RootCAs{}) + f.AddParser(reflect.TypeOf(traefiktls.RootCAs{}), &traefiktls.RootCAs{}) f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{}) f.AddParser(reflect.TypeOf(kubernetes.Namespaces{}), &kubernetes.Namespaces{}) f.AddParser(reflect.TypeOf(ecs.Clusters{}), &ecs.Clusters{}) diff --git a/integration/https_test.go b/integration/https_test.go index a5fbb2707..f93c28c77 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -12,7 +12,7 @@ import ( "github.com/BurntSushi/toml" "github.com/containous/traefik/integration/try" - traefikTls "github.com/containous/traefik/tls" + traefiktls "github.com/containous/traefik/tls" "github.com/containous/traefik/types" "github.com/go-check/check" checker "github.com/vdemeester/shakers" @@ -624,11 +624,11 @@ func modifyCertificateConfFileContent(c *check.C, certFileName, confFileName, en // If certificate file is not provided, just truncate the configuration file if len(certFileName) > 0 { tlsConf := types.Configuration{ - TLS: []*traefikTls.Configuration{ + TLS: []*traefiktls.Configuration{ { - Certificate: &traefikTls.Certificate{ - CertFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".cert"), - KeyFile: traefikTls.FileOrContent("fixtures/https/" + certFileName + ".key"), + Certificate: &traefiktls.Certificate{ + CertFile: traefiktls.FileOrContent("fixtures/https/" + certFileName + ".cert"), + KeyFile: traefiktls.FileOrContent("fixtures/https/" + certFileName + ".key"), }, EntryPoints: []string{entryPoint}, }, diff --git a/provider/acme/provider.go b/provider/acme/provider.go index 575e5d07f..820ad4a8d 100644 --- a/provider/acme/provider.go +++ b/provider/acme/provider.go @@ -112,12 +112,12 @@ func (p *Provider) init() error { p.account, err = p.Store.GetAccount() if err != nil { - return err + return fmt.Errorf("unable to get ACME account : %v", err) } p.certificates, err = p.Store.GetCertificates() if err != nil { - return err + return fmt.Errorf("unable to get ACME account : %v", err) } p.watchCertificate() @@ -215,30 +215,29 @@ func (p *Provider) resolveCertificate(domain types.Domain) (*acme.CertificateRes } domains = fun.Map(types.CanonicalDomain, domains).([]string) - log.Debugf("Looking for provided certificate to validate %s...", domains) - cert := searchProvidedCertificateForDomains(domains, p.staticCerts) - if cert != nil { - return nil, nil - } - if p.dynamicCerts != nil && p.dynamicCerts.Get() != nil && p.dynamicCerts.Get().(*traefikTLS.DomainsCertificates).Get() != nil { - cert = searchProvidedCertificateForDomains(domains, p.dynamicCerts.Get().(*traefikTLS.DomainsCertificates).Get().(map[string]*tls.Certificate)) - } - if cert != nil { + // Check provided certificates + uncheckedDomains := p.getUncheckedDomains(domains) + if len(uncheckedDomains) == 0 { return nil, nil } - log.Debugf("Loading ACME certificates %+v...", domains) + log.Debugf("Loading ACME certificates %+v...", uncheckedDomains) client, err := p.getClient() if err != nil { return nil, fmt.Errorf("cannot get ACME client %v", err) } bundle := true - certificate, failures := client.ObtainCertificate(domains, bundle, nil, OSCPMustStaple) + certificate, failures := client.ObtainCertificate(uncheckedDomains, bundle, nil, OSCPMustStaple) if len(failures) > 0 { return nil, fmt.Errorf("cannot obtain certificates %+v", failures) } - log.Debugf("Certificates obtained for domain %+v", domains) + log.Debugf("Certificates obtained for domain %+v", uncheckedDomains) + if len(uncheckedDomains) > 1 { + domain = types.Domain{Main: uncheckedDomains[0], SANs: uncheckedDomains[1:]} + } else { + domain = types.Domain{Main: uncheckedDomains[0]} + } p.addCertificateForDomain(domain, certificate.Certificate, certificate.PrivateKey) return &certificate, nil @@ -286,7 +285,7 @@ func (p *Provider) getClient() (*acme.Client, error) { account.Registration = reg err = client.AgreeToTOS() if err != nil { - return nil, err + return nil, fmt.Errorf("error sending ACME agreement to TOS: %+v: %v", account, err) } } @@ -527,24 +526,55 @@ func (p *Provider) AddRoutes(router *mux.Router) { })) } -func searchProvidedCertificateForDomains(domains []string, certs map[string]*tls.Certificate) *tls.Certificate { - // Use regex to test for provided certs that might have been added into TLSConfig - providedCertMatch := false - for k := range certs { - selector := "^" + strings.Replace(k, "*.", "[^\\.]*\\.?", -1) + "$" - for _, domainToCheck := range domains { - providedCertMatch, _ = regexp.MatchString(selector, domainToCheck) - if !providedCertMatch { +// Get provided certificate which check a domains list (Main and SANs) +// from static and dynamic provided certificates +func (p *Provider) getUncheckedDomains(domains []string) []string { + log.Debugf("Looking for provided certificate(s) to validate %q...", domains) + allCerts := make(map[string]*tls.Certificate) + + // Get static certificates + for domains, certificate := range p.staticCerts { + allCerts[domains] = certificate + } + + // Get dynamic certificates + if p.dynamicCerts != nil && p.dynamicCerts.Get() != nil { + for domains, certificate := range p.dynamicCerts.Get().(map[string]*tls.Certificate) { + allCerts[domains] = certificate + } + } + + return searchUncheckedDomains(domains, allCerts) +} + +func searchUncheckedDomains(domains []string, certs map[string]*tls.Certificate) []string { + uncheckedDomains := []string{} + for _, domainToCheck := range domains { + domainCheck := false + for certDomains := range certs { + domainCheck = false + for _, certDomain := range strings.Split(certDomains, ",") { + // Use regex to test for provided certs that might have been added into TLSConfig + selector := "^" + strings.Replace(certDomain, "*.", "[^\\.]*\\.?", -1) + "$" + domainCheck, _ = regexp.MatchString(selector, domainToCheck) + if domainCheck { + break + } + } + if domainCheck { break } } - if providedCertMatch { - log.Debugf("Got provided certificate for domains %s", domains) - return certs[k] - + if !domainCheck { + uncheckedDomains = append(uncheckedDomains, domainToCheck) } } - return nil + if len(uncheckedDomains) == 0 { + log.Debugf("No ACME certificate to generate for domains %q.", domains) + } else { + log.Debugf("Domains %q need ACME certificates generation for domains %q.", domains, strings.Join(uncheckedDomains, ",")) + } + return uncheckedDomains } func getX509Certificate(certificate *Certificate) (*x509.Certificate, error) { diff --git a/server/server.go b/server/server.go index 1b18049d3..299688e87 100644 --- a/server/server.go +++ b/server/server.go @@ -38,7 +38,7 @@ import ( "github.com/containous/traefik/rules" "github.com/containous/traefik/safe" "github.com/containous/traefik/server/cookie" - traefikTls "github.com/containous/traefik/tls" + traefiktls "github.com/containous/traefik/tls" "github.com/containous/traefik/types" "github.com/containous/traefik/whitelist" "github.com/eapache/channels" @@ -176,7 +176,7 @@ func createHTTPTransport(globalConfiguration configuration.GlobalConfiguration) return transport } -func createRootCACertPool(rootCAs traefikTls.RootCAs) *x509.CertPool { +func createRootCACertPool(rootCAs traefiktls.RootCAs) *x509.CertPool { roots := x509.NewCertPool() for _, cert := range rootCAs { @@ -480,12 +480,12 @@ func (s *serverEntryPoint) SetOnDemandListener(listener func(string) (*tls.Certi } // loadHTTPSConfiguration add/delete HTTPS certificate managed dynamically -func (s *Server) loadHTTPSConfiguration(configurations types.Configurations, defaultEntryPoints configuration.DefaultEntryPoints) (map[string]*traefikTls.DomainsCertificates, error) { - newEPCertificates := make(map[string]*traefikTls.DomainsCertificates) +func (s *Server) loadHTTPSConfiguration(configurations types.Configurations, defaultEntryPoints configuration.DefaultEntryPoints) (map[string]map[string]*tls.Certificate, error) { + newEPCertificates := make(map[string]map[string]*tls.Certificate) // Get all certificates for _, configuration := range configurations { if configuration.TLS != nil && len(configuration.TLS) > 0 { - if err := traefikTls.SortTLSPerEntryPoints(configuration.TLS, newEPCertificates, defaultEntryPoints); err != nil { + if err := traefiktls.SortTLSPerEntryPoints(configuration.TLS, newEPCertificates, defaultEntryPoints); err != nil { return nil, err } } @@ -497,7 +497,7 @@ func (s *Server) loadHTTPSConfiguration(configurations types.Configurations, def func (s *serverEntryPoint) getCertificate(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { domainToCheck := types.CanonicalDomain(clientHello.ServerName) if s.certs.Get() != nil { - for domains, cert := range *s.certs.Get().(*traefikTls.DomainsCertificates) { + for domains, cert := range s.certs.Get().(map[string]*tls.Certificate) { for _, domain := range strings.Split(domains, ",") { selector := "^" + strings.Replace(domain, "*.", "[^\\.]*\\.?", -1) + "$" domainCheck, _ := regexp.MatchString(selector, domainToCheck) @@ -569,7 +569,7 @@ func (s *Server) startProvider() { }) } -func createClientTLSConfig(entryPointName string, tlsOption *traefikTls.TLS) (*tls.Config, error) { +func createClientTLSConfig(entryPointName string, tlsOption *traefiktls.TLS) (*tls.Config, error) { if tlsOption == nil { return nil, errors.New("no TLS provided") } @@ -602,7 +602,7 @@ func createClientTLSConfig(entryPointName string, tlsOption *traefikTls.TLS) (*t } // creates a TLS config that allows terminating HTTPS for multiple domains using SNI -func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefikTls.TLS, router *middlewares.HandlerSwitcher) (*tls.Config, error) { +func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefiktls.TLS, router *middlewares.HandlerSwitcher) (*tls.Config, error) { if tlsOption == nil { return nil, nil } @@ -611,9 +611,8 @@ func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefikTls.TL if err != nil { return nil, err } - epDomainsCertificatesTmp := new(traefikTls.DomainsCertificates) - *epDomainsCertificatesTmp = make(map[string]*tls.Certificate) - s.serverEntryPoints[entryPointName].certs.Set(epDomainsCertificatesTmp) + + s.serverEntryPoints[entryPointName].certs.Set(make(map[string]*tls.Certificate)) // ensure http2 enabled config.NextProtos = []string{"h2", "http/1.1"} @@ -680,7 +679,7 @@ func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefikTls.TL } // Set the minimum TLS version if set in the config TOML - if minConst, exists := traefikTls.MinVersion[s.globalConfiguration.EntryPoints[entryPointName].TLS.MinVersion]; exists { + if minConst, exists := traefiktls.MinVersion[s.globalConfiguration.EntryPoints[entryPointName].TLS.MinVersion]; exists { config.PreferServerCipherSuites = true config.MinVersion = minConst } @@ -689,7 +688,7 @@ func (s *Server) createTLSConfig(entryPointName string, tlsOption *traefikTls.TL // if our list of CipherSuites is defined in the entrypoint config, we can re-initilize the suites list as empty config.CipherSuites = make([]uint16, 0) for _, cipher := range s.globalConfiguration.EntryPoints[entryPointName].TLS.CipherSuites { - if cipherConst, exists := traefikTls.CipherSuites[cipher]; exists { + if cipherConst, exists := traefiktls.CipherSuites[cipher]; exists { config.CipherSuites = append(config.CipherSuites, cipherConst) } else { // CipherSuite listed in the toml does not exist in our listed @@ -865,7 +864,7 @@ func (s *Server) buildEntryPoints(globalConfiguration configuration.GlobalConfig // getRoundTripper will either use server.defaultForwardingRoundTripper or create a new one // given a custom TLS configuration is passed and the passTLSCert option is set to true. -func (s *Server) getRoundTripper(entryPointName string, globalConfiguration configuration.GlobalConfiguration, passTLSCert bool, tls *traefikTls.TLS) (http.RoundTripper, error) { +func (s *Server) getRoundTripper(entryPointName string, globalConfiguration configuration.GlobalConfiguration, passTLSCert bool, tls *traefiktls.TLS) (http.RoundTripper, error) { if passTLSCert { tlsConfig, err := createClientTLSConfig(entryPointName, tls) if err != nil { diff --git a/tls/certificate.go b/tls/certificate.go index 101674fc4..30013208c 100644 --- a/tls/certificate.go +++ b/tls/certificate.go @@ -89,7 +89,7 @@ func (f FileOrContent) Read() ([]byte, error) { // CreateTLSConfig creates a TLS config from Certificate structures func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, error) { config := &tls.Config{} - domainsCertificates := make(map[string]*DomainsCertificates) + domainsCertificates := make(map[string]map[string]*tls.Certificate) if c.isEmpty() { config.Certificates = []tls.Certificate{} cert, err := generate.DefaultCertificate() @@ -105,7 +105,7 @@ func (c *Certificates) CreateTLSConfig(entryPointName string) (*tls.Config, erro continue } for _, certDom := range domainsCertificates { - for _, cert := range certDom.Get().(map[string]*tls.Certificate) { + for _, cert := range map[string]*tls.Certificate(certDom) { config.Certificates = append(config.Certificates, *cert) } } @@ -130,7 +130,7 @@ func (c *Certificates) isEmpty() bool { } // AppendCertificates appends a Certificate to a certificates map sorted by entrypoints -func (c *Certificate) AppendCertificates(certs map[string]*DomainsCertificates, ep string) error { +func (c *Certificate) AppendCertificates(certs map[string]map[string]*tls.Certificate, ep string) error { certContent, err := c.CertFile.Read() if err != nil { @@ -161,10 +161,9 @@ func (c *Certificate) AppendCertificates(certs map[string]*DomainsCertificates, certExists := false if certs[ep] == nil { - certs[ep] = new(DomainsCertificates) - *certs[ep] = make(map[string]*tls.Certificate) + certs[ep] = make(map[string]*tls.Certificate) } else { - for domains := range *certs[ep] { + for domains := range certs[ep] { if domains == certKey { certExists = true break @@ -175,7 +174,7 @@ func (c *Certificate) AppendCertificates(certs map[string]*DomainsCertificates, log.Warnf("Into EntryPoint %s, try to add certificate for domains which already have this certificate (%s). The new certificate will not be append to the EntryPoint.", ep, certKey) } else { log.Debugf("Add certificate for domains %s", certKey) - err = certs[ep].add(certKey, &tlsCert) + certs[ep][certKey] = &tlsCert } return err diff --git a/tls/tls.go b/tls/tls.go index a1600210a..dc103da7e 100644 --- a/tls/tls.go +++ b/tls/tls.go @@ -32,28 +32,12 @@ type TLS struct { // RootCAs hold the CA we want to have in root type RootCAs []FileOrContent -// DomainsCertificates allows mapping TLS certificates to a list of domains -type DomainsCertificates map[string]*tls.Certificate - // Configuration allows mapping a TLS certificate to a list of entrypoints type Configuration struct { EntryPoints []string Certificate *Certificate } -// Set is the method to set the flag value, part of the flag.Value interface. -// Set's argument is a string to be parsed to set the flag. -// It's a comma-separated list, so we split it. -func (dc *DomainsCertificates) add(domain string, cert *tls.Certificate) error { - dc.Get().(map[string]*tls.Certificate)[domain] = cert - return nil -} - -// Get method allow getting the map stored into the DomainsCertificates -func (dc *DomainsCertificates) Get() interface{} { - return map[string]*tls.Certificate(*dc) -} - // String is the method to format the flag's value, part of the flag.Value interface. // The String method's output will be used in diagnostics. func (r *RootCAs) String() string { @@ -94,9 +78,9 @@ func (r *RootCAs) Type() string { } // SortTLSPerEntryPoints converts TLS configuration sorted by Certificates into TLS configuration sorted by EntryPoints -func SortTLSPerEntryPoints(configurations []*Configuration, epConfiguration map[string]*DomainsCertificates, defaultEntryPoints []string) error { +func SortTLSPerEntryPoints(configurations []*Configuration, epConfiguration map[string]map[string]*tls.Certificate, defaultEntryPoints []string) error { if epConfiguration == nil { - epConfiguration = make(map[string]*DomainsCertificates) + epConfiguration = make(map[string]map[string]*tls.Certificate) } for _, conf := range configurations { if conf.EntryPoints == nil || len(conf.EntryPoints) == 0 { diff --git a/types/types.go b/types/types.go index a9954d7b6..e8cc82268 100644 --- a/types/types.go +++ b/types/types.go @@ -15,7 +15,7 @@ import ( "github.com/containous/flaeg" "github.com/containous/mux" "github.com/containous/traefik/log" - traefikTls "github.com/containous/traefik/tls" + traefiktls "github.com/containous/traefik/tls" "github.com/ryanuber/go-glob" ) @@ -223,7 +223,7 @@ type Configurations map[string]*Configuration type Configuration struct { Backends map[string]*Backend `json:"backends,omitempty"` Frontends map[string]*Frontend `json:"frontends,omitempty"` - TLS []*traefikTls.Configuration `json:"tls,omitempty"` + TLS []*traefiktls.Configuration `json:"tls,omitempty"` } // ConfigMessage hold configuration information exchanged between parts of traefik.