fix(proxy): address review — secure cookies, zone lookup, token perms

- settings.py: gate CSRF_COOKIE_SECURE/SESSION_COOKIE_SECURE on
  REVERSE_PROXY_TRUST_FORWARDED_PROTO so HTTPS deployments never send auth
  cookies over plain HTTP (default-off path unchanged).
- docker-compose.yml tunnel-init: resolve the Cloudflare zone by walking the
  domain labels to the registrable zone, so a subdomain BASE_URL no longer
  crashes provisioning; write the connector token with 0o600 perms.
- Remove .env.example; the env vars are documented inline in docker-compose.yml.
- etc/README.md: drop the .env.example reference.
This commit is contained in:
archivebox 2026-06-04 23:54:52 +00:00
parent 66fea50e81
commit 102a3ab4bc
No known key found for this signature in database
4 changed files with 13 additions and 21 deletions

View File

@ -1,16 +0,0 @@
# Copy to `.env` next to docker-compose.yml; `docker compose up -d` reads it automatically.
# Local default needs none of this — it's only for a public/HTTPS deployment.
BASE_URL=https://archive.example.com
SERVER_SECURITY_MODE=safe-subdomains-fullreplay
REVERSE_PROXY_TRUST_FORWARDED_PROTO=True
# Pick ONE ingress (see docker-compose.yml). Activates its profile with no extra flags:
# tunnel = Cloudflare Tunnel (no public IP needed) https = Caddy + Let's Encrypt (public IP)
COMPOSE_PROFILES=tunnel
# Cloudflare API token. For `tunnel`: Account:Cloudflare Tunnel:Edit + Zone:DNS:Edit + Zone:Read.
# For `https` (lego DNS-01): Zone:DNS:Edit + Zone:Read. Everything else is provisioned for you.
CLOUDFLARE_DNS_API_TOKEN=
# CLOUDFLARE_ACCOUNT_ID= # optional (tunnel only); first account is used if unset
# ARCHIVEBOX_ACME_EMAIL=admin@example.com # https profile: ACME contact email

View File

@ -432,8 +432,11 @@ SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
if CONFIG.REVERSE_PROXY_TRUST_FORWARDED_PROTO:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
CSRF_COOKIE_SECURE = False
SESSION_COOKIE_SECURE = False
# When the proxy is trusted the deployment is HTTPS end-to-end, so also mark the
# admin session + CSRF cookies Secure (never sent over plain HTTP). Scoped to the
# same flag so the default http://localhost:8000 setup keeps working unchanged.
CSRF_COOKIE_SECURE = CONFIG.REVERSE_PROXY_TRUST_FORWARDED_PROTO
SESSION_COOKIE_SECURE = CONFIG.REVERSE_PROXY_TRUST_FORWARDED_PROTO
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_NAME = f"archivebox_sessionid_{CONSTANTS.COLLECTION_ID}"
CSRF_COOKIE_NAME = f"archivebox_csrftoken_{CONSTANTS.COLLECTION_ID}"

View File

@ -127,7 +127,11 @@ services:
except urllib.error.HTTPError as e: return json.load(e)
assert DOMAIN and TOKEN, "set BASE_URL (https://archive.example.com) + CLOUDFLARE_DNS_API_TOKEN"
acct = os.environ.get("CLOUDFLARE_ACCOUNT_ID", "").strip() or call("GET", "/accounts")["result"][0]["id"]
zone = call("GET", f"/zones?name={DOMAIN}")["result"][0]["id"]
labels = DOMAIN.split("."); zone = None # DOMAIN may be a subdomain; find its registrable zone
for i in range(len(labels) - 1):
res = call("GET", f"/zones?name={'.'.join(labels[i:])}")["result"]
if res: zone = res[0]["id"]; break
assert zone, f"no Cloudflare zone found for {DOMAIN}"
NAME = "archivebox-" + DOMAIN.replace(".", "-")
ts = call("GET", f"/accounts/{acct}/cfd_tunnel?name={NAME}&is_deleted=false")["result"]
tid = ts[0]["id"] if ts else call("POST", f"/accounts/{acct}/cfd_tunnel", {"name": NAME,
@ -144,7 +148,7 @@ services:
desired = {"type": "CNAME", "name": name, "content": target, "proxied": True, "ttl": 1}
call("PUT", f"/zones/{zone}/dns_records/{cname[0]['id']}", desired) if cname else call("POST", f"/zones/{zone}/dns_records", desired)
tok = call("GET", f"/accounts/{acct}/cfd_tunnel/{tid}/token")["result"]
os.makedirs(os.path.dirname(OUT) or ".", exist_ok=True); open(OUT, "w").write(tok)
os.makedirs(os.path.dirname(OUT) or ".", exist_ok=True); open(OUT, "w").write(tok); os.chmod(OUT, 0o600)
print(f"[tunnel-init] {NAME} ({tid}): *.{DOMAIN} + {DOMAIN} -> {SERVICE}; connector token -> {OUT}")
cloudflared:

View File

@ -6,7 +6,8 @@ E.g. see `nginx.conf` for an example nginx config to serve your archive with SSL
For the recommended, batteries-included reverse proxy and TLS, you don't need a file
here at all — it's built into the main `../docker-compose.yml` as two opt-in, env-var
driven profiles (no extra files, Dockerfiles, or scripts). See `../.env.example`:
driven profiles (no extra files, Dockerfiles, or scripts) — set the documented env
vars in a `.env` next to `../docker-compose.yml`:
- `https` — Caddy + a `goacme/lego` DNS-01 sidecar fetch a single `*.<your-domain>`
wildcard cert (covering unlimited `snap-*` subdomains, ~150 DNS providers, no