From 49cdb67ddcf7a3b7a0a7ecf964e982c3bdc7bbe5 Mon Sep 17 00:00:00 2001 From: iamolegga Date: Thu, 29 Oct 2020 17:10:04 +0300 Subject: [PATCH] Middlewares: add forwardAuth.authResponseHeadersRegex --- docs/content/middlewares/forwardauth.md | 55 ++++++++++++++++++- .../dynamic-configuration/docker-labels.yml | 1 + .../reference/dynamic-configuration/file.toml | 1 + .../reference/dynamic-configuration/file.yaml | 1 + .../reference/dynamic-configuration/kv-ref.md | 1 + .../marathon-labels.json | 1 + pkg/config/dynamic/fixtures/sample.toml | 1 + pkg/config/dynamic/middlewares.go | 11 ++-- pkg/middlewares/auth/forward.go | 38 ++++++++++--- pkg/middlewares/auth/forward_test.go | 10 +++- pkg/provider/kubernetes/crd/kubernetes.go | 9 +-- .../crd/traefik/v1alpha1/middleware.go | 11 ++-- 12 files changed, 116 insertions(+), 24 deletions(-) diff --git a/docs/content/middlewares/forwardauth.md b/docs/content/middlewares/forwardauth.md index 5580e0e5f..04521925a 100644 --- a/docs/content/middlewares/forwardauth.md +++ b/docs/content/middlewares/forwardauth.md @@ -164,7 +164,7 @@ http: ### `authResponseHeaders` -The `authResponseHeaders` option is the list of the headers to copy from the authentication server to the request. +The `authResponseHeaders` option is the list of the headers to copy from the authentication server to the request. All incoming request's headers in this list are deleted from the request before any copy happens. ```yaml tab="Docker" labels: @@ -217,6 +217,59 @@ http: - "X-Secret" ``` +### `authResponseHeadersRegex` + +The `authResponseHeadersRegex` option is the regex to match the headers that should be copied from the authentication server to the request. All incoming request's headers matching this regex are deleted from the request before any copy happens. +It allows partial matching of the regular expression against the header's key. +You should use start of string (`^`) and end of string (`$`) anchors to ensure a full match against the header's key. + +```yaml tab="Docker" +labels: + - "traefik.http.middlewares.test-auth.forwardauth.authResponseHeadersRegex=^X-" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: test-auth +spec: + forwardAuth: + address: https://example.com/auth + authResponseHeadersRegex: ^X- +``` + +```yaml tab="Consul Catalog" +- "traefik.http.middlewares.test-auth.forwardauth.authResponseHeadersRegex=^X-" +``` + +```json tab="Marathon" +"labels": { + "traefik.http.middlewares.test-auth.forwardauth.authResponseHeadersRegex": "^X-" +} +``` + +```yaml tab="Rancher" +labels: + - "traefik.http.middlewares.test-auth.forwardauth.authResponseHeadersRegex=^X-" +``` + +```toml tab="File (TOML)" +[http.middlewares] + [http.middlewares.test-auth.forwardAuth] + address = "https://example.com/auth" + authResponseHeadersRegex = "^X-" +``` + +```yaml tab="File (YAML)" +http: + middlewares: + test-auth: + forwardAuth: + address: "https://example.com/auth" + authResponseHeadersRegex: "^X-" +``` + ### `authRequestHeaders` The `authRequestHeaders` option is the list of the headers to copy from the request to the authentication server. diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 080141f1a..0ff31abd7 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -24,6 +24,7 @@ - "traefik.http.middlewares.middleware08.errors.status=foobar, foobar" - "traefik.http.middlewares.middleware09.forwardauth.address=foobar" - "traefik.http.middlewares.middleware09.forwardauth.authresponseheaders=foobar, foobar" +- "traefik.http.middlewares.middleware09.forwardauth.authresponseheadersregex=foobar" - "traefik.http.middlewares.middleware09.forwardauth.authrequestheaders=foobar, foobar" - "traefik.http.middlewares.middleware09.forwardauth.tls.ca=foobar" - "traefik.http.middlewares.middleware09.forwardauth.tls.caoptional=true" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 2fc955ba8..37c7daafe 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -139,6 +139,7 @@ address = "foobar" trustForwardHeader = true authResponseHeaders = ["foobar", "foobar"] + authResponseHeadersRegex = "foobar" authRequestHeaders = ["foobar", "foobar"] [http.middlewares.Middleware09.forwardAuth.tls] ca = "foobar" diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index 08bc53795..cd8359311 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -158,6 +158,7 @@ http: authResponseHeaders: - foobar - foobar + authResponseHeadersRegex: foobar authRequestHeaders: - foobar - foobar diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index 2e6c012ff..f7db9f449 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -31,6 +31,7 @@ | `traefik/http/middlewares/Middleware09/forwardAuth/authRequestHeaders/1` | `foobar` | | `traefik/http/middlewares/Middleware09/forwardAuth/authResponseHeaders/0` | `foobar` | | `traefik/http/middlewares/Middleware09/forwardAuth/authResponseHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware09/forwardAuth/authResponseHeadersRegex` | `foobar` | | `traefik/http/middlewares/Middleware09/forwardAuth/tls/ca` | `foobar` | | `traefik/http/middlewares/Middleware09/forwardAuth/tls/caOptional` | `true` | | `traefik/http/middlewares/Middleware09/forwardAuth/tls/cert` | `foobar` | diff --git a/docs/content/reference/dynamic-configuration/marathon-labels.json b/docs/content/reference/dynamic-configuration/marathon-labels.json index a67628229..99e111b50 100644 --- a/docs/content/reference/dynamic-configuration/marathon-labels.json +++ b/docs/content/reference/dynamic-configuration/marathon-labels.json @@ -24,6 +24,7 @@ "traefik.http.middlewares.middleware08.errors.status": "foobar, foobar", "traefik.http.middlewares.middleware09.forwardauth.address": "foobar", "traefik.http.middlewares.middleware09.forwardauth.authresponseheaders": "foobar, foobar", +"traefik.http.middlewares.middleware09.forwardauth.authresponseheadersregex": "foobar", "traefik.http.middlewares.middleware09.forwardauth.authrequestheaders": "foobar, foobar", "traefik.http.middlewares.middleware09.forwardauth.tls.ca": "foobar", "traefik.http.middlewares.middleware09.forwardauth.tls.caoptional": "true", diff --git a/pkg/config/dynamic/fixtures/sample.toml b/pkg/config/dynamic/fixtures/sample.toml index ca69b7925..ff3f7735e 100644 --- a/pkg/config/dynamic/fixtures/sample.toml +++ b/pkg/config/dynamic/fixtures/sample.toml @@ -288,6 +288,7 @@ address = "foobar" trustForwardHeader = true authResponseHeaders = ["foobar", "foobar"] + authResponseHeadersRegex = "foobar" authRequestHeaders = ["foobar", "foobar"] [http.middlewares.Middleware15.forwardAuth.tls] ca = "foobar" diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index eec7ea88a..3f522907c 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -140,11 +140,12 @@ type ErrorPage struct { // ForwardAuth holds the http forward authentication configuration. type ForwardAuth struct { - Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` - TLS *ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty"` - TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"` - AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty"` - AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty"` + Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` + TLS *ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty"` + TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"` + AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty"` + AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty" toml:"authResponseHeadersRegex,omitempty" yaml:"authResponseHeadersRegex,omitempty"` + AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty"` } // +k8s:deepcopy-gen=true diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index 38ba9ed0a..9ddd8d35f 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "net" "net/http" + "regexp" "strings" "time" @@ -25,13 +26,14 @@ const ( ) type forwardAuth struct { - address string - authResponseHeaders []string - next http.Handler - name string - client http.Client - trustForwardHeader bool - authRequestHeaders []string + address string + authResponseHeaders []string + authResponseHeadersRegex *regexp.Regexp + next http.Handler + name string + client http.Client + trustForwardHeader bool + authRequestHeaders []string } // NewForward creates a forward auth middleware. @@ -66,6 +68,14 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu fa.client.Transport = tr } + if config.AuthResponseHeadersRegex != "" { + re, err := regexp.Compile(config.AuthResponseHeadersRegex) + if err != nil { + return nil, fmt.Errorf("error compiling regular expression %s: %w", config.AuthResponseHeadersRegex, err) + } + fa.authResponseHeadersRegex = re + } + return fa, nil } @@ -156,6 +166,20 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } + if fa.authResponseHeadersRegex != nil { + for headerKey := range req.Header { + if fa.authResponseHeadersRegex.MatchString(headerKey) { + req.Header.Del(headerKey) + } + } + + for headerKey, headerValues := range forwardResponse.Header { + if fa.authResponseHeadersRegex.MatchString(headerKey) { + req.Header[headerKey] = append([]string(nil), headerValues...) + } + } + } + req.RequestURI = req.URL.RequestURI() fa.next.ServeHTTP(rw, req) } diff --git a/pkg/middlewares/auth/forward_test.go b/pkg/middlewares/auth/forward_test.go index 0ce8bbf9e..65e6ad9cd 100644 --- a/pkg/middlewares/auth/forward_test.go +++ b/pkg/middlewares/auth/forward_test.go @@ -57,6 +57,7 @@ func TestForwardAuthSuccess(t *testing.T) { w.Header().Set("X-Auth-Secret", "secret") w.Header().Add("X-Auth-Group", "group1") w.Header().Add("X-Auth-Group", "group2") + w.Header().Add("Foo-Bar", "auth-value") fmt.Fprintln(w, "Success") })) t.Cleanup(server.Close) @@ -65,12 +66,15 @@ func TestForwardAuthSuccess(t *testing.T) { assert.Equal(t, "user@example.com", r.Header.Get("X-Auth-User")) assert.Empty(t, r.Header.Get("X-Auth-Secret")) assert.Equal(t, []string{"group1", "group2"}, r.Header["X-Auth-Group"]) + assert.Equal(t, "auth-value", r.Header.Get("Foo-Bar")) + assert.Empty(t, r.Header.Get("Foo-Baz")) fmt.Fprintln(w, "traefik") }) auth := dynamic.ForwardAuth{ - Address: server.URL, - AuthResponseHeaders: []string{"X-Auth-User", "X-Auth-Group"}, + Address: server.URL, + AuthResponseHeaders: []string{"X-Auth-User", "X-Auth-Group"}, + AuthResponseHeadersRegex: "^Foo-", } middleware, err := NewForward(context.Background(), next, auth, "authTest") require.NoError(t, err) @@ -80,6 +84,8 @@ func TestForwardAuthSuccess(t *testing.T) { req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil) req.Header.Set("X-Auth-Group", "admin_group") + req.Header.Set("Foo-Bar", "client-value") + req.Header.Set("Foo-Baz", "client-value") res, err := http.DefaultClient.Do(req) require.NoError(t, err) assert.Equal(t, http.StatusOK, res.StatusCode) diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index b30524ab7..811ef0bc0 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -373,10 +373,11 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alp } forwardAuth := &dynamic.ForwardAuth{ - Address: auth.Address, - TrustForwardHeader: auth.TrustForwardHeader, - AuthResponseHeaders: auth.AuthResponseHeaders, - AuthRequestHeaders: auth.AuthRequestHeaders, + Address: auth.Address, + TrustForwardHeader: auth.TrustForwardHeader, + AuthResponseHeaders: auth.AuthResponseHeaders, + AuthResponseHeadersRegex: auth.AuthResponseHeadersRegex, + AuthRequestHeaders: auth.AuthRequestHeaders, } if auth.TLS == nil { diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go index 450f5bad9..07e96611f 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go @@ -85,11 +85,12 @@ type DigestAuth struct { // ForwardAuth holds the http forward authentication configuration. type ForwardAuth struct { - Address string `json:"address,omitempty"` - TrustForwardHeader bool `json:"trustForwardHeader,omitempty"` - AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"` - AuthRequestHeaders []string `json:"authRequestHeaders,omitempty"` - TLS *ClientTLS `json:"tls,omitempty"` + Address string `json:"address,omitempty"` + TrustForwardHeader bool `json:"trustForwardHeader,omitempty"` + AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"` + AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty"` + AuthRequestHeaders []string `json:"authRequestHeaders,omitempty"` + TLS *ClientTLS `json:"tls,omitempty"` } // ClientTLS holds TLS specific configurations as client.