diff --git a/compose/lite/docker-compose.yml b/compose/lite/docker-compose.yml index d0e9f7619..dec8386b3 100644 --- a/compose/lite/docker-compose.yml +++ b/compose/lite/docker-compose.yml @@ -20,7 +20,7 @@ services: - 'traefik.http.routers.authelia.tls.certresolver=letsencrypt' - 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.example.com' - 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' - - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups' + - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email' expose: - 9091 restart: unless-stopped diff --git a/compose/local/docker-compose.yml b/compose/local/docker-compose.yml index 4c3908e6f..5e84f6a0f 100644 --- a/compose/local/docker-compose.yml +++ b/compose/local/docker-compose.yml @@ -20,7 +20,7 @@ services: - 'traefik.http.routers.authelia.tls.options=default' - 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://authelia.example.com' - 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' - - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups' + - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email' expose: - 9091 restart: unless-stopped diff --git a/docs/deployment/supported-proxies/haproxy.md b/docs/deployment/supported-proxies/haproxy.md index e1fc03b00..82947f805 100644 --- a/docs/deployment/supported-proxies/haproxy.md +++ b/docs/deployment/supported-proxies/haproxy.md @@ -114,11 +114,15 @@ backend be_authelia server authelia authelia:9091 backend be_nextcloud - # Pass Remote-User and Remote-Groups headers + # Pass Remote-User, Remote-Name, Remote-Email and Remote-Groups headers acl remote_user_exist var(req.auth_response_header.remote_user) -m found acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found + acl remote_name_exist var(req.auth_response_header.remote_name) -m found + acl remote_email_exist var(req.auth_response_header.remote_email) -m found http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist + http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist + http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist server nextcloud nextcloud:443 ssl verify none ``` @@ -179,11 +183,15 @@ listen authelia_proxy server authelia authelia:9091 ssl verify none backend be_nextcloud - # Pass Remote-User and Remote-Groups headers + # Pass Remote-User, Remote-Name, Remote-Email and Remote-Groups headers acl remote_user_exist var(req.auth_response_header.remote_user) -m found acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found + acl remote_name_exist var(req.auth_response_header.remote_name) -m found + acl remote_email_exist var(req.auth_response_header.remote_email) -m found http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist + http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist + http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist server nextcloud nextcloud:443 ssl verify none ``` diff --git a/docs/deployment/supported-proxies/index.md b/docs/deployment/supported-proxies/index.md index 4a5a375a4..b99658a07 100644 --- a/docs/deployment/supported-proxies/index.md +++ b/docs/deployment/supported-proxies/index.md @@ -35,8 +35,8 @@ appear in the configuration examples. ## How can the backend be aware of the authenticated users? -The only way Authelia can share information about the authenticated user currently is through the use of two HTTP headers: -`Remote-User` and `Remote-Groups`. +The only way Authelia can share information about the authenticated user currently is through the use of four HTTP headers: +`Remote-User`, `Remote-Name`, `Remote-Email` and `Remote-Groups`. Those headers are returned by Authelia on requests to `/api/verify` and must be forwarded by the reverse proxy to the backends needing them. The headers will be provided with each call to the backend once the user is authenticated. Please note that the backend must support the use of those headers to leverage that information, many diff --git a/docs/deployment/supported-proxies/nginx.md b/docs/deployment/supported-proxies/nginx.md index 111f17552..aada91c45 100644 --- a/docs/deployment/supported-proxies/nginx.md +++ b/docs/deployment/supported-proxies/nginx.md @@ -81,8 +81,12 @@ auth_request_set $target_url $scheme://$http_host$request_uri; # proxy. In the future, it's gonna be safe to just use OAuth. auth_request_set $user $upstream_http_remote_user; auth_request_set $groups $upstream_http_remote_groups; +auth_request_set $name $upstream_http_remote_name; +auth_request_set $email $upstream_http_remote_email; proxy_set_header Remote-User $user; proxy_set_header Remote-Groups $groups; +proxy_set_header Remote-Name $name; +proxy_set_header Remote-Email $email; # If Authelia returns 401, then nginx redirects the user to the login portal. # If it returns 200, then the request pass through to the backend. # For other type of errors, nginx will handle them as usual. diff --git a/docs/deployment/supported-proxies/traefik1.x.md b/docs/deployment/supported-proxies/traefik1.x.md index 0d84dc1fc..a5d27c730 100644 --- a/docs/deployment/supported-proxies/traefik1.x.md +++ b/docs/deployment/supported-proxies/traefik1.x.md @@ -86,7 +86,7 @@ services: - 'traefik.frontend.rule=Host:nextcloud.example.com' - 'traefik.frontend.auth.forward.address=http://authelia:9091/api/verify?rd=https://login.example.com/' - 'traefik.frontend.auth.forward.trustForwardHeader=true' - - 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User,Remote-Groups' + - 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email' expose: - 443 restart: unless-stopped diff --git a/docs/deployment/supported-proxies/traefik2.x.md b/docs/deployment/supported-proxies/traefik2.x.md index aaf2deb55..7d632bea4 100644 --- a/docs/deployment/supported-proxies/traefik2.x.md +++ b/docs/deployment/supported-proxies/traefik2.x.md @@ -76,7 +76,7 @@ services: - 'traefik.http.routers.authelia.tls=true' - 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://login.example.com/' - 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' - - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups' + - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email' expose: - 9091 restart: unless-stopped @@ -120,7 +120,7 @@ This can be avoided a couple different ways: ```yaml - 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://login.example.com/' - 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' -- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups' +- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email' ``` [Traefik 2.x]: https://docs.traefik.io/ diff --git a/docs/security/threat-model.md b/docs/security/threat-model.md index 9a414f147..48419827b 100644 --- a/docs/security/threat-model.md +++ b/docs/security/threat-model.md @@ -47,5 +47,5 @@ If properly configured, Authelia guarantees the following for security of your u It's important to note that Authelia is considered running in a trusted environment for two reasons 1. Requests coming to Authelia should be initiated by reverse proxies but CAN be initiated by any other server currently. There is no trusted relationship between Authelia and the reverse proxy so an attacker within the network could abuse Authelia and attack it. -2. Your environment should be considered trusted especially if you're using the `Remote-User` and `Remote-Groups` headers to forward authentication data to your backends. These headers are transmitted plain and unsigned to the backends, meaning a malicious user within the network could pretend to be Authelia and send those headers to bypass authentication and gain access to the service. A mitigation could be to transmit those headers with a digital signature which could be verified by the backend however, many backends just don't support it. It has therefore been decided to invest on OpenID Connect instead to solve that authentication delegation problem. Indeed, many backends +2. Your environment should be considered trusted especially if you're using the `Remote-User`, `Remote-Name`, `Remote-Email` and `Remote-Groups` headers to forward authentication data to your backends. These headers are transmitted plain and unsigned to the backends, meaning a malicious user within the network could pretend to be Authelia and send those headers to bypass authentication and gain access to the service. A mitigation could be to transmit those headers with a digital signature which could be verified by the backend however, many backends just don't support it. It has therefore been decided to invest on OpenID Connect instead to solve that authentication delegation problem. Indeed, many backends do support OAuth2 though since it has become a standard lately. diff --git a/internal/handlers/const.go b/internal/handlers/const.go index eabeab4cb..6a8654b91 100644 --- a/internal/handlers/const.go +++ b/internal/handlers/const.go @@ -14,6 +14,8 @@ const authPrefix = "Basic " // AuthorizationHeader is the basic-auth HTTP header Authelia utilises. const AuthorizationHeader = "Proxy-Authorization" const remoteUserHeader = "Remote-User" +const remoteNameHeader = "Remote-Name" +const remoteEmailHeader = "Remote-Email" const remoteGroupsHeader = "Remote-Groups" var protoHostSeparator = []byte("://") diff --git a/internal/handlers/handler_verify.go b/internal/handlers/handler_verify.go index 6094ee5f7..961f87c2f 100644 --- a/internal/handlers/handler_verify.go +++ b/internal/handlers/handler_verify.go @@ -125,39 +125,41 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U // verifyBasicAuth verify that the provided username and password are correct and // that the user is authorized to target the resource. -func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCtx) (username string, groups []string, authLevel authentication.Level, err error) { //nolint:unparam +func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCtx) (username, name string, groups, emails []string, authLevel authentication.Level, err error) { //nolint:unparam username, password, err := parseBasicAuth(string(auth)) if err != nil { - return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to parse content of %s header: %s", AuthorizationHeader, err) + return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("Unable to parse content of %s header: %s", AuthorizationHeader, err) } authenticated, err := ctx.Providers.UserProvider.CheckUserPassword(username, password) if err != nil { - return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check credentials extracted from %s header: %s", AuthorizationHeader, err) + return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check credentials extracted from %s header: %s", AuthorizationHeader, err) } // If the user is not correctly authenticated, send a 401. if !authenticated { // Request Basic Authentication otherwise - return "", nil, authentication.NotAuthenticated, fmt.Errorf("User %s is not authenticated", username) + return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("User %s is not authenticated", username) } details, err := ctx.Providers.UserProvider.GetDetails(username) if err != nil { - return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to retrieve details of user %s: %s", username, err) + return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("Unable to retrieve details of user %s: %s", username, err) } - return username, details.Groups, authentication.OneFactor, nil + return username, details.DisplayName, details.Groups, details.Emails, authentication.OneFactor, nil } -// setForwardedHeaders set the forwarded User and Groups headers. -func setForwardedHeaders(headers *fasthttp.ResponseHeader, username string, groups []string) { +// setForwardedHeaders set the forwarded User, Groups, Name and Email headers. +func setForwardedHeaders(headers *fasthttp.ResponseHeader, username, name string, groups, emails []string) { if username != "" { headers.Set(remoteUserHeader, username) headers.Set(remoteGroupsHeader, strings.Join(groups, ",")) + headers.Set(remoteNameHeader, name) + headers.Set(remoteEmailHeader, emails[0]) } } @@ -183,28 +185,28 @@ func hasUserBeenInactiveTooLong(ctx *middlewares.AutheliaCtx) (bool, error) { // // verifySessionCookie verifies if a user is identified by a cookie. func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userSession *session.UserSession, refreshProfile bool, - refreshProfileInterval time.Duration) (username string, groups []string, authLevel authentication.Level, err error) { + refreshProfileInterval time.Duration) (username, name string, groups, emails []string, authLevel authentication.Level, err error) { // No username in the session means the user is anonymous. isUserAnonymous := userSession.Username == "" if isUserAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated { - return "", nil, authentication.NotAuthenticated, fmt.Errorf("An anonymous user cannot be authenticated. That might be the sign of a compromise") + return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("An anonymous user cannot be authenticated. That might be the sign of a compromise") } if !userSession.KeepMeLoggedIn && !isUserAnonymous { inactiveLongEnough, err := hasUserBeenInactiveTooLong(ctx) if err != nil { - return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check if user has been inactive for a long time: %s", err) + return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check if user has been inactive for a long time: %s", err) } if inactiveLongEnough { // Destroy the session a new one will be regenerated on next request. err := ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx) if err != nil { - return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to destroy user session after long inactivity: %s", err) + return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("Unable to destroy user session after long inactivity: %s", err) } - return userSession.Username, userSession.Groups, authentication.NotAuthenticated, fmt.Errorf("User %s has been inactive for too long", userSession.Username) + return userSession.Username, userSession.DisplayName, userSession.Groups, userSession.Emails, authentication.NotAuthenticated, fmt.Errorf("User %s has been inactive for too long", userSession.Username) } } @@ -216,13 +218,13 @@ func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userS ctx.Logger.Error(fmt.Errorf("Unable to destroy user session after provider refresh didn't find the user: %s", err)) } - return userSession.Username, userSession.Groups, authentication.NotAuthenticated, err + return userSession.Username, userSession.DisplayName, userSession.Groups, userSession.Emails, authentication.NotAuthenticated, err } ctx.Logger.Warnf("Error occurred while attempting to update user details from LDAP: %s", err) } - return userSession.Username, userSession.Groups, userSession.AuthenticationLevel, nil + return userSession.Username, userSession.DisplayName, userSession.Groups, userSession.Emails, userSession.AuthenticationLevel, nil } func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, username string) { @@ -409,9 +411,9 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques return } - var username string + var username, name string - var groups []string + var groups, emails []string var authLevel authentication.Level @@ -420,9 +422,9 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques userSession := ctx.GetSession() if isBasicAuth { - username, groups, authLevel, err = verifyBasicAuth(proxyAuthorization, *targetURL, ctx) + username, name, groups, emails, authLevel, err = verifyBasicAuth(proxyAuthorization, *targetURL, ctx) } else { - username, groups, authLevel, err = verifySessionCookie(ctx, targetURL, &userSession, + username, name, groups, emails, authLevel, err = verifySessionCookie(ctx, targetURL, &userSession, refreshProfile, refreshProfileInterval) } @@ -449,7 +451,7 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques case NotAuthorized: handleUnauthorized(ctx, targetURL, username) case Authorized: - setForwardedHeaders(&ctx.Response.Header, username, groups) + setForwardedHeaders(&ctx.Response.Header, username, name, groups, emails) } if err := updateActivityTimestamp(ctx, isBasicAuth, username); err != nil { diff --git a/internal/handlers/handler_verify_test.go b/internal/handlers/handler_verify_test.go index 9f03dedb1..06cfb744a 100644 --- a/internal/handlers/handler_verify_test.go +++ b/internal/handlers/handler_verify_test.go @@ -204,7 +204,7 @@ func TestShouldVerifyWrongCredentials(t *testing.T) { Return(false, nil) url, _ := url.ParseRequestURI("https://test.example.com") - _, _, _, err := verifyBasicAuth([]byte("Basic am9objpwYXNzd29yZA=="), *url, mock.Ctx) + _, _, _, _, _, err := verifyBasicAuth([]byte("Basic am9objpwYXNzd29yZA=="), *url, mock.Ctx) assert.Error(t, err) } @@ -416,6 +416,7 @@ func TestShouldVerifyFailingDetailsFetchingInBasicAuth(t *testing.T) { type Pair struct { URL string Username string + Emails []string AuthenticationLevel authentication.Level ExpectedStatusCode int } @@ -427,23 +428,23 @@ func (p Pair) String() string { func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) { testCases := []Pair{ - {"https://test.example.com", "", authentication.NotAuthenticated, 401}, - {"https://bypass.example.com", "", authentication.NotAuthenticated, 200}, - {"https://one-factor.example.com", "", authentication.NotAuthenticated, 401}, - {"https://two-factor.example.com", "", authentication.NotAuthenticated, 401}, - {"https://deny.example.com", "", authentication.NotAuthenticated, 401}, + {"https://test.example.com", "", nil, authentication.NotAuthenticated, 401}, + {"https://bypass.example.com", "", nil, authentication.NotAuthenticated, 200}, + {"https://one-factor.example.com", "", nil, authentication.NotAuthenticated, 401}, + {"https://two-factor.example.com", "", nil, authentication.NotAuthenticated, 401}, + {"https://deny.example.com", "", nil, authentication.NotAuthenticated, 401}, - {"https://test.example.com", "john", authentication.OneFactor, 403}, - {"https://bypass.example.com", "john", authentication.OneFactor, 200}, - {"https://one-factor.example.com", "john", authentication.OneFactor, 200}, - {"https://two-factor.example.com", "john", authentication.OneFactor, 401}, - {"https://deny.example.com", "john", authentication.OneFactor, 403}, + {"https://test.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 403}, + {"https://bypass.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 200}, + {"https://one-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 200}, + {"https://two-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 401}, + {"https://deny.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 403}, - {"https://test.example.com", "john", authentication.TwoFactor, 403}, - {"https://bypass.example.com", "john", authentication.TwoFactor, 200}, - {"https://one-factor.example.com", "john", authentication.TwoFactor, 200}, - {"https://two-factor.example.com", "john", authentication.TwoFactor, 200}, - {"https://deny.example.com", "john", authentication.TwoFactor, 403}, + {"https://test.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 403}, + {"https://bypass.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 200}, + {"https://one-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 200}, + {"https://two-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 200}, + {"https://deny.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 403}, } for _, testCase := range testCases { @@ -454,6 +455,7 @@ func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) { userSession := mock.Ctx.GetSession() userSession.Username = testCase.Username + userSession.Emails = testCase.Emails userSession.AuthenticationLevel = testCase.AuthenticationLevel mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. @@ -466,8 +468,10 @@ func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) { if testCase.ExpectedStatusCode == 200 && testCase.Username != "" { assert.Equal(t, []byte(testCase.Username), mock.Ctx.Response.Header.Peek("Remote-User")) + assert.Equal(t, []byte("john.doe@example.com"), mock.Ctx.Response.Header.Peek("Remote-Email")) } else { assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek("Remote-User")) + assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek("Remote-Email")) } }) } @@ -544,6 +548,7 @@ func TestShouldKeepSessionWhenUserCheckedRememberMeAndIsInactiveForTooLong(t *te userSession := mock.Ctx.GetSession() userSession.Username = testUsername + userSession.Emails = []string{"john.doe@example.com"} userSession.AuthenticationLevel = authentication.TwoFactor userSession.LastActivity = 0 userSession.KeepMeLoggedIn = true @@ -575,6 +580,7 @@ func TestShouldKeepSessionWhenInactivityTimeoutHasNotBeenExceeded(t *testing.T) userSession := mock.Ctx.GetSession() userSession.Username = testUsername + userSession.Emails = []string{"john.doe@example.com"} userSession.AuthenticationLevel = authentication.TwoFactor userSession.LastActivity = past.Unix() mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. diff --git a/internal/suites/HighAvailability/configuration.yml b/internal/suites/HighAvailability/configuration.yml index 3c4d1b592..78e5f570b 100644 --- a/internal/suites/HighAvailability/configuration.yml +++ b/internal/suites/HighAvailability/configuration.yml @@ -24,6 +24,7 @@ authentication_backend: groups_filter: (&(member={dn})(objectclass=groupOfNames)) group_name_attribute: cn mail_attribute: mail + display_name_attribute: displayName user: cn=admin,dc=example,dc=com password: password diff --git a/internal/suites/example/compose/haproxy/haproxy.cfg b/internal/suites/example/compose/haproxy/haproxy.cfg index d810e56d9..8845973fe 100644 --- a/internal/suites/example/compose/haproxy/haproxy.cfg +++ b/internal/suites/example/compose/haproxy/haproxy.cfg @@ -64,8 +64,12 @@ backend fe_authelia backend be_httpbin acl remote_user_exist var(req.auth_response_header.remote_user) -m found acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found + acl remote_name_exist var(req.auth_response_header.remote_name) -m found + acl remote_email_exist var(req.auth_response_header.remote_email) -m found http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist + http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist + http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist server httpbin-backend httpbin:8000 diff --git a/internal/suites/example/compose/httpbin/docker-compose.yml b/internal/suites/example/compose/httpbin/docker-compose.yml index c3ec9d893..a76ba589a 100644 --- a/internal/suites/example/compose/httpbin/docker-compose.yml +++ b/internal/suites/example/compose/httpbin/docker-compose.yml @@ -11,7 +11,7 @@ services: - 'traefik.frontend.auth.forward.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080/' - 'traefik.frontend.auth.forward.tls.insecureSkipVerify=true' - 'traefik.frontend.auth.forward.trustForwardHeader=true' - - 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User,Remote-Groups' + - 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email' # Traefik 2.x - 'traefik.http.routers.httpbin.rule=Host(`public.example.com`) && Path(`/headers`)' - 'traefik.http.routers.httpbin.priority=150' diff --git a/internal/suites/example/compose/nginx/backend/docker-compose.yml b/internal/suites/example/compose/nginx/backend/docker-compose.yml index 325c1b790..6121db1a5 100644 --- a/internal/suites/example/compose/nginx/backend/docker-compose.yml +++ b/internal/suites/example/compose/nginx/backend/docker-compose.yml @@ -7,7 +7,7 @@ services: - 'traefik.frontend.auth.forward.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080' # Traefik 1.x - 'traefik.frontend.auth.forward.tls.insecureSkipVerify=true' # Traefik 1.x - 'traefik.frontend.auth.forward.trustForwardHeader=true' # Traefik 1.x - - 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User,Remote-Groups' # Traefik 1.x + - 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email' # Traefik 1.x - 'traefik.http.routers.protectedapps.rule=Host(`home.example.com`, `public.example.com`, `secure.example.com`, `admin.example.com`, `singlefactor.example.com`)' # Traefik 2.x - 'traefik.http.routers.protectedapps.entrypoints=https' # Traefik 2.x - 'traefik.http.routers.protectedapps.tls=true' # Traefik 2.x diff --git a/internal/suites/example/compose/nginx/portal/nginx.conf b/internal/suites/example/compose/nginx/portal/nginx.conf index c4d107203..8531aafb3 100644 --- a/internal/suites/example/compose/nginx/portal/nginx.conf +++ b/internal/suites/example/compose/nginx/portal/nginx.conf @@ -106,12 +106,18 @@ http { location / { auth_request /auth_verify; - auth_request_set $user $upstream_http_remote_user; - proxy_set_header X-Forwarded-User $user; + auth_request_set $user $upstream_http_remote_user; + proxy_set_header Remote-User $user; auth_request_set $groups $upstream_http_remote_groups; proxy_set_header Remote-Groups $groups; + auth_request_set $name $upstream_http_remote_name; + proxy_set_header Remote-Name $name; + + auth_request_set $email $upstream_http_remote_email; + proxy_set_header Remote-Email $email; + # Route the request to the correct virtual host in the backend. proxy_set_header Host $http_host; @@ -172,6 +178,12 @@ http { auth_request_set $groups $upstream_http_remote_groups; proxy_set_header Remote-Groups $groups; + auth_request_set $name $upstream_http_remote_name; + proxy_set_header Remote-Name $name; + + auth_request_set $email $upstream_http_remote_email; + proxy_set_header Remote-Email $email; + set $target_url $scheme://$http_host$request_uri; error_page 401 =302 https://login.example.com:8080/?rd=$target_url; diff --git a/internal/suites/example/compose/traefik2/docker-compose.yml b/internal/suites/example/compose/traefik2/docker-compose.yml index 85662a9de..47dbf7572 100644 --- a/internal/suites/example/compose/traefik2/docker-compose.yml +++ b/internal/suites/example/compose/traefik2/docker-compose.yml @@ -12,7 +12,7 @@ services: - 'traefik.http.middlewares.authelia.forwardauth.address=https://authelia-backend:9091${PathPrefix}/api/verify?rd=https://login.example.com:8080${PathPrefix}' # Traefik 2.x - 'traefik.http.middlewares.authelia.forwardauth.tls.insecureSkipVerify=true' # Traefik 2.x - 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' # Traefik 2.x - - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups' # Traefik 2.x + - 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email' # Traefik 2.x command: - '--api' - '--providers.docker=true' diff --git a/internal/suites/scenario_custom_headers_test.go b/internal/suites/scenario_custom_headers_test.go index 57a6306e9..54a831a3f 100644 --- a/internal/suites/scenario_custom_headers_test.go +++ b/internal/suites/scenario_custom_headers_test.go @@ -66,10 +66,14 @@ func (s *CustomHeadersScenario) TestShouldNotForwardCustomHeaderForUnauthenticat s.Assert().NoError(err) s.Assert().NotContains(b, "john") s.Assert().NotContains(b, "admins") + s.Assert().NotContains(b, "John Doe") + s.Assert().NotContains(b, "john.doe@authelia.com") } type Headers struct { + ForwardedEmail string `json:"Remote-Email"` ForwardedGroups string `json:"Remote-Groups"` + ForwardedName string `json:"Remote-Name"` ForwardedUser string `json:"Remote-User"` } @@ -113,7 +117,8 @@ func (s *CustomHeadersScenario) TestShouldForwardCustomHeaderForAuthenticatedUse actualGroups.Add(group) } - return strings.Contains(payload.Headers.ForwardedUser, "john") && expectedGroups.Equal(actualGroups), nil + return strings.Contains(payload.Headers.ForwardedUser, "john") && expectedGroups.Equal(actualGroups) && + strings.Contains(payload.Headers.ForwardedName, "John Doe") && strings.Contains(payload.Headers.ForwardedEmail, "john.doe@authelia.com"), nil }) require.NoError(s.T(), err)