From 268a6a2f81f99cf875c1d5d92d473702eabc0d81 Mon Sep 17 00:00:00 2001 From: Brandyn / Techy Date: Sat, 31 Oct 2026 15:09:13 -0500 Subject: [PATCH] copyparty idp-h-key: Caddy injects a header with a secret name (COPYPARTY_IDP_KEY); IdP headers ignored without it Co-Authored-By: Claude Fable 5.1 --- .env.example | 2 ++ README.md | 2 ++ configure.py | 10 +++++++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 2de10fd..37bafea 100644 --- a/.env.example +++ b/.env.example @@ -20,3 +20,5 @@ BOT_REQUIRE_GROUP=bot-users LIVE_REQUIRE_GROUP= APEX_HOST= APEX_REDIRECT= +COPYPARTY_IDP_KEY= +SYNC_REQUIRE_GROUP=media-admins diff --git a/README.md b/README.md index bca7fa6..d500d08 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,8 @@ 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` (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. + 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 diff --git a/configure.py b/configure.py index 6336d02..cb6ad46 100755 --- a/configure.py +++ b/configure.py @@ -268,9 +268,11 @@ name: {{http.request.header.X-Authentik-Name}}" 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 {{ reverse_proxy 127.0.0.1:3923 {{ + header_up X-Vanguard-{env.get("COPYPARTY_IDP_KEY", "key")} 1 flush_interval -1 }} }} @@ -286,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_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("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("LIVE_REQUIRE_GROUP", "") env["GIT_HOST"] = git_host or env.get("GIT_HOST", "git.example.com") @@ -378,7 +381,8 @@ paths: def write_copyparty(): """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 |). # 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). @@ -393,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 idp-h-usr: X-Authentik-Username 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 name: vanguard