From 077b39d7c628fe3867fee2fba9717a10bd74f39a Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 30 Nov 2017 08:52:03 +0100 Subject: [PATCH 01/11] Add option -s to gofmt for autogen --- autogen/gentemplates/gen.go | 22 +++++++++++----------- generate.go | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index cdcd6448a..22d47f452 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -884,17 +884,17 @@ type bintree struct { } var _bintree = &bintree{nil, map[string]*bintree{ - "templates": &bintree{nil, map[string]*bintree{ - "consul_catalog.tmpl": &bintree{templatesConsul_catalogTmpl, map[string]*bintree{}}, - "docker.tmpl": &bintree{templatesDockerTmpl, map[string]*bintree{}}, - "ecs.tmpl": &bintree{templatesEcsTmpl, map[string]*bintree{}}, - "eureka.tmpl": &bintree{templatesEurekaTmpl, map[string]*bintree{}}, - "kubernetes.tmpl": &bintree{templatesKubernetesTmpl, map[string]*bintree{}}, - "kv.tmpl": &bintree{templatesKvTmpl, map[string]*bintree{}}, - "marathon.tmpl": &bintree{templatesMarathonTmpl, map[string]*bintree{}}, - "mesos.tmpl": &bintree{templatesMesosTmpl, map[string]*bintree{}}, - "notFound.tmpl": &bintree{templatesNotfoundTmpl, map[string]*bintree{}}, - "rancher.tmpl": &bintree{templatesRancherTmpl, map[string]*bintree{}}, + "templates": {nil, map[string]*bintree{ + "consul_catalog.tmpl": {templatesConsul_catalogTmpl, map[string]*bintree{}}, + "docker.tmpl": {templatesDockerTmpl, map[string]*bintree{}}, + "ecs.tmpl": {templatesEcsTmpl, map[string]*bintree{}}, + "eureka.tmpl": {templatesEurekaTmpl, map[string]*bintree{}}, + "kubernetes.tmpl": {templatesKubernetesTmpl, map[string]*bintree{}}, + "kv.tmpl": {templatesKvTmpl, map[string]*bintree{}}, + "marathon.tmpl": {templatesMarathonTmpl, map[string]*bintree{}}, + "mesos.tmpl": {templatesMesosTmpl, map[string]*bintree{}}, + "notFound.tmpl": {templatesNotfoundTmpl, map[string]*bintree{}}, + "rancher.tmpl": {templatesRancherTmpl, map[string]*bintree{}}, }}, }} diff --git a/generate.go b/generate.go index 8de57bf3b..5d3c0e2c4 100644 --- a/generate.go +++ b/generate.go @@ -2,7 +2,7 @@ //go:generate rm -vf autogen/genstatic/gen.go //go:generate mkdir -p static //go:generate go-bindata -pkg gentemplates -nometadata -nocompress -o autogen/gentemplates/gen.go ./templates/... -//go:generate gofmt -w autogen/gentemplates/gen.go +//go:generate gofmt -s -w autogen/gentemplates/gen.go //go:generate go-bindata -pkg genstatic -nocompress -o autogen/genstatic/gen.go ./static/... package main From f084d2a28b31fb8c2c54c65bb1cf7cc140631795 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Thu, 30 Nov 2017 09:26:03 +0100 Subject: [PATCH 02/11] Fix Labels/annotation logs and values. --- provider/docker/docker.go | 1 - provider/docker/labels.go | 21 +++++++------------ provider/kubernetes/annotations_parser.go | 5 ++--- provider/string_util.go | 19 ----------------- types/common_label.go | 16 ++++++++++++++ .../common_label_test.go | 2 +- 6 files changed, 27 insertions(+), 37 deletions(-) delete mode 100644 provider/string_util.go rename provider/string_util_test.go => types/common_label_test.go (98%) diff --git a/provider/docker/docker.go b/provider/docker/docker.go index ad90b39f5..063d34e37 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -121,7 +121,6 @@ func (p Provider) createClient() (client.APIClient, error) { } return client.NewClient(p.Endpoint, apiVersion, httpClient, httpHeaders) - } // Provide allows the docker provider to provide configurations to traefik diff --git a/provider/docker/labels.go b/provider/docker/labels.go index 070d3b55d..41f494cc1 100644 --- a/provider/docker/labels.go +++ b/provider/docker/labels.go @@ -2,12 +2,10 @@ package docker import ( "fmt" - "math" "strconv" "strings" "github.com/containous/traefik/log" - "github.com/containous/traefik/provider" "github.com/containous/traefik/types" ) @@ -26,13 +24,12 @@ type labelServiceProperties map[string]map[string]string func getFuncInt64Label(labelName string, defaultValue int64) func(container dockerData) int64 { return func(container dockerData) int64 { - if label, err := getLabel(container, labelName); err == nil { - i, errConv := strconv.ParseInt(label, 10, 64) - if errConv != nil { - log.Errorf("Unable to parse traefik.backend.maxconn.amount %s", label) - return math.MaxInt64 + if rawValue, err := getLabel(container, labelName); err == nil { + value, errConv := strconv.ParseInt(rawValue, 10, 64) + if errConv == nil { + return value } - return i + log.Errorf("Unable to parse %q: %q", labelName, rawValue) } return defaultValue } @@ -96,7 +93,7 @@ func getSliceStringLabel(container dockerData, labelName string) []string { var value []string if label, err := getLabel(container, labelName); err == nil { - value = provider.SplitAndTrimString(label) + value = types.SplitAndTrimString(label) } if len(value) == 0 { @@ -173,10 +170,8 @@ func hasLabel(label string) func(container dockerData) bool { } func getLabel(container dockerData, label string) (string, error) { - for key, value := range container.Labels { - if key == label { - return value, nil - } + if value, ok := container.Labels[label]; ok { + return value, nil } return "", fmt.Errorf("label not found: %s", label) } diff --git a/provider/kubernetes/annotations_parser.go b/provider/kubernetes/annotations_parser.go index 361a87f04..8897611b3 100644 --- a/provider/kubernetes/annotations_parser.go +++ b/provider/kubernetes/annotations_parser.go @@ -4,7 +4,6 @@ import ( "strings" "github.com/containous/traefik/log" - "github.com/containous/traefik/provider" "github.com/containous/traefik/types" "k8s.io/client-go/pkg/apis/extensions/v1beta1" ) @@ -20,7 +19,7 @@ func getBoolAnnotation(meta *v1beta1.Ingress, name string, defaultValue bool) bo case annotationStringValue == "true": annotationValue = true default: - log.Warnf("Unknown value '%s' for %s, falling back to %s", name, types.LabelFrontendPassTLSCert, defaultValue) + log.Warnf("Unknown value %q for %q, falling back to %q", annotationStringValue, name, defaultValue) } return annotationValue } @@ -33,7 +32,7 @@ func getStringAnnotation(meta *v1beta1.Ingress, name string) string { func getSliceAnnotation(meta *v1beta1.Ingress, name string) []string { var value []string if annotation, ok := meta.Annotations[name]; ok && annotation != "" { - value = provider.SplitAndTrimString(annotation) + value = types.SplitAndTrimString(annotation) } if len(value) == 0 { log.Debugf("Could not load %v annotation, skipping...", name) diff --git a/provider/string_util.go b/provider/string_util.go deleted file mode 100644 index f3877e293..000000000 --- a/provider/string_util.go +++ /dev/null @@ -1,19 +0,0 @@ -package provider - -import "strings" - -// SplitAndTrimString splits separatedString at the comma character and trims each -// piece, filtering out empty pieces. Returns the list of pieces or nil if the input -// did not contain a non-empty piece. -func SplitAndTrimString(base string) []string { - var trimmedStrings []string - - for _, s := range strings.Split(base, ",") { - s = strings.TrimSpace(s) - if len(s) > 0 { - trimmedStrings = append(trimmedStrings, s) - } - } - - return trimmedStrings -} diff --git a/types/common_label.go b/types/common_label.go index 52240ce17..5b2e19351 100644 --- a/types/common_label.go +++ b/types/common_label.go @@ -75,3 +75,19 @@ func ServiceLabel(key, serviceName string) string { } return key } + +// SplitAndTrimString splits separatedString at the comma character and trims each +// piece, filtering out empty pieces. Returns the list of pieces or nil if the input +// did not contain a non-empty piece. +func SplitAndTrimString(base string) []string { + var trimmedStrings []string + + for _, s := range strings.Split(base, ",") { + s = strings.TrimSpace(s) + if len(s) > 0 { + trimmedStrings = append(trimmedStrings, s) + } + } + + return trimmedStrings +} diff --git a/provider/string_util_test.go b/types/common_label_test.go similarity index 98% rename from provider/string_util_test.go rename to types/common_label_test.go index 5b8c6854d..b97be2085 100644 --- a/provider/string_util_test.go +++ b/types/common_label_test.go @@ -1,4 +1,4 @@ -package provider +package types import ( "testing" From 37a1aaad641054ad6e68e5c2f8c2b9109bf47688 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 30 Nov 2017 10:12:03 +0100 Subject: [PATCH 03/11] Improve consul documentation --- docs/configuration/backends/consul.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/configuration/backends/consul.md b/docs/configuration/backends/consul.md index ba1fb1866..25d809906 100644 --- a/docs/configuration/backends/consul.md +++ b/docs/configuration/backends/consul.md @@ -62,7 +62,6 @@ To enable constraints see [backend-specific constraints section](/configuration/ Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on Traefik KV structure. - ## Consul Catalog backend Træfik can be configured to use service discovery catalog of Consul as a backend configuration. @@ -134,3 +133,19 @@ Additional settings can be defined using Consul Catalog tags. | `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions | | `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | | `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) | + +### Examples + +If you want that Træfik uses Consul tags correctly you need to defined them like that: +```json +traefik.enable=true +traefik.tags=api +traefik.tags=external +``` + +If the prefix defined in Træfik configuration is `bla`, tags need to be defined like that: +```json +bla.enable=true +bla.tags=api +bla.tags=external +``` \ No newline at end of file From 313357a6b37af05144f1d66f865073a3ae9f4f34 Mon Sep 17 00:00:00 2001 From: Daniel Tomcej Date: Thu, 30 Nov 2017 03:42:02 -0600 Subject: [PATCH 04/11] quote template strings --- autogen/gentemplates/gen.go | 10 +++++----- templates/docker.tmpl | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index 22d47f452..d0bac7c6c 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -213,7 +213,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $container}} {{end}} {{if hasSSLHostHeaders $container}} - SSLHost = {{getSSLHostHeaders $container}} + SSLHost = "{{getSSLHostHeaders $container}}" {{end}} {{if hasSTSSecondsHeaders $container}} STSSeconds = {{getSTSSecondsHeaders $container}} @@ -231,7 +231,7 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} FrameDeny = {{getFrameDenyHeaders $container}} {{end}} {{if hasCustomFrameOptionsValueHeaders $container}} - CustomFrameOptionsValue = {{getCustomFrameOptionsValueHeaders $container}} + CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $container}}" {{end}} {{if hasContentTypeNosniffHeaders $container}} ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}} @@ -240,13 +240,13 @@ var _templatesDockerTmpl = []byte(`{{$backendServers := .Servers}} BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}} {{end}} {{if hasContentSecurityPolicyHeaders $container}} - ContentSecurityPolicy = {{getContentSecurityPolicyHeaders $container}} + ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $container}}" {{end}} {{if hasPublicKeyHeaders $container}} - PublicKey = {{getPublicKeyHeaders $container}} + PublicKey = "{{getPublicKeyHeaders $container}}" {{end}} {{if hasReferrerPolicyHeaders $container}} - ReferrerPolicy = {{getReferrerPolicyHeaders $container}} + ReferrerPolicy = "{{getReferrerPolicyHeaders $container}}" {{end}} {{if hasIsDevelopmentHeaders $container}} IsDevelopment = {{getIsDevelopmentHeaders $container}} diff --git a/templates/docker.tmpl b/templates/docker.tmpl index b135ae47b..d4a7c9a84 100644 --- a/templates/docker.tmpl +++ b/templates/docker.tmpl @@ -88,7 +88,7 @@ SSLTemporaryRedirect = {{getSSLTemporaryRedirectHeaders $container}} {{end}} {{if hasSSLHostHeaders $container}} - SSLHost = {{getSSLHostHeaders $container}} + SSLHost = "{{getSSLHostHeaders $container}}" {{end}} {{if hasSTSSecondsHeaders $container}} STSSeconds = {{getSTSSecondsHeaders $container}} @@ -106,7 +106,7 @@ FrameDeny = {{getFrameDenyHeaders $container}} {{end}} {{if hasCustomFrameOptionsValueHeaders $container}} - CustomFrameOptionsValue = {{getCustomFrameOptionsValueHeaders $container}} + CustomFrameOptionsValue = "{{getCustomFrameOptionsValueHeaders $container}}" {{end}} {{if hasContentTypeNosniffHeaders $container}} ContentTypeNosniff = {{getContentTypeNosniffHeaders $container}} @@ -115,13 +115,13 @@ BrowserXSSFilter = {{getBrowserXSSFilterHeaders $container}} {{end}} {{if hasContentSecurityPolicyHeaders $container}} - ContentSecurityPolicy = {{getContentSecurityPolicyHeaders $container}} + ContentSecurityPolicy = "{{getContentSecurityPolicyHeaders $container}}" {{end}} {{if hasPublicKeyHeaders $container}} - PublicKey = {{getPublicKeyHeaders $container}} + PublicKey = "{{getPublicKeyHeaders $container}}" {{end}} {{if hasReferrerPolicyHeaders $container}} - ReferrerPolicy = {{getReferrerPolicyHeaders $container}} + ReferrerPolicy = "{{getReferrerPolicyHeaders $container}}" {{end}} {{if hasIsDevelopmentHeaders $container}} IsDevelopment = {{getIsDevelopmentHeaders $container}} From 40b3c177035c12d66d598348c30c18568f45f381 Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Thu, 30 Nov 2017 12:18:03 +0100 Subject: [PATCH 05/11] Fix metrics problem on multiple entrypoints --- integration/basic_test.go | 35 +++++++++++++++++++++++++ integration/fixtures/simple_stats.toml | 30 +++++++++++++++++++++ integration/resources/compose/stats.yml | 4 +++ server/server.go | 8 ++++-- 4 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 integration/fixtures/simple_stats.toml create mode 100644 integration/resources/compose/stats.yml diff --git a/integration/basic_test.go b/integration/basic_test.go index 2d78343af..017205d7c 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -189,6 +189,41 @@ func (s *SimpleSuite) TestApiOnSameEntryPoint(c *check.C) { c.Assert(err, checker.IsNil) } +func (s *SimpleSuite) TestStatsWithMultipleEntryPoint(c *check.C) { + s.createComposeProject(c, "stats") + s.composeProject.Start(c) + + whoami1 := "http://" + s.composeProject.Container(c, "whoami1").NetworkSettings.IPAddress + ":80" + whoami2 := "http://" + s.composeProject.Container(c, "whoami2").NetworkSettings.IPAddress + ":80" + + file := s.adaptFile(c, "fixtures/simple_stats.toml", struct { + Server1 string + Server2 string + }{whoami1, whoami2}) + cmd, output := s.traefikCmd(withConfigFile(file)) + defer output(c) + + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + err = try.GetRequest("http://127.0.0.1:8080/api", 1*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("PathPrefix")) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8080/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8080/health", 1*time.Second, try.BodyContains(`"total_status_code_count":{"200":2}`)) + c.Assert(err, checker.IsNil) + +} + func (s *SimpleSuite) TestNoAuthOnPing(c *check.C) { s.createComposeProject(c, "base") s.composeProject.Start(c) diff --git a/integration/fixtures/simple_stats.toml b/integration/fixtures/simple_stats.toml new file mode 100644 index 000000000..b5e38511b --- /dev/null +++ b/integration/fixtures/simple_stats.toml @@ -0,0 +1,30 @@ +debug=true + +[entryPoints] + [entryPoints.http] + address = ":8000" + +[api] +[file] + [backends] + [backends.backend1] + [backends.backend1.servers.server1] + url = "{{ .Server1 }}" + [backends.backend2] + [backends.backend2.servers.server1] + url = "{{ .Server2 }}" + + [frontends] + [frontends.frontend1] + entrypoints=["http"] + + backend = "backend1" + [frontends.frontend1.routes.test_1] + rule = "PathPrefix:/whoami" + + [frontends.frontend2] + backend = "backend2" + entrypoints=["traefik"] + + [frontends.frontend2.routes.test_1] + rule = "PathPrefix:/whoami" diff --git a/integration/resources/compose/stats.yml b/integration/resources/compose/stats.yml new file mode 100644 index 000000000..0256f9622 --- /dev/null +++ b/integration/resources/compose/stats.yml @@ -0,0 +1,4 @@ +whoami1: + image: emilevauge/whoami +whoami2: + image: emilevauge/whoami \ No newline at end of file diff --git a/server/server.go b/server/server.go index 70e2f72cc..3bffebc1f 100644 --- a/server/server.go +++ b/server/server.go @@ -289,10 +289,14 @@ func (s *Server) setupServerEntryPoint(newServerEntryPointName string, newServer serverMiddlewares = append(serverMiddlewares, middlewares.NewMetricsWrapper(s.metricsRegistry, newServerEntryPointName)) } if s.globalConfiguration.API != nil { - s.globalConfiguration.API.Stats = thoas_stats.New() + if s.globalConfiguration.API.Stats == nil { + s.globalConfiguration.API.Stats = thoas_stats.New() + } serverMiddlewares = append(serverMiddlewares, s.globalConfiguration.API.Stats) if s.globalConfiguration.API.Statistics != nil { - s.globalConfiguration.API.StatsRecorder = middlewares.NewStatsRecorder(s.globalConfiguration.API.Statistics.RecentErrors) + if s.globalConfiguration.API.StatsRecorder == nil { + s.globalConfiguration.API.StatsRecorder = middlewares.NewStatsRecorder(s.globalConfiguration.API.Statistics.RecentErrors) + } serverMiddlewares = append(serverMiddlewares, s.globalConfiguration.API.StatsRecorder) } From 1c8acf392972bd44698492b49cb11e147c7d732e Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Thu, 30 Nov 2017 14:12:04 +0100 Subject: [PATCH 06/11] Doesn't ignore web params when web.metrics.prometheus is set --- glide.lock | 4 ++-- vendor/github.com/containous/flaeg/flaeg.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/glide.lock b/glide.lock index 55d7c44ed..17a9a2040 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ hash: 4595cac0a682ce8e36b78630f12a3cfab75307fc58cb4a1f5e416017d3ae20d6 -updated: 2017-11-29T12:05:49.613148632+01:00 +updated: 2017-11-30T10:34:41.246378337+01:00 imports: - name: cloud.google.com/go version: 2e6a95edb1071d750f6d7db777bf66cd2997af6c @@ -88,7 +88,7 @@ imports: - name: github.com/codegangsta/cli version: bf4a526f48af7badd25d2cb02d587e1b01be3b50 - name: github.com/containous/flaeg - version: b5d2dc5878df07c2d74413348186982e7b865871 + version: 60c87a513a955ca7225e1b1c772581cea8420cb4 - name: github.com/containous/mux version: 06ccd3e75091eb659b1d720cda0e16bc7057954c - name: github.com/containous/staert diff --git a/vendor/github.com/containous/flaeg/flaeg.go b/vendor/github.com/containous/flaeg/flaeg.go index 10aac07fe..fb4512537 100644 --- a/vendor/github.com/containous/flaeg/flaeg.go +++ b/vendor/github.com/containous/flaeg/flaeg.go @@ -343,7 +343,7 @@ func fillStructRecursive(objValue reflect.Value, defaultPointerValmap map[string contains := false for flag := range valmap { // TODO replace by regexp - if strings.Contains(flag, name+".") { + if strings.HasPrefix(flag, name+".") { contains = true break } From e27e65eb765d73b70b492d6a54f42ace1c750d76 Mon Sep 17 00:00:00 2001 From: SALLEYRON Julien Date: Thu, 30 Nov 2017 16:10:02 +0100 Subject: [PATCH 07/11] Fix wrong defaultentrypoint and unexisting entrypoint issue --- cmd/traefik/configuration.go | 2 +- integration/basic_test.go | 38 ++++++++++++++++++++++++++++++++++++ server/server.go | 10 +++++++--- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/cmd/traefik/configuration.go b/cmd/traefik/configuration.go index 1b041d582..692a956c4 100644 --- a/cmd/traefik/configuration.go +++ b/cmd/traefik/configuration.go @@ -279,7 +279,7 @@ func NewTraefikConfiguration() *TraefikConfiguration { LogLevel: "ERROR", EntryPoints: map[string]*configuration.EntryPoint{}, Constraints: types.Constraints{}, - DefaultEntryPoints: []string{}, + DefaultEntryPoints: []string{"http"}, ProvidersThrottleDuration: flaeg.Duration(2 * time.Second), MaxIdleConnsPerHost: 200, IdleTimeout: flaeg.Duration(0), diff --git a/integration/basic_test.go b/integration/basic_test.go index 017205d7c..cc3eacd57 100644 --- a/integration/basic_test.go +++ b/integration/basic_test.go @@ -298,3 +298,41 @@ func (s *SimpleSuite) TestWebCompatibilityWithPath(c *check.C) { err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) c.Assert(err, checker.IsNil) } + +func (s *SimpleSuite) TestDefaultEntrypointHTTP(c *check.C) { + + s.createComposeProject(c, "base") + s.composeProject.Start(c) + + cmd, output := s.traefikCmd("--entryPoints=Name:http Address::8000", "--debug", "--docker", "--api") + defer output(c) + + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("PathPrefix")) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) +} + +func (s *SimpleSuite) TestWithUnexistingEntrypoint(c *check.C) { + + s.createComposeProject(c, "base") + s.composeProject.Start(c) + + cmd, output := s.traefikCmd("--defaultEntryPoints=https,http", "--entryPoints=Name:http Address::8000", "--debug", "--docker", "--api") + defer output(c) + + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + err = try.GetRequest("http://127.0.0.1:8080/api/providers", 1*time.Second, try.BodyContains("PathPrefix")) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8000/whoami", 1*time.Second, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) +} diff --git a/server/server.go b/server/server.go index 3bffebc1f..e2255131a 100644 --- a/server/server.go +++ b/server/server.go @@ -910,13 +910,17 @@ func (s *Server) loadConfig(configurations types.Configurations, globalConfigura log.Errorf("Skipping frontend %s...", frontendName) continue frontend } - + var failedEntrypoints int for _, entryPointName := range frontend.EntryPoints { log.Debugf("Wiring frontend %s to entryPoint %s", frontendName, entryPointName) if _, ok := serverEntryPoints[entryPointName]; !ok { log.Errorf("Undefined entrypoint '%s' for frontend %s", entryPointName, frontendName) - log.Errorf("Skipping frontend %s...", frontendName) - continue frontend + failedEntrypoints++ + if failedEntrypoints == len(frontend.EntryPoints) { + log.Errorf("Skipping frontend %s...", frontendName) + continue frontend + } + continue } newServerRoute := &serverRoute{route: serverEntryPoints[entryPointName].httpRouter.GetHandler().NewRoute().Name(frontendName)} From c228e73b261cb62c2fd3e0bde8da651c983a0be2 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 1 Dec 2017 09:36:02 +0100 Subject: [PATCH 08/11] fix Docker labels documentation render. --- docs/configuration/backends/docker.md | 46 ++++++++++++++------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index 20200d28a..9c491c88e 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -1,3 +1,4 @@ + # Docker Backend Træfik can be configured to use Docker as a backend configuration. @@ -174,28 +175,29 @@ Labels can be used on containers to override default behaviour. #### Security Headers -| Label | Description | -|-----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` | -|`traefik.frontend.headers.customrequestheaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: `HEADER:value,HEADER2:value2` | -| `traefik.frontend.headers.customresponseheaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: `HEADER:value,HEADER2:value2` | -|`traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` | -| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. | -| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. | -| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. | -| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as X-Forwarded-For:https). Format: `HEADER:value,HEADER2:value2` | -| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. | -| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the IncludeSubdomains section of the STS header. | -| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. | -| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. | -| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. | -| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. | -| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. | -| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. | -| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. | -| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. | -| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. | -| `traefik.frontend.headers.isDevelopment=false` | This will cause the AllowedHosts, SSLRedirect, and STSSeconds/STSIncludeSubdomains options to be ignored during development. When deploying to production, be sure to set this to false. | +| Label | Description | +|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` | +| `traefik.frontend.headers.customrequestheaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: `HEADER:value,HEADER2:value2` | +| `traefik.frontend.headers.customresponseheaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: `HEADER:value,HEADER2:value2` | +| `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` | +| `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. | +| `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. | +| `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. | +| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: `HEADER:value,HEADER2:value2` | +| `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. | +| `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. | +| `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. | +| `traefik.frontend.headers.forceSTSHeader=false` | Adds the STS header to non-SSL requests. | +| `traefik.frontend.headers.frameDeny=false` | Adds the `X-Frame-Options` header with the value of `DENY`. | +| `traefik.frontend.headers.customFrameOptionsValue=VALUE` | Overrides the `X-Frame-Options` header with the custom value. | +| `traefik.frontend.headers.contentTypeNosniff=true` | Adds the `X-Content-Type-Options` header with the value `nosniff`. | +| `traefik.frontend.headers.browserXSSFilter=true` | Adds the X-XSS-Protection header with the value `1; mode=block`. | +| `traefik.frontend.headers.contentSecurityPolicy=VALUE` | Adds CSP Header with the custom value. | +| `traefik.frontend.headers.publicKey=VALUE` | Adds pinned HTST public key header. | +| `traefik.frontend.headers.referrerPolicy=VALUE` | Adds referrer policy header. | +| `traefik.frontend.headers.isDevelopment=false` | This will cause the `AllowedHosts`, `SSLRedirect`, and `STSSeconds`/`STSIncludeSubdomains` options to be ignored during development.
When deploying to production, be sure to set this to false. | + ### On Service Services labels can be used for overriding default behaviour From aaf120f26359b661fe3a0908ca4e0e0696cf7c4f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 1 Dec 2017 14:00:04 +0100 Subject: [PATCH 09/11] Reduce logs with new Kubernetes security annotations --- provider/kubernetes/annotations_parser.go | 35 ++++++++++++++--------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/provider/kubernetes/annotations_parser.go b/provider/kubernetes/annotations_parser.go index 8897611b3..ba2538325 100644 --- a/provider/kubernetes/annotations_parser.go +++ b/provider/kubernetes/annotations_parser.go @@ -19,7 +19,7 @@ func getBoolAnnotation(meta *v1beta1.Ingress, name string, defaultValue bool) bo case annotationStringValue == "true": annotationValue = true default: - log.Warnf("Unknown value %q for %q, falling back to %q", annotationStringValue, name, defaultValue) + log.Warnf("Unknown value %q for %q, falling back to %v", annotationStringValue, name, defaultValue) } return annotationValue } @@ -41,21 +41,30 @@ func getSliceAnnotation(meta *v1beta1.Ingress, name string) []string { return value } -func getMapAnnotation(meta *v1beta1.Ingress, name string) map[string]string { - value := make(map[string]string) - if annotation := meta.Annotations[name]; annotation != "" { - for _, v := range strings.Split(annotation, ",") { - pair := strings.Split(v, ":") +func getMapAnnotation(meta *v1beta1.Ingress, annotName string) map[string]string { + if values, ok := meta.Annotations[annotName]; ok { + + if len(values) == 0 { + log.Errorf("Missing value for annotation %q", annotName) + return nil + } + + mapValue := make(map[string]string) + for _, parts := range strings.Split(values, ",") { + pair := strings.Split(parts, ":") if len(pair) != 2 { - log.Debugf("Could not load annotation (%v) with value: %v, skipping...", name, pair) + log.Warnf("Could not load %q: %v, skipping...", annotName, pair) } else { - value[pair[0]] = pair[1] + mapValue[pair[0]] = pair[1] } } + + if len(mapValue) == 0 { + log.Errorf("Could not load %q, skipping...", annotName) + return nil + } + return mapValue } - if len(value) == 0 { - log.Debugf("Could not load %v annotation, skipping...", name) - return nil - } - return value + + return nil } From d6ad7e2e6491266c4327bbe0394693f790163582 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 1 Dec 2017 14:34:03 +0100 Subject: [PATCH 10/11] Fix empty IP for backend when dnsrr in Docker swarm mode --- provider/docker/docker.go | 29 +++++---- provider/docker/swarm_test.go | 118 ++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 14 deletions(-) diff --git a/provider/docker/docker.go b/provider/docker/docker.go index 063d34e37..e13c79412 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -749,7 +749,7 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD networkMap := make(map[string]*dockertypes.NetworkResource) if err != nil { - log.Debug("Failed to network inspect on client for docker, error: %s", err) + log.Debugf("Failed to network inspect on client for docker, error: %s", err) return []dockerData{}, err } for _, network := range networkList { @@ -762,16 +762,18 @@ func listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerD for _, service := range serviceList { dockerData := parseService(service, networkMap) - useSwarmLB, _ := strconv.ParseBool(getIsBackendLBSwarm(dockerData)) - isGlobalSvc := service.Spec.Mode.Global != nil + if len(dockerData.NetworkSettings.Networks) > 0 { + useSwarmLB, _ := strconv.ParseBool(getIsBackendLBSwarm(dockerData)) - if useSwarmLB { - dockerDataList = append(dockerDataList, dockerData) - } else { - dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dockerData, networkMap, isGlobalSvc) + if useSwarmLB { + dockerDataList = append(dockerDataList, dockerData) + } else { + isGlobalSvc := service.Spec.Mode.Global != nil + dockerDataListTasks, err = listTasks(ctx, dockerClient, service.ID, dockerData, networkMap, isGlobalSvc) - for _, dockerDataTask := range dockerDataListTasks { - dockerDataList = append(dockerDataList, dockerDataTask) + for _, dockerDataTask := range dockerDataListTasks { + dockerDataList = append(dockerDataList, dockerDataTask) + } } } } @@ -787,10 +789,9 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes } if service.Spec.EndpointSpec != nil { - switch service.Spec.EndpointSpec.Mode { - case swarmtypes.ResolutionModeDNSRR: - log.Debug("Ignored endpoint-mode not supported, service name: %s", dockerData.Name) - case swarmtypes.ResolutionModeVIP: + if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR { + log.Warnf("Ignored endpoint-mode not supported, service name: %s", service.Spec.Annotations.Name) + } else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP { dockerData.NetworkSettings.Networks = make(map[string]*networkData) for _, virtualIP := range service.Endpoint.VirtualIPs { networkService := networkMap[virtualIP.NetworkID] @@ -803,7 +804,7 @@ func parseService(service swarmtypes.Service, networkMap map[string]*dockertypes } dockerData.NetworkSettings.Networks[network.Name] = network } else { - log.Debug("Network not found, id: %s", virtualIP.NetworkID) + log.Debugf("Network not found, id: %s", virtualIP.NetworkID) } } } diff --git a/provider/docker/swarm_test.go b/provider/docker/swarm_test.go index 374e5a6ee..32f95cf74 100644 --- a/provider/docker/swarm_test.go +++ b/provider/docker/swarm_test.go @@ -5,6 +5,7 @@ import ( "strconv" "strings" "testing" + "time" "github.com/containous/traefik/types" "github.com/davecgh/go-spew/spew" @@ -12,6 +13,7 @@ import ( dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" dockerclient "github.com/docker/docker/client" + "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) @@ -765,3 +767,119 @@ func TestListTasks(t *testing.T) { }) } } + +type fakeServicesClient struct { + dockerclient.APIClient + dockerVersion string + networks []dockertypes.NetworkResource + services []swarm.Service + err error +} + +func (c *fakeServicesClient) ServiceList(ctx context.Context, options dockertypes.ServiceListOptions) ([]swarm.Service, error) { + return c.services, c.err +} + +func (c *fakeServicesClient) ServerVersion(ctx context.Context) (dockertypes.Version, error) { + return dockertypes.Version{APIVersion: c.dockerVersion}, c.err +} + +func (c *fakeServicesClient) NetworkList(ctx context.Context, options dockertypes.NetworkListOptions) ([]dockertypes.NetworkResource, error) { + return c.networks, c.err +} + +func TestListServices(t *testing.T) { + testCases := []struct { + desc string + services []swarm.Service + dockerVersion string + networks []dockertypes.NetworkResource + expectedServices []string + }{ + { + desc: "Should return no service due to no networks defined", + services: []swarm.Service{ + swarmService( + serviceName("service1"), + serviceLabels(map[string]string{ + labelDockerNetwork: "barnet", + labelBackendLoadBalancerSwarm: "true", + }), + withEndpointSpec(modeVIP), + withEndpoint( + virtualIP("1", "10.11.12.13/24"), + virtualIP("2", "10.11.12.99/24"), + )), + swarmService( + serviceName("service2"), + serviceLabels(map[string]string{ + labelDockerNetwork: "barnet", + }), + withEndpointSpec(modeDNSSR)), + }, + dockerVersion: "1.30", + networks: []dockertypes.NetworkResource{}, + expectedServices: []string{}, + }, + { + desc: "Should return only service1", + services: []swarm.Service{ + swarmService( + serviceName("service1"), + serviceLabels(map[string]string{ + labelDockerNetwork: "barnet", + labelBackendLoadBalancerSwarm: "true", + }), + withEndpointSpec(modeVIP), + withEndpoint( + virtualIP("yk6l57rfwizjzxxzftn4amaot", "10.11.12.13/24"), + virtualIP("2", "10.11.12.99/24"), + )), + swarmService( + serviceName("service2"), + serviceLabels(map[string]string{ + labelDockerNetwork: "barnet", + }), + withEndpointSpec(modeDNSSR)), + }, + dockerVersion: "1.30", + networks: []dockertypes.NetworkResource{ + { + Name: "network_name", + ID: "yk6l57rfwizjzxxzftn4amaot", + Created: time.Now(), + Scope: "swarm", + Driver: "overlay", + EnableIPv6: false, + Internal: true, + Ingress: false, + ConfigOnly: false, + Options: map[string]string{ + "com.docker.network.driver.overlay.vxlanid_list": "4098", + "com.docker.network.enable_ipv6": "false", + }, + Labels: map[string]string{ + "com.docker.stack.namespace": "test", + }, + }, + }, + expectedServices: []string{ + "service1", + }, + }, + } + + for caseID, test := range testCases { + test := test + t.Run(strconv.Itoa(caseID), func(t *testing.T) { + t.Parallel() + dockerClient := &fakeServicesClient{services: test.services, dockerVersion: test.dockerVersion, networks: test.networks} + serviceDockerData, _ := listServices(context.Background(), dockerClient) + + assert.Equal(t, len(test.expectedServices), len(serviceDockerData)) + for i, serviceName := range test.expectedServices { + assert.Equal(t, serviceName, serviceDockerData[i].Name) + } + }) + } +} From 3a99c86cb3bd12a5f0eeafca8370f8893f6ac33b Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 4 Dec 2017 11:40:03 +0100 Subject: [PATCH 11/11] Change custom headers separator --- docs/configuration/backends/docker.md | 6 ++--- docs/configuration/backends/kubernetes.md | 6 ++--- provider/docker/docker.go | 10 ++++---- provider/docker/labels.go | 30 +++++++++++++++-------- provider/kubernetes/annotations_parser.go | 7 +++--- provider/kubernetes/kubernetes.go | 4 +-- types/common_label.go | 4 +-- 7 files changed, 39 insertions(+), 28 deletions(-) diff --git a/docs/configuration/backends/docker.md b/docs/configuration/backends/docker.md index 9c491c88e..2dfd9fdd7 100644 --- a/docs/configuration/backends/docker.md +++ b/docs/configuration/backends/docker.md @@ -178,13 +178,13 @@ Labels can be used on containers to override default behaviour. | Label | Description | |----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `traefik.frontend.headers.allowedHosts=EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` | -| `traefik.frontend.headers.customrequestheaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: `HEADER:value,HEADER2:value2` | -| `traefik.frontend.headers.customresponseheaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: `HEADER:value,HEADER2:value2` | +| `traefik.frontend.headers.customRequestHeaders=EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: HEADER:value||HEADER2:value2 | +| `traefik.frontend.headers.customResponseHeaders=EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: HEADER:value||HEADER2:value2 | | `traefik.frontend.headers.hostsProxyHeaders=EXPR ` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` | | `traefik.frontend.headers.SSLRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. | | `traefik.frontend.headers.SSLTemporaryRedirect=true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. | | `traefik.frontend.headers.SSLHost=HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. | -| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: `HEADER:value,HEADER2:value2` | +| `traefik.frontend.headers.SSLProxyHeaders=EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: HEADER:value||HEADER2:value2 | | `traefik.frontend.headers.STSSeconds=315360000` | Sets the max-age of the STS header. | | `traefik.frontend.headers.STSIncludeSubdomains=true` | Adds the `IncludeSubdomains` section of the STS header. | | `traefik.frontend.headers.STSPreload=true` | Adds the preload flag to the STS header. | diff --git a/docs/configuration/backends/kubernetes.md b/docs/configuration/backends/kubernetes.md index b2d6d27dd..069054602 100644 --- a/docs/configuration/backends/kubernetes.md +++ b/docs/configuration/backends/kubernetes.md @@ -139,13 +139,13 @@ The following security annotations can be applied to the ingress object to add s | Annotation | Description | |----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `ingress.kubernetes.io/allowed-hosts:EXPR` | Provides a list of allowed hosts that requests will be processed. Format: `Host1,Host2` | -| `ingress.kubernetes.io/custom-request-headers:EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: `HEADER:value,HEADER2:value2` | -| `ingress.kubernetes.io/custom-response-headers:EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: `HEADER:value,HEADER2:value2` | +| `ingress.kubernetes.io/custom-request-headers:EXPR ` | Provides the container with custom request headers that will be appended to each request forwarded to the container. Format: HEADER:value||HEADER2:value2 | +| `ingress.kubernetes.io/custom-response-headers:EXPR` | Appends the headers to each response returned by the container, before forwarding the response to the client. Format: HEADER:value||HEADER2:value2 | | `ingress.kubernetes.io/proxy-headers:EXPR ` | Provides a list of headers that the proxied hostname may be stored. Format: `HEADER1,HEADER2` | | `ingress.kubernetes.io/ssl-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent. | | `ingress.kubernetes.io/ssl-temporary-redirect:true` | Forces the frontend to redirect to SSL if a non-SSL request is sent, but by sending a 302 instead of a 301. | | `ingress.kubernetes.io/ssl-host:HOST` | This setting configures the hostname that redirects will be based on. Default is "", which is the same host as the request. | -| `ingress.kubernetes.io/ssl-proxy-headers:EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: `HEADER:value,HEADER2:value2` | +| `ingress.kubernetes.io/ssl-proxy-headers:EXPR` | Header combinations that would signify a proper SSL Request (Such as `X-Forwarded-For:https`). Format: HEADER:value||HEADER2:value2 | | `ingress.kubernetes.io/hsts-max-age:315360000` | Sets the max-age of the HSTS header. | | `ngress.kubernetes.io/hsts-include-subdomains:true` | Adds the IncludeSubdomains section of the STS header. | | `ingress.kubernetes.io/hsts-preload:true` | Adds the preload flag to the HSTS header. | diff --git a/provider/docker/docker.go b/provider/docker/docker.go index e13c79412..721953425 100644 --- a/provider/docker/docker.go +++ b/provider/docker/docker.go @@ -86,7 +86,7 @@ type networkData struct { ID string } -func (p Provider) createClient() (client.APIClient, error) { +func (p *Provider) createClient() (client.APIClient, error) { var httpClient *http.Client if p.TLS != nil { @@ -292,10 +292,10 @@ func (p *Provider) loadDockerConfig(containersInspected []dockerData) *types.Con "getServiceRedirect": getFuncServiceStringLabel(types.SuffixFrontendRedirect, defaultFrontendRedirect), "getWhitelistSourceRange": getFuncSliceStringLabel(types.LabelTraefikFrontendWhitelistSourceRange), - "hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeader), - "getRequestHeaders": getFuncMapLabel(types.LabelFrontendRequestHeader), - "hasResponseHeaders": hasLabel(types.LabelFrontendResponseHeader), - "getResponseHeaders": getFuncMapLabel(types.LabelFrontendResponseHeader), + "hasRequestHeaders": hasLabel(types.LabelFrontendRequestHeaders), + "getRequestHeaders": getFuncMapLabel(types.LabelFrontendRequestHeaders), + "hasResponseHeaders": hasLabel(types.LabelFrontendResponseHeaders), + "getResponseHeaders": getFuncMapLabel(types.LabelFrontendResponseHeaders), "hasAllowedHostsHeaders": hasLabel(types.LabelFrontendAllowedHosts), "getAllowedHostsHeaders": getFuncSliceStringLabel(types.LabelFrontendAllowedHosts), "hasHostsProxyHeaders": hasLabel(types.LabelFrontendHostsProxyHeaders), diff --git a/provider/docker/labels.go b/provider/docker/labels.go index 41f494cc1..ad12a271b 100644 --- a/provider/docker/labels.go +++ b/provider/docker/labels.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "net/http" "strconv" "strings" @@ -42,21 +43,30 @@ func getFuncMapLabel(labelName string) func(container dockerData) map[string]str } func parseMapLabel(container dockerData, labelName string) map[string]string { - customHeaders := make(map[string]string) - if label, err := getLabel(container, labelName); err == nil { - for _, headers := range strings.Split(label, ",") { - pair := strings.Split(headers, ":") + if parts, err := getLabel(container, labelName); err == nil { + if len(parts) == 0 { + log.Errorf("Could not load %q", labelName) + return nil + } + + values := make(map[string]string) + for _, headers := range strings.Split(parts, "||") { + pair := strings.SplitN(headers, ":", 2) if len(pair) != 2 { - log.Warnf("Could not load header %q: %v, skipping...", labelName, pair) + log.Warnf("Could not load %q: %v, skipping...", labelName, pair) } else { - customHeaders[pair[0]] = pair[1] + values[http.CanonicalHeaderKey(strings.TrimSpace(pair[0]))] = strings.TrimSpace(pair[1]) } } + + if len(values) == 0 { + log.Errorf("Could not load %q", labelName) + return nil + } + return values } - if len(customHeaders) == 0 { - log.Errorf("Could not load %q", labelName) - } - return customHeaders + + return nil } func getFuncStringLabel(label string, defaultValue string) func(container dockerData) string { diff --git a/provider/kubernetes/annotations_parser.go b/provider/kubernetes/annotations_parser.go index ba2538325..813146e6f 100644 --- a/provider/kubernetes/annotations_parser.go +++ b/provider/kubernetes/annotations_parser.go @@ -1,6 +1,7 @@ package kubernetes import ( + "net/http" "strings" "github.com/containous/traefik/log" @@ -50,12 +51,12 @@ func getMapAnnotation(meta *v1beta1.Ingress, annotName string) map[string]string } mapValue := make(map[string]string) - for _, parts := range strings.Split(values, ",") { - pair := strings.Split(parts, ":") + for _, parts := range strings.Split(values, "||") { + pair := strings.SplitN(parts, ":", 2) if len(pair) != 2 { log.Warnf("Could not load %q: %v, skipping...", annotName, pair) } else { - mapValue[pair[0]] = pair[1] + mapValue[http.CanonicalHeaderKey(strings.TrimSpace(pair[0]))] = strings.TrimSpace(pair[1]) } } diff --git a/provider/kubernetes/kubernetes.go b/provider/kubernetes/kubernetes.go index 5284a2220..804510d73 100644 --- a/provider/kubernetes/kubernetes.go +++ b/provider/kubernetes/kubernetes.go @@ -73,7 +73,7 @@ type Provider struct { lastConfiguration safe.Safe } -func (p Provider) newK8sClient() (Client, error) { +func (p *Provider) newK8sClient() (Client, error) { withEndpoint := "" if p.Endpoint != "" { withEndpoint = fmt.Sprintf(" with endpoint %v", p.Endpoint) @@ -356,7 +356,7 @@ func (p *Provider) loadIngresses(k8sClient Client) (*types.Configuration, error) return &templateObjects, nil } -func (p Provider) loadConfig(templateObjects types.Configuration) *types.Configuration { +func (p *Provider) loadConfig(templateObjects types.Configuration) *types.Configuration { var FuncMap = template.FuncMap{} configuration, err := p.GetConfiguration("templates/kubernetes.tmpl", FuncMap, templateObjects) if err != nil { diff --git a/types/common_label.go b/types/common_label.go index 5b2e19351..af543b573 100644 --- a/types/common_label.go +++ b/types/common_label.go @@ -32,8 +32,8 @@ const ( LabelFrontendRedirect = LabelPrefix + SuffixFrontendRedirect LabelTraefikFrontendValue = LabelPrefix + "frontend.value" LabelTraefikFrontendWhitelistSourceRange = LabelPrefix + "frontend.whitelistSourceRange" - LabelFrontendRequestHeader = LabelPrefix + "frontend.headers.customrequestheaders" - LabelFrontendResponseHeader = LabelPrefix + "frontend.headers.customresponseheaders" + LabelFrontendRequestHeaders = LabelPrefix + "frontend.headers.customRequestHeaders" + LabelFrontendResponseHeaders = LabelPrefix + "frontend.headers.customResponseHeaders" LabelFrontendAllowedHosts = LabelPrefix + "frontend.headers.allowedHosts" LabelFrontendHostsProxyHeaders = LabelPrefix + "frontend.headers.hostsProxyHeaders" LabelFrontendSSLRedirect = LabelPrefix + "frontend.headers.SSLRedirect"