Allow role based access control + Hostname pattern matching for domain level forward auth
It's pretty much in the title - https://www.authelia.com/docs/configuration/access-control.html authelia provides a good reference
I tried to roll my own, but i couldn't figure out how to access the original host - Also i think this use case is common enough to warrant a feature
Here was my attempt at an expression policy that achieved this
dest_host_key = 'X-Forwarded-Host-Original'
protected_subdomains = ['movies']
headers = request.http_request.headers
dest_host = headers[dest_host_key] if dest_host_key in headers else ""
ak_logger.info(f"Destination Host: {dest_host}")
if not dest_host:
ak_message(f"No value found for {dest_host_key}")
return False
ak_message(f"Let's try to auth to {dest_host}")
ak_message(f"Headers: {headers}")
host_components = dest_host.split('.')
# Determine if subdomain should be protected
require_admin = False
for host_component in host_components:
if regex_match(host_component, r"^([:alpha:]+-)?admin$"):
require_admin = True
# Or if last domain component is a protected app
for subdomain in protected_subdomains:
if subdomain == host_components[0]:
require_admin = True
if not require_admin:
ak_message(f"{dest_host} does not require admin.")
return True
is_admin = ak_is_group_member(request.user, name = "authentik Admins")
if not is_admin:
ak_message(f"{request.user} is not an admin but {dest_host} requires admin!")
return False
ak_message(f"{request.user} is an admin and {dest_host} requires admin, granting acess!")
return True
apologies for the above devolving into debug spam
For now, I'm just going to make a separate google oauth app for admin forward auth
Update on this - I just realized I can set up an OIDC provider in authentik, and create an application that applies a group membership policy, then configure https://github.com/thomseddon/traefik-forward-auth to use the authentik provider.
This is a relatively lightweight way to achieve what I need, which is really just separation of a few groups of users.
The way domain-level authentication works, this can't be implemented currently. The outpost is go and the core is python, and authorization is only done during the authentication of the user. The outpost could use the API to run policies and further check access, however this would be very slow.
@BeryJu thanks for the reply - What you're saying is that the outpost processes the requests, and the core checks access, and when expression policies are run, it's by core, and core doesn't really have access to the same context as the outpost?
The outpost checks if the request has a valid auth cookie, if not redirects the user to the core for a full OAuth flow, where the authentication and authorization happens, which the outpost then gets and writes said cookie.
in a world where the entire backend is Go, this would still happen similarly, except the outpost would talk directly to the core via GRPC/etc to do additional checks, and this would also allow property mappings in the proxy provider to change instantly vs. currently requiring users to re-login
I don't know whether there are plans to migrate the backend eventually to make this viable, but I've actually configured the traefik-forward-auth instance I was previously using for super-user apps to use authentik as an OIDC provider, and the authentik-side app to check admin access, and I'm very happy with the result, so I'm not too bothered.
This is roughly what my setup looks like.
You need to configure an OIDC provider application in authentik, and apply some kind of admin membership policy to that ( https://authentik.${root_domain}/application/o/admin/ )
For regular users, we make the standard proxy outpost, with whatever policies should apply to that.
This applies at the domain level.
We configure traefik-forward-auth to use https://authentik.${root_domain}/application/o/admin/ as its id issuer.
To quickly configure a service to use one policy or the other, we just set it's middleware to either authentik@docker or traefik-forward-auth (if its admin)
services:
whoami-user:
traefik.http.routers.whoami-admin.rule: Host(`whoami-user.${root_domain}`)
# service
traefik.http.services.whoami-user.loadbalancer.server.port: "8080"
traefik.http.routers.whoami-user.middlewares: authentik@docker
whoami-admin:
traefik.http.routers.whoami-admin.rule: Host(`whoami-admin.${root_domain}`)
# service
traefik.http.services.whoami-admin.loadbalancer.server.port: "8080"
traefik.http.routers.whoami-admin.middlewares: traefik-forward-auth
# OIDC forward authentication for traefik
traefik-forward-auth:
image: thomseddon/traefik-forward-auth:latest
<<: *restart
<<: *logging
mem_limit: 100m
environment:
# PROVIDERS_GOOGLE_CLIENT_ID: ${OAUTH2_PROXY_CLIENT_ID}
# PROVIDERS_GOOGLE_CLIENT_SECRET: ${OAUTH2_PROXY_CLIENT_SECRET}
SECRET: ${OAUTH2_PROXY_COOKIE_SECRET}
DEFAULT_PROVIDER: oidc
PROVIDERS_OIDC_ISSUER_URL: https://authentik.${root_domain}/application/o/admin/
PROVIDERS_OIDC_CLIENT_ID: ${OAUTH2_PROXY_CLIENT_ID}
PROVIDERS_OIDC_CLIENT_SECRET: ${OAUTH2_PROXY_CLIENT_SECRET}
AUTH_HOST: "oauth.${root_domain}"
COOKIE_DOMAIN: "${root_domain}"
COOKIE_NAME: _admin_forward_auth
CSRF_COOKIE_NAME: _admin_forward_auth_csrf
# This is in seconds
LIFETIME: "86400"
networks:
- traefik
labels:
<<: *traefik
# HTTP entrypoint
traefik.http.routers.traefik-forward-auth.rule: Host(`oauth.${root_domain}`)
# service
traefik.http.services.traefik-forward-auth.loadbalancer.server.port: "4181"
# Forward auth
traefik.http.middlewares.traefik-forward-auth.forwardauth.address: http://traefik-forward-auth:4181
traefik.http.routers.traefik-forward-auth.middlewares: traefik-forward-auth
traefik.http.middlewares.traefik-forward-auth.forwardauth.authResponseHeaders: X-Forwarded-User
authentik:
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.4.1}
restart: unless-stopped
networks:
# TODO: Change to own networks (auth proxy + internal auth)
- traefik
command: server
environment:
<<: *conf
PGID: ${docker_group_id}
AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY:?Need a secret key lol}"
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-pg
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRESQL__PASSWORD}
AUTHENTIK_EMAIL__HOST: ${AUTHENTIK_EMAIL__HOST}
AUTHENTIK_EMAIL__PORT: ${AUTHENTIK_EMAIL__PORT}
AUTHENTIK_EMAIL__USERNAME: ${AUTHENTIK_EMAIL__USERNAME}
AUTHENTIK_EMAIL__PASSWORD: ${AUTHENTIK_EMAIL__PASSWORD}
AUTHENTIK_EMAIL__USE_TLS: "true"
AUTHENTIK_EMAIL__USE_SSL: "false"
AUTHENTIK_EMAIL__TIMEOUT: 10
AUTHENTIK_EMAIL__FROM: ${AUTHENTIK_EMAIL__FROM}
# AUTHENTIK_ERROR_REPORTING__ENABLED: "true"
# WORKERS: 2
volumes:
- ${docker_data_folder}/authentik/media:/media
- ${docker_data_folder}/authentik/templates:/templates
#// ports:
#// - "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000"
#// - "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
labels:
<<: *traefik
traefik.http.routers.authentik.rule: Host(`authentik.${root_domain}`)
# && PathPrefix(`/outpost.goauthentik.io/`)
traefik.http.services.authentik.loadbalancer.server.port: "9000"
# `authentik-proxy` refers to the service name in the compose file.
traefik.http.middlewares.authentik.forwardauth.address: http://authentik:9000/outpost.goauthentik.io/auth/traefik
traefik.http.middlewares.authentik.forwardauth.trustForwardHeader: true
traefik.http.middlewares.authentik.forwardauth.authResponseHeaders: X-authentik-username,X-authentik-groups,X-authentik-email,X-authentik-name,X-authentik-uid,X-authentik-jwt,X-authentik-meta-jwks,X-authentik-meta-outpost,X-authentik-meta-provider,X-authentik-meta-app,X-authentik-meta-version