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.
This commit is contained in:
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,7 +39,7 @@ 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,

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.");

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,6 +5,10 @@ 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";