Filter ForwardAuth request headers

This commit is contained in:
Nikita Konev 2020-10-07 17:36:04 +03:00 committed by GitHub
parent f2e53a3569
commit 326be29568
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 171 additions and 3 deletions

View file

@ -217,6 +217,63 @@ http:
- "X-Secret" - "X-Secret"
``` ```
### `authRequestHeaders`
The `authRequestHeaders` option is the list of the headers to copy from the request to the authentication server.
It allows to prevent passing headers that have not to be passed to the authentication server.
If not set or empty then all request headers will be passed.
```yaml tab="Docker"
labels:
- "traefik.http.middlewares.test-auth.forwardauth.authRequestHeaders=Accept,X-CustomHeader"
```
```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-auth
spec:
forwardAuth:
address: https://example.com/auth
authRequestHeaders:
- "Accept"
- "X-CustomHeader"
```
```yaml tab="Consul Catalog"
- "traefik.http.middlewares.test-auth.forwardauth.authRequestHeaders=Accept,X-CustomHeader"
```
```json tab="Marathon"
"labels": {
"traefik.http.middlewares.test-auth.forwardauth.authRequestHeaders": "Accept,X-CustomHeader"
}
```
```yaml tab="Rancher"
labels:
- "traefik.http.middlewares.test-auth.forwardauth.authRequestHeaders=Accept,X-CustomHeader"
```
```toml tab="File (TOML)"
[http.middlewares]
[http.middlewares.test-auth.forwardAuth]
address = "https://example.com/auth"
authRequestHeaders = "Accept,X-CustomHeader"
```
```yaml tab="File (YAML)"
http:
middlewares:
test-auth:
forwardAuth:
address: "https://example.com/auth"
authRequestHeaders:
- "Accept"
- "X-CustomHeader"
```
### `tls` ### `tls`
The `tls` option is the TLS configuration from Traefik to the authentication server. The `tls` option is the TLS configuration from Traefik to the authentication server.

View file

@ -24,6 +24,7 @@
- "traefik.http.middlewares.middleware08.errors.status=foobar, foobar" - "traefik.http.middlewares.middleware08.errors.status=foobar, foobar"
- "traefik.http.middlewares.middleware09.forwardauth.address=foobar" - "traefik.http.middlewares.middleware09.forwardauth.address=foobar"
- "traefik.http.middlewares.middleware09.forwardauth.authresponseheaders=foobar, foobar" - "traefik.http.middlewares.middleware09.forwardauth.authresponseheaders=foobar, foobar"
- "traefik.http.middlewares.middleware09.forwardauth.authrequestheaders=foobar, foobar"
- "traefik.http.middlewares.middleware09.forwardauth.tls.ca=foobar" - "traefik.http.middlewares.middleware09.forwardauth.tls.ca=foobar"
- "traefik.http.middlewares.middleware09.forwardauth.tls.caoptional=true" - "traefik.http.middlewares.middleware09.forwardauth.tls.caoptional=true"
- "traefik.http.middlewares.middleware09.forwardauth.tls.cert=foobar" - "traefik.http.middlewares.middleware09.forwardauth.tls.cert=foobar"

View file

@ -139,6 +139,7 @@
address = "foobar" address = "foobar"
trustForwardHeader = true trustForwardHeader = true
authResponseHeaders = ["foobar", "foobar"] authResponseHeaders = ["foobar", "foobar"]
authRequestHeaders = ["foobar", "foobar"]
[http.middlewares.Middleware09.forwardAuth.tls] [http.middlewares.Middleware09.forwardAuth.tls]
ca = "foobar" ca = "foobar"
caOptional = true caOptional = true

View file

@ -158,6 +158,9 @@ http:
authResponseHeaders: authResponseHeaders:
- foobar - foobar
- foobar - foobar
authRequestHeaders:
- foobar
- foobar
Middleware10: Middleware10:
headers: headers:
customRequestHeaders: customRequestHeaders:

View file

@ -24,6 +24,7 @@
"traefik.http.middlewares.middleware08.errors.status": "foobar, foobar", "traefik.http.middlewares.middleware08.errors.status": "foobar, foobar",
"traefik.http.middlewares.middleware09.forwardauth.address": "foobar", "traefik.http.middlewares.middleware09.forwardauth.address": "foobar",
"traefik.http.middlewares.middleware09.forwardauth.authresponseheaders": "foobar, foobar", "traefik.http.middlewares.middleware09.forwardauth.authresponseheaders": "foobar, foobar",
"traefik.http.middlewares.middleware09.forwardauth.authrequestheaders": "foobar, foobar",
"traefik.http.middlewares.middleware09.forwardauth.tls.ca": "foobar", "traefik.http.middlewares.middleware09.forwardauth.tls.ca": "foobar",
"traefik.http.middlewares.middleware09.forwardauth.tls.caoptional": "true", "traefik.http.middlewares.middleware09.forwardauth.tls.caoptional": "true",
"traefik.http.middlewares.middleware09.forwardauth.tls.cert": "foobar", "traefik.http.middlewares.middleware09.forwardauth.tls.cert": "foobar",

View file

@ -288,6 +288,7 @@
address = "foobar" address = "foobar"
trustForwardHeader = true trustForwardHeader = true
authResponseHeaders = ["foobar", "foobar"] authResponseHeaders = ["foobar", "foobar"]
authRequestHeaders = ["foobar", "foobar"]
[http.middlewares.Middleware15.forwardAuth.tls] [http.middlewares.Middleware15.forwardAuth.tls]
ca = "foobar" ca = "foobar"
caOptional = true caOptional = true

View file

@ -144,6 +144,7 @@ type ForwardAuth struct {
TLS *ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,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"` TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"`
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty"` AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty"`
AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty"`
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true

View file

@ -344,6 +344,11 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.AuthRequestHeaders != nil {
in, out := &in.AuthRequestHeaders, &out.AuthRequestHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }

View file

@ -36,6 +36,7 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware6.errors.status": "foobar, fiibar", "traefik.http.middlewares.Middleware6.errors.status": "foobar, fiibar",
"traefik.http.middlewares.Middleware7.forwardauth.address": "foobar", "traefik.http.middlewares.Middleware7.forwardauth.address": "foobar",
"traefik.http.middlewares.Middleware7.forwardauth.authresponseheaders": "foobar, fiibar", "traefik.http.middlewares.Middleware7.forwardauth.authresponseheaders": "foobar, fiibar",
"traefik.http.middlewares.Middleware7.forwardauth.authrequestheaders": "foobar, fiibar",
"traefik.http.middlewares.Middleware7.forwardauth.tls.ca": "foobar", "traefik.http.middlewares.Middleware7.forwardauth.tls.ca": "foobar",
"traefik.http.middlewares.Middleware7.forwardauth.tls.caoptional": "true", "traefik.http.middlewares.Middleware7.forwardauth.tls.caoptional": "true",
"traefik.http.middlewares.Middleware7.forwardauth.tls.cert": "foobar", "traefik.http.middlewares.Middleware7.forwardauth.tls.cert": "foobar",
@ -496,6 +497,10 @@ func TestDecodeConfiguration(t *testing.T) {
"foobar", "foobar",
"fiibar", "fiibar",
}, },
AuthRequestHeaders: []string{
"foobar",
"fiibar",
},
}, },
}, },
"Middleware8": { "Middleware8": {
@ -964,6 +969,10 @@ func TestEncodeConfiguration(t *testing.T) {
"foobar", "foobar",
"fiibar", "fiibar",
}, },
AuthRequestHeaders: []string{
"foobar",
"fiibar",
},
}, },
}, },
"Middleware8": { "Middleware8": {
@ -1134,6 +1143,7 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware6.Errors.Status": "foobar, fiibar", "traefik.HTTP.Middlewares.Middleware6.Errors.Status": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.Address": "foobar", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.Address": "foobar",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthResponseHeaders": "foobar, fiibar", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthResponseHeaders": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.AuthRequestHeaders": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CA": "foobar", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CA": "foobar",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CAOptional": "true", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.CAOptional": "true",
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Cert": "foobar", "traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Cert": "foobar",

View file

@ -31,6 +31,7 @@ type forwardAuth struct {
name string name string
client http.Client client http.Client
trustForwardHeader bool trustForwardHeader bool
authRequestHeaders []string
} }
// NewForward creates a forward auth middleware. // NewForward creates a forward auth middleware.
@ -43,6 +44,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
next: next, next: next,
name: name, name: name,
trustForwardHeader: config.TrustForwardHeader, trustForwardHeader: config.TrustForwardHeader,
authRequestHeaders: config.AuthRequestHeaders,
} }
// Ensure our request client does not follow redirects // Ensure our request client does not follow redirects
@ -89,7 +91,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// forwardReq. // forwardReq.
tracing.InjectRequestHeaders(req) tracing.InjectRequestHeaders(req)
writeHeader(req, forwardReq, fa.trustForwardHeader) writeHeader(req, forwardReq, fa.trustForwardHeader, fa.authRequestHeaders)
forwardResponse, forwardErr := fa.client.Do(forwardReq) forwardResponse, forwardErr := fa.client.Do(forwardReq)
if forwardErr != nil { if forwardErr != nil {
@ -158,10 +160,12 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
fa.next.ServeHTTP(rw, req) fa.next.ServeHTTP(rw, req)
} }
func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool) { func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool, allowedHeaders []string) {
utils.CopyHeaders(forwardReq.Header, req.Header) utils.CopyHeaders(forwardReq.Header, req.Header)
utils.RemoveHeaders(forwardReq.Header, forward.HopHeaders...) utils.RemoveHeaders(forwardReq.Header, forward.HopHeaders...)
forwardReq.Header = filterForwardRequestHeaders(forwardReq.Header, allowedHeaders)
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
if trustForwardHeader { if trustForwardHeader {
if prior, ok := req.Header[forward.XForwardedFor]; ok { if prior, ok := req.Header[forward.XForwardedFor]; ok {
@ -215,3 +219,19 @@ func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool) {
forwardReq.Header.Del(xForwardedURI) forwardReq.Header.Del(xForwardedURI)
} }
} }
func filterForwardRequestHeaders(forwardRequestHeaders http.Header, allowedHeaders []string) http.Header {
if len(allowedHeaders) == 0 {
return forwardRequestHeaders
}
filteredHeaders := http.Header{}
for _, headerName := range allowedHeaders {
values := forwardRequestHeaders.Values(headerName)
if len(values) > 0 {
filteredHeaders[http.CanonicalHeaderKey(headerName)] = append([]string(nil), values...)
}
}
return filteredHeaders
}

View file

@ -242,6 +242,7 @@ func Test_writeHeader(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
headers map[string]string headers map[string]string
authRequestHeaders []string
trustForwardHeader bool trustForwardHeader bool
emptyHost bool emptyHost bool
expectedHeaders map[string]string expectedHeaders map[string]string
@ -368,6 +369,45 @@ func Test_writeHeader(t *testing.T) {
}, },
checkForUnexpectedHeaders: true, checkForUnexpectedHeaders: true,
}, },
{
name: "filter forward request headers",
headers: map[string]string{
"X-CustomHeader": "CustomHeader",
"Content-Type": "multipart/form-data; boundary=---123456",
},
authRequestHeaders: []string{
"X-CustomHeader",
},
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",
},
checkForUnexpectedHeaders: true,
},
{
name: "filter forward request headers doesn't add new headers",
headers: map[string]string{
"X-CustomHeader": "CustomHeader",
"Content-Type": "multipart/form-data; boundary=---123456",
},
authRequestHeaders: []string{
"X-CustomHeader",
"X-Non-Exists-Header",
},
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",
},
checkForUnexpectedHeaders: true,
},
} }
for _, test := range testCases { for _, test := range testCases {
@ -383,9 +423,10 @@ func Test_writeHeader(t *testing.T) {
forwardReq := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/path?q=1", nil) forwardReq := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/path?q=1", nil)
writeHeader(req, forwardReq, test.trustForwardHeader) writeHeader(req, forwardReq, test.trustForwardHeader, test.authRequestHeaders)
actualHeaders := forwardReq.Header actualHeaders := forwardReq.Header
expectedHeaders := test.expectedHeaders expectedHeaders := test.expectedHeaders
for key, value := range expectedHeaders { for key, value := range expectedHeaders {
assert.Equal(t, value, actualHeaders.Get(key)) assert.Equal(t, value, actualHeaders.Get(key))

View file

@ -376,6 +376,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alp
Address: auth.Address, Address: auth.Address,
TrustForwardHeader: auth.TrustForwardHeader, TrustForwardHeader: auth.TrustForwardHeader,
AuthResponseHeaders: auth.AuthResponseHeaders, AuthResponseHeaders: auth.AuthResponseHeaders,
AuthRequestHeaders: auth.AuthRequestHeaders,
} }
if auth.TLS == nil { if auth.TLS == nil {

View file

@ -88,6 +88,7 @@ type ForwardAuth struct {
Address string `json:"address,omitempty"` Address string `json:"address,omitempty"`
TrustForwardHeader bool `json:"trustForwardHeader,omitempty"` TrustForwardHeader bool `json:"trustForwardHeader,omitempty"`
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"` AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"`
AuthRequestHeaders []string `json:"authRequestHeaders,omitempty"`
TLS *ClientTLS `json:"tls,omitempty"` TLS *ClientTLS `json:"tls,omitempty"`
} }

View file

@ -171,6 +171,11 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
*out = make([]string, len(*in)) *out = make([]string, len(*in))
copy(*out, *in) copy(*out, *in)
} }
if in.AuthRequestHeaders != nil {
in, out := &in.AuthRequestHeaders, &out.AuthRequestHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.TLS != nil { if in.TLS != nil {
in, out := &in.TLS, &out.TLS in, out := &in.TLS, &out.TLS
*out = new(ClientTLS) *out = new(ClientTLS)

View file

@ -71,6 +71,8 @@ func Test_buildConfiguration(t *testing.T) {
"traefik/http/services/Service03/weighted/services/1/weight": "42", "traefik/http/services/Service03/weighted/services/1/weight": "42",
"traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/0": "foobar", "traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/0": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/1": "foobar", "traefik/http/middlewares/Middleware08/forwardAuth/authResponseHeaders/1": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/authRequestHeaders/0": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/authRequestHeaders/1": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/key": "foobar", "traefik/http/middlewares/Middleware08/forwardAuth/tls/key": "foobar",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/insecureSkipVerify": "true", "traefik/http/middlewares/Middleware08/forwardAuth/tls/insecureSkipVerify": "true",
"traefik/http/middlewares/Middleware08/forwardAuth/tls/ca": "foobar", "traefik/http/middlewares/Middleware08/forwardAuth/tls/ca": "foobar",
@ -407,6 +409,10 @@ func Test_buildConfiguration(t *testing.T) {
"foobar", "foobar",
"foobar", "foobar",
}, },
AuthRequestHeaders: []string{
"foobar",
"foobar",
},
}, },
}, },
"Middleware06": { "Middleware06": {

View file

@ -305,6 +305,20 @@
</div> </div>
</div> </div>
</q-card-section> </q-card-section>
<!-- EXTRA FIELDS FROM MIDDLEWARES - [forwardAuth] - authRequestHeaders -->
<q-card-section v-if="middleware.forwardAuth">
<div class="row items-start no-wrap">
<div class="col">
<div class="text-subtitle2">Auth Request Headers</div>
<q-chip
v-for="(reqHeader, key) in exData(middleware).authRequestHeaders" :key="key"
dense
class="app-chip app-chip-green">
{{ reqHeader }}
</q-chip>
</div>
</div>
</q-card-section>
<!-- EXTRA FIELDS FROM MIDDLEWARES - [headers] - customRequestHeaders --> <!-- EXTRA FIELDS FROM MIDDLEWARES - [headers] - customRequestHeaders -->
<q-card-section v-if="middleware.headers"> <q-card-section v-if="middleware.headers">