[FEATURE] Add Remote-Name and Remote-Email headers (#1402)

pull/1413/head
Amir Zarrinkafsh 2020-10-26 22:38:08 +11:00 committed by GitHub
parent 9891f99752
commit a83ccd7188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 96 additions and 52 deletions

View File

@ -20,7 +20,7 @@ services:
- 'traefik.http.routers.authelia.tls.certresolver=letsencrypt' - '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.address=http://authelia:9091/api/verify?rd=https://auth.example.com'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' - '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: expose:
- 9091 - 9091
restart: unless-stopped restart: unless-stopped

View File

@ -20,7 +20,7 @@ services:
- 'traefik.http.routers.authelia.tls.options=default' - '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.address=http://authelia:9091/api/verify?rd=https://authelia.example.com'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' - '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: expose:
- 9091 - 9091
restart: unless-stopped restart: unless-stopped

View File

@ -114,11 +114,15 @@ backend be_authelia
server authelia authelia:9091 server authelia authelia:9091
backend be_nextcloud 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_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_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-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-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 server nextcloud nextcloud:443 ssl verify none
``` ```
@ -179,11 +183,15 @@ listen authelia_proxy
server authelia authelia:9091 ssl verify none server authelia authelia:9091 ssl verify none
backend be_nextcloud 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_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_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-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-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 server nextcloud nextcloud:443 ssl verify none
``` ```

View File

@ -35,8 +35,8 @@ appear in the configuration examples.
## How can the backend be aware of the authenticated users? ## 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: The only way Authelia can share information about the authenticated user currently is through the use of four HTTP headers:
`Remote-User` and `Remote-Groups`. `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 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. 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 Please note that the backend must support the use of those headers to leverage that information, many

View File

@ -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. # proxy. In the future, it's gonna be safe to just use OAuth.
auth_request_set $user $upstream_http_remote_user; auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups; 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-User $user;
proxy_set_header Remote-Groups $groups; 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 Authelia returns 401, then nginx redirects the user to the login portal.
# If it returns 200, then the request pass through to the backend. # If it returns 200, then the request pass through to the backend.
# For other type of errors, nginx will handle them as usual. # For other type of errors, nginx will handle them as usual.

View File

@ -86,7 +86,7 @@ services:
- 'traefik.frontend.rule=Host:nextcloud.example.com' - '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.address=http://authelia:9091/api/verify?rd=https://login.example.com/'
- 'traefik.frontend.auth.forward.trustForwardHeader=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'
expose: expose:
- 443 - 443
restart: unless-stopped restart: unless-stopped

View File

@ -76,7 +76,7 @@ services:
- 'traefik.http.routers.authelia.tls=true' - '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.address=http://authelia:9091/api/verify?rd=https://login.example.com/'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' - '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: expose:
- 9091 - 9091
restart: unless-stopped restart: unless-stopped
@ -120,7 +120,7 @@ This can be avoided a couple different ways:
```yaml ```yaml
- 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://login.example.com/' - '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.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/ [Traefik 2.x]: https://docs.traefik.io/

View File

@ -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 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. 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. do support OAuth2 though since it has become a standard lately.

View File

@ -14,6 +14,8 @@ const authPrefix = "Basic "
// AuthorizationHeader is the basic-auth HTTP header Authelia utilises. // AuthorizationHeader is the basic-auth HTTP header Authelia utilises.
const AuthorizationHeader = "Proxy-Authorization" const AuthorizationHeader = "Proxy-Authorization"
const remoteUserHeader = "Remote-User" const remoteUserHeader = "Remote-User"
const remoteNameHeader = "Remote-Name"
const remoteEmailHeader = "Remote-Email"
const remoteGroupsHeader = "Remote-Groups" const remoteGroupsHeader = "Remote-Groups"
var protoHostSeparator = []byte("://") var protoHostSeparator = []byte("://")

View File

@ -125,39 +125,41 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
// verifyBasicAuth verify that the provided username and password are correct and // verifyBasicAuth verify that the provided username and password are correct and
// that the user is authorized to target the resource. // 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)) username, password, err := parseBasicAuth(string(auth))
if err != nil { 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) authenticated, err := ctx.Providers.UserProvider.CheckUserPassword(username, password)
if err != nil { 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 the user is not correctly authenticated, send a 401.
if !authenticated { if !authenticated {
// Request Basic Authentication otherwise // 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) details, err := ctx.Providers.UserProvider.GetDetails(username)
if err != nil { 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. // setForwardedHeaders set the forwarded User, Groups, Name and Email headers.
func setForwardedHeaders(headers *fasthttp.ResponseHeader, username string, groups []string) { func setForwardedHeaders(headers *fasthttp.ResponseHeader, username, name string, groups, emails []string) {
if username != "" { if username != "" {
headers.Set(remoteUserHeader, username) headers.Set(remoteUserHeader, username)
headers.Set(remoteGroupsHeader, strings.Join(groups, ",")) 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. // verifySessionCookie verifies if a user is identified by a cookie.
func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userSession *session.UserSession, refreshProfile bool, 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. // No username in the session means the user is anonymous.
isUserAnonymous := userSession.Username == "" isUserAnonymous := userSession.Username == ""
if isUserAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated { 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 { if !userSession.KeepMeLoggedIn && !isUserAnonymous {
inactiveLongEnough, err := hasUserBeenInactiveTooLong(ctx) inactiveLongEnough, err := hasUserBeenInactiveTooLong(ctx)
if err != nil { 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 { if inactiveLongEnough {
// Destroy the session a new one will be regenerated on next request. // Destroy the session a new one will be regenerated on next request.
err := ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx) err := ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx)
if err != nil { 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)) 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) 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) { func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, username string) {
@ -409,9 +411,9 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques
return return
} }
var username string var username, name string
var groups []string var groups, emails []string
var authLevel authentication.Level var authLevel authentication.Level
@ -420,9 +422,9 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques
userSession := ctx.GetSession() userSession := ctx.GetSession()
if isBasicAuth { if isBasicAuth {
username, groups, authLevel, err = verifyBasicAuth(proxyAuthorization, *targetURL, ctx) username, name, groups, emails, authLevel, err = verifyBasicAuth(proxyAuthorization, *targetURL, ctx)
} else { } else {
username, groups, authLevel, err = verifySessionCookie(ctx, targetURL, &userSession, username, name, groups, emails, authLevel, err = verifySessionCookie(ctx, targetURL, &userSession,
refreshProfile, refreshProfileInterval) refreshProfile, refreshProfileInterval)
} }
@ -449,7 +451,7 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques
case NotAuthorized: case NotAuthorized:
handleUnauthorized(ctx, targetURL, username) handleUnauthorized(ctx, targetURL, username)
case Authorized: case Authorized:
setForwardedHeaders(&ctx.Response.Header, username, groups) setForwardedHeaders(&ctx.Response.Header, username, name, groups, emails)
} }
if err := updateActivityTimestamp(ctx, isBasicAuth, username); err != nil { if err := updateActivityTimestamp(ctx, isBasicAuth, username); err != nil {

View File

@ -204,7 +204,7 @@ func TestShouldVerifyWrongCredentials(t *testing.T) {
Return(false, nil) Return(false, nil)
url, _ := url.ParseRequestURI("https://test.example.com") 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) assert.Error(t, err)
} }
@ -416,6 +416,7 @@ func TestShouldVerifyFailingDetailsFetchingInBasicAuth(t *testing.T) {
type Pair struct { type Pair struct {
URL string URL string
Username string Username string
Emails []string
AuthenticationLevel authentication.Level AuthenticationLevel authentication.Level
ExpectedStatusCode int ExpectedStatusCode int
} }
@ -427,23 +428,23 @@ func (p Pair) String() string {
func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) { func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
testCases := []Pair{ testCases := []Pair{
{"https://test.example.com", "", authentication.NotAuthenticated, 401}, {"https://test.example.com", "", nil, authentication.NotAuthenticated, 401},
{"https://bypass.example.com", "", authentication.NotAuthenticated, 200}, {"https://bypass.example.com", "", nil, authentication.NotAuthenticated, 200},
{"https://one-factor.example.com", "", authentication.NotAuthenticated, 401}, {"https://one-factor.example.com", "", nil, authentication.NotAuthenticated, 401},
{"https://two-factor.example.com", "", authentication.NotAuthenticated, 401}, {"https://two-factor.example.com", "", nil, authentication.NotAuthenticated, 401},
{"https://deny.example.com", "", authentication.NotAuthenticated, 401}, {"https://deny.example.com", "", nil, authentication.NotAuthenticated, 401},
{"https://test.example.com", "john", authentication.OneFactor, 403}, {"https://test.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 403},
{"https://bypass.example.com", "john", authentication.OneFactor, 200}, {"https://bypass.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 200},
{"https://one-factor.example.com", "john", authentication.OneFactor, 200}, {"https://one-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 200},
{"https://two-factor.example.com", "john", authentication.OneFactor, 401}, {"https://two-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 401},
{"https://deny.example.com", "john", authentication.OneFactor, 403}, {"https://deny.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 403},
{"https://test.example.com", "john", authentication.TwoFactor, 403}, {"https://test.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 403},
{"https://bypass.example.com", "john", authentication.TwoFactor, 200}, {"https://bypass.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 200},
{"https://one-factor.example.com", "john", authentication.TwoFactor, 200}, {"https://one-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 200},
{"https://two-factor.example.com", "john", authentication.TwoFactor, 200}, {"https://two-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 200},
{"https://deny.example.com", "john", authentication.TwoFactor, 403}, {"https://deny.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 403},
} }
for _, testCase := range testCases { for _, testCase := range testCases {
@ -454,6 +455,7 @@ func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
userSession := mock.Ctx.GetSession() userSession := mock.Ctx.GetSession()
userSession.Username = testCase.Username userSession.Username = testCase.Username
userSession.Emails = testCase.Emails
userSession.AuthenticationLevel = testCase.AuthenticationLevel userSession.AuthenticationLevel = testCase.AuthenticationLevel
mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. 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 != "" { if testCase.ExpectedStatusCode == 200 && testCase.Username != "" {
assert.Equal(t, []byte(testCase.Username), mock.Ctx.Response.Header.Peek("Remote-User")) 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 { } else {
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek("Remote-User")) 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 := mock.Ctx.GetSession()
userSession.Username = testUsername userSession.Username = testUsername
userSession.Emails = []string{"john.doe@example.com"}
userSession.AuthenticationLevel = authentication.TwoFactor userSession.AuthenticationLevel = authentication.TwoFactor
userSession.LastActivity = 0 userSession.LastActivity = 0
userSession.KeepMeLoggedIn = true userSession.KeepMeLoggedIn = true
@ -575,6 +580,7 @@ func TestShouldKeepSessionWhenInactivityTimeoutHasNotBeenExceeded(t *testing.T)
userSession := mock.Ctx.GetSession() userSession := mock.Ctx.GetSession()
userSession.Username = testUsername userSession.Username = testUsername
userSession.Emails = []string{"john.doe@example.com"}
userSession.AuthenticationLevel = authentication.TwoFactor userSession.AuthenticationLevel = authentication.TwoFactor
userSession.LastActivity = past.Unix() userSession.LastActivity = past.Unix()
mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting. mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.

View File

@ -24,6 +24,7 @@ authentication_backend:
groups_filter: (&(member={dn})(objectclass=groupOfNames)) groups_filter: (&(member={dn})(objectclass=groupOfNames))
group_name_attribute: cn group_name_attribute: cn
mail_attribute: mail mail_attribute: mail
display_name_attribute: displayName
user: cn=admin,dc=example,dc=com user: cn=admin,dc=example,dc=com
password: password password: password

View File

@ -64,8 +64,12 @@ backend fe_authelia
backend be_httpbin backend be_httpbin
acl remote_user_exist var(req.auth_response_header.remote_user) -m found 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_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-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-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 server httpbin-backend httpbin:8000

View File

@ -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.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080/'
- 'traefik.frontend.auth.forward.tls.insecureSkipVerify=true' - 'traefik.frontend.auth.forward.tls.insecureSkipVerify=true'
- 'traefik.frontend.auth.forward.trustForwardHeader=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 2.x
- 'traefik.http.routers.httpbin.rule=Host(`public.example.com`) && Path(`/headers`)' - 'traefik.http.routers.httpbin.rule=Host(`public.example.com`) && Path(`/headers`)'
- 'traefik.http.routers.httpbin.priority=150' - 'traefik.http.routers.httpbin.priority=150'

View File

@ -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.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.tls.insecureSkipVerify=true' # Traefik 1.x
- 'traefik.frontend.auth.forward.trustForwardHeader=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.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.entrypoints=https' # Traefik 2.x
- 'traefik.http.routers.protectedapps.tls=true' # Traefik 2.x - 'traefik.http.routers.protectedapps.tls=true' # Traefik 2.x

View File

@ -106,12 +106,18 @@ http {
location / { location / {
auth_request /auth_verify; auth_request /auth_verify;
auth_request_set $user $upstream_http_remote_user; auth_request_set $user $upstream_http_remote_user;
proxy_set_header X-Forwarded-User $user; proxy_set_header Remote-User $user;
auth_request_set $groups $upstream_http_remote_groups; auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-Groups $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. # Route the request to the correct virtual host in the backend.
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
@ -172,6 +178,12 @@ http {
auth_request_set $groups $upstream_http_remote_groups; auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-Groups $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; set $target_url $scheme://$http_host$request_uri;
error_page 401 =302 https://login.example.com:8080/?rd=$target_url; error_page 401 =302 https://login.example.com:8080/?rd=$target_url;

View File

@ -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.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.tls.insecureSkipVerify=true' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=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: command:
- '--api' - '--api'
- '--providers.docker=true' - '--providers.docker=true'

View File

@ -66,10 +66,14 @@ func (s *CustomHeadersScenario) TestShouldNotForwardCustomHeaderForUnauthenticat
s.Assert().NoError(err) s.Assert().NoError(err)
s.Assert().NotContains(b, "john") s.Assert().NotContains(b, "john")
s.Assert().NotContains(b, "admins") s.Assert().NotContains(b, "admins")
s.Assert().NotContains(b, "John Doe")
s.Assert().NotContains(b, "john.doe@authelia.com")
} }
type Headers struct { type Headers struct {
ForwardedEmail string `json:"Remote-Email"`
ForwardedGroups string `json:"Remote-Groups"` ForwardedGroups string `json:"Remote-Groups"`
ForwardedName string `json:"Remote-Name"`
ForwardedUser string `json:"Remote-User"` ForwardedUser string `json:"Remote-User"`
} }
@ -113,7 +117,8 @@ func (s *CustomHeadersScenario) TestShouldForwardCustomHeaderForAuthenticatedUse
actualGroups.Add(group) 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) require.NoError(s.T(), err)