I am starting to toy around with Kubernetes in my homelab. One of the services I recently set up outside of Kubernetes was Authentik which has allowed me to configure authentication centrally. It's pretty minimal at this point, but one thing I've really enjoyed is just putting Caddy's forward_auth into some backends that don't support a great auth mechanism.
Because Traefik's Kubernetes support seems more mature and because Gateway API is the new way, I wanted to replicate this setup in my new cluster. However, I couldn't find any documentation for this specifically, so I wanted to share what was necessary to get this working.
Implementation
There are a few resources necessary to implement this setup. First it's important to understand what Traefik's ForwardAuth implementation looks like without Kubernetes: It's a middleware you add to your router, so we'll have to translate that across into Kubernetes. Next, we need to adapt our solution to Gateway API. Finally, we need to put it all together with a little more yaml glue. I am assuming you've already got your gateway configured. Here's what we'll need:
- Authentik outpost running in Kubernetes
ReferenceGrant
to allow cross-namespace connectivity.Middleware
to configure for forward authHTTPRoute
for the service we want to protect
First we need a new Authentik outpost inside the cluster.
Authentik Outpost
Gateway API can only refer to cluster-internal backends, i.e. you can't have it point at a generic URL. This is relevant for the outpost prefix in the route. So instead of point at my (out-of-cluster) route, I deployed a new outpost for Authentik. Here's the full manifest:
apiVersion: v1
kind: Namespace
metadata:
labels:
app.kubernetes.io/instance: k8s-outpost
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.12.3
name: authentik-outpost
---
apiVersion: v1
data:
authentik_host: __HOST__
authentik_host_insecure: ZmFsc2U=
token: |
__TOKEN__
kind: Secret
metadata:
labels:
app.kubernetes.io/instance: k8s-outpost
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.12.3
name: authentik-outpost-api-58g5k79468
namespace: authentik-outpost
type: Opaque
---
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/instance: k8s-outpost
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.12.3
name: authentik-outpost
namespace: authentik-outpost
spec:
ports:
- name: http
port: 9000
protocol: TCP
targetPort: http
- name: https
port: 9443
protocol: TCP
targetPort: https
selector:
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/instance: k8s-outpost
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.12.3
name: authentik-outpost
namespace: authentik-outpost
spec:
selector:
matchLabels:
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.12.3
template:
metadata:
labels:
app.kubernetes.io/managed-by: goauthentik.io
app.kubernetes.io/name: authentik-proxy
app.kubernetes.io/version: 2021.12.3
spec:
containers:
- env:
- name: AUTHENTIK_HOST
valueFrom:
secretKeyRef:
key: authentik_host
name: authentik-outpost-api-58g5k79468
- name: AUTHENTIK_TOKEN
valueFrom:
secretKeyRef:
key: token
name: authentik-outpost-api-58g5k79468
- name: AUTHENTIK_INSECURE
valueFrom:
secretKeyRef:
key: authentik_host_insecure
name: authentik-outpost-api-58g5k79468
image: ghcr.io/goauthentik/proxy:2024.12.1
name: proxy
ports:
- containerPort: 9000
name: http
protocol: TCP
- containerPort: 9443
name: https
protocol: TCP
This is generated using Kustomize, but you should be able to adapt this to your own preferences. It will spin up a new Authentik outpost. Make sure to configure the host & token provided by Authentik.
ReferenceGrant
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: traefik-grant
namespace: authentik-outpost
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: monitoring
to:
- group: ""
kind: Service
ReferenceGrant is
a new mechanism for Gateway API which is necessary to allow the HTTPRoute
to
point at a backend in a different namespace. This grant belongs in the same
namespace as our outpost, which is the target backend (and has to allow this
reference).
Middleware
Traefik middleware configuration is unchanged between Ingress & Gateway API:
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: forward-auth
namespace: monitoring
spec:
forwardAuth:
address: http://authentik-outpost.authentik-outpost.svc.cluster.local:9000/outpost.goauthentik.io/auth/traefik
trustForwardHeader: true
authResponseHeaders:
- X-authentik-username
- X-authentik-groups
- X-authentik-entitlements
- 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
This is copied straight from Authentik's example (though I renamed it). It's interesting to me that this seems to bypass the reference granting mechanism and I don't fully understand what the security model is here. Services can always refer to each other at the network level (by default). I didn't bother researching this further though.
HTTPRoute
Finally, we have all the pieces together to put a route behind forward auth. Here's my example for putting the Prometheus UI behind auth:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: prometheus
namespace: monitoring
spec:
parentRefs:
- name: traefik-gateway
namespace: traefik
hostnames:
- prometheus.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /outpost.goauthentik.io/
backendRefs:
- name: authentik-outpost
namespace: authentik-outpost
port: 9000
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: prometheus-server
port: 80
filters:
- type: ExtensionRef
extensionRef:
group: traefik.io
kind: Middleware
name: forward-auth
There are a few notable differences to a "normal" route:
- The
/outpost.goauthentik.io/
prefix: This is necessary for the client to reach the outpost through this route. Authentik identifies its "provider" from theHost
header. Authentik provides theIngress
version in their docs, this is adapted from that. - The
ExtensionRef
filter: This is a special mechanism so Traefik can offer vendor-specific extensions that aren't standardised. In other words: This solution doesn't work with all gateways, only with Traefik. Gateway API hasn't standardised a filter for forward auth.
Summary
Once put together this is quite straightforward by Kubernetes standards, it's not that much YAML! However, because Gateway API is so new, these recipes just aren't well documented yet. I did find all the documentation to piece this together though, so here are the references I used:
- ReferenceGrant
to explain how to do cross-namespace references in
HTTPRoute
- ForwardAuth to understand Traefik's middelware system.
- Using Traefik middleware as HTTPRoute filter
has instructions on how to add any middleware to
HTTPRoute
. - Manual Outpost deployment on Kubernetes explains how to deploy an Authentik outpost to Kubernetes manually. I've adapted this example to my needs.
Troubleshooting
I made some mistakes while setting this up so here are some tips in case it's not working right away:
- Check the
HTTPRoute
status viakubectl describe
: If there are issues, it will tell you. For example, I didn't know about the reference grant until I saw the error message there. - Run Traefik with log level
DEBUG
: If you don't, you'll get a generic 500 with no explanation what the issue is. I actually raised an issue for this to maybe raise the log level to help diagnose mistakes quicker.