[FEATURE] Add Remote-Name and Remote-Email headers (#1402)
parent
9891f99752
commit
a83ccd7188
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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/
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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("://")
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue