Compare commits

...

2 Commits

3 changed files with 23 additions and 4 deletions

View File

@@ -20,3 +20,5 @@ BOT_REQUIRE_GROUP=bot-users
LIVE_REQUIRE_GROUP= LIVE_REQUIRE_GROUP=
APEX_HOST= APEX_HOST=
APEX_REDIRECT= APEX_REDIRECT=
COPYPARTY_IDP_KEY=
SYNC_REQUIRE_GROUP=media-admins

View File

@@ -237,7 +237,11 @@ Design: **Authentik authenticates once for the whole domain; Caddy authorizes pe
role-id → group table). Bind it to `default-source-enrollment` **and** `default-source-authentication` role-id → group table). Bind it to `default-source-enrollment` **and** `default-source-authentication`
(engine mode `all`, after `discord-guild-member`). From then on giving someone a Discord role gives them (engine mode `all`, after `discord-guild-member`). From then on giving someone a Discord role gives them
the matching access on next login; removing it removes it. the matching access on next login; removing it removes it.
5. Test in a private window: `https://live.HOST/` → Discord login → portal; `https://bot.HOST/` → 403 unless copyparty additionally requires a header with a secret name that only Caddy adds (`idp-h-key`,
`COPYPARTY_IDP_KEY` in `.env`), so identity headers cannot be forged even if copyparty were reached directly.
5. Debug: `https://HOST/whoami` prints the username/groups headers exactly as the apps receive them.
Group changes in Authentik apply on the next login — log out at `https://auth.HOST/` and back in.
6. Test in a private window: `https://live.HOST/` → Discord login → portal; `https://bot.HOST/` → 403 unless
you hold the bot role; `https://live.HOST/me/stream-key` shows your username. you hold the bot role; `https://live.HOST/me/stream-key` shows your username.
Jellyfin and Gitea use OIDC providers instead, not this. Jellyfin and Gitea use OIDC providers instead, not this.

View File

@@ -240,6 +240,15 @@ def write_caddy(domain, auth_host="", watch_host="", git_host="", bot_host="", l
{domain} {{ {domain} {{
encode zstd gzip encode zstd gzip
{AUTHENTIK_FORWARD_AUTH} {AUTHENTIK_FORWARD_AUTH}
# who does Authentik say I am? (debug: shows exactly the identity headers the apps receive)
handle /whoami {{
header Content-Type text/plain
respond "user: {{http.request.header.X-Authentik-Username}}
groups: {{http.request.header.X-Authentik-Groups}}
email: {{http.request.header.X-Authentik-Email}}
name: {{http.request.header.X-Authentik-Name}}"
}}
# dump endpoint (python service on the host; identity from the X-Authentik-* headers Caddy just set) # dump endpoint (python service on the host; identity from the X-Authentik-* headers Caddy just set)
handle /dump* {{ handle /dump* {{
reverse_proxy 127.0.0.1:8080 reverse_proxy 127.0.0.1:8080
@@ -259,9 +268,11 @@ def write_caddy(domain, auth_host="", watch_host="", git_host="", bot_host="", l
file_server file_server
}} }}
# copyparty (web UI + WebDAV). It trusts X-Authentik-Username / -Groups from this proxy only. # copyparty. It honours X-Authentik-Username / -Groups only when the request also carries a header whose
# secret NAME only this proxy knows (copyparty idp-h-key). Clients cannot forge identity.
handle {{ handle {{
reverse_proxy 127.0.0.1:3923 {{ reverse_proxy 127.0.0.1:3923 {{
header_up X-Vanguard-{env.get("COPYPARTY_IDP_KEY", "key")} 1
flush_interval -1 flush_interval -1
}} }}
}} }}
@@ -277,6 +288,7 @@ def write_env(watch_host, discord_token=None, discord_guild=None, git_host="", b
env.setdefault("APEX_HOST", "") # bare domain to serve (A record -> this box); empty = skip env.setdefault("APEX_HOST", "") # bare domain to serve (A record -> this box); empty = skip
env.setdefault("APEX_REDIRECT", "") # where the bare domain sends people, e.g. https://watch.HOST/ env.setdefault("APEX_REDIRECT", "") # where the bare domain sends people, e.g. https://watch.HOST/
env.setdefault("SYNC_REQUIRE_GROUP", "media-admins") # Caddy gate for /sync on the main host env.setdefault("SYNC_REQUIRE_GROUP", "media-admins") # Caddy gate for /sync on the main host
env.setdefault("COPYPARTY_IDP_KEY", "".join(c for c in secrets.token_urlsafe(40) if c.isalnum())[:32]) # Caddy->copyparty idp-h-key
env.setdefault("BOT_REQUIRE_GROUP", "bot-users") # Caddy 403s anyone not in this Authentik group; empty = any logged-in member env.setdefault("BOT_REQUIRE_GROUP", "bot-users") # Caddy 403s anyone not in this Authentik group; empty = any logged-in member
env.setdefault("LIVE_REQUIRE_GROUP", "") env.setdefault("LIVE_REQUIRE_GROUP", "")
env["GIT_HOST"] = git_host or env.get("GIT_HOST", "git.example.com") env["GIT_HOST"] = git_host or env.get("GIT_HOST", "git.example.com")
@@ -369,7 +381,8 @@ paths:
def write_copyparty(): def write_copyparty():
"""copyparty behind Caddy + Authentik: identity and groups arrive as headers; permissions are per group.""" """copyparty behind Caddy + Authentik: identity and groups arrive as headers; permissions are per group."""
Path("copyparty.conf").write_text("""# copyparty — behind Caddy + Authentik forward-auth. (generated by configure.py) key = read_env().get("COPYPARTY_IDP_KEY", "key")
Path("copyparty.conf").write_text(f"""# copyparty — behind Caddy + Authentik forward-auth. (generated by configure.py)
# Identity comes from the proxy: X-Authentik-Username / X-Authentik-Groups (groups separated by |). # Identity comes from the proxy: X-Authentik-Username / X-Authentik-Groups (groups separated by |).
# No local accounts. Groups: editors (read streams/clips, write finals), media-admins (everything incl. trash). # No local accounts. Groups: editors (read streams/clips, write finals), media-admins (everything incl. trash).
# No WebDAV: login is Discord only (browser session); download via the web UI (zip/tar for folders). # No WebDAV: login is Discord only (browser session); download via the web UI (zip/tar for folders).
@@ -384,7 +397,7 @@ def write_copyparty():
xff-src: 172.16.0.0/12, 10.0.0.0/8, 192.168.0.0/16, 127.0.0.0/8 xff-src: 172.16.0.0/12, 10.0.0.0/8, 192.168.0.0/16, 127.0.0.0/8
idp-h-usr: X-Authentik-Username idp-h-usr: X-Authentik-Username
idp-h-grp: X-Authentik-Groups idp-h-grp: X-Authentik-Groups
idp-gsep: | idp-h-key: X-Vanguard-{key} # header Caddy inserts on every request; IdP headers are ignored without it
no-robots no-robots
name: vanguard name: vanguard