Build x-original-url from forwarded headers

This is to allow broader support for proxies. In particular, this allows
support with Traefik.

This patch also includes some examples of configuration with Traefik.
pull/355/head
ViViDboarder 2019-02-07 12:56:29 -08:00 committed by Clément Michaud
parent 36d65c284e
commit 0922b3c215
9 changed files with 192 additions and 12 deletions

View File

@ -0,0 +1,61 @@
###############################################################
# Authelia minimal configuration #
###############################################################
logs_level: debug
authentication_backend:
file:
path: /etc/authelia/users_database.yml
session:
secret: unsecure_session_secret
domain: example.com
# Configuration of the storage backend used to store data and secrets. i.e. totp data
storage:
local:
path: /etc/authelia/storage
# TOTP Issuer Name
#
# This will be the issuer name displayed in Google Authenticator
# See: https://github.com/google/google-authenticator/wiki/Key-Uri-Format for more info on issuer names
totp:
issuer: example.com
# Access Control
#
# Access control is a set of rules you can use to restrict user access to certain
# resources.
access_control:
# Default policy can either be `bypass`, `one_factor`, `two_factor` or `deny`.
default_policy: deny
rules:
- domain: traefik.example.com
policy: two_factor
- domain: who.example.com
policy: two_factor
# Configuration of the authentication regulation mechanism.
regulation:
# Set it to 0 to disable max_retries.
max_retries: 3
# The user is banned if the authenticaction failed `max_retries` times in a `find_time` seconds window.
find_time: 120
# The length of time before a banned user can login again.
ban_time: 300
# Default redirection URL
#
# Note: this parameter is optional. If not provided, user won't
# be redirected upon successful authentication.
#default_redirection_url: https://authelia.example.domain
notifier:
# For testing purpose, notifications can be sent in a file
filesystem:
filename: /tmp/authelia/notification.txt

View File

@ -0,0 +1,38 @@
version: '2'
services:
traefik:
image: traefik
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik.toml:/etc/traefik/traefik.toml
- /etc/traefik
labels:
- traefik.frontend.rule=Host:traefik.example.com
- traefik.port=8080
- traefik.frontend.auth.forward.address=https://auth.example.com/api/verify?rd=https://auth.example.com
- traefik.frontend.auth.forward.tls.insecureSkipVerify=true
authelia:
# image: clems4ever/authelia:latest
build:
context: ../..
dockerfile: Dockerfile.dev
restart: always
volumes:
- ./config.minimal.yml:/etc/authelia/config.yml:ro
- ./users_database.yml:/etc/authelia/users_database.yml:rw
- /tmp/authelia:/tmp/authelia
environment:
- NODE_TLS_REJECT_UNAUTHORIZED=1
labels:
- traefik.frontend.rule=Host:auth.example.com
whoami:
image: emilevauge/whoami
labels:
- traefik.frontend.rule=Host:who.example.com
- traefik.frontend.auth.forward.address=https://auth.example.com/api/verify?rd=https://auth.example.com
- traefik.frontend.auth.forward.tls.insecureSkipVerify=true

View File

@ -0,0 +1,30 @@
defaultEntryPoints = ["http", "https"]
# logLevel = "DEBUG"
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[entryPoints.api]
address = ":8080"
[api]
# This is exposed via a subdomain and a proxy
entryPoint = "api"
dashboard = true
[docker]
# Docker server endpoint. Can be a tcp or a unix socket endpoint.
endpoint = "unix:///var/run/docker.sock"
# network = "traefik_default"
# Default domain used.
# Can be overridden by setting the "traefik.domain" label on a container.
domain = "localhost"
# Enable watch docker changes
watch = true

View File

@ -0,0 +1,29 @@
###############################################################
# Users Database #
###############################################################
# This file can be used if you do not have an LDAP set up.
# List of users
users:
john:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: john.doe@authelia.com
groups:
- admins
- dev
harry:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
emails: harry.potter@authelia.com
groups: []
bob:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: bob.dylan@authelia.com
groups:
- dev
james:
password: "{CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/"
email: james.dean@authelia.com

View File

@ -9,8 +9,8 @@ import { AuthenticationSessionHandler }
from "../../AuthenticationSessionHandler"; from "../../AuthenticationSessionHandler";
import { AuthenticationSession } import { AuthenticationSession }
from "../../../../types/AuthenticationSession"; from "../../../../types/AuthenticationSession";
import GetHeader from "../../utils/GetHeader";
import HasHeader from "../..//utils/HasHeader"; import HasHeader from "../..//utils/HasHeader";
import { RequestUrlGetter } from "../../utils/RequestUrlGetter";
async function verifyWithSelectedMethod(req: Express.Request, res: Express.Response, async function verifyWithSelectedMethod(req: Express.Request, res: Express.Response,
@ -31,7 +31,7 @@ async function verifyWithSelectedMethod(req: Express.Request, res: Express.Respo
* @param res The response to write Redirect header to. * @param res The response to write Redirect header to.
*/ */
function setRedirectHeader(req: Express.Request, res: Express.Response) { function setRedirectHeader(req: Express.Request, res: Express.Response) {
const originalUrl = GetHeader(req, Constants.HEADER_X_ORIGINAL_URL); const originalUrl = RequestUrlGetter.getOriginalUrl(req);
res.set(Constants.HEADER_REDIRECT, originalUrl); res.set(Constants.HEADER_REDIRECT, originalUrl);
} }

View File

@ -3,10 +3,11 @@ import { ServerVariables } from "../../ServerVariables";
import { URLDecomposer } from "../../utils/URLDecomposer"; import { URLDecomposer } from "../../utils/URLDecomposer";
import { Level } from "../../authentication/Level"; import { Level } from "../../authentication/Level";
import GetHeader from "../../utils/GetHeader"; import GetHeader from "../../utils/GetHeader";
import { HEADER_X_ORIGINAL_URL, HEADER_PROXY_AUTHORIZATION } from "../../../../../shared/constants"; import { HEADER_PROXY_AUTHORIZATION } from "../../../../../shared/constants";
import setUserAndGroupsHeaders from "./SetUserAndGroupsHeaders"; import setUserAndGroupsHeaders from "./SetUserAndGroupsHeaders";
import CheckAuthorizations from "./CheckAuthorizations"; import CheckAuthorizations from "./CheckAuthorizations";
import { Level as AuthorizationLevel } from "../../authorization/Level"; import { Level as AuthorizationLevel } from "../../authorization/Level";
import { RequestUrlGetter } from "../../utils/RequestUrlGetter";
export default async function(req: Express.Request, res: Express.Response, export default async function(req: Express.Request, res: Express.Response,
vars: ServerVariables) vars: ServerVariables)
@ -38,10 +39,10 @@ export default async function(req: Express.Request, res: Express.Response,
const password = splittedToken[1]; const password = splittedToken[1];
const groupsAndEmails = await vars.usersDatabase.checkUserPassword(username, password); const groupsAndEmails = await vars.usersDatabase.checkUserPassword(username, password);
const uri = GetHeader(req, HEADER_X_ORIGINAL_URL); const uri = RequestUrlGetter.getOriginalUrl(req);
const urlDecomposition = URLDecomposer.fromUrl(uri); const urlDecomposition = URLDecomposer.fromUrl(uri);
CheckAuthorizations(vars.authorizer, urlDecomposition.domain, urlDecomposition.path, CheckAuthorizations(vars.authorizer, urlDecomposition.domain, urlDecomposition.path,
username, groupsAndEmails.groups, req.ip, Level.ONE_FACTOR); username, groupsAndEmails.groups, req.ip, Level.ONE_FACTOR);
setUserAndGroupsHeaders(res, username, groupsAndEmails.groups); setUserAndGroupsHeaders(res, username, groupsAndEmails.groups);
} }

View File

@ -3,13 +3,10 @@ import { ServerVariables } from "../../ServerVariables";
import { AuthenticationSession } import { AuthenticationSession }
from "../../../../types/AuthenticationSession"; from "../../../../types/AuthenticationSession";
import { URLDecomposer } from "../../utils/URLDecomposer"; import { URLDecomposer } from "../../utils/URLDecomposer";
import GetHeader from "../../utils/GetHeader";
import {
HEADER_X_ORIGINAL_URL,
} from "../../../../../shared/constants";
import setUserAndGroupsHeaders from "./SetUserAndGroupsHeaders"; import setUserAndGroupsHeaders from "./SetUserAndGroupsHeaders";
import CheckAuthorizations from "./CheckAuthorizations"; import CheckAuthorizations from "./CheckAuthorizations";
import CheckInactivity from "./CheckInactivity"; import CheckInactivity from "./CheckInactivity";
import { RequestUrlGetter } from "../../utils/RequestUrlGetter";
export default async function (req: Express.Request, res: Express.Response, export default async function (req: Express.Request, res: Express.Response,
@ -19,7 +16,7 @@ export default async function (req: Express.Request, res: Express.Response,
throw new Error("No cookie detected."); throw new Error("No cookie detected.");
} }
const originalUrl = GetHeader(req, HEADER_X_ORIGINAL_URL); const originalUrl = RequestUrlGetter.getOriginalUrl(req);
if (!originalUrl) { if (!originalUrl) {
throw new Error("Cannot detect the original URL from headers."); throw new Error("Cannot detect the original URL from headers.");
@ -36,4 +33,4 @@ export default async function (req: Express.Request, res: Express.Response,
CheckAuthorizations(vars.authorizer, d.domain, d.path, username, groups, req.ip, authSession.authentication_level); CheckAuthorizations(vars.authorizer, d.domain, d.path, username, groups, req.ip, authSession.authentication_level);
CheckInactivity(req, authSession, vars.config, vars.logger); CheckInactivity(req, authSession, vars.config, vars.logger);
setUserAndGroupsHeaders(res, username, groups); setUserAndGroupsHeaders(res, username, groups);
} }

View File

@ -0,0 +1,20 @@
import Constants = require("../../../../../shared/constants");
import Express = require("express");
import GetHeader from "../../utils/GetHeader";
import HasHeader from "../..//utils/HasHeader";
export class RequestUrlGetter {
static getOriginalUrl(req: Express.Request): string {
if HasHeader(req, Constants.HEADER_X_ORIGINAL_URL) {
return GetHeader(req, Constants.HEADER_X_ORIGINAL_URL);
}
const proto = GetHeader(req, Constants.HEADER_X_FORWARDED_PROTO);
const host = GetHeader(req, Constants.HEADER_X_FORWARDED_HOST);
const port = GetHeader(req, Constants.HEADER_X_FORWARDED_PORT);
const uri = GetHeader(req, Constants.HEADER_X_FORWARDED_URI);
return "${proto}://${host}:${port}${uri}";
}
}

View File

@ -5,10 +5,14 @@ export const REDIRECT_QUERY_PARAM = "rd";
export const HEADER_X_TARGET_URL = "x-target-url"; export const HEADER_X_TARGET_URL = "x-target-url";
export const HEADER_X_ORIGINAL_URL = "x-original-url"; export const HEADER_X_ORIGINAL_URL = "x-original-url";
export const HEADER_X_FORWARDED_PROTO = "x-forwarded-proto";
export const HEADER_X_FORWARDED_HOST = "x-forwarded-host";
export const HEADER_X_FORWARDED_PORT = "x-forwarded-port";
export const HEADER_X_FORWARDED_URI = "x-forwarded-uri";
export const HEADER_PROXY_AUTHORIZATION = "proxy-authorization"; export const HEADER_PROXY_AUTHORIZATION = "proxy-authorization";
export const HEADER_REDIRECT = "redirect"; export const HEADER_REDIRECT = "redirect";
export const GET_VARIABLE_KEY = "variables"; export const GET_VARIABLE_KEY = "variables";
export const HEADER_REMOTE_USER = "Remote-User"; export const HEADER_REMOTE_USER = "Remote-User";
export const HEADER_REMOTE_GROUPS = "Remote-Groups"; export const HEADER_REMOTE_GROUPS = "Remote-Groups";