Authentik is Cloudflare Access's OpenID Connect identity provider, not something in the request path, and the tunnel's requests reach ipx from the content_default gateway, 192.168.16.1, not 127.0.0.1. The page is rewritten from what was measured, with checks for both the trusted and the refused path, and docs/history.md records every change made to get there with how to undo it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TAC7sLVqfKmY6rsTLXzNgk
183 lines
8.6 KiB
Markdown
183 lines
8.6 KiB
Markdown
# Signing in through Cloudflare Access and Authentik
|
|
|
|
ipx can take the signed-in identity from whatever sits in front of it, instead of asking for a
|
|
password itself. The proxy authenticates the person and passes the result to ipx in a **header**;
|
|
ipx reads it, finds (or creates) the matching account, and gets on with it.
|
|
|
|
Read [How this is secured](#how-this-is-secured) before exposing anything. The short version: a
|
|
header is worth exactly as much as the hop that set it, so ipx only believes one from an address you
|
|
list.
|
|
|
|
---
|
|
|
|
## How ipodderx.sdf1.net does it
|
|
|
|
Checked end to end on 2026-09-12. An earlier version of this page had never been tried against a
|
|
real setup and pointed at the wrong address.
|
|
|
|
```
|
|
browser ─► Cloudflare Access, app "ipodderx" ─── sign in ───► Authentik (OpenID Connect)
|
|
─► tunnel "rays-unraid" (the cloudflared container on Tower)
|
|
─► http://192.168.1.130:8099 ─► ipx
|
|
```
|
|
|
|
Authentik is not in the request path. It is the identity provider Cloudflare Access asks. Access
|
|
then adds `Cf-Access-Authenticated-User-Email`, the email address Authentik gave it, to every
|
|
request it forwards through the tunnel, and ipx signs that person in.
|
|
|
|
| Piece | Where | Setting |
|
|
|---|---|---|
|
|
| Identity provider | Zero Trust → Settings → Authentication | `Authentik`, OpenID Connect; scopes `openid email profile` |
|
|
| Access application | Zero Trust → Access → Applications → `ipodderx` | Domain `ipodderx.sdf1.net`; identity providers: Authentik only, with instant auth; session 730h; policy *Require Login* allows a list of email addresses |
|
|
| Tunnel route | Zero Trust → Networks → Tunnels → `rays-unraid` → Public hostnames | `ipodderx.sdf1.net` → HTTP `192.168.1.130:8099` |
|
|
| DNS | `sdf1.net` | `ipodderx` CNAME to the tunnel, proxied |
|
|
| ipx | `/mnt/fast/appdata/ipodderx/config.toml`, `[web]` | below |
|
|
|
|
```toml
|
|
[web]
|
|
enabled = true
|
|
bind = "0.0.0.0:8099"
|
|
trusted_header = "Cf-Access-Authenticated-User-Email"
|
|
trusted_proxies = ["127.0.0.1", "::1", "192.168.16.1"]
|
|
auto_create_users = true
|
|
session_days = 30
|
|
```
|
|
|
|
Restart ipx after editing it: `docker compose -f /mnt/fast/arcane/projects/content/compose.yaml
|
|
restart ipodderx`.
|
|
|
|
### What was missing
|
|
|
|
Cloudflare and Authentik were already right. Three things on the ipx side were not:
|
|
|
|
1. **`trusted_header` was empty**, which switches the whole proxy path off. ipx ignored the header
|
|
and asked for a password.
|
|
2. **`trusted_proxies` listed only `127.0.0.1`.** The tunnel's requests do not come from there;
|
|
see the next section.
|
|
3. **The account had the wrong name.** It was made by hand as `rays`, but the header carries
|
|
`rays@sdf1.net`. With `auto_create_users` on, the first visit would have made a second, empty
|
|
account. `ipx user rename rays rays@sdf1.net` fixed that without losing anything.
|
|
|
|
### The address to trust, and why it is 192.168.16.1
|
|
|
|
`cloudflared` runs in its own container and reaches ipx through the host's published port. Docker
|
|
(iptables firewall backend) masquerades traffic between its bridge networks, so the tunnel's
|
|
requests arrive from the **gateway of ipx's own network**, `content_default`:
|
|
|
|
```sh
|
|
docker network inspect content_default -f '{{range .IPAM.Config}}{{.Gateway}}{{end}}'
|
|
```
|
|
|
|
That was measured, not assumed. ipx does not log where a request came from, so the addresses were
|
|
read from the kernel's connection table inside the container while the site was open. (`/proc/net/tcp`
|
|
lists them in hex.)
|
|
|
|
If the `content` project's network is ever recreated, its gateway can change. Check it again, and
|
|
update `trusted_proxies` to match.
|
|
|
|
### Names
|
|
|
|
The username is the email address, lower-cased: `rays@sdf1.net`. To sign in at `/login` with a
|
|
password from the LAN, use that name too.
|
|
|
|
To let someone else in, add their address to the Access policy; they need an Authentik account with
|
|
that email. With `auto_create_users = true` they get an ipx account on their first visit, as an
|
|
ordinary user with no feeds. An account made before the proxy can be given the name the proxy will
|
|
send:
|
|
|
|
```sh
|
|
docker exec iPodderX ipx user rename <old name> <email address>
|
|
```
|
|
|
|
### Check it
|
|
|
|
```sh
|
|
# From Tower itself: not a trusted address, so the header is ignored.
|
|
curl -s -H 'Accept: application/json' -H 'Cf-Access-Authenticated-User-Email: rays@sdf1.net' \
|
|
http://192.168.1.130:8099/api/me # -> sign in
|
|
|
|
# From a container on a Docker bridge, as cloudflared is: believed.
|
|
docker run --rm --network bridge mirror.gcr.io/library/busybox wget -qO- \
|
|
--header 'Accept: application/json' --header 'Cf-Access-Authenticated-User-Email: rays@sdf1.net' \
|
|
http://192.168.1.130:8099/api/me # -> {"admin":true,"name":"rays@sdf1.net"}
|
|
```
|
|
|
|
Then open `https://ipodderx.sdf1.net` in a private window. Authentik should ask who you are, and
|
|
ipx should show `rays@sdf1.net` in the sidebar footer without asking for a password.
|
|
|
|
---
|
|
|
|
## The ipx settings
|
|
|
|
| Key | What it does |
|
|
|---|---|
|
|
| `trusted_header` | The header the proxy sets. Empty, the default, turns the proxy path off. |
|
|
| `trusted_proxies` | The addresses allowed to set it. Nothing else is believed. |
|
|
| `auto_create_users` | Make an account the first time the proxy vouches for a name ipx has not seen. |
|
|
| `session_days` | How long a password sign-in lasts without use. |
|
|
|
|
The first account ever created is an admin. Every later one is an ordinary user, who cannot change
|
|
global settings, a feed's URL or folder, or how often feeds are scanned: the API refuses those with
|
|
a `403`, not just the UI. Everything else about a feed is theirs alone; see [users.md](users.md).
|
|
|
|
Local sign-in at `/login` keeps working alongside the proxy, which is how you get in from the LAN
|
|
when the tunnel is down. So does the shared `[web] token`, which signs in as the admin and is the
|
|
way back in if you lock yourself out. A brand new database starts with **admin / ipodderx**;
|
|
change it.
|
|
|
|
---
|
|
|
|
## Authentik in the request path instead
|
|
|
|
Not what ipodderx.sdf1.net uses, and **not verified**. Authentik can also sit in front of ipx
|
|
itself, with a **Proxy Provider** and an **outpost** that adds `X-authentik-username`:
|
|
|
|
- Applications → Providers → Create → Proxy Provider; mode **Proxy** (the outpost talks to ipx) or
|
|
**Forward auth** (an existing reverse proxy asks the outpost).
|
|
- Applications → Create, bound to that provider, with a policy; add the provider to an outpost.
|
|
- In ipx: `trusted_header = "X-authentik-username"`, and the outpost's or reverse proxy's address
|
|
in `trusted_proxies`. Measure that address as above rather than guessing it.
|
|
|
|
---
|
|
|
|
## How this is secured
|
|
|
|
**The header is only believed from `trusted_proxies`.** Every other source is ignored, and the
|
|
request falls through to a session cookie or the shared token. That is the whole security boundary.
|
|
|
|
With the tunnel reaching ipx through the host's port, `192.168.16.1` means **any container on Tower
|
|
that connects to `192.168.1.130:8099`**, not only `cloudflared`. Machines on the LAN, and Tower
|
|
itself, arrive under their own addresses and cannot set the header; the checks above show both
|
|
sides. Never list a LAN address or range: anyone there could then send
|
|
`Cf-Access-Authenticated-User-Email: rays@sdf1.net` and be you.
|
|
|
|
**What ipx does not do:** it does not verify Cloudflare's signed `Cf-Access-Jwt-Assertion`. It
|
|
trusts the hop. Verifying the signature would make the containers on Tower irrelevant to the
|
|
boundary, and is the upgrade if that ever matters.
|
|
|
|
**Turning it off:** clear `trusted_header` and restart. Proxy-made accounts stay, but nobody can sign
|
|
in with them until they are given a password (`ipx user passwd <name>`).
|
|
|
|
---
|
|
|
|
## Everyday administration
|
|
|
|
```sh
|
|
ipx user list # who exists, how each signs in, and when
|
|
echo -n 'secret123' | ipx user add sam # local account, password on stdin
|
|
ipx user add sam@example.com --no-password # proxy-only account, made ahead of time
|
|
ipx user rename sam sam@example.com # give an account the name the proxy sends
|
|
echo -n 'newsecret' | ipx user passwd sam # change a password
|
|
ipx user rm sam # remove the account
|
|
```
|
|
|
|
In the container, put `docker exec iPodderX` in front, and `docker exec -i iPodderX` for the ones
|
|
that read a password.
|
|
|
|
Set `auto_create_users = false` once everyone who should have an account has one. After that the
|
|
proxy vouching for an unknown name is logged and refused. Make people ahead of time instead, with
|
|
the exact name the header will carry.
|
|
|
|
See also [users.md](users.md) for what several people share, [configuration.md](configuration.md)
|
|
for every `[web]` key, and [cli.md](cli.md) for the `ipx user` commands.
|