From 3677252e17945a1e161c507bff064c606511b8ef Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Tue, 1 Sep 2020 17:34:04 +0200 Subject: [PATCH 1/6] Add missing IPStrategy struct tag for YAML --- docs/content/reference/dynamic-configuration/file.yaml | 4 ++-- pkg/config/dynamic/middlewares.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 2537abee7..2ad0a7b73 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -222,7 +222,7 @@ http: inFlightReq: amount: 42 sourceCriterion: - ipstrategy: + ipStrategy: depth: 42 excludedIPs: - foobar @@ -259,7 +259,7 @@ http: period: 42 burst: 42 sourceCriterion: - ipstrategy: + ipStrategy: depth: 42 excludedIPs: - foobar diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 8727b818b..19e0a685d 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -300,7 +300,7 @@ type PassTLSClientCert struct { // If none are set, the default is to use the request's remote address field. // All fields are mutually exclusive. type SourceCriterion struct { - IPStrategy *IPStrategy `json:"ipStrategy" toml:"ipStrategy, omitempty"` + IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty"` RequestHeaderName string `json:"requestHeaderName,omitempty" toml:"requestHeaderName,omitempty" yaml:"requestHeaderName,omitempty"` RequestHost bool `json:"requestHost,omitempty" toml:"requestHost,omitempty" yaml:"requestHost,omitempty"` } From 52790d3c379058bb41929695748a7a810b7a3ec6 Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Tue, 1 Sep 2020 18:16:04 +0200 Subject: [PATCH 2/6] Headers response modifier is directly applied by headers middleware Co-authored-by: Ludovic Fernandez --- .../fixtures/headers/secure_multiple.toml | 33 + integration/headers_test.go | 41 ++ pkg/middlewares/customerrors/custom_errors.go | 4 +- .../customerrors/custom_errors_test.go | 2 +- pkg/middlewares/headers/header.go | 170 ++++++ pkg/middlewares/headers/header_test.go | 492 +++++++++++++++ pkg/middlewares/headers/headers.go | 205 +------ pkg/middlewares/headers/headers_test.go | 575 ++---------------- pkg/middlewares/headers/responsewriter.go | 75 +++ pkg/middlewares/headers/secure.go | 54 ++ pkg/middlewares/headers/secure_test.go | 191 ++++++ pkg/responsemodifiers/headers.go | 53 -- pkg/responsemodifiers/log.go | 13 - pkg/responsemodifiers/response_modifier.go | 68 --- .../response_modifier_test.go | 214 ------- pkg/server/middleware/middlewares.go | 2 +- pkg/server/router/router.go | 18 +- pkg/server/router/router_test.go | 18 +- pkg/server/routerfactory.go | 4 +- pkg/server/service/internalhandler.go | 80 +-- pkg/server/service/proxy.go | 9 +- pkg/server/service/proxy_test.go | 2 +- pkg/server/service/proxy_websocket_test.go | 28 +- pkg/server/service/service.go | 27 +- pkg/server/service/service_test.go | 6 +- 25 files changed, 1144 insertions(+), 1240 deletions(-) create mode 100644 integration/fixtures/headers/secure_multiple.toml create mode 100644 pkg/middlewares/headers/header.go create mode 100644 pkg/middlewares/headers/header_test.go create mode 100644 pkg/middlewares/headers/responsewriter.go create mode 100644 pkg/middlewares/headers/secure.go create mode 100644 pkg/middlewares/headers/secure_test.go delete mode 100644 pkg/responsemodifiers/headers.go delete mode 100644 pkg/responsemodifiers/log.go delete mode 100644 pkg/responsemodifiers/response_modifier.go delete mode 100644 pkg/responsemodifiers/response_modifier_test.go diff --git a/integration/fixtures/headers/secure_multiple.toml b/integration/fixtures/headers/secure_multiple.toml new file mode 100644 index 000000000..9603046e5 --- /dev/null +++ b/integration/fixtures/headers/secure_multiple.toml @@ -0,0 +1,33 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router1] + rule = "Host(`test.localhost`)" + middlewares = ["foo", "bar"] + service = "service1" + + +[http.middlewares] + [http.middlewares.foo.headers] + frameDeny = true + [http.middlewares.bar.headers] + contentTypeNosniff = true + +[http.services] + [http.services.service1.loadBalancer] + [[http.services.service1.loadBalancer.servers]] + url = "http://127.0.0.1:9000" diff --git a/integration/headers_test.go b/integration/headers_test.go index a5cc4259a..c464bf97c 100644 --- a/integration/headers_test.go +++ b/integration/headers_test.go @@ -162,3 +162,44 @@ func (s *HeadersSuite) TestSecureHeadersResponses(c *check.C) { c.Assert(err, checker.IsNil) } } + +func (s *HeadersSuite) TestMultipleSecureHeadersResponses(c *check.C) { + file := s.adaptFile(c, "fixtures/headers/secure_multiple.toml", struct{}{}) + defer os.Remove(file) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) + + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer cmd.Process.Kill() + + backend := startTestServer("9000", http.StatusOK, "") + defer backend.Close() + + err = try.GetRequest(backend.URL, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) + + testCase := []struct { + desc string + expected http.Header + reqHost string + }{ + { + desc: "Feature-Policy Set", + expected: http.Header{ + "X-Frame-Options": {"DENY"}, + "X-Content-Type-Options": {"nosniff"}, + }, + reqHost: "test.localhost", + }, + } + + for _, test := range testCase { + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = test.reqHost + + err = try.Request(req, 500*time.Millisecond, try.HasHeaderStruct(test.expected)) + c.Assert(err, checker.IsNil) + } +} diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go index d5f687fde..3789142e5 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -33,7 +33,7 @@ const ( ) type serviceBuilder interface { - BuildHTTP(ctx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) + BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error) } // customErrors is a middleware that provides the custom error pages.. @@ -54,7 +54,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.ErrorPage, servi return nil, err } - backend, err := serviceBuilder.BuildHTTP(ctx, config.Service, nil) + backend, err := serviceBuilder.BuildHTTP(ctx, config.Service) if err != nil { return nil, err } diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go index 58a3a1673..0a608cc44 100644 --- a/pkg/middlewares/customerrors/custom_errors_test.go +++ b/pkg/middlewares/customerrors/custom_errors_test.go @@ -150,7 +150,7 @@ type mockServiceBuilder struct { handler http.Handler } -func (m *mockServiceBuilder) BuildHTTP(_ context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) { +func (m *mockServiceBuilder) BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error) { return m.handler, nil } diff --git a/pkg/middlewares/headers/header.go b/pkg/middlewares/headers/header.go new file mode 100644 index 000000000..862b376ed --- /dev/null +++ b/pkg/middlewares/headers/header.go @@ -0,0 +1,170 @@ +package headers + +import ( + "context" + "net/http" + "strconv" + "strings" + + "github.com/containous/traefik/v2/pkg/config/dynamic" + "github.com/containous/traefik/v2/pkg/log" +) + +// Header is a middleware that helps setup a few basic security features. +// A single headerOptions struct can be provided to configure which features should be enabled, +// and the ability to override a few of the default values. +type Header struct { + next http.Handler + hasCustomHeaders bool + hasCorsHeaders bool + headers *dynamic.Headers +} + +// NewHeader constructs a new header instance from supplied frontend header struct. +func NewHeader(next http.Handler, cfg dynamic.Headers) *Header { + hasCustomHeaders := cfg.HasCustomHeadersDefined() + hasCorsHeaders := cfg.HasCorsHeadersDefined() + + ctx := log.With(context.Background(), log.Str(log.MiddlewareType, typeName)) + handleDeprecation(ctx, &cfg) + + return &Header{ + next: next, + headers: &cfg, + hasCustomHeaders: hasCustomHeaders, + hasCorsHeaders: hasCorsHeaders, + } +} + +func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + // Handle Cors headers and preflight if configured. + if isPreflight := s.processCorsHeaders(rw, req); isPreflight { + return + } + + if s.hasCustomHeaders { + s.modifyCustomRequestHeaders(req) + } + + // If there is a next, call it. + if s.next != nil { + s.next.ServeHTTP(newResponseModifier(rw, req, s.PostRequestModifyResponseHeaders), req) + } +} + +// modifyCustomRequestHeaders sets or deletes custom request headers. +func (s *Header) modifyCustomRequestHeaders(req *http.Request) { + // Loop through Custom request headers + for header, value := range s.headers.CustomRequestHeaders { + switch { + case value == "": + req.Header.Del(header) + + case strings.EqualFold(header, "Host"): + req.Host = value + + default: + req.Header.Set(header, value) + } + } +} + +// PostRequestModifyResponseHeaders set or delete response headers. +// This method is called AFTER the response is generated from the backend +// and can merge/override headers from the backend response. +func (s *Header) PostRequestModifyResponseHeaders(res *http.Response) error { + // Loop through Custom response headers + for header, value := range s.headers.CustomResponseHeaders { + if value == "" { + res.Header.Del(header) + } else { + res.Header.Set(header, value) + } + } + + if res != nil && res.Request != nil { + originHeader := res.Request.Header.Get("Origin") + allowed, match := s.isOriginAllowed(originHeader) + + if allowed { + res.Header.Set("Access-Control-Allow-Origin", match) + } + } + + if s.headers.AccessControlAllowCredentials { + res.Header.Set("Access-Control-Allow-Credentials", "true") + } + + if len(s.headers.AccessControlExposeHeaders) > 0 { + exposeHeaders := strings.Join(s.headers.AccessControlExposeHeaders, ",") + res.Header.Set("Access-Control-Expose-Headers", exposeHeaders) + } + + if !s.headers.AddVaryHeader { + return nil + } + + varyHeader := res.Header.Get("Vary") + if varyHeader == "Origin" { + return nil + } + + if varyHeader != "" { + varyHeader += "," + } + varyHeader += "Origin" + + res.Header.Set("Vary", varyHeader) + return nil +} + +// processCorsHeaders processes the incoming request, +// and returns if it is a preflight request. +// If not a preflight, it handles the preRequestModifyCorsResponseHeaders. +func (s *Header) processCorsHeaders(rw http.ResponseWriter, req *http.Request) bool { + if !s.hasCorsHeaders { + return false + } + + reqAcMethod := req.Header.Get("Access-Control-Request-Method") + originHeader := req.Header.Get("Origin") + + if reqAcMethod != "" && originHeader != "" && req.Method == http.MethodOptions { + // If the request is an OPTIONS request with an Access-Control-Request-Method header, + // and Origin headers, then it is a CORS preflight request, + // and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request + if s.headers.AccessControlAllowCredentials { + rw.Header().Set("Access-Control-Allow-Credentials", "true") + } + + allowHeaders := strings.Join(s.headers.AccessControlAllowHeaders, ",") + if allowHeaders != "" { + rw.Header().Set("Access-Control-Allow-Headers", allowHeaders) + } + + allowMethods := strings.Join(s.headers.AccessControlAllowMethods, ",") + if allowMethods != "" { + rw.Header().Set("Access-Control-Allow-Methods", allowMethods) + } + + allowed, match := s.isOriginAllowed(originHeader) + if allowed { + rw.Header().Set("Access-Control-Allow-Origin", match) + } + + rw.Header().Set("Access-Control-Max-Age", strconv.Itoa(int(s.headers.AccessControlMaxAge))) + return true + } + + return false +} + +func (s *Header) isOriginAllowed(origin string) (bool, string) { + for _, item := range s.headers.AccessControlAllowOriginList { + if item == "*" || item == origin { + return true, item + } + } + + return false, "" +} diff --git a/pkg/middlewares/headers/header_test.go b/pkg/middlewares/headers/header_test.go new file mode 100644 index 000000000..f0aa43218 --- /dev/null +++ b/pkg/middlewares/headers/header_test.go @@ -0,0 +1,492 @@ +package headers + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/containous/traefik/v2/pkg/config/dynamic" + "github.com/stretchr/testify/assert" +) + +func TestNewHeader_customRequestHeader(t *testing.T) { + testCases := []struct { + desc string + cfg dynamic.Headers + expected http.Header + }{ + { + desc: "adds a header", + cfg: dynamic.Headers{ + CustomRequestHeaders: map[string]string{ + "X-Custom-Request-Header": "test_request", + }, + }, + expected: http.Header{"Foo": []string{"bar"}, "X-Custom-Request-Header": []string{"test_request"}}, + }, + { + desc: "delete a header", + cfg: dynamic.Headers{ + CustomRequestHeaders: map[string]string{ + "X-Custom-Request-Header": "", + "Foo": "", + }, + }, + expected: http.Header{}, + }, + { + desc: "override a header", + cfg: dynamic.Headers{ + CustomRequestHeaders: map[string]string{ + "Foo": "test", + }, + }, + expected: http.Header{"Foo": []string{"test"}}, + }, + } + + emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + mid := NewHeader(emptyHandler, test.cfg) + + req := httptest.NewRequest(http.MethodGet, "/foo", nil) + req.Header.Set("Foo", "bar") + + rw := httptest.NewRecorder() + + mid.ServeHTTP(rw, req) + + assert.Equal(t, http.StatusOK, rw.Code) + assert.Equal(t, test.expected, req.Header) + }) + } +} + +func TestNewHeader_customRequestHeader_Host(t *testing.T) { + testCases := []struct { + desc string + customHeaders map[string]string + expectedHost string + expectedURLHost string + }{ + { + desc: "standard Host header", + customHeaders: map[string]string{}, + expectedHost: "example.org", + expectedURLHost: "example.org", + }, + { + desc: "custom Host header", + customHeaders: map[string]string{ + "Host": "example.com", + }, + expectedHost: "example.com", + expectedURLHost: "example.org", + }, + } + + emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + mid := NewHeader(emptyHandler, dynamic.Headers{CustomRequestHeaders: test.customHeaders}) + + req := httptest.NewRequest(http.MethodGet, "http://example.org/foo", nil) + + rw := httptest.NewRecorder() + + mid.ServeHTTP(rw, req) + + assert.Equal(t, http.StatusOK, rw.Code) + assert.Equal(t, test.expectedHost, req.Host) + assert.Equal(t, test.expectedURLHost, req.URL.Host) + }) + } +} + +func TestNewHeader_CORSPreflights(t *testing.T) { + testCases := []struct { + desc string + cfg dynamic.Headers + requestHeaders http.Header + expected http.Header + }{ + { + desc: "Test Simple Preflight", + cfg: dynamic.Headers{ + AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, + AccessControlAllowOriginList: []string{"https://foo.bar.org"}, + AccessControlMaxAge: 600, + }, + requestHeaders: map[string][]string{ + "Access-Control-Request-Headers": {"origin"}, + "Access-Control-Request-Method": {"GET", "OPTIONS"}, + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"https://foo.bar.org"}, + "Access-Control-Max-Age": {"600"}, + "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, + }, + }, + { + desc: "Wildcard origin Preflight", + cfg: dynamic.Headers{ + AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, + AccessControlAllowOriginList: []string{"*"}, + AccessControlMaxAge: 600, + }, + requestHeaders: map[string][]string{ + "Access-Control-Request-Headers": {"origin"}, + "Access-Control-Request-Method": {"GET", "OPTIONS"}, + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"*"}, + "Access-Control-Max-Age": {"600"}, + "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, + }, + }, + { + desc: "Allow Credentials Preflight", + cfg: dynamic.Headers{ + AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, + AccessControlAllowOriginList: []string{"*"}, + AccessControlAllowCredentials: true, + AccessControlMaxAge: 600, + }, + requestHeaders: map[string][]string{ + "Access-Control-Request-Headers": {"origin"}, + "Access-Control-Request-Method": {"GET", "OPTIONS"}, + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"*"}, + "Access-Control-Max-Age": {"600"}, + "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, + "Access-Control-Allow-Credentials": {"true"}, + }, + }, + { + desc: "Allow Headers Preflight", + cfg: dynamic.Headers{ + AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, + AccessControlAllowOriginList: []string{"*"}, + AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"}, + AccessControlMaxAge: 600, + }, + requestHeaders: map[string][]string{ + "Access-Control-Request-Headers": {"origin"}, + "Access-Control-Request-Method": {"GET", "OPTIONS"}, + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"*"}, + "Access-Control-Max-Age": {"600"}, + "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, + "Access-Control-Allow-Headers": {"origin,X-Forwarded-For"}, + }, + }, + { + desc: "No Request Headers Preflight", + cfg: dynamic.Headers{ + AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, + AccessControlAllowOriginList: []string{"*"}, + AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"}, + AccessControlMaxAge: 600, + }, + requestHeaders: map[string][]string{ + "Access-Control-Request-Method": {"GET", "OPTIONS"}, + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"*"}, + "Access-Control-Max-Age": {"600"}, + "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, + "Access-Control-Allow-Headers": {"origin,X-Forwarded-For"}, + }, + }, + } + + emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + mid := NewHeader(emptyHandler, test.cfg) + + req := httptest.NewRequest(http.MethodOptions, "/foo", nil) + req.Header = test.requestHeaders + + rw := httptest.NewRecorder() + + mid.ServeHTTP(rw, req) + + assert.Equal(t, test.expected, rw.Result().Header) + }) + } +} + +func TestNewHeader_CORSResponses(t *testing.T) { + emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) + + testCases := []struct { + desc string + next http.Handler + cfg dynamic.Headers + requestHeaders http.Header + expected http.Header + }{ + { + desc: "Test Simple Request", + next: emptyHandler, + cfg: dynamic.Headers{ + AccessControlAllowOriginList: []string{"https://foo.bar.org"}, + }, + requestHeaders: map[string][]string{ + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"https://foo.bar.org"}, + }, + }, + { + desc: "Wildcard origin Request", + next: emptyHandler, + cfg: dynamic.Headers{ + AccessControlAllowOriginList: []string{"*"}, + }, + requestHeaders: map[string][]string{ + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"*"}, + }, + }, + { + desc: "Empty origin Request", + next: emptyHandler, + cfg: dynamic.Headers{ + AccessControlAllowOriginList: []string{"https://foo.bar.org"}, + }, + requestHeaders: map[string][]string{}, + expected: map[string][]string{}, + }, + { + desc: "Not Defined origin Request", + next: emptyHandler, + requestHeaders: map[string][]string{}, + expected: map[string][]string{}, + }, + { + desc: "Allow Credentials Request", + next: emptyHandler, + cfg: dynamic.Headers{ + AccessControlAllowOriginList: []string{"*"}, + AccessControlAllowCredentials: true, + }, + requestHeaders: map[string][]string{ + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"*"}, + "Access-Control-Allow-Credentials": {"true"}, + }, + }, + { + desc: "Expose Headers Request", + next: emptyHandler, + cfg: dynamic.Headers{ + AccessControlAllowOriginList: []string{"*"}, + AccessControlExposeHeaders: []string{"origin", "X-Forwarded-For"}, + }, + requestHeaders: map[string][]string{ + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"*"}, + "Access-Control-Expose-Headers": {"origin,X-Forwarded-For"}, + }, + }, + { + desc: "Test Simple Request with Vary Headers", + next: emptyHandler, + cfg: dynamic.Headers{ + AccessControlAllowOriginList: []string{"https://foo.bar.org"}, + AddVaryHeader: true, + }, + requestHeaders: map[string][]string{ + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"https://foo.bar.org"}, + "Vary": {"Origin"}, + }, + }, + { + desc: "Test Simple Request with Vary Headers and non-empty response", + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // nonEmptyHandler + w.Header().Set("Vary", "Testing") + w.WriteHeader(http.StatusOK) + }), + cfg: dynamic.Headers{ + AccessControlAllowOriginList: []string{"https://foo.bar.org"}, + AddVaryHeader: true, + }, + requestHeaders: map[string][]string{ + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"https://foo.bar.org"}, + "Vary": {"Testing,Origin"}, + }, + }, + { + desc: "Test Simple Request with Vary Headers and existing vary:origin response", + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // existingOriginHandler + w.Header().Set("Vary", "Origin") + w.WriteHeader(http.StatusOK) + }), + cfg: dynamic.Headers{ + AccessControlAllowOriginList: []string{"https://foo.bar.org"}, + AddVaryHeader: true, + }, + requestHeaders: map[string][]string{ + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"https://foo.bar.org"}, + "Vary": {"Origin"}, + }, + }, + { + desc: "Test Simple Request with non-empty response: set ACAO", + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // existingAccessControlAllowOriginHandlerSet + w.Header().Set("Access-Control-Allow-Origin", "http://foo.bar.org") + w.WriteHeader(http.StatusOK) + }), + cfg: dynamic.Headers{ + AccessControlAllowOriginList: []string{"*"}, + }, + requestHeaders: map[string][]string{ + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"*"}, + }, + }, + { + desc: "Test Simple Request with non-empty response: add ACAO", + next: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // existingAccessControlAllowOriginHandlerAdd + w.Header().Add("Access-Control-Allow-Origin", "http://foo.bar.org") + w.WriteHeader(http.StatusOK) + }), + cfg: dynamic.Headers{ + AccessControlAllowOriginList: []string{"*"}, + }, + requestHeaders: map[string][]string{ + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{ + "Access-Control-Allow-Origin": {"*"}, + }, + }, + { + desc: "Test Simple CustomRequestHeaders Not Hijacked by CORS", + next: emptyHandler, + cfg: dynamic.Headers{ + CustomRequestHeaders: map[string]string{"foo": "bar"}, + }, + requestHeaders: map[string][]string{ + "Access-Control-Request-Headers": {"origin"}, + "Access-Control-Request-Method": {"GET", "OPTIONS"}, + "Origin": {"https://foo.bar.org"}, + }, + expected: map[string][]string{}, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + mid := NewHeader(test.next, test.cfg) + + req := httptest.NewRequest(http.MethodGet, "/foo", nil) + req.Header = test.requestHeaders + + rw := httptest.NewRecorder() + + mid.ServeHTTP(rw, req) + + assert.Equal(t, test.expected, rw.Result().Header) + }) + } +} + +func TestNewHeader_customResponseHeaders(t *testing.T) { + testCases := []struct { + desc string + config map[string]string + expected http.Header + }{ + { + desc: "Test Simple Response", + config: map[string]string{ + "Testing": "foo", + "Testing2": "bar", + }, + expected: map[string][]string{ + "Foo": {"bar"}, + "Testing": {"foo"}, + "Testing2": {"bar"}, + }, + }, + { + desc: "empty Custom Header", + config: map[string]string{ + "Testing": "foo", + "Testing2": "", + }, + expected: map[string][]string{ + "Foo": {"bar"}, + "Testing": {"foo"}, + }, + }, + { + desc: "Deleting Custom Header", + config: map[string]string{ + "Testing": "foo", + "Foo": "", + }, + expected: map[string][]string{ + "Testing": {"foo"}, + }, + }, + } + + emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Foo", "bar") + w.WriteHeader(http.StatusOK) + }) + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + mid := NewHeader(emptyHandler, dynamic.Headers{CustomResponseHeaders: test.config}) + + req := httptest.NewRequest(http.MethodGet, "/foo", nil) + + rw := httptest.NewRecorder() + + mid.ServeHTTP(rw, req) + + assert.Equal(t, test.expected, rw.Result().Header) + }) + } +} diff --git a/pkg/middlewares/headers/headers.go b/pkg/middlewares/headers/headers.go index 53cd34163..881ad1565 100644 --- a/pkg/middlewares/headers/headers.go +++ b/pkg/middlewares/headers/headers.go @@ -5,15 +5,12 @@ import ( "context" "errors" "net/http" - "strconv" - "strings" "github.com/containous/traefik/v2/pkg/config/dynamic" "github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/middlewares" "github.com/containous/traefik/v2/pkg/tracing" "github.com/opentracing/opentracing-go/ext" - "github.com/unrolled/secure" ) const ( @@ -55,7 +52,7 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin if hasSecureHeaders { logger.Debugf("Setting up secureHeaders from %v", cfg) - handler = newSecure(next, cfg) + handler = newSecure(next, cfg, name) nextHandler = handler } @@ -77,203 +74,3 @@ func (h *headers) GetTracingInformation() (string, ext.SpanKindEnum) { func (h *headers) ServeHTTP(rw http.ResponseWriter, req *http.Request) { h.handler.ServeHTTP(rw, req) } - -type secureHeader struct { - next http.Handler - secure *secure.Secure -} - -// newSecure constructs a new secure instance with supplied options. -func newSecure(next http.Handler, cfg dynamic.Headers) *secureHeader { - opt := secure.Options{ - BrowserXssFilter: cfg.BrowserXSSFilter, - ContentTypeNosniff: cfg.ContentTypeNosniff, - ForceSTSHeader: cfg.ForceSTSHeader, - FrameDeny: cfg.FrameDeny, - IsDevelopment: cfg.IsDevelopment, - SSLRedirect: cfg.SSLRedirect, - SSLForceHost: cfg.SSLForceHost, - SSLTemporaryRedirect: cfg.SSLTemporaryRedirect, - STSIncludeSubdomains: cfg.STSIncludeSubdomains, - STSPreload: cfg.STSPreload, - ContentSecurityPolicy: cfg.ContentSecurityPolicy, - CustomBrowserXssValue: cfg.CustomBrowserXSSValue, - CustomFrameOptionsValue: cfg.CustomFrameOptionsValue, - PublicKey: cfg.PublicKey, - ReferrerPolicy: cfg.ReferrerPolicy, - SSLHost: cfg.SSLHost, - AllowedHosts: cfg.AllowedHosts, - HostsProxyHeaders: cfg.HostsProxyHeaders, - SSLProxyHeaders: cfg.SSLProxyHeaders, - STSSeconds: cfg.STSSeconds, - FeaturePolicy: cfg.FeaturePolicy, - } - - return &secureHeader{ - next: next, - secure: secure.New(opt), - } -} - -func (s secureHeader) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - s.secure.HandlerFuncWithNextForRequestOnly(rw, req, s.next.ServeHTTP) -} - -// Header is a middleware that helps setup a few basic security features. -// A single headerOptions struct can be provided to configure which features should be enabled, -// and the ability to override a few of the default values. -type Header struct { - next http.Handler - hasCustomHeaders bool - hasCorsHeaders bool - headers *dynamic.Headers -} - -// NewHeader constructs a new header instance from supplied frontend header struct. -func NewHeader(next http.Handler, cfg dynamic.Headers) *Header { - hasCustomHeaders := cfg.HasCustomHeadersDefined() - hasCorsHeaders := cfg.HasCorsHeadersDefined() - - ctx := log.With(context.Background(), log.Str(log.MiddlewareType, typeName)) - handleDeprecation(ctx, &cfg) - - return &Header{ - next: next, - headers: &cfg, - hasCustomHeaders: hasCustomHeaders, - hasCorsHeaders: hasCorsHeaders, - } -} - -func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - // Handle Cors headers and preflight if configured. - if isPreflight := s.processCorsHeaders(rw, req); isPreflight { - return - } - - if s.hasCustomHeaders { - s.modifyCustomRequestHeaders(req) - } - - // If there is a next, call it. - if s.next != nil { - s.next.ServeHTTP(rw, req) - } -} - -// modifyCustomRequestHeaders sets or deletes custom request headers. -func (s *Header) modifyCustomRequestHeaders(req *http.Request) { - // Loop through Custom request headers - for header, value := range s.headers.CustomRequestHeaders { - switch { - case value == "": - req.Header.Del(header) - - case strings.EqualFold(header, "Host"): - req.Host = value - - default: - req.Header.Set(header, value) - } - } -} - -// PostRequestModifyResponseHeaders set or delete response headers. -// This method is called AFTER the response is generated from the backend -// and can merge/override headers from the backend response. -func (s *Header) PostRequestModifyResponseHeaders(res *http.Response) error { - // Loop through Custom response headers - for header, value := range s.headers.CustomResponseHeaders { - if value == "" { - res.Header.Del(header) - } else { - res.Header.Set(header, value) - } - } - - if res != nil && res.Request != nil { - originHeader := res.Request.Header.Get("Origin") - allowed, match := s.isOriginAllowed(originHeader) - - if allowed { - res.Header.Set("Access-Control-Allow-Origin", match) - } - } - - if s.headers.AccessControlAllowCredentials { - res.Header.Set("Access-Control-Allow-Credentials", "true") - } - - if len(s.headers.AccessControlExposeHeaders) > 0 { - exposeHeaders := strings.Join(s.headers.AccessControlExposeHeaders, ",") - res.Header.Set("Access-Control-Expose-Headers", exposeHeaders) - } - - if !s.headers.AddVaryHeader { - return nil - } - - varyHeader := res.Header.Get("Vary") - if varyHeader == "Origin" { - return nil - } - - if varyHeader != "" { - varyHeader += "," - } - varyHeader += "Origin" - - res.Header.Set("Vary", varyHeader) - return nil -} - -// processCorsHeaders processes the incoming request, -// and returns if it is a preflight request. -// If not a preflight, it handles the preRequestModifyCorsResponseHeaders. -func (s *Header) processCorsHeaders(rw http.ResponseWriter, req *http.Request) bool { - if !s.hasCorsHeaders { - return false - } - - reqAcMethod := req.Header.Get("Access-Control-Request-Method") - originHeader := req.Header.Get("Origin") - - if reqAcMethod != "" && originHeader != "" && req.Method == http.MethodOptions { - // If the request is an OPTIONS request with an Access-Control-Request-Method header, - // and Origin headers, then it is a CORS preflight request, - // and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request - if s.headers.AccessControlAllowCredentials { - rw.Header().Set("Access-Control-Allow-Credentials", "true") - } - - allowHeaders := strings.Join(s.headers.AccessControlAllowHeaders, ",") - if allowHeaders != "" { - rw.Header().Set("Access-Control-Allow-Headers", allowHeaders) - } - - allowMethods := strings.Join(s.headers.AccessControlAllowMethods, ",") - if allowMethods != "" { - rw.Header().Set("Access-Control-Allow-Methods", allowMethods) - } - - allowed, match := s.isOriginAllowed(originHeader) - if allowed { - rw.Header().Set("Access-Control-Allow-Origin", match) - } - - rw.Header().Set("Access-Control-Max-Age", strconv.Itoa(int(s.headers.AccessControlMaxAge))) - return true - } - - return false -} - -func (s *Header) isOriginAllowed(origin string) (bool, string) { - for _, item := range s.headers.AccessControlAllowOriginList { - if item == "*" || item == origin { - return true, item - } - } - - return false, "" -} diff --git a/pkg/middlewares/headers/headers_test.go b/pkg/middlewares/headers/headers_test.go index eef384a5c..a7be06a97 100644 --- a/pkg/middlewares/headers/headers_test.go +++ b/pkg/middlewares/headers/headers_test.go @@ -9,104 +9,21 @@ import ( "testing" "github.com/containous/traefik/v2/pkg/config/dynamic" - "github.com/containous/traefik/v2/pkg/testhelpers" "github.com/containous/traefik/v2/pkg/tracing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestCustomRequestHeader(t *testing.T) { - emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) +func TestNew_withoutOptions(t *testing.T) { + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) - header := NewHeader(emptyHandler, dynamic.Headers{ - CustomRequestHeaders: map[string]string{ - "X-Custom-Request-Header": "test_request", - }, - }) + mid, err := New(context.Background(), next, dynamic.Headers{}, "testing") + require.Errorf(t, err, "headers configuration not valid") - res := httptest.NewRecorder() - req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil) - - header.ServeHTTP(res, req) - - assert.Equal(t, http.StatusOK, res.Code) - assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header")) + assert.Nil(t, mid) } -func TestCustomRequestHeader_Host(t *testing.T) { - emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - - testCases := []struct { - desc string - customHeaders map[string]string - expectedHost string - expectedURLHost string - }{ - { - desc: "standard Host header", - customHeaders: map[string]string{}, - expectedHost: "example.org", - expectedURLHost: "example.org", - }, - { - desc: "custom Host header", - customHeaders: map[string]string{ - "Host": "example.com", - }, - expectedHost: "example.com", - expectedURLHost: "example.org", - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - header := NewHeader(emptyHandler, dynamic.Headers{ - CustomRequestHeaders: test.customHeaders, - }) - - res := httptest.NewRecorder() - req, err := http.NewRequest(http.MethodGet, "http://example.org/foo", nil) - require.NoError(t, err) - - header.ServeHTTP(res, req) - - assert.Equal(t, http.StatusOK, res.Code) - assert.Equal(t, test.expectedHost, req.Host) - assert.Equal(t, test.expectedURLHost, req.URL.Host) - }) - } -} - -func TestCustomRequestHeaderEmptyValue(t *testing.T) { - emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - - header := NewHeader(emptyHandler, dynamic.Headers{ - CustomRequestHeaders: map[string]string{ - "X-Custom-Request-Header": "test_request", - }, - }) - - res := httptest.NewRecorder() - req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil) - - header.ServeHTTP(res, req) - - assert.Equal(t, http.StatusOK, res.Code) - assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header")) - - header = NewHeader(emptyHandler, dynamic.Headers{ - CustomRequestHeaders: map[string]string{ - "X-Custom-Request-Header": "", - }, - }) - - header.ServeHTTP(res, req) - - assert.Equal(t, http.StatusOK, res.Code) - assert.Equal(t, "", req.Header.Get("X-Custom-Request-Header")) -} - -func TestSecureHeader(t *testing.T) { +func TestNew_allowedHosts(t *testing.T) { testCases := []struct { desc string fromHost string @@ -129,10 +46,13 @@ func TestSecureHeader(t *testing.T) { }, } - emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - header, err := New(context.Background(), emptyHandler, dynamic.Headers{ + emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) + + cfg := dynamic.Headers{ AllowedHosts: []string{"foo.com", "bar.com"}, - }, "foo") + } + + mid, err := New(context.Background(), emptyHandler, cfg, "foo") require.NoError(t, err) for _, test := range testCases { @@ -140,479 +60,54 @@ func TestSecureHeader(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - res := httptest.NewRecorder() - req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil) + req := httptest.NewRequest(http.MethodGet, "/foo", nil) req.Host = test.fromHost - header.ServeHTTP(res, req) - assert.Equal(t, test.expected, res.Code) - }) - } -} - -func TestSSLForceHost(t *testing.T) { - next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - _, _ = rw.Write([]byte("OK")) - }) - - testCases := []struct { - desc string - host string - secureMiddleware *secureHeader - expected int - }{ - { - desc: "http should return a 301", - host: "http://powpow.example.com", - secureMiddleware: newSecure(next, dynamic.Headers{ - SSLRedirect: true, - SSLForceHost: true, - SSLHost: "powpow.example.com", - }), - expected: http.StatusMovedPermanently, - }, - { - desc: "http sub domain should return a 301", - host: "http://www.powpow.example.com", - secureMiddleware: newSecure(next, dynamic.Headers{ - SSLRedirect: true, - SSLForceHost: true, - SSLHost: "powpow.example.com", - }), - expected: http.StatusMovedPermanently, - }, - { - desc: "https should return a 200", - host: "https://powpow.example.com", - secureMiddleware: newSecure(next, dynamic.Headers{ - SSLRedirect: true, - SSLForceHost: true, - SSLHost: "powpow.example.com", - }), - expected: http.StatusOK, - }, - { - desc: "https sub domain should return a 301", - host: "https://www.powpow.example.com", - secureMiddleware: newSecure(next, dynamic.Headers{ - SSLRedirect: true, - SSLForceHost: true, - SSLHost: "powpow.example.com", - }), - expected: http.StatusMovedPermanently, - }, - { - desc: "http without force host and sub domain should return a 301", - host: "http://www.powpow.example.com", - secureMiddleware: newSecure(next, dynamic.Headers{ - SSLRedirect: true, - SSLForceHost: false, - SSLHost: "powpow.example.com", - }), - expected: http.StatusMovedPermanently, - }, - { - desc: "https without force host and sub domain should return a 301", - host: "https://www.powpow.example.com", - secureMiddleware: newSecure(next, dynamic.Headers{ - SSLRedirect: true, - SSLForceHost: false, - SSLHost: "powpow.example.com", - }), - expected: http.StatusOK, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - req := testhelpers.MustNewRequest(http.MethodGet, test.host, nil) rw := httptest.NewRecorder() - test.secureMiddleware.ServeHTTP(rw, req) - assert.Equal(t, test.expected, rw.Result().StatusCode) + mid.ServeHTTP(rw, req) + + assert.Equal(t, test.expected, rw.Code) }) } } -func TestCORSPreflights(t *testing.T) { - emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) +func TestNew_customHeaders(t *testing.T) { + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) - testCases := []struct { - desc string - header *Header - requestHeaders http.Header - expected http.Header - }{ - { - desc: "Test Simple Preflight", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, - AccessControlAllowOriginList: []string{"https://foo.bar.org"}, - AccessControlMaxAge: 600, - }), - requestHeaders: map[string][]string{ - "Access-Control-Request-Headers": {"origin"}, - "Access-Control-Request-Method": {"GET", "OPTIONS"}, - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"https://foo.bar.org"}, - "Access-Control-Max-Age": {"600"}, - "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, - }, - }, - { - desc: "Wildcard origin Preflight", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, - AccessControlAllowOriginList: []string{"*"}, - AccessControlMaxAge: 600, - }), - requestHeaders: map[string][]string{ - "Access-Control-Request-Headers": {"origin"}, - "Access-Control-Request-Method": {"GET", "OPTIONS"}, - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"*"}, - "Access-Control-Max-Age": {"600"}, - "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, - }, - }, - { - desc: "Allow Credentials Preflight", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, - AccessControlAllowOriginList: []string{"*"}, - AccessControlAllowCredentials: true, - AccessControlMaxAge: 600, - }), - requestHeaders: map[string][]string{ - "Access-Control-Request-Headers": {"origin"}, - "Access-Control-Request-Method": {"GET", "OPTIONS"}, - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"*"}, - "Access-Control-Max-Age": {"600"}, - "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, - "Access-Control-Allow-Credentials": {"true"}, - }, - }, - { - desc: "Allow Headers Preflight", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, - AccessControlAllowOriginList: []string{"*"}, - AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"}, - AccessControlMaxAge: 600, - }), - requestHeaders: map[string][]string{ - "Access-Control-Request-Headers": {"origin"}, - "Access-Control-Request-Method": {"GET", "OPTIONS"}, - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"*"}, - "Access-Control-Max-Age": {"600"}, - "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, - "Access-Control-Allow-Headers": {"origin,X-Forwarded-For"}, - }, - }, - { - desc: "No Request Headers Preflight", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"}, - AccessControlAllowOriginList: []string{"*"}, - AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"}, - AccessControlMaxAge: 600, - }), - requestHeaders: map[string][]string{ - "Access-Control-Request-Method": {"GET", "OPTIONS"}, - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"*"}, - "Access-Control-Max-Age": {"600"}, - "Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"}, - "Access-Control-Allow-Headers": {"origin,X-Forwarded-For"}, - }, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - req := testhelpers.MustNewRequest(http.MethodOptions, "/foo", nil) - req.Header = test.requestHeaders - - rw := httptest.NewRecorder() - test.header.ServeHTTP(rw, req) - - assert.Equal(t, test.expected, rw.Result().Header) - }) - } -} - -func TestEmptyHeaderObject(t *testing.T) { - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - - _, err := New(context.Background(), next, dynamic.Headers{}, "testing") - require.Errorf(t, err, "headers configuration not valid") -} - -func TestCustomHeaderHandler(t *testing.T) { - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - - header, _ := New(context.Background(), next, dynamic.Headers{ + cfg := dynamic.Headers{ CustomRequestHeaders: map[string]string{ "X-Custom-Request-Header": "test_request", }, - }, "testing") + CustomResponseHeaders: map[string]string{ + "X-Custom-Response-Header": "test_response", + }, + } - res := httptest.NewRecorder() - req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil) + mid, err := New(context.Background(), next, cfg, "testing") + require.NoError(t, err) - header.ServeHTTP(res, req) + req := httptest.NewRequest(http.MethodGet, "/foo", nil) - assert.Equal(t, http.StatusOK, res.Code) + rw := httptest.NewRecorder() + + mid.ServeHTTP(rw, req) + + assert.Equal(t, http.StatusOK, rw.Code) assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header")) + assert.Equal(t, "test_response", rw.Header().Get("X-Custom-Response-Header")) } -func TestGetTracingInformation(t *testing.T) { +func Test_headers_getTracingInformation(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - header := &headers{ + mid := &headers{ handler: next, name: "testing", } - name, trace := header.GetTracingInformation() + name, trace := mid.GetTracingInformation() assert.Equal(t, "testing", name) assert.Equal(t, tracing.SpanKindNoneEnum, trace) } - -func TestCORSResponses(t *testing.T) { - emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - nonEmptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Vary", "Testing") }) - existingOriginHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Vary", "Origin") }) - existingAccessControlAllowOriginHandlerSet := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "http://foo.bar.org") - }) - existingAccessControlAllowOriginHandlerAdd := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Access-Control-Allow-Origin", "http://foo.bar.org") - }) - - testCases := []struct { - desc string - header *Header - requestHeaders http.Header - expected http.Header - }{ - { - desc: "Test Simple Request", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowOriginList: []string{"https://foo.bar.org"}, - }), - requestHeaders: map[string][]string{ - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"https://foo.bar.org"}, - }, - }, - { - desc: "Wildcard origin Request", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowOriginList: []string{"*"}, - }), - requestHeaders: map[string][]string{ - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"*"}, - }, - }, - { - desc: "Empty origin Request", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowOriginList: []string{"https://foo.bar.org"}, - }), - requestHeaders: map[string][]string{}, - expected: map[string][]string{}, - }, - { - desc: "Not Defined origin Request", - header: NewHeader(emptyHandler, dynamic.Headers{}), - requestHeaders: map[string][]string{}, - expected: map[string][]string{}, - }, - { - desc: "Allow Credentials Request", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowOriginList: []string{"*"}, - AccessControlAllowCredentials: true, - }), - requestHeaders: map[string][]string{ - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"*"}, - "Access-Control-Allow-Credentials": {"true"}, - }, - }, - { - desc: "Expose Headers Request", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowOriginList: []string{"*"}, - AccessControlExposeHeaders: []string{"origin", "X-Forwarded-For"}, - }), - requestHeaders: map[string][]string{ - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"*"}, - "Access-Control-Expose-Headers": {"origin,X-Forwarded-For"}, - }, - }, - { - desc: "Test Simple Request with Vary Headers", - header: NewHeader(emptyHandler, dynamic.Headers{ - AccessControlAllowOriginList: []string{"https://foo.bar.org"}, - AddVaryHeader: true, - }), - requestHeaders: map[string][]string{ - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"https://foo.bar.org"}, - "Vary": {"Origin"}, - }, - }, - { - desc: "Test Simple Request with Vary Headers and non-empty response", - header: NewHeader(nonEmptyHandler, dynamic.Headers{ - AccessControlAllowOriginList: []string{"https://foo.bar.org"}, - AddVaryHeader: true, - }), - requestHeaders: map[string][]string{ - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"https://foo.bar.org"}, - "Vary": {"Testing,Origin"}, - }, - }, - { - desc: "Test Simple Request with Vary Headers and existing vary:origin response", - header: NewHeader(existingOriginHandler, dynamic.Headers{ - AccessControlAllowOriginList: []string{"https://foo.bar.org"}, - AddVaryHeader: true, - }), - requestHeaders: map[string][]string{ - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"https://foo.bar.org"}, - "Vary": {"Origin"}, - }, - }, - { - desc: "Test Simple Request with non-empty response: set ACAO", - header: NewHeader(existingAccessControlAllowOriginHandlerSet, dynamic.Headers{ - AccessControlAllowOriginList: []string{"*"}, - }), - requestHeaders: map[string][]string{ - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"*"}, - }, - }, - { - desc: "Test Simple Request with non-empty response: add ACAO", - header: NewHeader(existingAccessControlAllowOriginHandlerAdd, dynamic.Headers{ - AccessControlAllowOriginList: []string{"*"}, - }), - requestHeaders: map[string][]string{ - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{ - "Access-Control-Allow-Origin": {"*"}, - }, - }, - { - desc: "Test Simple CustomRequestHeaders Not Hijacked by CORS", - header: NewHeader(emptyHandler, dynamic.Headers{ - CustomRequestHeaders: map[string]string{"foo": "bar"}, - }), - requestHeaders: map[string][]string{ - "Access-Control-Request-Headers": {"origin"}, - "Access-Control-Request-Method": {"GET", "OPTIONS"}, - "Origin": {"https://foo.bar.org"}, - }, - expected: map[string][]string{}, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil) - req.Header = test.requestHeaders - rw := httptest.NewRecorder() - test.header.ServeHTTP(rw, req) - res := rw.Result() - res.Request = req - err := test.header.PostRequestModifyResponseHeaders(res) - require.NoError(t, err) - assert.Equal(t, test.expected, rw.Result().Header) - }) - } -} - -func TestCustomResponseHeaders(t *testing.T) { - emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - - testCases := []struct { - desc string - header *Header - expected http.Header - }{ - { - desc: "Test Simple Response", - header: NewHeader(emptyHandler, dynamic.Headers{ - CustomResponseHeaders: map[string]string{ - "Testing": "foo", - "Testing2": "bar", - }, - }), - expected: map[string][]string{ - "Testing": {"foo"}, - "Testing2": {"bar"}, - }, - }, - { - desc: "Deleting Custom Header", - header: NewHeader(emptyHandler, dynamic.Headers{ - CustomResponseHeaders: map[string]string{ - "Testing": "foo", - "Testing2": "", - }, - }), - expected: map[string][]string{ - "Testing": {"foo"}, - }, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil) - rw := httptest.NewRecorder() - test.header.ServeHTTP(rw, req) - err := test.header.PostRequestModifyResponseHeaders(rw.Result()) - require.NoError(t, err) - assert.Equal(t, test.expected, rw.Result().Header) - }) - } -} diff --git a/pkg/middlewares/headers/responsewriter.go b/pkg/middlewares/headers/responsewriter.go new file mode 100644 index 000000000..274a58181 --- /dev/null +++ b/pkg/middlewares/headers/responsewriter.go @@ -0,0 +1,75 @@ +package headers + +import ( + "net/http" + + "github.com/containous/traefik/v2/pkg/log" +) + +type responseModifier struct { + r *http.Request + w http.ResponseWriter + + headersSent bool // whether headers have already been sent + code int // status code, must default to 200 + + modifier func(*http.Response) error // can be nil + modified bool // whether modifier has already been called for the current request + modifierErr error // returned by modifier call +} + +// modifier can be nil. +func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) *responseModifier { + return &responseModifier{ + r: r, + w: w, + modifier: modifier, + code: http.StatusOK, + } +} + +func (w *responseModifier) WriteHeader(code int) { + if w.headersSent { + return + } + defer func() { + w.code = code + w.headersSent = true + }() + + if w.modifier == nil || w.modified { + w.w.WriteHeader(code) + return + } + + resp := http.Response{ + Header: w.w.Header(), + Request: w.r, + } + + if err := w.modifier(&resp); err != nil { + w.modifierErr = err + // we are propagating when we are called in Write, but we're logging anyway, + // because we could be called from another place which does not take care of + // checking w.modifierErr. + log.WithoutContext().Errorf("Error when applying response modifier: %v", err) + w.w.WriteHeader(http.StatusInternalServerError) + return + } + + w.modified = true + w.w.WriteHeader(code) +} + +func (w *responseModifier) Header() http.Header { + return w.w.Header() +} + +func (w *responseModifier) Write(b []byte) (int, error) { + w.WriteHeader(w.code) + if w.modifierErr != nil { + return 0, w.modifierErr + } + + return w.w.Write(b) +} diff --git a/pkg/middlewares/headers/secure.go b/pkg/middlewares/headers/secure.go new file mode 100644 index 000000000..92cbe20d7 --- /dev/null +++ b/pkg/middlewares/headers/secure.go @@ -0,0 +1,54 @@ +package headers + +import ( + "net/http" + + "github.com/containous/traefik/v2/pkg/config/dynamic" + "github.com/unrolled/secure" +) + +type secureHeader struct { + next http.Handler + secure *secure.Secure + cfg dynamic.Headers +} + +// newSecure constructs a new secure instance with supplied options. +func newSecure(next http.Handler, cfg dynamic.Headers, contextKey string) *secureHeader { + opt := secure.Options{ + BrowserXssFilter: cfg.BrowserXSSFilter, + ContentTypeNosniff: cfg.ContentTypeNosniff, + ForceSTSHeader: cfg.ForceSTSHeader, + FrameDeny: cfg.FrameDeny, + IsDevelopment: cfg.IsDevelopment, + SSLRedirect: cfg.SSLRedirect, + SSLForceHost: cfg.SSLForceHost, + SSLTemporaryRedirect: cfg.SSLTemporaryRedirect, + STSIncludeSubdomains: cfg.STSIncludeSubdomains, + STSPreload: cfg.STSPreload, + ContentSecurityPolicy: cfg.ContentSecurityPolicy, + CustomBrowserXssValue: cfg.CustomBrowserXSSValue, + CustomFrameOptionsValue: cfg.CustomFrameOptionsValue, + PublicKey: cfg.PublicKey, + ReferrerPolicy: cfg.ReferrerPolicy, + SSLHost: cfg.SSLHost, + AllowedHosts: cfg.AllowedHosts, + HostsProxyHeaders: cfg.HostsProxyHeaders, + SSLProxyHeaders: cfg.SSLProxyHeaders, + STSSeconds: cfg.STSSeconds, + FeaturePolicy: cfg.FeaturePolicy, + SecureContextKey: contextKey, + } + + return &secureHeader{ + next: next, + secure: secure.New(opt), + cfg: cfg, + } +} + +func (s secureHeader) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + s.secure.HandlerFuncWithNextForRequestOnly(rw, req, func(writer http.ResponseWriter, request *http.Request) { + s.next.ServeHTTP(newResponseModifier(writer, request, s.secure.ModifyResponseHeaders), request) + }) +} diff --git a/pkg/middlewares/headers/secure_test.go b/pkg/middlewares/headers/secure_test.go new file mode 100644 index 000000000..66cc564d9 --- /dev/null +++ b/pkg/middlewares/headers/secure_test.go @@ -0,0 +1,191 @@ +package headers + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/containous/traefik/v2/pkg/config/dynamic" + "github.com/stretchr/testify/assert" +) + +// Middleware tests based on https://github.com/unrolled/secure + +func Test_newSecure_sslForceHost(t *testing.T) { + type expected struct { + statusCode int + location string + } + + testCases := []struct { + desc string + host string + cfg dynamic.Headers + expected + }{ + { + desc: "http should return a 301", + host: "http://powpow.example.com", + cfg: dynamic.Headers{ + SSLRedirect: true, + SSLForceHost: true, + SSLHost: "powpow.example.com", + }, + expected: expected{ + statusCode: http.StatusMovedPermanently, + location: "https://powpow.example.com", + }, + }, + { + desc: "http sub domain should return a 301", + host: "http://www.powpow.example.com", + cfg: dynamic.Headers{ + SSLRedirect: true, + SSLForceHost: true, + SSLHost: "powpow.example.com", + }, + expected: expected{ + statusCode: http.StatusMovedPermanently, + location: "https://powpow.example.com", + }, + }, + { + desc: "https should return a 200", + host: "https://powpow.example.com", + cfg: dynamic.Headers{ + SSLRedirect: true, + SSLForceHost: true, + SSLHost: "powpow.example.com", + }, + expected: expected{statusCode: http.StatusOK}, + }, + { + desc: "https sub domain should return a 301", + host: "https://www.powpow.example.com", + cfg: dynamic.Headers{ + SSLRedirect: true, + SSLForceHost: true, + SSLHost: "powpow.example.com", + }, + expected: expected{ + statusCode: http.StatusMovedPermanently, + location: "https://powpow.example.com", + }, + }, + { + desc: "http without force host and sub domain should return a 301", + host: "http://www.powpow.example.com", + cfg: dynamic.Headers{ + SSLRedirect: true, + SSLForceHost: false, + SSLHost: "powpow.example.com", + }, + expected: expected{ + statusCode: http.StatusMovedPermanently, + location: "https://powpow.example.com", + }, + }, + { + desc: "https without force host and sub domain should return a 301", + host: "https://www.powpow.example.com", + cfg: dynamic.Headers{ + SSLRedirect: true, + SSLForceHost: false, + SSLHost: "powpow.example.com", + }, + expected: expected{statusCode: http.StatusOK}, + }, + } + + next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + _, _ = rw.Write([]byte("OK")) + }) + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + mid := newSecure(next, test.cfg, "mymiddleware") + + req := httptest.NewRequest(http.MethodGet, test.host, nil) + + rw := httptest.NewRecorder() + + mid.ServeHTTP(rw, req) + + assert.Equal(t, test.expected.statusCode, rw.Result().StatusCode) + assert.Equal(t, test.expected.location, rw.Header().Get("Location")) + }) + } +} + +func Test_newSecure_modifyResponse(t *testing.T) { + testCases := []struct { + desc string + cfg dynamic.Headers + expected http.Header + }{ + { + desc: "FeaturePolicy", + cfg: dynamic.Headers{ + FeaturePolicy: "vibrate 'none';", + }, + expected: http.Header{"Feature-Policy": []string{"vibrate 'none';"}}, + }, + { + desc: "STSSeconds", + cfg: dynamic.Headers{ + STSSeconds: 1, + ForceSTSHeader: true, + }, + expected: http.Header{"Strict-Transport-Security": []string{"max-age=1"}}, + }, + { + desc: "STSSeconds and STSPreload", + cfg: dynamic.Headers{ + STSSeconds: 1, + ForceSTSHeader: true, + STSPreload: true, + }, + expected: http.Header{"Strict-Transport-Security": []string{"max-age=1; preload"}}, + }, + { + desc: "CustomFrameOptionsValue", + cfg: dynamic.Headers{ + CustomFrameOptionsValue: "foo", + }, + expected: http.Header{"X-Frame-Options": []string{"foo"}}, + }, + { + desc: "FrameDeny", + cfg: dynamic.Headers{ + FrameDeny: true, + }, + expected: http.Header{"X-Frame-Options": []string{"DENY"}}, + }, + { + desc: "ContentTypeNosniff", + cfg: dynamic.Headers{ + ContentTypeNosniff: true, + }, + expected: http.Header{"X-Content-Type-Options": []string{"nosniff"}}, + }, + } + + emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + secure := newSecure(emptyHandler, test.cfg, "mymiddleware") + + req := httptest.NewRequest(http.MethodGet, "/foo", nil) + + rw := httptest.NewRecorder() + + secure.ServeHTTP(rw, req) + + assert.Equal(t, test.expected, rw.Result().Header) + }) + } +} diff --git a/pkg/responsemodifiers/headers.go b/pkg/responsemodifiers/headers.go deleted file mode 100644 index 403785eea..000000000 --- a/pkg/responsemodifiers/headers.go +++ /dev/null @@ -1,53 +0,0 @@ -package responsemodifiers - -import ( - "net/http" - - "github.com/containous/traefik/v2/pkg/config/dynamic" - "github.com/containous/traefik/v2/pkg/middlewares/headers" - "github.com/unrolled/secure" -) - -func buildHeaders(hdrs *dynamic.Headers) func(*http.Response) error { - opt := secure.Options{ - BrowserXssFilter: hdrs.BrowserXSSFilter, - ContentTypeNosniff: hdrs.ContentTypeNosniff, - ForceSTSHeader: hdrs.ForceSTSHeader, - FrameDeny: hdrs.FrameDeny, - IsDevelopment: hdrs.IsDevelopment, - SSLRedirect: hdrs.SSLRedirect, - SSLForceHost: hdrs.SSLForceHost, - SSLTemporaryRedirect: hdrs.SSLTemporaryRedirect, - STSIncludeSubdomains: hdrs.STSIncludeSubdomains, - STSPreload: hdrs.STSPreload, - ContentSecurityPolicy: hdrs.ContentSecurityPolicy, - CustomBrowserXssValue: hdrs.CustomBrowserXSSValue, - CustomFrameOptionsValue: hdrs.CustomFrameOptionsValue, - PublicKey: hdrs.PublicKey, - ReferrerPolicy: hdrs.ReferrerPolicy, - SSLHost: hdrs.SSLHost, - AllowedHosts: hdrs.AllowedHosts, - HostsProxyHeaders: hdrs.HostsProxyHeaders, - SSLProxyHeaders: hdrs.SSLProxyHeaders, - STSSeconds: hdrs.STSSeconds, - FeaturePolicy: hdrs.FeaturePolicy, - } - - return func(resp *http.Response) error { - if hdrs.HasCustomHeadersDefined() || hdrs.HasCorsHeadersDefined() { - err := headers.NewHeader(nil, *hdrs).PostRequestModifyResponseHeaders(resp) - if err != nil { - return err - } - } - - if hdrs.HasSecureHeadersDefined() { - err := secure.New(opt).ModifyResponseHeaders(resp) - if err != nil { - return err - } - } - - return nil - } -} diff --git a/pkg/responsemodifiers/log.go b/pkg/responsemodifiers/log.go deleted file mode 100644 index 43eac2a1b..000000000 --- a/pkg/responsemodifiers/log.go +++ /dev/null @@ -1,13 +0,0 @@ -package responsemodifiers - -import ( - "context" - - "github.com/containous/traefik/v2/pkg/log" - "github.com/sirupsen/logrus" -) - -// getLogger creates a logger configured with the middleware fields. -func getLogger(ctx context.Context, middleware, middlewareType string) logrus.FieldLogger { - return log.FromContext(ctx).WithField(log.MiddlewareName, middleware).WithField(log.MiddlewareType, middlewareType) -} diff --git a/pkg/responsemodifiers/response_modifier.go b/pkg/responsemodifiers/response_modifier.go deleted file mode 100644 index b6bd351be..000000000 --- a/pkg/responsemodifiers/response_modifier.go +++ /dev/null @@ -1,68 +0,0 @@ -package responsemodifiers - -import ( - "context" - "net/http" - - "github.com/containous/traefik/v2/pkg/config/runtime" - "github.com/containous/traefik/v2/pkg/server/provider" -) - -// NewBuilder creates a builder. -func NewBuilder(configs map[string]*runtime.MiddlewareInfo) *Builder { - return &Builder{configs: configs} -} - -// Builder holds builder configuration. -type Builder struct { - configs map[string]*runtime.MiddlewareInfo -} - -// Build Builds the response modifier. -// It returns nil if there is no modifier to apply. -func (f *Builder) Build(ctx context.Context, names []string) func(*http.Response) error { - var modifiers []func(*http.Response) error - - for _, middleName := range names { - conf, ok := f.configs[middleName] - if !ok { - getLogger(ctx, middleName, "undefined").Debug("Middleware name not found in config (ResponseModifier)") - continue - } - if conf == nil || conf.Middleware == nil { - getLogger(ctx, middleName, "undefined").Error("Invalid Middleware configuration (ResponseModifier)") - continue - } - - if conf.Headers != nil { - getLogger(ctx, middleName, "Headers").Debug("Creating Middleware (ResponseModifier)") - - modifiers = append(modifiers, buildHeaders(conf.Headers)) - } else if conf.Chain != nil { - chainCtx := provider.AddInContext(ctx, middleName) - getLogger(chainCtx, middleName, "Chain").Debug("Creating Middleware (ResponseModifier)") - var qualifiedNames []string - for _, name := range conf.Chain.Middlewares { - qualifiedNames = append(qualifiedNames, provider.GetQualifiedName(chainCtx, name)) - } - - if rm := f.Build(ctx, qualifiedNames); rm != nil { - modifiers = append(modifiers, rm) - } - } - } - - if len(modifiers) > 0 { - return func(resp *http.Response) error { - for i := len(modifiers); i > 0; i-- { - err := modifiers[i-1](resp) - if err != nil { - return err - } - } - return nil - } - } - - return nil -} diff --git a/pkg/responsemodifiers/response_modifier_test.go b/pkg/responsemodifiers/response_modifier_test.go deleted file mode 100644 index 7b3822831..000000000 --- a/pkg/responsemodifiers/response_modifier_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package responsemodifiers - -import ( - "context" - "net/http" - "net/http/httptest" - "testing" - - "github.com/containous/traefik/v2/pkg/config/dynamic" - "github.com/containous/traefik/v2/pkg/config/runtime" - "github.com/containous/traefik/v2/pkg/middlewares/headers" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func stubResponse(_ map[string]*dynamic.Middleware) *http.Response { - return &http.Response{Header: make(http.Header)} -} - -func TestBuilderBuild(t *testing.T) { - testCases := []struct { - desc string - middlewares []string - // buildResponse is needed because secure use a private context key - buildResponse func(map[string]*dynamic.Middleware) *http.Response - conf map[string]*dynamic.Middleware - assertResponse func(*testing.T, *http.Response) - }{ - { - desc: "no configuration", - middlewares: []string{"foo", "bar"}, - buildResponse: stubResponse, - conf: map[string]*dynamic.Middleware{}, - assertResponse: func(t *testing.T, resp *http.Response) {}, - }, - { - desc: "one modifier", - middlewares: []string{"foo", "bar"}, - buildResponse: stubResponse, - conf: map[string]*dynamic.Middleware{ - "foo": { - Headers: &dynamic.Headers{ - CustomResponseHeaders: map[string]string{"X-Foo": "foo"}, - }, - }, - }, - assertResponse: func(t *testing.T, resp *http.Response) { - t.Helper() - - assert.Equal(t, "foo", resp.Header.Get("X-Foo")) - }, - }, - { - desc: "secure: one modifier", - middlewares: []string{"foo", "bar"}, - buildResponse: func(middlewares map[string]*dynamic.Middleware) *http.Response { - ctx := context.Background() - - var request *http.Request - next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - request = req - }) - - headerM := *middlewares["foo"].Headers - handler, err := headers.New(ctx, next, headerM, "secure") - require.NoError(t, err) - - handler.ServeHTTP(httptest.NewRecorder(), - httptest.NewRequest(http.MethodGet, "http://foo.com", nil)) - - return &http.Response{Header: make(http.Header), Request: request} - }, - conf: map[string]*dynamic.Middleware{ - "foo": { - Headers: &dynamic.Headers{ - ReferrerPolicy: "no-referrer", - }, - }, - "bar": { - Headers: &dynamic.Headers{ - CustomResponseHeaders: map[string]string{"X-Bar": "bar"}, - }, - }, - }, - assertResponse: func(t *testing.T, resp *http.Response) { - t.Helper() - - assert.Equal(t, "no-referrer", resp.Header.Get("Referrer-Policy")) - }, - }, - { - desc: "two modifiers", - middlewares: []string{"foo", "bar"}, - buildResponse: stubResponse, - conf: map[string]*dynamic.Middleware{ - "foo": { - Headers: &dynamic.Headers{ - CustomResponseHeaders: map[string]string{"X-Foo": "foo"}, - }, - }, - "bar": { - Headers: &dynamic.Headers{ - CustomResponseHeaders: map[string]string{"X-Bar": "bar"}, - }, - }, - }, - assertResponse: func(t *testing.T, resp *http.Response) { - t.Helper() - - assert.Equal(t, "foo", resp.Header.Get("X-Foo")) - assert.Equal(t, "bar", resp.Header.Get("X-Bar")) - }, - }, - { - desc: "modifier order", - middlewares: []string{"foo", "bar"}, - buildResponse: stubResponse, - conf: map[string]*dynamic.Middleware{ - "foo": { - Headers: &dynamic.Headers{ - CustomResponseHeaders: map[string]string{"X-Foo": "foo"}, - }, - }, - "bar": { - Headers: &dynamic.Headers{ - CustomResponseHeaders: map[string]string{"X-Foo": "bar"}, - }, - }, - }, - assertResponse: func(t *testing.T, resp *http.Response) { - t.Helper() - - assert.Equal(t, "foo", resp.Header.Get("X-Foo")) - }, - }, - { - desc: "chain", - middlewares: []string{"chain"}, - buildResponse: stubResponse, - conf: map[string]*dynamic.Middleware{ - "foo": { - Headers: &dynamic.Headers{ - CustomResponseHeaders: map[string]string{"X-Foo": "foo"}, - }, - }, - "bar": { - Headers: &dynamic.Headers{ - CustomResponseHeaders: map[string]string{"X-Foo": "bar"}, - }, - }, - "chain": { - Chain: &dynamic.Chain{ - Middlewares: []string{"foo", "bar"}, - }, - }, - }, - assertResponse: func(t *testing.T, resp *http.Response) { - t.Helper() - - assert.Equal(t, "foo", resp.Header.Get("X-Foo")) - }, - }, - { - desc: "nil middleware", - middlewares: []string{"foo"}, - buildResponse: stubResponse, - conf: map[string]*dynamic.Middleware{ - "foo": nil, - }, - assertResponse: func(t *testing.T, resp *http.Response) {}, - }, - - { - desc: "chain without headers", - middlewares: []string{"chain"}, - buildResponse: stubResponse, - conf: map[string]*dynamic.Middleware{ - "foo": {IPWhiteList: &dynamic.IPWhiteList{}}, - "chain": { - Chain: &dynamic.Chain{ - Middlewares: []string{"foo"}, - }, - }, - }, - assertResponse: func(t *testing.T, resp *http.Response) {}, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - rtConf := runtime.NewConfig(dynamic.Configuration{ - HTTP: &dynamic.HTTPConfiguration{ - Middlewares: test.conf, - }, - }) - builder := NewBuilder(rtConf.Middlewares) - - rm := builder.Build(context.Background(), test.middlewares) - if rm == nil { - return - } - - resp := test.buildResponse(test.conf) - - err := rm(resp) - require.NoError(t, err) - - test.assertResponse(t, resp) - }) - } -} diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go index c1398d831..2fc0ccc3d 100644 --- a/pkg/server/middleware/middlewares.go +++ b/pkg/server/middleware/middlewares.go @@ -44,7 +44,7 @@ type Builder struct { } type serviceBuilder interface { - BuildHTTP(ctx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) + BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error) } // NewBuilder creates a new Builder. diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index 3392e94ff..994e0a595 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -24,12 +24,8 @@ type middlewareBuilder interface { BuildChain(ctx context.Context, names []string) *alice.Chain } -type responseModifierBuilder interface { - Build(ctx context.Context, names []string) func(*http.Response) error -} - type serviceManager interface { - BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) + BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) LaunchHealthCheck() } @@ -39,22 +35,15 @@ type Manager struct { serviceManager serviceManager middlewaresBuilder middlewareBuilder chainBuilder *middleware.ChainBuilder - modifierBuilder responseModifierBuilder conf *runtime.Configuration } // NewManager Creates a new Manager. -func NewManager(conf *runtime.Configuration, - serviceManager serviceManager, - middlewaresBuilder middlewareBuilder, - modifierBuilder responseModifierBuilder, - chainBuilder *middleware.ChainBuilder, -) *Manager { +func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder) *Manager { return &Manager{ routerHandlers: make(map[string]http.Handler), serviceManager: serviceManager, middlewaresBuilder: middlewaresBuilder, - modifierBuilder: modifierBuilder, chainBuilder: chainBuilder, conf: conf, } @@ -176,13 +165,12 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn qualifiedNames = append(qualifiedNames, provider.GetQualifiedName(ctx, name)) } router.Middlewares = qualifiedNames - rm := m.modifierBuilder.Build(ctx, qualifiedNames) if router.Service == "" { return nil, errors.New("the service is missing on the router") } - sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service, rm) + sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service) if err != nil { return nil, err } diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index 113335086..8febc0e67 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -13,7 +13,6 @@ import ( "github.com/containous/traefik/v2/pkg/config/static" "github.com/containous/traefik/v2/pkg/middlewares/accesslog" "github.com/containous/traefik/v2/pkg/middlewares/requestdecorator" - "github.com/containous/traefik/v2/pkg/responsemodifiers" "github.com/containous/traefik/v2/pkg/server/middleware" "github.com/containous/traefik/v2/pkg/server/service" "github.com/containous/traefik/v2/pkg/testhelpers" @@ -290,10 +289,9 @@ func TestRouterManager_Get(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) - responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares) chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil) - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) @@ -395,10 +393,9 @@ func TestAccessLog(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) - responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares) chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil) - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) @@ -683,10 +680,9 @@ func TestRuntimeConfiguration(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) - responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{}) chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil) - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) @@ -765,10 +761,9 @@ func TestProviderOnMiddlewares(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) - responseModifierFactory := responsemodifiers.NewBuilder(map[string]*runtime.MiddlewareInfo{}) chainBuilder := middleware.NewChainBuilder(staticCfg, nil, nil) - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) @@ -826,10 +821,9 @@ func BenchmarkRouterServe(b *testing.B) { serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) - responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares) chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil) - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, chainBuilder) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder) handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false) @@ -871,7 +865,7 @@ func BenchmarkService(b *testing.B) { w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) - handler, _ := serviceManager.BuildHTTP(context.Background(), "foo-service", nil) + handler, _ := serviceManager.BuildHTTP(context.Background(), "foo-service") b.ReportAllocs() for i := 0; i < b.N; i++ { handler.ServeHTTP(w, req) diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go index 1a2dfdc1c..00d17b46c 100644 --- a/pkg/server/routerfactory.go +++ b/pkg/server/routerfactory.go @@ -7,7 +7,6 @@ import ( "github.com/containous/traefik/v2/pkg/config/runtime" "github.com/containous/traefik/v2/pkg/config/static" "github.com/containous/traefik/v2/pkg/log" - "github.com/containous/traefik/v2/pkg/responsemodifiers" "github.com/containous/traefik/v2/pkg/server/middleware" "github.com/containous/traefik/v2/pkg/server/router" routertcp "github.com/containous/traefik/v2/pkg/server/router/tcp" @@ -67,9 +66,8 @@ func (f *RouterFactory) CreateRouters(conf dynamic.Configuration) (map[string]*t serviceManager := f.managerFactory.Build(rtConf) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager) - responseModifierFactory := responsemodifiers.NewBuilder(rtConf.Middlewares) - routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, responseModifierFactory, f.chainBuilder) + routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder) handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false) handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true) diff --git a/pkg/server/service/internalhandler.go b/pkg/server/service/internalhandler.go index 6ff7e0a1e..f50e37065 100644 --- a/pkg/server/service/internalhandler.go +++ b/pkg/server/service/internalhandler.go @@ -8,11 +8,10 @@ import ( "strings" "github.com/containous/traefik/v2/pkg/config/runtime" - "github.com/containous/traefik/v2/pkg/log" ) type serviceManager interface { - BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) + BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) LaunchHealthCheck() } @@ -43,87 +42,18 @@ func NewInternalHandlers(api func(configuration *runtime.Configuration) http.Han } } -type responseModifier struct { - r *http.Request - w http.ResponseWriter - - headersSent bool // whether headers have already been sent - code int // status code, must default to 200 - - modifier func(*http.Response) error // can be nil - modified bool // whether modifier has already been called for the current request - modifierErr error // returned by modifier call -} - -// modifier can be nil. -func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) *responseModifier { - return &responseModifier{ - r: r, - w: w, - modifier: modifier, - code: http.StatusOK, - } -} - -func (w *responseModifier) WriteHeader(code int) { - if w.headersSent { - return - } - defer func() { - w.code = code - w.headersSent = true - }() - - if w.modifier == nil || w.modified { - w.w.WriteHeader(code) - return - } - - resp := http.Response{ - Header: w.w.Header(), - Request: w.r, - } - - if err := w.modifier(&resp); err != nil { - w.modifierErr = err - // we are propagating when we are called in Write, but we're logging anyway, - // because we could be called from another place which does not take care of - // checking w.modifierErr. - log.Errorf("Error when applying response modifier: %v", err) - w.w.WriteHeader(http.StatusInternalServerError) - return - } - - w.modified = true - w.w.WriteHeader(code) -} - -func (w *responseModifier) Header() http.Header { - return w.w.Header() -} - -func (w *responseModifier) Write(b []byte) (int, error) { - w.WriteHeader(w.code) - if w.modifierErr != nil { - return 0, w.modifierErr - } - - return w.w.Write(b) -} - // BuildHTTP builds an HTTP handler. -func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string, respModifier func(*http.Response) error) (http.Handler, error) { +func (m *InternalHandlers) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) { if !strings.HasSuffix(serviceName, "@internal") { - return m.serviceManager.BuildHTTP(rootCtx, serviceName, respModifier) + return m.serviceManager.BuildHTTP(rootCtx, serviceName) } internalHandler, err := m.get(serviceName) if err != nil { return nil, err } - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - internalHandler.ServeHTTP(newResponseModifier(w, r, respModifier), r) - }), nil + + return internalHandler, nil } func (m *InternalHandlers) get(serviceName string) (http.Handler, error) { diff --git a/pkg/server/service/proxy.go b/pkg/server/service/proxy.go index 21a67329f..5e8c85407 100644 --- a/pkg/server/service/proxy.go +++ b/pkg/server/service/proxy.go @@ -21,7 +21,7 @@ const StatusClientClosedRequest = 499 // StatusClientClosedRequestText non-standard HTTP status for client disconnection. const StatusClientClosedRequestText = "Client Closed Request" -func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, defaultRoundTripper http.RoundTripper, bufferPool httputil.BufferPool, responseModifier func(*http.Response) error) (http.Handler, error) { +func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, defaultRoundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) { var flushInterval types.Duration if responseForwarding != nil { err := flushInterval.Set(responseForwarding.FlushInterval) @@ -76,10 +76,9 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar delete(outReq.Header, "Sec-Websocket-Protocol") delete(outReq.Header, "Sec-Websocket-Version") }, - Transport: defaultRoundTripper, - FlushInterval: time.Duration(flushInterval), - ModifyResponse: responseModifier, - BufferPool: bufferPool, + Transport: defaultRoundTripper, + FlushInterval: time.Duration(flushInterval), + BufferPool: bufferPool, ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) { statusCode := http.StatusInternalServerError diff --git a/pkg/server/service/proxy_test.go b/pkg/server/service/proxy_test.go index 38966b509..5318a0a67 100644 --- a/pkg/server/service/proxy_test.go +++ b/pkg/server/service/proxy_test.go @@ -28,7 +28,7 @@ func BenchmarkProxy(b *testing.B) { req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) pool := newBufferPool() - handler, _ := buildProxy(Bool(false), nil, &staticTransport{res}, pool, nil) + handler, _ := buildProxy(Bool(false), nil, &staticTransport{res}, pool) b.ReportAllocs() for i := 0; i < b.N; i++ { diff --git a/pkg/server/service/proxy_websocket_test.go b/pkg/server/service/proxy_websocket_test.go index d309e5379..fe169e1c5 100644 --- a/pkg/server/service/proxy_websocket_test.go +++ b/pkg/server/service/proxy_websocket_test.go @@ -20,7 +20,7 @@ import ( func Bool(v bool) *bool { return &v } func TestWebSocketTCPClose(t *testing.T) { - f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) errChan := make(chan error, 1) @@ -59,7 +59,7 @@ func TestWebSocketTCPClose(t *testing.T) { } func TestWebSocketPingPong(t *testing.T) { - f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) @@ -125,7 +125,7 @@ func TestWebSocketPingPong(t *testing.T) { } func TestWebSocketEcho(t *testing.T) { - f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) mux := http.NewServeMux() @@ -191,7 +191,7 @@ func TestWebSocketPassHost(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - f, err := buildProxy(Bool(test.passHost), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(test.passHost), nil, http.DefaultTransport, nil) require.NoError(t, err) @@ -250,7 +250,7 @@ func TestWebSocketPassHost(t *testing.T) { } func TestWebSocketServerWithoutCheckOrigin(t *testing.T) { - f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) upgrader := gorillawebsocket.Upgrader{CheckOrigin: func(r *http.Request) bool { @@ -291,7 +291,7 @@ func TestWebSocketServerWithoutCheckOrigin(t *testing.T) { } func TestWebSocketRequestWithOrigin(t *testing.T) { - f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) upgrader := gorillawebsocket.Upgrader{} @@ -337,7 +337,7 @@ func TestWebSocketRequestWithOrigin(t *testing.T) { } func TestWebSocketRequestWithQueryParams(t *testing.T) { - f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) upgrader := gorillawebsocket.Upgrader{} @@ -377,7 +377,7 @@ func TestWebSocketRequestWithQueryParams(t *testing.T) { } func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) { - f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) mux := http.NewServeMux() @@ -409,7 +409,7 @@ func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) { } func TestWebSocketRequestWithEncodedChar(t *testing.T) { - f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) upgrader := gorillawebsocket.Upgrader{} @@ -449,7 +449,7 @@ func TestWebSocketRequestWithEncodedChar(t *testing.T) { } func TestWebSocketUpgradeFailed(t *testing.T) { - f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) mux := http.NewServeMux() @@ -499,7 +499,7 @@ func TestWebSocketUpgradeFailed(t *testing.T) { } func TestForwardsWebsocketTraffic(t *testing.T) { - f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + f, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) mux := http.NewServeMux() @@ -555,7 +555,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) { srv := createTLSWebsocketServer() defer srv.Close() - forwarderWithoutTLSConfig, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + forwarderWithoutTLSConfig, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) proxyWithoutTLSConfig := createProxyWithForwarder(t, forwarderWithoutTLSConfig, srv.URL) @@ -574,7 +574,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) { transport := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } - forwarderWithTLSConfig, err := buildProxy(Bool(true), nil, transport, nil, nil) + forwarderWithTLSConfig, err := buildProxy(Bool(true), nil, transport, nil) require.NoError(t, err) proxyWithTLSConfig := createProxyWithForwarder(t, forwarderWithTLSConfig, srv.URL) @@ -593,7 +593,7 @@ func TestWebSocketTransferTLSConfig(t *testing.T) { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - forwarderWithTLSConfigFromDefaultTransport, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil, nil) + forwarderWithTLSConfigFromDefaultTransport, err := buildProxy(Bool(true), nil, http.DefaultTransport, nil) require.NoError(t, err) proxyWithTLSConfigFromDefaultTransport := createProxyWithForwarder(t, forwarderWithTLSConfigFromDefaultTransport, srv.URL) diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 2c641cb0e..37f02c4db 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -62,7 +62,7 @@ type Manager struct { } // BuildHTTP Creates a http.Handler for a service configuration. -func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, responseModifier func(*http.Response) error) (http.Handler, error) { +func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string) (http.Handler, error) { ctx := log.With(rootCtx, log.Str(log.ServiceName, serviceName)) serviceName = provider.GetQualifiedName(ctx, serviceName) @@ -91,21 +91,21 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons switch { case conf.LoadBalancer != nil: var err error - lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer, responseModifier) + lb, err = m.getLoadBalancerServiceHandler(ctx, serviceName, conf.LoadBalancer) if err != nil { conf.AddError(err, true) return nil, err } case conf.Weighted != nil: var err error - lb, err = m.getWRRServiceHandler(ctx, serviceName, conf.Weighted, responseModifier) + lb, err = m.getWRRServiceHandler(ctx, serviceName, conf.Weighted) if err != nil { conf.AddError(err, true) return nil, err } case conf.Mirroring != nil: var err error - lb, err = m.getMirrorServiceHandler(ctx, conf.Mirroring, responseModifier) + lb, err = m.getMirrorServiceHandler(ctx, conf.Mirroring) if err != nil { conf.AddError(err, true) return nil, err @@ -119,8 +119,8 @@ func (m *Manager) BuildHTTP(rootCtx context.Context, serviceName string, respons return lb, nil } -func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.Mirroring, responseModifier func(*http.Response) error) (http.Handler, error) { - serviceHandler, err := m.BuildHTTP(ctx, config.Service, responseModifier) +func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.Mirroring) (http.Handler, error) { + serviceHandler, err := m.BuildHTTP(ctx, config.Service) if err != nil { return nil, err } @@ -131,7 +131,7 @@ func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.M } handler := mirror.New(serviceHandler, m.routinePool, maxBodySize) for _, mirrorConfig := range config.Mirrors { - mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name, responseModifier) + mirrorHandler, err := m.BuildHTTP(ctx, mirrorConfig.Name) if err != nil { return nil, err } @@ -144,7 +144,7 @@ func (m *Manager) getMirrorServiceHandler(ctx context.Context, config *dynamic.M return handler, nil } -func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin, responseModifier func(*http.Response) error) (http.Handler, error) { +func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, config *dynamic.WeightedRoundRobin) (http.Handler, error) { // TODO Handle accesslog and metrics with multiple service name if config.Sticky != nil && config.Sticky.Cookie != nil { config.Sticky.Cookie.Name = cookie.GetName(config.Sticky.Cookie.Name, serviceName) @@ -152,7 +152,7 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, balancer := wrr.New(config.Sticky) for _, service := range config.Services { - serviceHandler, err := m.BuildHTTP(ctx, service.Name, responseModifier) + serviceHandler, err := m.BuildHTTP(ctx, service.Name) if err != nil { return nil, err } @@ -162,18 +162,13 @@ func (m *Manager) getWRRServiceHandler(ctx context.Context, serviceName string, return balancer, nil } -func (m *Manager) getLoadBalancerServiceHandler( - ctx context.Context, - serviceName string, - service *dynamic.ServersLoadBalancer, - responseModifier func(*http.Response) error, -) (http.Handler, error) { +func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName string, service *dynamic.ServersLoadBalancer) (http.Handler, error) { if service.PassHostHeader == nil { defaultPassHostHeader := true service.PassHostHeader = &defaultPassHostHeader } - fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, m.defaultRoundTripper, m.bufferPool, responseModifier) + fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, m.defaultRoundTripper, m.bufferPool) if err != nil { return nil, err } diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 522f5308b..23b5f6b35 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -259,7 +259,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) { for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { - handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, test.service, test.responseModifier) + handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, test.service) assert.NoError(t, err) assert.NotNil(t, handler) @@ -339,7 +339,7 @@ func TestManager_Build(t *testing.T) { ctx = provider.AddInContext(ctx, "foobar@"+test.providerName) } - _, err := manager.BuildHTTP(ctx, test.serviceName, nil) + _, err := manager.BuildHTTP(ctx, test.serviceName) require.NoError(t, err) }) } @@ -357,7 +357,7 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) { manager := NewManager(services, http.DefaultTransport, nil, nil) - _, err := manager.BuildHTTP(context.Background(), "test@file", nil) + _, err := manager.BuildHTTP(context.Background(), "test@file") assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead") } From f3090a452aa72a75afb453b7976e4887c3a8e1e5 Mon Sep 17 00:00:00 2001 From: Romain Date: Wed, 2 Sep 2020 17:16:04 +0200 Subject: [PATCH 3/6] doc: specify HostSNI rule removal only for HTTP routers --- docs/content/migration/v2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 654c875e7..053021a2b 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -312,5 +312,5 @@ Since `v2.2.5` this global option has been removed, and you should not use it an ### HostSNI rule matcher removal -In `v2.2.2` we introduced a new rule matcher (`HostSNI`) which was allowing to match the Server Name Indication at the router level. -Since `v2.2.5` this rule has been removed, and you should not use it anymore. +In `v2.2.2` we introduced a new rule matcher (`HostSNI`) for HTTP routers which was allowing to match the Server Name Indication at the router level. +Since `v2.2.5` this rule has been removed for HTTP routers, and you should not use it anymore. From 41aa2672cdcde7e66b59133f0bc399cfc6d7d572 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Fri, 4 Sep 2020 10:52:03 +0200 Subject: [PATCH 4/6] Update go-acme/lego to v4.0.1 --- docs/content/https/acme.md | 40 ++++- docs/content/https/ref-acme.toml | 10 ++ docs/content/https/ref-acme.txt | 10 ++ docs/content/https/ref-acme.yaml | 10 ++ .../reference/static-configuration/cli-ref.md | 3 + .../reference/static-configuration/env-ref.md | 3 + .../reference/static-configuration/file.toml | 2 + .../reference/static-configuration/file.yaml | 2 + go.mod | 14 +- go.sum | 138 ++++++++++-------- pkg/config/static/static_config.go | 2 +- pkg/provider/acme/account.go | 4 +- pkg/provider/acme/challenge_http.go | 4 +- pkg/provider/acme/challenge_tls.go | 4 +- pkg/provider/acme/provider.go | 47 +++--- pkg/provider/acme/provider_test.go | 2 +- pkg/tls/tlsmanager.go | 2 +- 17 files changed, 197 insertions(+), 100 deletions(-) diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index 4cca2ec62..f6f7b0866 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -308,9 +308,10 @@ For complete details, refer to your provider's _Additional configuration_ link. | [Dyn](https://dyn.com) | `dyn` | `DYN_CUSTOMER_NAME`, `DYN_USER_NAME`, `DYN_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/dyn) | | [Dynu](https://www.dynu.com) | `dynu` | `DYNU_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/dynu) | | [EasyDNS](https://easydns.com/) | `easydns` | `EASYDNS_TOKEN`, `EASYDNS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/easydns) | +| [EdgeDNS](https://www.akamai.com/) | `edgedns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) | | External Program | `exec` | `EXEC_PATH` | [Additional configuration](https://go-acme.github.io/lego/dns/exec) | | [Exoscale](https://www.exoscale.com) | `exoscale` | `EXOSCALE_API_KEY`, `EXOSCALE_API_SECRET`, `EXOSCALE_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/exoscale) | -| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/fastdns) | +| [Fast DNS](https://www.akamai.com/) | `fastdns` | `AKAMAI_CLIENT_TOKEN`, `AKAMAI_CLIENT_SECRET`, `AKAMAI_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/edgedns) | | [Gandi](https://www.gandi.net) | `gandi` | `GANDI_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandi) | | [Gandi v5](http://doc.livedns.gandi.net) | `gandiv5` | `GANDIV5_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/gandiv5) | | [Glesys](https://glesys.com/) | `glesys` | `GLESYS_API_USER`, `GLESYS_API_KEY`, `GLESYS_DOMAIN` | [Additional configuration](https://go-acme.github.io/lego/dns/glesys) | @@ -319,12 +320,12 @@ For complete details, refer to your provider's _Additional configuration_ link. | [Hetzner](https://hetzner.com) | `hetzner` | `HETZNER_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/hetzner) | | [hosting.de](https://www.hosting.de) | `hostingde` | `HOSTINGDE_API_KEY`, `HOSTINGDE_ZONE_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/hostingde) | | HTTP request | `httpreq` | `HTTPREQ_ENDPOINT`, `HTTPREQ_MODE`, `HTTPREQ_USERNAME`, `HTTPREQ_PASSWORD` [^1] | [Additional configuration](https://go-acme.github.io/lego/dns/httpreq) | +| [HyperOne](https://www.hyperone.com) | `hyperone` | `HYPERONE_PASSPORT_LOCATION`, `HYPERONE_LOCATION_ID` | [Additional configuration](https://go-acme.github.io/lego/dns/hyperone) | | [IIJ](https://www.iij.ad.jp/) | `iij` | `IIJ_API_ACCESS_KEY`, `IIJ_API_SECRET_KEY`, `IIJ_DO_SERVICE_CODE` | [Additional configuration](https://go-acme.github.io/lego/dns/iij) | | [INWX](https://www.inwx.de/en) | `inwx` | `INWX_USERNAME`, `INWX_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/inwx) | | [Joker.com](https://joker.com) | `joker` | `JOKER_API_KEY` or `JOKER_USERNAME`, `JOKER_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/joker) | | [Lightsail](https://aws.amazon.com/lightsail/) | `lightsail` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `DNS_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/lightsail) | -| [Linode](https://www.linode.com) | `linode` | `LINODE_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) | -| [Linode v4](https://www.linode.com) | `linodev4` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linodev4) | +| [Linode v4](https://www.linode.com) | `linode` | `LINODE_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/linode) | | [Liquid Web](https://www.liquidweb.com/) | `liquidweb` | `LIQUID_WEB_PASSWORD`, `LIQUID_WEB_USERNAME`, `LIQUID_WEB_ZONE` | [Additional configuration](https://go-acme.github.io/lego/dns/liquidweb) | | [LuaDNS](https://luadns.com) | `luadns` | `LUADNS_API_USERNAME`, `LUADNS_API_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/luadns) | | manual | `manual` | none, but you need to run Traefik interactively [^4], turn on debug log to see instructions and press Enter. | | @@ -336,7 +337,7 @@ For complete details, refer to your provider's _Additional configuration_ link. | [Netcup](https://www.netcup.eu/) | `netcup` | `NETCUP_CUSTOMER_NUMBER`, `NETCUP_API_KEY`, `NETCUP_API_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/netcup) | | [Netlify](https://www.netlify.com) | `netlify` | `NETLIFY_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/netlify) | | [NIFCloud](https://cloud.nifty.com/service/dns.htm) | `nifcloud` | `NIFCLOUD_ACCESS_KEY_ID`, `NIFCLOUD_SECRET_ACCESS_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/nifcloud) | -| [Ns1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ns1) | +| [NS1](https://ns1.com/) | `ns1` | `NS1_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ns1) | | [Open Telekom Cloud](https://cloud.telekom.de) | `otc` | `OTC_DOMAIN_NAME`, `OTC_USER_NAME`, `OTC_PASSWORD`, `OTC_PROJECT_NAME`, `OTC_IDENTITY_ENDPOINT` | [Additional configuration](https://go-acme.github.io/lego/dns/otc) | | [OVH](https://www.ovh.com) | `ovh` | `OVH_ENDPOINT`, `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, `OVH_CONSUMER_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ovh) | | [Openstack Designate](https://docs.openstack.org/designate) | `designate` | `OS_AUTH_URL`, `OS_USERNAME`, `OS_PASSWORD`, `OS_TENANT_NAME`, `OS_REGION_NAME` | [Additional configuration](https://go-acme.github.io/lego/dns/designate) | @@ -484,6 +485,37 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik !!! warning For concurrency reasons, this file cannot be shared across multiple instances of Traefik. +### `preferredChain` + +_Optional, Default=""_ + +Preferred chain to use. + +If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. +If no match, the default offered chain will be used. + +```toml tab="File (TOML)" +[certificatesResolvers.myresolver.acme] + # ... + preferredChain = "ISRG Root X1" + # ... +``` + +```yaml tab="File (YAML)" +certificatesResolvers: + myresolver: + acme: + # ... + preferredChain: 'ISRG Root X1' + # ... +``` + +```bash tab="CLI" +# ... +--certificatesresolvers.myresolver.acme.preferredChain="ISRG Root X1" +# ... +``` + ## Fallback If Let's Encrypt is not reachable, the following certificates will apply: diff --git a/docs/content/https/ref-acme.toml b/docs/content/https/ref-acme.toml index e4dcf90db..39cd22689 100644 --- a/docs/content/https/ref-acme.toml +++ b/docs/content/https/ref-acme.toml @@ -22,6 +22,16 @@ # # caServer = "https://acme-staging-v02.api.letsencrypt.org/directory" + # Preferred chain to use. + # + # If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. + # If no match, the default offered chain will be used. + # + # Optional + # Default: "" + # + # preferredChain = "ISRG Root X1" + # KeyType to use. # # Optional diff --git a/docs/content/https/ref-acme.txt b/docs/content/https/ref-acme.txt index 27f8942f2..7144e7932 100644 --- a/docs/content/https/ref-acme.txt +++ b/docs/content/https/ref-acme.txt @@ -21,6 +21,16 @@ # --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory +# Preferred chain to use. +# +# If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. +# If no match, the default offered chain will be used. +# +# Optional +# Default: "" +# +--certificatesresolvers.myresolver.acme.preferredchain="ISRG Root X1" + # KeyType to use. # # Optional diff --git a/docs/content/https/ref-acme.yaml b/docs/content/https/ref-acme.yaml index d4b7fd7a4..43802083a 100644 --- a/docs/content/https/ref-acme.yaml +++ b/docs/content/https/ref-acme.yaml @@ -24,6 +24,16 @@ certificatesResolvers: # # caServer: "https://acme-staging-v02.api.letsencrypt.org/directory" + # Preferred chain to use. + # + # If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. + # If no match, the default offered chain will be used. + # + # Optional + # Default: "" + # + # preferredChain: 'ISRG Root X1' + # KeyType to use. # # Optional diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index aed610b9a..2055b36eb 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -81,6 +81,9 @@ HTTP challenge EntryPoint `--certificatesresolvers..acme.keytype`: KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```) +`--certificatesresolvers..acme.preferredchain`: +Preferred chain to use. + `--certificatesresolvers..acme.storage`: Storage to use. (Default: ```acme.json```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 2228bec10..59a6fd266 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -81,6 +81,9 @@ HTTP challenge EntryPoint `TRAEFIK_CERTIFICATESRESOLVERS__ACME_KEYTYPE`: KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. (Default: ```RSA4096```) +`TRAEFIK_CERTIFICATESRESOLVERS__ACME_PREFERREDCHAIN`: +Preferred chain to use. + `TRAEFIK_CERTIFICATESRESOLVERS__ACME_STORAGE`: Storage to use. (Default: ```acme.json```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 2e689e038..45a049e88 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -314,6 +314,7 @@ [certificatesResolvers.CertificateResolver0.acme] email = "foobar" caServer = "foobar" + preferredChain = "foobar" storage = "foobar" keyType = "foobar" [certificatesResolvers.CertificateResolver0.acme.dnsChallenge] @@ -328,6 +329,7 @@ [certificatesResolvers.CertificateResolver1.acme] email = "foobar" caServer = "foobar" + preferredChain = "foobar" storage = "foobar" keyType = "foobar" [certificatesResolvers.CertificateResolver1.acme.dnsChallenge] diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index f8eea1ef5..21282f93a 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -328,6 +328,7 @@ certificatesResolvers: acme: email: foobar caServer: foobar + preferredChain: foobar storage: foobar keyType: foobar dnsChallenge: @@ -344,6 +345,7 @@ certificatesResolvers: acme: email: foobar caServer: foobar + preferredChain: foobar storage: foobar keyType: foobar dnsChallenge: diff --git a/go.mod b/go.mod index c49473b02..527d28b90 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000 github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd github.com/c0va23/go-proxyprotocol v0.9.1 - github.com/cenkalti/backoff/v4 v4.0.0 + github.com/cenkalti/backoff/v4 v4.0.2 github.com/containerd/containerd v1.3.2 // indirect github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf @@ -36,7 +36,7 @@ require ( github.com/fatih/structs v1.1.0 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2 - github.com/go-acme/lego/v3 v3.8.0 + github.com/go-acme/lego/v4 v4.0.1 github.com/go-check/check v0.0.0-00010101000000-000000000000 github.com/go-kit/kit v0.9.0 github.com/gogo/protobuf v1.3.0 // indirect @@ -55,7 +55,7 @@ require ( github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591 github.com/magiconair/properties v1.8.1 // indirect github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f - github.com/miekg/dns v1.1.27 + github.com/miekg/dns v1.1.31 github.com/mitchellh/copystructure v1.0.0 github.com/mitchellh/hashstructure v1.0.0 github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect @@ -72,7 +72,7 @@ require ( github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac github.com/sirupsen/logrus v1.4.2 - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.6.1 github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 github.com/tinylib/msgp v1.0.2 // indirect github.com/uber/jaeger-client-go v2.22.1+incompatible @@ -84,13 +84,13 @@ require ( github.com/vulcand/predicate v1.1.0 go.elastic.co/apm v1.7.0 go.elastic.co/apm/module/apmot v1.7.0 - golang.org/x/net v0.0.0-20200301022130-244492dfa37a - golang.org/x/time v0.0.0-20191024005414-555d28b269f0 + golang.org/x/net v0.0.0-20200822124328-c89045814202 + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e google.golang.org/grpc v1.27.1 gopkg.in/DataDog/dd-trace-go.v1 v1.19.0 gopkg.in/fsnotify.v1 v1.4.7 gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect - gopkg.in/yaml.v2 v2.2.8 + gopkg.in/yaml.v2 v2.3.0 k8s.io/api v0.17.3 k8s.io/apimachinery v0.17.3 k8s.io/client-go v0.17.3 diff --git a/go.sum b/go.sum index 26c8e4503..02dfa771e 100644 --- a/go.sum +++ b/go.sum @@ -101,12 +101,12 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd h1:UlQRt3CZdeD+WfDamDtdDDOu84CYbGIh9/B28TgzCZk= github.com/abronan/valkeyrie v0.0.0-20200127174252-ef4277a138cd/go.mod h1:2RUNONRAQ8bS1QcVJF3dYO/faiEro6NAAIQ6CqBkpD0= -github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8 h1:6rJvj+NXjjauunLeS7uGy891F1cuAwsWKa9iGzTjz1s= -github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.8/go.mod h1:aVvklgKsPENRkl29bNwrHISa1F+YLGTHArMxZMBqWM8= +github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.18 h1:KyEv96ncdgOIJRTKMcWlIqM0umf8X3LQP6oOyg0hNsM= +github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.18/go.mod h1:L+HB2uBoDgi3+r1pJEJcbGwyyHhd2QXaGsKLbDwtm8Q= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.112 h1:E273ePcLllLIBGg5BHr3T0Fp1BJTvUyh5Y57ziSy81w= -github.com/aliyun/alibaba-cloud-sdk-go v1.61.112/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.458 h1:UdFGeD4Eg6gZFQ7tLWdguNLpBTevJwBa97S0YunGy1k= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.458/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA= github.com/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -130,8 +130,8 @@ github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8 github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/c0va23/go-proxyprotocol v0.9.1 h1:5BCkp0fDJOhzzH1lhjUgHhmZz9VvRMMif1U2D31hb34= github.com/c0va23/go-proxyprotocol v0.9.1/go.mod h1:TNjUV+llvk8TvWJxlPYAeAYZgSzT/iicNr3nWBWX320= -github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU= -github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= +github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs= +github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -141,8 +141,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.10.2 h1:VBodKICVPnwmDxstcW3biKcDSpFIfS/RELUXsZSBYK4= -github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY= +github.com/cloudflare/cloudflare-go v0.13.2 h1:bhMGoNhAg21DuqJjU9jQepRRft6vYfo6pejT3NN4V6A= +github.com/cloudflare/cloudflare-go v0.13.2/go.mod h1:27kfc1apuifUmJhp069y0+hwlKDg4bd8LWlu7oKeZvM= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY= @@ -183,8 +183,8 @@ github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pq github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpu/goacmedns v0.0.2 h1:hYAgjnPu7HogTgb8trqQouR/RrBgXq1TPBgmxbK9eRA= -github.com/cpu/goacmedns v0.0.2/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ= +github.com/cpu/goacmedns v0.0.3 h1:QOeMpIEsIdm1LSASSswjaTf8CXmzcrgy5OeCfHjppA4= +github.com/cpu/goacmedns v0.0.3/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -194,10 +194,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= -github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2 h1:G9/PqfhOrt8JXnw0DGTfVoOkKHDhOlEZqhE/cu+NvQM= -github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/dnsimple/dnsimple-go v0.60.0 h1:N+q+ML1CZGf+5r4udu9Opy7WJNtOaFT9aM86Af9gLhk= -github.com/dnsimple/dnsimple-go v0.60.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg= +github.com/dnsimple/dnsimple-go v0.63.0 h1:0doY8VW/ckRIMTmOw4E1vwqo+bhtjDzvh1pU2ZteFGA= +github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg= github.com/docker/cli v0.0.0-20200221155518-740919cc7fc0 h1:hlGHcYGaaHs/yffSubcUKlp8TyV1v7qhcZZ5nGNQ2Fw= github.com/docker/cli v0.0.0-20200221155518-740919cc7fc0/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= @@ -245,8 +243,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/exoscale/egoscale v0.18.1 h1:1FNZVk8jHUx0AvWhOZxLEDNlacTU0chMXUUNkm9EZaI= -github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE= +github.com/exoscale/egoscale v0.23.0 h1:hoUDzrO8yNoobNdnrRvlRFjfg3Ng0vQTrv6bXRJu6z0= +github.com/exoscale/egoscale v0.23.0/go.mod h1:hRo78jkjkCDKpivQdRBEpNYF5+cVpCJCPDg2/r45KaY= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= @@ -261,8 +259,8 @@ github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2/go.mod h1:GLy github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-acme/lego/v3 v3.8.0 h1:9OOEn54eZvEPRRdM7xiC5f7EBW0MlEeChr+kzlIhdN8= -github.com/go-acme/lego/v3 v3.8.0/go.mod h1:kYiHYgSRzb1l2NQPWvWvkVG5etNCusGFsZc2MTak3m0= +github.com/go-acme/lego/v4 v4.0.1 h1:vPwbTYfw5+fOaON9rWCN43iNrPw5cdJBhNMnA8oxBTM= +github.com/go-acme/lego/v4 v4.0.1/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= @@ -291,6 +289,8 @@ github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dp github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 h1:JVrqSeQfdhYRFk24TvhTZWU0q8lfCojxZQFi3Ou7+uY= +github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -370,8 +370,11 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC github.com/googleapis/gnostic v0.1.0 h1:rVsPeBmXbYv4If/cumu1AzZPwV58q433hvONV1UEZoI= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gophercloud/gophercloud v0.3.0 h1:6sjpKIpVwRIIwmcEGp+WwNovNsem+c+2vm6oxshRpL8= -github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gophercloud/gophercloud v0.6.1-0.20191122030953-d8ac278c1c9d/go.mod h1:ozGNgr9KYOVATV5jsgHl/ceCDXGuguqOZAzoQ/2vcNM= +github.com/gophercloud/gophercloud v0.7.0 h1:vhmQQEM2SbnGCg2/3EzQnQZ3V7+UCGy9s8exQCprNYg= +github.com/gophercloud/gophercloud v0.7.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss= +github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c h1:iawx2ojEQA7c+GmkaVO5sN+k8YONibXyDO8RlsC+1bs= +github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= @@ -417,8 +420,8 @@ github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1: github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM= -github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo= +github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= @@ -503,10 +506,10 @@ github.com/libkermit/docker v0.0.0-20171122101128-e6674d32b807 h1:/7J1WDQd6Xn1Pr github.com/libkermit/docker v0.0.0-20171122101128-e6674d32b807/go.mod h1:std11u6pTaNwryy0Hy1dTQNdHKka1jNpflEieKtv5VE= github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591 h1:+zkZyvOyHZZUnITx0oJxAG/2+YLOjmy8YMMa1aWyrs4= github.com/libkermit/docker-check v0.0.0-20171122104347-1113af38e591/go.mod h1:EBQ0jeOrBpOTkquwjmJl4W6z5xqlf5oA2LZfTqRNcO0= -github.com/linode/linodego v0.10.0 h1:AMdb82HVgY8o3mjBXJcUv9B+fnJjfDMn2rNRGbX+jvM= -github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA= -github.com/liquidweb/liquidweb-go v1.6.0 h1:vIj1I/Wf97fUnyirD+bi6Y63c0GiXk9nKI1+sFFl3G0= -github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ= +github.com/linode/linodego v0.21.0 h1:XykohqzVIV6hvjBn03cj7FGxYARFSrlfJodQrtHynqk= +github.com/linode/linodego v0.21.0/go.mod h1:UTpq1JUZD0CZsJ8rt+0CRkqbzrp1MbGakVPt2DXY5Mk= +github.com/liquidweb/liquidweb-go v1.6.1 h1:O51RbJo3ZEWFkZFfP32zIF6MCoZzwuuybuXsvZvVEEI= +github.com/liquidweb/liquidweb-go v1.6.1/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ= github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20= github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -524,13 +527,13 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM= -github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= +github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= @@ -547,8 +550,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA= -github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -569,13 +572,15 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nrdcg/auroradns v1.0.1 h1:m/kBq83Xvy3cU261MOknd8BdnOk12q4lAWM+kOdsC2Y= github.com/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI= +github.com/nrdcg/desec v0.5.0 h1:foL7hqivYOMlv0qDhHXJtuuEXkqf0wW9EQMqyrt228g= +github.com/nrdcg/desec v0.5.0/go.mod h1:2ejvMazkav1VdDbv2HeQO7w+Ta1CGHqzQr27ZBYTuEQ= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= -github.com/nrdcg/goinwx v0.7.0 h1:j6JlOp0nNwtvaP09TvKqc9pktjH81nOad0+Gx9S1t9U= -github.com/nrdcg/goinwx v0.7.0/go.mod h1:4tKJOCi/1lTxuw9/yB2Ez0aojwtUCSkckjc22eALpqE= +github.com/nrdcg/goinwx v0.8.1 h1:20EQ/JaGFnSKwiDH2JzjIpicffl3cPk6imJBDqVBVtU= +github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c= github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -612,10 +617,10 @@ github.com/openzipkin/zipkin-go v0.2.1 h1:noL5/5Uf1HpVl3wNsfkZhIKbSWCVi5jgqkONNx github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2 h1:nY8Hti+WKaP0cRsSeQ026wU03QsM762XBeCXBb9NAWI= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/oracle/oci-go-sdk v7.0.0+incompatible h1:oj5ESjXwwkFRdhZSnPlShvLWYdt/IZ65RQxveYM3maA= -github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= -github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014 h1:37VE5TYj2m/FLA9SNr4z0+A0JefvTmR60Zwf8XSEV7c= -github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ= +github.com/oracle/oci-go-sdk v24.2.0+incompatible h1:T+OS7BSWy5vVKfngy6Ln5lzIO09nqVxNxHJY2Waivs8= +github.com/oracle/oci-go-sdk v24.2.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= +github.com/ovh/go-ovh v1.1.0 h1:bHXZmw8nTgZin4Nv7JuaLs0KG5x54EQR7migYTd1zrk= +github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -677,8 +682,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sacloud/libsacloud v1.26.1 h1:td3Kd7lvpSAxxHEVpnaZ9goHmmhi0D/RfP0Rqqf/kek= -github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ= +github.com/sacloud/libsacloud v1.36.2 h1:aosI7clbQ9IU0Hj+3rpk3SKJop5nLPpLThnWCivPqjI= +github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/santhosh-tekuri/jsonschema v1.2.4 h1:hNhW8e7t+H1vgY+1QeEQpveR6D4+OwKPXCfD2aieJis= @@ -717,17 +722,17 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7ecQ8JscCNQXDGYAKDiWjDeXnpN/+BY9g= github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7 h1:CpHxIaZzVy26GqJn8ptRyto8fuoYOd1v0fXm9bG3wQ8= -github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY= github.com/tinylib/msgp v1.0.2 h1:DfdQrzQa7Yh2es9SuLkixqxuXS2SxsdYn0KbdrOGWD8= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/transip/gotransip/v6 v6.0.2 h1:rOCMY607PYF+YvMHHtJt7eZRd0mx/uhyz6dsXWPmn+4= -github.com/transip/gotransip/v6 v6.0.2/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= +github.com/transip/gotransip/v6 v6.2.0 h1:0Z+qVsyeiQdWfcAUeJyF0IEKAPvhJwwpwPi2WGtBIiE= +github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/uber-go/atomic v1.3.2 h1:Azu9lPBWRNKzYXSIwRfgRuDuS0YKsK4NFhiQv98gkxo= github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= @@ -740,15 +745,15 @@ github.com/unrolled/render v1.0.2/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0ob github.com/unrolled/secure v1.0.7 h1:BcQHp3iKZyZCKj5gRqwQG+5urnGBF00wGgoPPwtheVQ= github.com/unrolled/secure v1.0.7/go.mod h1:uGc1OcRF8gCVBA+ANksKmvM85Hka6SZtQIbrKc3sHS4= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vdemeester/shakers v0.1.0 h1:K+n9sSyUCg2ywmZkv+3c7vsYZfivcfKhMh8kRxCrONM= github.com/vdemeester/shakers v0.1.0/go.mod h1:IZ1HHynUOQt32iQ3rvAeVddXLd19h/6LWiKsh9RZtAQ= github.com/vulcand/oxy v1.1.0 h1:DbBijGo1+6cFqR9jarkMxasdj0lgWwrrFtue6ijek4Q= github.com/vulcand/oxy v1.1.0/go.mod h1:ADiMYHi8gkGl2987yQIzDRoXZilANF4WtKaQ92OppKY= github.com/vulcand/predicate v1.1.0 h1:Gq/uWopa4rx/tnZu2opOSBqHK63Yqlou/SzrbwdJiNg= github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg= -github.com/vultr/govultr v0.4.2 h1:9i8xKZ+xp6vwZ9raqHoBLzhB4wCnMj7nOQTj5YIRLWY= -github.com/vultr/govultr v0.4.2/go.mod h1:TUuUizMOFc7z+PNMssb6iGjKjQfpw5arIaOLfocVudQ= +github.com/vultr/govultr v0.5.0 h1:iQzYhzbokmpDARbvIkvTkoyS7WMH82zVTKAL1PZ4JOA= +github.com/vultr/govultr v0.5.0/go.mod h1:wZZXZbYbqyY1n3AldoeYNZK4Wnmmoq6dNFkvd5TV3ss= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= @@ -805,10 +810,11 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49N golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6 h1:TjszyFsQsyZNHwdVdZ5m7bjmreu0znc2kRYsEml9/Ww= golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -845,7 +851,6 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -863,13 +868,13 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 h1:6KET3Sqa7fkVfD63QnAM81ZeYg5n4HwApOJkufONnHA= -golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -878,6 +883,8 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -924,6 +931,7 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPT golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -934,6 +942,8 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -943,10 +953,10 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 h1:xQwXv67TxFo9nC1GJFyab5eq/5B590r6RlnL/G8Sz7w= -golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -977,6 +987,7 @@ golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361 h1:RIIXAeV6GvDBuADKumTODatUqANFZ+5BPMnzsy4hulY= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -1064,6 +1075,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= @@ -1074,6 +1087,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= @@ -1084,23 +1099,28 @@ gopkg.in/jcmturner/gokrb5.v7 v7.2.3 h1:hHMV/yKPwMnJhPuPx7pH2Uw/3Qyf+thJYlisUc440 gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= -gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc h1:GAcf+t0o8gdJAdSFYdE9wChu4bIyguMVqz0RHiFL5VY= -gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw= +gopkg.in/ns1/ns1-go.v2 v2.4.2 h1:H6VnvLez0GjxXsXat6MUFmKuiMFuDaMBdGF9qtkmODo= +gopkg.in/ns1/ns1-go.v2 v2.4.2/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk= gopkg.in/redis.v5 v5.2.9 h1:MNZYOLPomQzZMfpN3ZtD1uyJ2IDonTTlxYiV/pEApiw= gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY= -gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 1fac9e019..0ad5b8a62 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -30,7 +30,7 @@ import ( "github.com/containous/traefik/v2/pkg/tracing/zipkin" "github.com/containous/traefik/v2/pkg/types" assetfs "github.com/elazarl/go-bindata-assetfs" - legolog "github.com/go-acme/lego/v3/log" + legolog "github.com/go-acme/lego/v4/log" "github.com/sirupsen/logrus" ) diff --git a/pkg/provider/acme/account.go b/pkg/provider/acme/account.go index bf6fb654a..66ca15710 100644 --- a/pkg/provider/acme/account.go +++ b/pkg/provider/acme/account.go @@ -8,8 +8,8 @@ import ( "crypto/x509" "github.com/containous/traefik/v2/pkg/log" - "github.com/go-acme/lego/v3/certcrypto" - "github.com/go-acme/lego/v3/registration" + "github.com/go-acme/lego/v4/certcrypto" + "github.com/go-acme/lego/v4/registration" ) // Account is used to store lets encrypt registration info. diff --git a/pkg/provider/acme/challenge_http.go b/pkg/provider/acme/challenge_http.go index 5da1d8c4f..42575a54c 100644 --- a/pkg/provider/acme/challenge_http.go +++ b/pkg/provider/acme/challenge_http.go @@ -9,8 +9,8 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/safe" - "github.com/go-acme/lego/v3/challenge" - "github.com/go-acme/lego/v3/challenge/http01" + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/http01" "github.com/gorilla/mux" ) diff --git a/pkg/provider/acme/challenge_tls.go b/pkg/provider/acme/challenge_tls.go index 2adba5537..d841e7fbc 100644 --- a/pkg/provider/acme/challenge_tls.go +++ b/pkg/provider/acme/challenge_tls.go @@ -5,8 +5,8 @@ import ( "github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/types" - "github.com/go-acme/lego/v3/challenge" - "github.com/go-acme/lego/v3/challenge/tlsalpn01" + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/tlsalpn01" ) var _ challenge.Provider = (*challengeTLSALPN)(nil) diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index 33f234e28..fe4fe4127 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -19,12 +19,12 @@ import ( traefiktls "github.com/containous/traefik/v2/pkg/tls" "github.com/containous/traefik/v2/pkg/types" "github.com/containous/traefik/v2/pkg/version" - "github.com/go-acme/lego/v3/certificate" - "github.com/go-acme/lego/v3/challenge" - "github.com/go-acme/lego/v3/challenge/dns01" - "github.com/go-acme/lego/v3/lego" - "github.com/go-acme/lego/v3/providers/dns" - "github.com/go-acme/lego/v3/registration" + "github.com/go-acme/lego/v4/certificate" + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/lego" + "github.com/go-acme/lego/v4/providers/dns" + "github.com/go-acme/lego/v4/registration" ) // oscpMustStaple enables OSCP stapling as from https://github.com/go-acme/lego/issues/270. @@ -32,13 +32,14 @@ var oscpMustStaple = false // Configuration holds ACME configuration provided by users. type Configuration struct { - Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"` - CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"` - Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty"` - KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty"` - DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty"` - HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty"` - TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty"` + Email string `description:"Email address used for registration." json:"email,omitempty" toml:"email,omitempty" yaml:"email,omitempty"` + CAServer string `description:"CA server to use." json:"caServer,omitempty" toml:"caServer,omitempty" yaml:"caServer,omitempty"` + PreferredChain string `description:"Preferred chain to use." json:"preferredChain,omitempty" toml:"preferredChain,omitempty" yaml:"preferredChain,omitempty"` + Storage string `description:"Storage to use." json:"storage,omitempty" toml:"storage,omitempty" yaml:"storage,omitempty"` + KeyType string `description:"KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'." json:"keyType,omitempty" toml:"keyType,omitempty" yaml:"keyType,omitempty"` + DNSChallenge *DNSChallenge `description:"Activate DNS-01 Challenge." json:"dnsChallenge,omitempty" toml:"dnsChallenge,omitempty" yaml:"dnsChallenge,omitempty" label:"allowEmpty"` + HTTPChallenge *HTTPChallenge `description:"Activate HTTP-01 Challenge." json:"httpChallenge,omitempty" toml:"httpChallenge,omitempty" yaml:"httpChallenge,omitempty" label:"allowEmpty"` + TLSChallenge *TLSChallenge `description:"Activate TLS-ALPN-01 Challenge." json:"tlsChallenge,omitempty" toml:"tlsChallenge,omitempty" yaml:"tlsChallenge,omitempty" label:"allowEmpty"` } // SetDefaults sets the default values. @@ -262,14 +263,18 @@ func (p *Provider) getClient() (*lego.Client, error) { err = client.Challenge.SetDNS01Provider(provider, dns01.CondOption(len(p.DNSChallenge.Resolvers) > 0, dns01.AddRecursiveNameservers(p.DNSChallenge.Resolvers)), - dns01.CondOption(p.DNSChallenge.DisablePropagationCheck || p.DNSChallenge.DelayBeforeCheck > 0, - dns01.AddPreCheck(func(_, _ string) (bool, error) { - if p.DNSChallenge.DelayBeforeCheck > 0 { - log.Debugf("Delaying %d rather than validating DNS propagation now.", p.DNSChallenge.DelayBeforeCheck) - time.Sleep(time.Duration(p.DNSChallenge.DelayBeforeCheck)) - } + dns01.WrapPreCheck(func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) { + if p.DNSChallenge.DisablePropagationCheck { return true, nil - })), + } + + if p.DNSChallenge.DelayBeforeCheck > 0 { + logger.Debugf("Delaying %d rather than validating DNS propagation now.", p.DNSChallenge.DelayBeforeCheck) + time.Sleep(time.Duration(p.DNSChallenge.DelayBeforeCheck)) + } + + return check(fqdn, value) + }), ) if err != nil { return nil, err @@ -626,7 +631,7 @@ func (p *Provider) renewCertificates(ctx context.Context) { Domain: cert.Domain.Main, PrivateKey: cert.Key, Certificate: cert.Certificate.Certificate, - }, true, oscpMustStaple) + }, true, oscpMustStaple, p.PreferredChain) if err != nil { logger.Errorf("Error renewing certificate from LE: %v, %v", cert.Domain, err) continue diff --git a/pkg/provider/acme/provider_test.go b/pkg/provider/acme/provider_test.go index 49df9ac5b..27022910e 100644 --- a/pkg/provider/acme/provider_test.go +++ b/pkg/provider/acme/provider_test.go @@ -7,7 +7,7 @@ import ( "github.com/containous/traefik/v2/pkg/safe" "github.com/containous/traefik/v2/pkg/types" - "github.com/go-acme/lego/v3/certcrypto" + "github.com/go-acme/lego/v4/certcrypto" "github.com/stretchr/testify/assert" ) diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index bae3e623a..36d6d06d6 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -11,7 +11,7 @@ import ( "github.com/containous/traefik/v2/pkg/log" "github.com/containous/traefik/v2/pkg/tls/generate" "github.com/containous/traefik/v2/pkg/types" - "github.com/go-acme/lego/v3/challenge/tlsalpn01" + "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/sirupsen/logrus" ) From 322f7b2ad405efab2121337391d34fe9e9bbaba8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doumenjou Date: Fri, 4 Sep 2020 17:14:03 +0200 Subject: [PATCH 5/6] Prepare release 2.2.9 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89af253d3..cd5c8a1a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## [v2.2.9](https://github.com/containous/traefik/tree/v2.2.9) (2020-09-04) +[All Commits](https://github.com/containous/traefik/compare/v2.2.8...v2.2.9) + +**Bug fixes:** +- **[acme]** Update go-acme/lego to v4.0.1 ([#7238](https://github.com/containous/traefik/pull/7238) by [ldez](https://github.com/ldez)) +- **[middleware]** Add missing IPStrategy struct tag for YAML ([#7233](https://github.com/containous/traefik/pull/7233) by [kevinpollet](https://github.com/kevinpollet)) +- **[middleware]** Headers response modifier is directly applied by headers middleware ([#7230](https://github.com/containous/traefik/pull/7230) by [juliens](https://github.com/juliens)) +- **[webui]** chore(webui): upgrade nodejs to Node current LTS ([#7125](https://github.com/containous/traefik/pull/7125) by [Slashgear](https://github.com/Slashgear)) + +**Documentation:** +- **[docker]** doc: fix dead link. ([#7172](https://github.com/containous/traefik/pull/7172) by [ldez](https://github.com/ldez)) +- **[k8s]** kubernetes-crd: fix whitespace in configuration examples ([#7134](https://github.com/containous/traefik/pull/7134) by [NT-florianernst](https://github.com/NT-florianernst)) +- **[k8s]** doc: replace underscore by hyphen for k8s metadata names. ([#7131](https://github.com/containous/traefik/pull/7131) by [ldez](https://github.com/ldez)) +- **[logs]** doc: added tz section to access log ([#7178](https://github.com/containous/traefik/pull/7178) by [notsureifkevin](https://github.com/notsureifkevin)) +- **[tls]** doc: Minor language improvement in TLS documentation ([#7206](https://github.com/containous/traefik/pull/7206) by [sharmarajdaksh](https://github.com/sharmarajdaksh)) +- doc: fix typo in migration guide ([#7181](https://github.com/containous/traefik/pull/7181) by [ScuttleSE](https://github.com/ScuttleSE)) +- doc: specify HostSNI rule removal only for HTTP routers ([#7237](https://github.com/containous/traefik/pull/7237) by [rtribotte](https://github.com/rtribotte)) +- Reorder migrations for v2 minor upgrades ([#7214](https://github.com/containous/traefik/pull/7214) by [peschmae](https://github.com/peschmae)) +- Harmonize docs ([#7124](https://github.com/containous/traefik/pull/7124) by [matthieuh](https://github.com/matthieuh)) + ## [v2.2.8](https://github.com/containous/traefik/tree/v2.2.8) (2020-07-28) [All Commits](https://github.com/containous/traefik/compare/v2.2.7...v2.2.8) From 2d1a973ee5e958353a242b9332eaecadedf16f7c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Doumenjou Date: Fri, 4 Sep 2020 17:40:03 +0200 Subject: [PATCH 6/6] Prepare release v2.2.10 --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd5c8a1a5..50960bbbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ -## [v2.2.9](https://github.com/containous/traefik/tree/v2.2.9) (2020-09-04) -[All Commits](https://github.com/containous/traefik/compare/v2.2.8...v2.2.9) +## [v2.2.10](https://github.com/containous/traefik/tree/v2.2.10) (2020-09-04) +[All Commits](https://github.com/containous/traefik/compare/v2.2.0...v2.2.10) **Bug fixes:** - **[acme]** Update go-acme/lego to v4.0.1 ([#7238](https://github.com/containous/traefik/pull/7238) by [ldez](https://github.com/ldez)) @@ -18,6 +18,11 @@ - Reorder migrations for v2 minor upgrades ([#7214](https://github.com/containous/traefik/pull/7214) by [peschmae](https://github.com/peschmae)) - Harmonize docs ([#7124](https://github.com/containous/traefik/pull/7124) by [matthieuh](https://github.com/matthieuh)) +## [v2.2.9](https://github.com/containous/traefik/tree/v2.2.9) (2020-09-04) +[All Commits](https://github.com/containous/traefik/compare/v2.2.8...v2.2.9) + +Release canceled due to a bad tag. + ## [v2.2.8](https://github.com/containous/traefik/tree/v2.2.8) (2020-07-28) [All Commits](https://github.com/containous/traefik/compare/v2.2.7...v2.2.8)