Add authSignInURL in forward auth middleware

This commit is contained in:
kyounghoonJang
2026-01-26 18:12:05 +09:00
committed by GitHub
parent 94eba471f1
commit 27912e3849
12 changed files with 144 additions and 19 deletions
@@ -1434,6 +1434,10 @@ spec:
AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.
More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex
type: string
authSigninURL:
description: AuthSigninURL specifies the URL to redirect to when
the authentication server returns 401 Unauthorized.
type: string
forwardBody:
description: ForwardBody defines whether to send the request body
to the authentication server.
@@ -589,6 +589,10 @@ spec:
AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.
More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex
type: string
authSigninURL:
description: AuthSigninURL specifies the URL to redirect to when
the authentication server returns 401 Unauthorized.
type: string
forwardBody:
description: ForwardBody defines whether to send the request body
to the authentication server.
@@ -53,25 +53,26 @@ spec:
## Configuration Options
| Field | Description | Default | Required |
|:-----------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
| <a id="opt-address" href="#opt-address" title="#opt-address">`address`</a> | Authentication server address. | "" | Yes |
| <a id="opt-trustForwardHeader" href="#opt-trustForwardHeader" title="#opt-trustForwardHeader">`trustForwardHeader`</a> | Trust all `X-Forwarded-*` headers. | false | No |
| <a id="opt-authResponseHeaders" href="#opt-authResponseHeaders" title="#opt-authResponseHeaders">`authResponseHeaders`</a> | List of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers. | [] | No |
| <a id="opt-authResponseHeadersRegex" href="#opt-authResponseHeadersRegex" title="#opt-authResponseHeadersRegex">`authResponseHeadersRegex`</a> | Regex to match by the headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.<br /> More information [here](#authresponseheadersregex). | "" | No |
| <a id="opt-authRequestHeaders" href="#opt-authRequestHeaders" title="#opt-authRequestHeaders">`authRequestHeaders`</a> | List of the headers to copy from the request to the authentication server. <br /> It allows filtering headers that should not be passed to the authentication server. <br /> If not set or empty, then all request headers are passed. | [] | No |
| <a id="opt-addAuthCookiesToResponse" href="#opt-addAuthCookiesToResponse" title="#opt-addAuthCookiesToResponse">`addAuthCookiesToResponse`</a> | List of cookies to copy from the authentication server to the response, replacing any existing conflicting cookie from the forwarded response.<br /> Please note that all backend cookies matching the configured list will not be added to the response. | [] | No |
| <a id="opt-forwardBody" href="#opt-forwardBody" title="#opt-forwardBody">`forwardBody`</a> | Sets the `forwardBody` option to `true` to send the Body. As body is read inside Traefik before forwarding, this breaks streaming. | false | No |
| <a id="opt-maxBodySize" href="#opt-maxBodySize" title="#opt-maxBodySize">`maxBodySize`</a> | Set the `maxBodySize` to limit the body size in bytes. If body is bigger than this, it returns a 401 (unauthorized). If left unset, the request body size is unrestricted which can have performance or security implications. < br/>More information [here](#maxbodysize).| -1 | No |
| <a id="opt-headerField" href="#opt-headerField" title="#opt-headerField">`headerField`</a> | Defines a header field to store the authenticated user. | "" | No |
| <a id="opt-preserveLocationHeader" href="#opt-preserveLocationHeader" title="#opt-preserveLocationHeader">`preserveLocationHeader`</a> | Defines whether to forward the Location header to the client as is or prefix it with the domain name of the authentication server. | false | No |
| <a id="opt-preserveRequestMethod" href="#opt-preserveRequestMethod" title="#opt-preserveRequestMethod">`preserveRequestMethod`</a> | Defines whether to preserve the original request method while forwarding the request to the authentication server. | false | No |
| <a id="opt-tls-ca" href="#opt-tls-ca" title="#opt-tls-ca">`tls.ca`</a> | Sets the path to the certificate authority used for the secured connection to the authentication server, it defaults to the system bundle. | "" | No |
| <a id="opt-tls-cert" href="#opt-tls-cert" title="#opt-tls-cert">`tls.cert`</a> | Sets the path to the public certificate used for the secure connection to the authentication server. When using this option, setting the key option is required. | "" | No |
| <a id="opt-tls-key" href="#opt-tls-key" title="#opt-tls-key">`tls.key`</a> | Sets the path to the private key used for the secure connection to the authentication server. When using this option, setting the `cert` option is required. | "" | No |
| <a id="opt-tls-caSecret" href="#opt-tls-caSecret" title="#opt-tls-caSecret">`tls.caSecret`</a> | Defines the secret that contains the certificate authority used for the secured connection to the authentication server, it defaults to the system bundle. **This option is only available for the Kubernetes CRD**. | | No |
| <a id="opt-tls-certSecret" href="#opt-tls-certSecret" title="#opt-tls-certSecret">`tls.certSecret`</a> | Defines the secret that contains both the private and public certificates used for the secure connection to the authentication server. **This option is only available for the Kubernetes CRD**. | | No |
| <a id="opt-tls-insecureSkipVerify" href="#opt-tls-insecureSkipVerify" title="#opt-tls-insecureSkipVerify">`tls.insecureSkipVerify`</a> | During TLS connections, if this option is set to `true`, the authentication server will accept any certificate presented by the server regardless of the host names it covers. | false | No |
| Field | Description | Default | Required |
|:-----------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
| <a id="opt-address" href="#opt-address" title="#opt-address">`address`</a> | Authentication server address. | "" | Yes |
| <a id="opt-trustForwardHeader" href="#opt-trustForwardHeader" title="#opt-trustForwardHeader">`trustForwardHeader`</a> | Trust all `X-Forwarded-*` headers. | false | No |
| <a id="opt-authResponseHeaders" href="#opt-authResponseHeaders" title="#opt-authResponseHeaders">`authResponseHeaders`</a> | List of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers. | [] | No |
| <a id="opt-authResponseHeadersRegex" href="#opt-authResponseHeadersRegex" title="#opt-authResponseHeadersRegex">`authResponseHeadersRegex`</a> | Regex to match by the headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.<br /> More information [here](#authresponseheadersregex). | "" | No |
| <a id="opt-authRequestHeaders" href="#opt-authRequestHeaders" title="#opt-authRequestHeaders">`authRequestHeaders`</a> | List of the headers to copy from the request to the authentication server. <br /> It allows filtering headers that should not be passed to the authentication server. <br /> If not set or empty, then all request headers are passed. | [] | No |
| <a id="opt-addAuthCookiesToResponse" href="#opt-addAuthCookiesToResponse" title="#opt-addAuthCookiesToResponse">`addAuthCookiesToResponse`</a> | List of cookies to copy from the authentication server to the response, replacing any existing conflicting cookie from the forwarded response.<br /> Please note that all backend cookies matching the configured list will not be added to the response. | [] | No |
| <a id="opt-forwardBody" href="#opt-forwardBody" title="#opt-forwardBody">`forwardBody`</a> | Sets the `forwardBody` option to `true` to send the Body. As body is read inside Traefik before forwarding, this breaks streaming. | false | No |
| <a id="opt-maxBodySize" href="#opt-maxBodySize" title="#opt-maxBodySize">`maxBodySize`</a> | Set the `maxBodySize` to limit the body size in bytes. If body is bigger than this, it returns a 401 (unauthorized). If left unset, the request body size is unrestricted which can have performance or security implications. < br/>More information [here](#maxbodysize). | -1 | No |
| <a id="opt-headerField" href="#opt-headerField" title="#opt-headerField">`headerField`</a> | Defines a header field to store the authenticated user. | "" | No |
| <a id="opt-preserveLocationHeader" href="#opt-preserveLocationHeader" title="#opt-preserveLocationHeader">`preserveLocationHeader`</a> | Defines whether to forward the Location header to the client as is or prefix it with the domain name of the authentication server. | false | No |
| <a id="opt-preserveRequestMethod" href="#opt-preserveRequestMethod" title="#opt-preserveRequestMethod">`preserveRequestMethod`</a> | Defines whether to preserve the original request method while forwarding the request to the authentication server. | false | No |
| <a id="opt-authSigninURL" href="#opt-authSigninURL" title="#opt-authSigninURL">`authSigninURL`</a> | Specifies the URL to redirect to when the authentication server returns 401 Unauthorized. | "" | No |
| <a id="opt-tls-ca" href="#opt-tls-ca" title="#opt-tls-ca">`tls.ca`</a> | Sets the path to the certificate authority used for the secured connection to the authentication server, it defaults to the system bundle. | "" | No |
| <a id="opt-tls-cert" href="#opt-tls-cert" title="#opt-tls-cert">`tls.cert`</a> | Sets the path to the public certificate used for the secure connection to the authentication server. When using this option, setting the key option is required. | "" | No |
| <a id="opt-tls-key" href="#opt-tls-key" title="#opt-tls-key">`tls.key`</a> | Sets the path to the private key used for the secure connection to the authentication server. When using this option, setting the `cert` option is required. | "" | No |
| <a id="opt-tls-caSecret" href="#opt-tls-caSecret" title="#opt-tls-caSecret">`tls.caSecret`</a> | Defines the secret that contains the certificate authority used for the secured connection to the authentication server, it defaults to the system bundle. **This option is only available for the Kubernetes CRD**. | | No |
| <a id="opt-tls-certSecret" href="#opt-tls-certSecret" title="#opt-tls-certSecret">`tls.certSecret`</a> | Defines the secret that contains both the private and public certificates used for the secure connection to the authentication server. **This option is only available for the Kubernetes CRD**. | | No |
| <a id="opt-tls-insecureSkipVerify" href="#opt-tls-insecureSkipVerify" title="#opt-tls-insecureSkipVerify">`tls.insecureSkipVerify`</a> | During TLS connections, if this option is set to `true`, the authentication server will accept any certificate presented by the server regardless of the host names it covers. | false | No |
### authResponseHeadersRegex
@@ -220,6 +220,7 @@
maxBodySize = 42
preserveLocationHeader = true
preserveRequestMethod = true
authSigninURL = "foobar"
[http.middlewares.Middleware11.forwardAuth.tls]
ca = "foobar"
cert = "foobar"
@@ -248,6 +248,7 @@ http:
maxBodySize: 42
preserveLocationHeader: true
preserveRequestMethod: true
authSigninURL: foobar
Middleware12:
grpcWeb:
allowOrigins:
@@ -1435,6 +1435,10 @@ spec:
AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.
More info: https://doc.traefik.io/traefik/v3.6/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex
type: string
authSigninURL:
description: AuthSigninURL specifies the URL to redirect to when
the authentication server returns 401 Unauthorized.
type: string
forwardBody:
description: ForwardBody defines whether to send the request body
to the authentication server.
+2
View File
@@ -292,6 +292,8 @@ type ForwardAuth struct {
PreserveLocationHeader bool `json:"preserveLocationHeader,omitempty" toml:"preserveLocationHeader,omitempty" yaml:"preserveLocationHeader,omitempty" export:"true"`
// PreserveRequestMethod defines whether to preserve the original request method while forwarding the request to the authentication server.
PreserveRequestMethod bool `json:"preserveRequestMethod,omitempty" toml:"preserveRequestMethod,omitempty" yaml:"preserveRequestMethod,omitempty" export:"true"`
// AuthSigninURL specifies the URL to redirect to when the authentication server returns 401 Unauthorized.
AuthSigninURL string `json:"authSigninURL,omitempty" toml:"authSigninURL,omitempty" yaml:"authSigninURL,omitempty" export:"true"`
}
func (f *ForwardAuth) SetDefaults() {
+11
View File
@@ -59,6 +59,7 @@ type forwardAuth struct {
maxBodySize int64
preserveLocationHeader bool
preserveRequestMethod bool
authSigninURL string
}
// NewForward creates a forward auth middleware.
@@ -84,6 +85,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
maxBodySize: dynamic.ForwardAuthDefaultMaxBodySize,
preserveLocationHeader: config.PreserveLocationHeader,
preserveRequestMethod: config.PreserveRequestMethod,
authSigninURL: config.AuthSigninURL,
}
if config.MaxBodySize != nil {
@@ -232,6 +234,15 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}
}
// If auth server returns 401 and AuthSigninURL is configured, redirect to signin URL.
if fa.authSigninURL != "" && forwardResponse.StatusCode == http.StatusUnauthorized {
logger.Debug().Msgf("Redirecting to signin URL: %s", fa.authSigninURL)
tracer.CaptureResponse(forwardSpan, forwardResponse.Header, http.StatusFound, trace.SpanKindClient)
http.Redirect(rw, req, fa.authSigninURL, http.StatusFound)
return
}
// Pass the forward response's body and selected headers if it
// didn't return a response within the range of [200, 300).
if forwardResponse.StatusCode < http.StatusOK || forwardResponse.StatusCode >= http.StatusMultipleChoices {
+85
View File
@@ -872,6 +872,91 @@ func TestForwardAuthPreserveRequestMethod(t *testing.T) {
}
}
func TestForwardAuthAuthSigninURL(t *testing.T) {
testCases := []struct {
desc string
authSigninURL string
authServerStatus int
expectedStatus int
expectedLocation string
nextShouldBeCalled bool
}{
{
desc: "redirects to signin URL on 401",
authSigninURL: "https://auth.example.com/login",
authServerStatus: http.StatusUnauthorized,
expectedStatus: http.StatusFound,
expectedLocation: "https://auth.example.com/login",
nextShouldBeCalled: false,
},
{
desc: "no redirect on 401 without signin URL",
authServerStatus: http.StatusUnauthorized,
expectedStatus: http.StatusUnauthorized,
nextShouldBeCalled: false,
},
{
desc: "no redirect on other error statuses with signin URL",
authSigninURL: "https://auth.example.com/login",
authServerStatus: http.StatusForbidden,
expectedStatus: http.StatusForbidden,
nextShouldBeCalled: false,
},
{
desc: "no redirect on OK status with signin URL",
authSigninURL: "https://auth.example.com/login",
authServerStatus: http.StatusOK,
expectedStatus: http.StatusOK,
nextShouldBeCalled: true,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
authServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(test.authServerStatus), test.authServerStatus)
}))
t.Cleanup(authServer.Close)
nextCalled := false
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCalled = true
})
auth := dynamic.ForwardAuth{
Address: authServer.URL,
AuthSigninURL: test.authSigninURL,
}
middleware, err := NewForward(t.Context(), next, auth, "authTest")
require.NoError(t, err)
ts := httptest.NewServer(middleware)
t.Cleanup(ts.Close)
client := &http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
res, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, test.expectedStatus, res.StatusCode)
assert.Equal(t, test.nextShouldBeCalled, nextCalled)
if test.expectedLocation != "" {
location, err := res.Location()
require.NoError(t, err)
assert.Equal(t, test.expectedLocation, location.String())
} else {
assert.Empty(t, res.Header.Get("Location"))
}
})
}
}
type mockTracer struct {
embedded.Tracer
@@ -41,6 +41,7 @@ type ForwardAuthApplyConfiguration struct {
MaxBodySize *int64 `json:"maxBodySize,omitempty"`
PreserveLocationHeader *bool `json:"preserveLocationHeader,omitempty"`
PreserveRequestMethod *bool `json:"preserveRequestMethod,omitempty"`
AuthSigninURL *string `json:"authSigninURL,omitempty"`
}
// ForwardAuthApplyConfiguration constructs a declarative configuration of the ForwardAuth type for use with
@@ -150,3 +151,11 @@ func (b *ForwardAuthApplyConfiguration) WithPreserveRequestMethod(value bool) *F
b.PreserveRequestMethod = &value
return b
}
// WithAuthSigninURL sets the AuthSigninURL field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the AuthSigninURL field is set to the value of the last call.
func (b *ForwardAuthApplyConfiguration) WithAuthSigninURL(value string) *ForwardAuthApplyConfiguration {
b.AuthSigninURL = &value
return b
}
@@ -1011,6 +1011,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef
ForwardBody: auth.ForwardBody,
PreserveLocationHeader: auth.PreserveLocationHeader,
PreserveRequestMethod: auth.PreserveRequestMethod,
AuthSigninURL: auth.AuthSigninURL,
}
forwardAuth.SetDefaults()
@@ -185,6 +185,8 @@ type ForwardAuth struct {
PreserveLocationHeader bool `json:"preserveLocationHeader,omitempty"`
// PreserveRequestMethod defines whether to preserve the original request method while forwarding the request to the authentication server.
PreserveRequestMethod bool `json:"preserveRequestMethod,omitempty"`
// AuthSigninURL specifies the URL to redirect to when the authentication server returns 401 Unauthorized.
AuthSigninURL string `json:"authSigninURL,omitempty"`
}
// +k8s:deepcopy-gen=true