README quick start and configuration updated for init/serve split and paskia.kantadb; API.md server-config endpoints replaced with admin realm CRUD and the well-known webauthn endpoint; proxy guides point at the realm auth-host setting; oidc.md documents per-realm issuers; MultiSite.md rewritten from the implementation plan into documentation of the shipped mechanics, policy model, and design rationale.
9.0 KiB
Envoy External Authorization (ext_authz)
/auth/api/forward · Proxy guides
This guide uses Envoy's external authorization filter (ext_authz) to ask Paskia whether each request is allowed.
Overview
Envoy's ext_authz HTTP filter calls an external HTTP service before forwarding a request to the upstream. The filter needs to know which headers from the original request to send to Paskia, and which headers from Paskia's response to add to the upstream request or to the client response.
A minimal static configuration looks like this:
static_resources:
listeners:
- name: app_listener
address:
socket_address:
address: 0.0.0.0
port_value: 8080
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress_http
use_remote_address: true
route_config:
name: local_route
virtual_hosts:
- name: app
domains: ["*"]
routes:
# Pass /auth/ straight to Paskia, bypassing ext_authz.
- match:
prefix: "/auth/"
route:
cluster: paskia
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
disabled: true
# Protect everything else.
- match:
prefix: "/"
route:
cluster: app_backend
http_filters:
- name: envoy.filters.http.ext_authz
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
transport_api_version: v3
http_service:
server_uri:
uri: localhost:4401
cluster: paskia
timeout: 0.5s
# Send every auth check to /auth/api/forward with the
# required permission, regardless of the original path.
path_override: "/auth/api/forward?perm=myapp:login"
authorization_request:
allowed_headers:
patterns:
- exact: Host
- exact: Cookie
- exact: Accept
# Add the headers Paskia logs / expects.
headers_to_add:
- key: X-Forwarded-Method
value: "%REQ(:METHOD)%"
- key: X-Forwarded-Uri
value: "%REQ(:PATH)%"
- key: X-Forwarded-Proto
value: "%REQ(:SCHEME)%"
- key: X-Forwarded-For
value: "%REQ(X-Forwarded-For)%"
authorization_response:
# Forward every Remote-* header to the backend.
allowed_upstream_headers:
patterns:
- prefix: Remote-
# Forward the response Content-Type to the client on 401/403.
allowed_client_headers:
patterns:
- exact: Content-Type
failure_mode_allow: false
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: app_backend
connect_timeout: 0.25s
type: logical_dns
lb_policy: round_robin
load_assignment:
cluster_name: app_backend
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: localhost
port_value: 3000
- name: paskia
connect_timeout: 0.25s
type: logical_dns
lb_policy: round_robin
load_assignment:
cluster_name: paskia
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: localhost
port_value: 4401
Important configuration details
path_override— the auth request always goes to/auth/api/forward?perm=myapp:login, no matter which path the client requested. The original path is sent inX-Forwarded-Urifor Paskia to log.authorization_request.allowed_headers— Envoy only forwards the headers you explicitly allow. We allowHost,Cookie, andAccept. TheX-Forwarded-*headers are added viaheaders_to_addso they are based on Envoy's view of the request, not spoofed client values.headers_to_add— Envoy supports substitution format strings such as%REQ(:METHOD)%and%REQ(:PATH)%. These set the headers Paskia uses for logging and host validation.authorization_response.allowed_upstream_headers—prefix: Remote-tells Envoy to copy every response header starting withRemote-to the upstream request. This also removes any client-suppliedRemote-*headers, so the backend can trust them.authorization_response.allowed_client_headers— on a 401/403 response, Envoy forwards only the allowed response headers to the client. Paskia returns HTML or JSON with aContent-Typeheader, so we allow that. (Paskia does not set cookies on the forward-auth response.)typed_per_filter_configon the/auth/route disablesext_authzso users can reach the login/profile pages without already being authenticated.
Per-route requirements
Different routes often need different permissions or max_age values. Use typed_per_filter_config on each route to override the http_service.path_override:
routes:
- match:
prefix: "/reports"
route:
cluster: app_backend
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
check_settings:
http_service:
path_override: "/auth/api/forward?perm=myapp:reports&max_age=5min"
- match:
prefix: "/"
route:
cluster: app_backend
typed_per_filter_config:
envoy.filters.http.ext_authz:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
check_settings:
http_service:
path_override: "/auth/api/forward?perm=myapp:login"
See perm argument and max_age argument for query parameter syntax.
WebSocket support for /auth/
If you use a dedicated authentication host (the realm's auth-host setting), route auth.example.com to the Paskia cluster and you do not need the /auth/ bypass above. Otherwise, make sure the /auth/ route keeps the Upgrade and Connection headers so passkey WebSocket endpoints work. The default Envoy router handles Upgrade headers when the client requests them.
Public access
For routes where anonymous visitors are allowed but logged-in users should still be identified, add public=1 to path_override (globally or per route):
path_override: "/auth/api/forward?public=1"
path_override: "/auth/api/forward?public=1&perm=myapp:reports"
The auth check then always returns 204 (except reauth with max_age, which still returns the 401 auth flow), and the Remote-Public header — matched by the prefix: Remote- rule in allowed_upstream_headers — marks each request as anonymous, forbidden or authenticated. The backend always runs and must check Remote-Public before treating the request as authorized. See public access and trusted headers.
Notes
- Envoy's
ext_authzfilter does not send the request body to the auth server by default. For Paskia this is fine. - If Paskia is running behind TLS, use
https://inserver_uri.uriand configure the cluster's transport socket. - The
failure_mode_allow: falsesetting means that if Paskia cannot be reached, Envoy will reject the request. In testing you may prefertrue, but usefalsein production so a failed auth service cannot accidentally allow traffic.