From a90b2a672e88a643e0bad2aa1f90e4f363289641 Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Thu, 21 Jan 2021 10:04:04 +0100 Subject: [PATCH 1/5] perf: improve forwarded header and recovery middlewares Co-authored-by: Ludovic Fernandez --- .../forwardedheaders/forwarded_header.go | 66 +++++++++++++------ pkg/middlewares/recovery/recovery.go | 21 +++--- pkg/middlewares/recovery/recovery_test.go | 2 +- pkg/server/router/router.go | 6 +- 4 files changed, 59 insertions(+), 36 deletions(-) diff --git a/pkg/middlewares/forwardedheaders/forwarded_header.go b/pkg/middlewares/forwardedheaders/forwarded_header.go index 5917a5c69..5d2355dfd 100644 --- a/pkg/middlewares/forwardedheaders/forwarded_header.go +++ b/pkg/middlewares/forwardedheaders/forwarded_header.go @@ -84,19 +84,28 @@ func (x *XForwarded) isTrustedIP(ip string) bool { // removeIPv6Zone removes the zone if the given IP is an ipv6 address and it has {zone} information in it, // like "[fe80::d806:a55d:eb1b:49cc%vEthernet (vmxnet3 Ethernet Adapter - Virtual Switch)]:64692". func removeIPv6Zone(clientIP string) string { - return strings.Split(clientIP, "%")[0] + if idx := strings.Index(clientIP, "%"); idx != -1 { + return clientIP[:idx] + } + return clientIP } // isWebsocketRequest returns whether the specified HTTP request is a websocket handshake request. func isWebsocketRequest(req *http.Request) bool { containsHeader := func(name, value string) bool { - items := strings.Split(req.Header.Get(name), ",") - for _, item := range items { - if value == strings.ToLower(strings.TrimSpace(item)) { + h := unsafeHeader(req.Header).Get(name) + for { + pos := strings.Index(h, ",") + if pos == -1 { + return strings.EqualFold(value, strings.TrimSpace(h)) + } + + if strings.EqualFold(value, strings.TrimSpace(h[:pos])) { return true } + + h = h[pos:] } - return false } return containsHeader(connection, "upgrade") && containsHeader(upgrade, "websocket") } @@ -110,7 +119,7 @@ func forwardedPort(req *http.Request) string { return port } - if req.Header.Get(xForwardedProto) == "https" || req.Header.Get(xForwardedProto) == "wss" { + if unsafeHeader(req.Header).Get(xForwardedProto) == "https" || unsafeHeader(req.Header).Get(xForwardedProto) == "wss" { return "443" } @@ -125,38 +134,38 @@ func (x *XForwarded) rewrite(outreq *http.Request) { if clientIP, _, err := net.SplitHostPort(outreq.RemoteAddr); err == nil { clientIP = removeIPv6Zone(clientIP) - if outreq.Header.Get(xRealIP) == "" { - outreq.Header.Set(xRealIP, clientIP) + if unsafeHeader(outreq.Header).Get(xRealIP) == "" { + unsafeHeader(outreq.Header).Set(xRealIP, clientIP) } } - xfProto := outreq.Header.Get(xForwardedProto) + xfProto := unsafeHeader(outreq.Header).Get(xForwardedProto) if xfProto == "" { if isWebsocketRequest(outreq) { if outreq.TLS != nil { - outreq.Header.Set(xForwardedProto, "wss") + unsafeHeader(outreq.Header).Set(xForwardedProto, "wss") } else { - outreq.Header.Set(xForwardedProto, "ws") + unsafeHeader(outreq.Header).Set(xForwardedProto, "ws") } } else { if outreq.TLS != nil { - outreq.Header.Set(xForwardedProto, "https") + unsafeHeader(outreq.Header).Set(xForwardedProto, "https") } else { - outreq.Header.Set(xForwardedProto, "http") + unsafeHeader(outreq.Header).Set(xForwardedProto, "http") } } } - if xfPort := outreq.Header.Get(xForwardedPort); xfPort == "" { - outreq.Header.Set(xForwardedPort, forwardedPort(outreq)) + if xfPort := unsafeHeader(outreq.Header).Get(xForwardedPort); xfPort == "" { + unsafeHeader(outreq.Header).Set(xForwardedPort, forwardedPort(outreq)) } - if xfHost := outreq.Header.Get(xForwardedHost); xfHost == "" && outreq.Host != "" { - outreq.Header.Set(xForwardedHost, outreq.Host) + if xfHost := unsafeHeader(outreq.Header).Get(xForwardedHost); xfHost == "" && outreq.Host != "" { + unsafeHeader(outreq.Header).Set(xForwardedHost, outreq.Host) } if x.hostname != "" { - outreq.Header.Set(xForwardedServer, x.hostname) + unsafeHeader(outreq.Header).Set(xForwardedServer, x.hostname) } } @@ -164,7 +173,7 @@ func (x *XForwarded) rewrite(outreq *http.Request) { func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !x.insecure && !x.isTrustedIP(r.RemoteAddr) { for _, h := range xHeaders { - r.Header.Del(h) + unsafeHeader(r.Header).Del(h) } } @@ -172,3 +181,22 @@ func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) { x.next.ServeHTTP(w, r) } + +// unsafeHeader allows to manage Header values. +// Must be used only when the header name is already a canonical key. +type unsafeHeader map[string][]string + +func (h unsafeHeader) Set(key, value string) { + h[key] = []string{value} +} + +func (h unsafeHeader) Get(key string) string { + if len(h[key]) == 0 { + return "" + } + return h[key][0] +} + +func (h unsafeHeader) Del(key string) { + delete(h, key) +} diff --git a/pkg/middlewares/recovery/recovery.go b/pkg/middlewares/recovery/recovery.go index 7040c91c2..753a5801b 100644 --- a/pkg/middlewares/recovery/recovery.go +++ b/pkg/middlewares/recovery/recovery.go @@ -10,42 +10,41 @@ import ( ) const ( - typeName = "Recovery" + typeName = "Recovery" + middlewareName = "traefik-internal-recovery" ) type recovery struct { next http.Handler - name string } // New creates recovery middleware. -func New(ctx context.Context, next http.Handler, name string) (http.Handler, error) { - log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeName)).Debug("Creating middleware") +func New(ctx context.Context, next http.Handler) (http.Handler, error) { + log.FromContext(middlewares.GetLoggerCtx(ctx, middlewareName, typeName)).Debug("Creating middleware") return &recovery{ next: next, - name: name, }, nil } func (re *recovery) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - defer recoverFunc(middlewares.GetLoggerCtx(req.Context(), re.name, typeName), rw, req) + defer recoverFunc(rw, req) re.next.ServeHTTP(rw, req) } -func recoverFunc(ctx context.Context, rw http.ResponseWriter, r *http.Request) { +func recoverFunc(rw http.ResponseWriter, r *http.Request) { if err := recover(); err != nil { + logger := log.FromContext(middlewares.GetLoggerCtx(r.Context(), middlewareName, typeName)) if !shouldLogPanic(err) { - log.FromContext(ctx).Debugf("Request has been aborted [%s - %s]: %v", r.RemoteAddr, r.URL, err) + logger.Debugf("Request has been aborted [%s - %s]: %v", r.RemoteAddr, r.URL, err) return } - log.FromContext(ctx).Errorf("Recovered from panic in HTTP handler [%s - %s]: %+v", r.RemoteAddr, r.URL, err) - + logger.Errorf("Recovered from panic in HTTP handler [%s - %s]: %+v", r.RemoteAddr, r.URL, err) const size = 64 << 10 buf := make([]byte, size) buf = buf[:runtime.Stack(buf, false)] - log.FromContext(ctx).Errorf("Stack: %s", buf) + logger.Errorf("Stack: %s", buf) http.Error(rw, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } diff --git a/pkg/middlewares/recovery/recovery_test.go b/pkg/middlewares/recovery/recovery_test.go index 0871f3909..162a9734f 100644 --- a/pkg/middlewares/recovery/recovery_test.go +++ b/pkg/middlewares/recovery/recovery_test.go @@ -14,7 +14,7 @@ func TestRecoverHandler(t *testing.T) { fn := func(w http.ResponseWriter, r *http.Request) { panic("I love panicing!") } - recovery, err := New(context.Background(), http.HandlerFunc(fn), "foo-recovery") + recovery, err := New(context.Background(), http.HandlerFunc(fn)) require.NoError(t, err) server := httptest.NewServer(recovery) diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index 965dd9fa4..195b85159 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -16,10 +16,6 @@ import ( "github.com/traefik/traefik/v2/pkg/server/provider" ) -const ( - recoveryMiddlewareName = "traefik-internal-recovery" -) - type middlewareBuilder interface { BuildChain(ctx context.Context, names []string) *alice.Chain } @@ -130,7 +126,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string chain := alice.New() chain = chain.Append(func(next http.Handler) (http.Handler, error) { - return recovery.New(ctx, next, recoveryMiddlewareName) + return recovery.New(ctx, next) }) return chain.Then(router) From f0d78471af46e775d79106cd707378274f7d2cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Fautr=C3=A9?= Date: Thu, 21 Jan 2021 18:34:04 +0100 Subject: [PATCH 2/5] Forward Proxy-Authorization header to authentication server --- pkg/middlewares/auth/forward.go | 16 ++++++++++++++-- pkg/middlewares/auth/forward_test.go | 16 ++++++++++------ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index 381c687b4..65722316a 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -26,6 +26,18 @@ const ( forwardedTypeName = "ForwardedAuthType" ) +// hopHeaders Hop-by-hop headers to be removed in the authentication request. +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html +// Proxy-Authorization header is forwarded to the authentication server (see https://tools.ietf.org/html/rfc7235#section-4.4). +var hopHeaders = []string{ + forward.Connection, + forward.KeepAlive, + forward.Te, // canonicalized version of "TE" + forward.Trailers, + forward.TransferEncoding, + forward.Upgrade, +} + type forwardAuth struct { address string authResponseHeaders []string @@ -131,7 +143,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { logger.Debugf("Remote error %s. StatusCode: %d", fa.address, forwardResponse.StatusCode) utils.CopyHeaders(rw.Header(), forwardResponse.Header) - utils.RemoveHeaders(rw.Header(), forward.HopHeaders...) + utils.RemoveHeaders(rw.Header(), hopHeaders...) // Grab the location header, if any. redirectURL, err := forwardResponse.Location() @@ -187,7 +199,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool, allowedHeaders []string) { utils.CopyHeaders(forwardReq.Header, req.Header) - utils.RemoveHeaders(forwardReq.Header, forward.HopHeaders...) + utils.RemoveHeaders(forwardReq.Header, hopHeaders...) forwardReq.Header = filterForwardRequestHeaders(forwardReq.Header, allowedHeaders) diff --git a/pkg/middlewares/auth/forward_test.go b/pkg/middlewares/auth/forward_test.go index 65e6ad9cd..13a8f5a5f 100644 --- a/pkg/middlewares/auth/forward_test.go +++ b/pkg/middlewares/auth/forward_test.go @@ -26,6 +26,7 @@ func TestForwardAuthFail(t *testing.T) { }) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set(forward.ProxyAuthenticate, "test") http.Error(w, "Forbidden", http.StatusForbidden) })) t.Cleanup(server.Close) @@ -48,6 +49,7 @@ func TestForwardAuthFail(t *testing.T) { err = res.Body.Close() require.NoError(t, err) + assert.Equal(t, "test", res.Header.Get(forward.ProxyAuthenticate)) assert.Equal(t, "Forbidden\n", string(body)) } @@ -142,7 +144,7 @@ func TestForwardAuthRedirect(t *testing.T) { func TestForwardAuthRemoveHopByHopHeaders(t *testing.T) { authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { headers := w.Header() - for _, header := range forward.HopHeaders { + for _, header := range hopHeaders { if header == forward.TransferEncoding { headers.Set(header, "chunked") } else { @@ -367,11 +369,13 @@ func Test_writeHeader(t *testing.T) { }, trustForwardHeader: false, expectedHeaders: map[string]string{ - "X-CustomHeader": "CustomHeader", - "X-Forwarded-Proto": "http", - "X-Forwarded-Host": "foo.bar", - "X-Forwarded-Uri": "/path?q=1", - "X-Forwarded-Method": "GET", + "X-CustomHeader": "CustomHeader", + "X-Forwarded-Proto": "http", + "X-Forwarded-Host": "foo.bar", + "X-Forwarded-Uri": "/path?q=1", + "X-Forwarded-Method": "GET", + forward.ProxyAuthenticate: "ProxyAuthenticate", + forward.ProxyAuthorization: "ProxyAuthorization", }, checkForUnexpectedHeaders: true, }, From fb10687168129eb00c5b69c2f09b073f2c9b6bd4 Mon Sep 17 00:00:00 2001 From: Anton Kulikov <4ops@protonmail.com> Date: Fri, 22 Jan 2021 11:02:04 +0300 Subject: [PATCH 3/5] fix: YAML syntax in providers docs --- docs/check.Dockerfile | 2 +- docs/content/providers/kubernetes-gateway.md | 2 +- docs/docs.Dockerfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/check.Dockerfile b/docs/check.Dockerfile index 4126bf2f7..45bacdbf4 100644 --- a/docs/check.Dockerfile +++ b/docs/check.Dockerfile @@ -1,5 +1,5 @@ -FROM alpine:3.10 as alpine +FROM alpine:3.13 as alpine RUN apk --no-cache --no-progress add \ libcurl \ diff --git a/docs/content/providers/kubernetes-gateway.md b/docs/content/providers/kubernetes-gateway.md index f5247ec83..2c1d1bca4 100644 --- a/docs/content/providers/kubernetes-gateway.md +++ b/docs/content/providers/kubernetes-gateway.md @@ -145,7 +145,7 @@ _Optional, Default=empty_ ```yaml tab="File (YAML)" providers: kubernetesGateway: - token = "mytoken" + token: "mytoken" # ... ``` diff --git a/docs/docs.Dockerfile b/docs/docs.Dockerfile index 1905378db..177e3f9cc 100644 --- a/docs/docs.Dockerfile +++ b/docs/docs.Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.12 +FROM alpine:3.13 ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/.local/bin From da0a16e1224345aa56427510f608456d447362d7 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 25 Jan 2021 09:28:04 +0100 Subject: [PATCH 4/5] Update go-acme/lego to v4.2.0 --- docs/content/https/acme.md | 2 ++ go.mod | 2 +- go.sum | 16 ++++++++-------- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index e95229f3b..a7d4d59c6 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -324,10 +324,12 @@ For complete details, refer to your provider's _Additional configuration_ link. | [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) | | [Infomaniak](https://www.infomaniak.com) | `infomaniak` | `INFOMANIAK_ACCESS_TOKEN` | [Additional configuration](https://go-acme.github.io/lego/dns/infomaniak) | | [INWX](https://www.inwx.de/en) | `inwx` | `INWX_USERNAME`, `INWX_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/inwx) | +| [ionos](https://ionos.com/) | `ionos` | `IONOS_API_KEY` | [Additional configuration](https://go-acme.github.io/lego/dns/ionos) | | [Joker.com](https://joker.com) | `joker` | `JOKER_API_MODE` with `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 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) | +| [Loopia](https://loopia.com/) | `loopia` | `LOOPIA_API_PASSWORD`, `LOOPIA_API_USER` | [Additional configuration](https://go-acme.github.io/lego/dns/loopia) | | [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. | | | [MyDNS.jp](https://www.mydns.jp/) | `mydnsjp` | `MYDNSJP_MASTER_ID`, `MYDNSJP_PASSWORD` | [Additional configuration](https://go-acme.github.io/lego/dns/mydnsjp) | diff --git a/go.mod b/go.mod index 00ae45762..548f4c645 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,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/v4 v4.1.3 + github.com/go-acme/lego/v4 v4.2.0 github.com/go-check/check v0.0.0-00010101000000-000000000000 github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea github.com/golang/protobuf v1.4.2 diff --git a/go.sum b/go.sum index 1d09a784a..024679a81 100644 --- a/go.sum +++ b/go.sum @@ -220,8 +220,8 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 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.3 h1:QOeMpIEsIdm1LSASSswjaTf8CXmzcrgy5OeCfHjppA4= -github.com/cpu/goacmedns v0.0.3/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ= +github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4= +github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -310,8 +310,8 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-acme/lego/v4 v4.1.3 h1:D8nnzrijQFUAqdNPwnbvm6tJ3AJAzQAlnROeecUNG/4= -github.com/go-acme/lego/v4 v4.1.3/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE= +github.com/go-acme/lego/v4 v4.2.0 h1:zEvpcDLqvzOlNUGBMA0MCKPpb9UBbnBzgWwCIbTEt2g= +github.com/go-acme/lego/v4 v4.2.0/go.mod h1:jmhqxBaangB8txXZKjRLTPXFXUwPCTU2fU8S9/eQzBI= 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= @@ -509,8 +509,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.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo= -github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +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-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= @@ -907,8 +907,8 @@ 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.5.0 h1:iQzYhzbokmpDARbvIkvTkoyS7WMH82zVTKAL1PZ4JOA= -github.com/vultr/govultr v0.5.0/go.mod h1:wZZXZbYbqyY1n3AldoeYNZK4Wnmmoq6dNFkvd5TV3ss= +github.com/vultr/govultr/v2 v2.0.0 h1:+lAtqfWy3g9VwL7tT2Fpyad8Vv4MxOhT/NU8O5dk+EQ= +github.com/vultr/govultr/v2 v2.0.0/go.mod h1:2PsEeg+gs3p/Fo5Pw8F9mv+DUBEOlrNZ8GmCTGmhOhs= 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= From 1305bf49a5b304b052b1da9b6f23f57dcd794f83 Mon Sep 17 00:00:00 2001 From: Harold Ozouf Date: Mon, 25 Jan 2021 11:08:04 +0100 Subject: [PATCH 5/5] Fix plugin type on middleware endpoint response --- pkg/api/handler.go | 8 +++ pkg/api/handler_test.go | 109 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/pkg/api/handler.go b/pkg/api/handler.go index dc2fa712e..d35483f82 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -8,6 +8,7 @@ import ( assetfs "github.com/elazarl/go-bindata-assetfs" "github.com/gorilla/mux" + "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/config/runtime" "github.com/traefik/traefik/v2/pkg/config/static" "github.com/traefik/traefik/v2/pkg/log" @@ -157,6 +158,13 @@ func extractType(element interface{}) string { v := reflect.ValueOf(element).Elem() for i := 0; i < v.NumField(); i++ { field := v.Field(i) + + if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeOf(dynamic.PluginConf{}) { + if keys := field.MapKeys(); len(keys) == 1 { + return keys[0].String() + } + } + if field.Kind() == reflect.Ptr && field.Elem().Kind() == reflect.Struct { if !field.IsNil() { return v.Type().Field(i).Name diff --git a/pkg/api/handler_test.go b/pkg/api/handler_test.go index 6028348ba..065bb9035 100644 --- a/pkg/api/handler_test.go +++ b/pkg/api/handler_test.go @@ -171,3 +171,112 @@ func TestHandler_RawData(t *testing.T) { }) } } + +func TestHandler_GetMiddleware(t *testing.T) { + testCases := []struct { + desc string + middlewareName string + conf runtime.Configuration + expectedStatus int + expected interface{} + }{ + { + desc: "Middleware not found", + middlewareName: "auth@myprovider", + conf: runtime.Configuration{ + Middlewares: map[string]*runtime.MiddlewareInfo{}, + }, + expectedStatus: http.StatusNotFound, + }, + { + desc: "Get middleware", + middlewareName: "auth@myprovider", + conf: runtime.Configuration{ + Middlewares: map[string]*runtime.MiddlewareInfo{ + "auth@myprovider": { + Middleware: &dynamic.Middleware{ + BasicAuth: &dynamic.BasicAuth{ + Users: []string{"admin:admin"}, + }, + }, + }, + }, + }, + expectedStatus: http.StatusOK, + expected: middlewareRepresentation{ + MiddlewareInfo: &runtime.MiddlewareInfo{ + Middleware: &dynamic.Middleware{ + BasicAuth: &dynamic.BasicAuth{ + Users: []string{"admin:admin"}, + }, + }, + }, + Name: "auth@myprovider", + Provider: "myprovider", + Type: "basicauth", + }, + }, + { + desc: "Get plugin middleware", + middlewareName: "myplugin@myprovider", + conf: runtime.Configuration{ + Middlewares: map[string]*runtime.MiddlewareInfo{ + "myplugin@myprovider": { + Middleware: &dynamic.Middleware{ + Plugin: map[string]dynamic.PluginConf{ + "mysuperplugin": { + "foo": "bar", + }, + }, + }, + }, + }, + }, + expectedStatus: http.StatusOK, + expected: middlewareRepresentation{ + MiddlewareInfo: &runtime.MiddlewareInfo{ + Middleware: &dynamic.Middleware{ + Plugin: map[string]dynamic.PluginConf{ + "mysuperplugin": { + "foo": "bar", + }, + }, + }, + }, + Name: "myplugin@myprovider", + Provider: "myprovider", + Type: "mysuperplugin", + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, &test.conf) + server := httptest.NewServer(handler.createRouter()) + + resp, err := http.DefaultClient.Get(server.URL + "/api/http/middlewares/" + test.middlewareName) + require.NoError(t, err) + + assert.Equal(t, test.expectedStatus, resp.StatusCode) + + if test.expected == nil { + return + } + + data, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + err = resp.Body.Close() + require.NoError(t, err) + + expected, err := json.Marshal(test.expected) + require.NoError(t, err) + + assert.JSONEq(t, string(expected), string(data)) + }) + } +}