Close out issue 001: document CSRF/reverse-proxy requirement

The app-side fix (CSRF_TRUSTED_ORIGINS, SECURE_PROXY_SSL_HEADER in
settings.py) was already committed as d458cd3 after the first Cloud-VM-1
deployment hit "CSRF verification failed" on login behind Apache. This
finishes the rest of that issue's checklist: documents
DJANGO_CSRF_TRUSTED_ORIGINS in deploy/env.example, adds the required
X-Forwarded-Proto header to the Apache/Caddy examples in
cloud_agent_deploy.md (plus a checklist item that verifies via an actual
login, not just an unauthenticated capabilities curl), and adds the same
proxy-agnostic guidance to docs/deployment.md.

Also introduces issues/, a tracked one-file-per-issue log for the project
going forward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
peter 2026-09-02 12:28:41 +01:00
parent d458cd3673
commit f2f2a9c96b
5 changed files with 114 additions and 3 deletions

View file

@ -223,6 +223,11 @@ network/VPN), do this before calling the deployment done:
- Put a reverse proxy in front with TLS. Bearer tokens go over plaintext HTTP
otherwise.
- Add the proxy's public hostname to `DJANGO_ALLOWED_HOSTS`.
- Set `DJANGO_CSRF_TRUSTED_ORIGINS=https://<public-hostname>` in `deploy/.env`,
**and** make sure the proxy forwards `X-Forwarded-Proto: https` (examples
below). Without both, login fails with "CSRF verification failed" — Django
can't tell the original request was HTTPS once a proxy terminates TLS and
forwards plain HTTP to the container.
- Keep `DJANGO_DEBUG=0` (already set above).
- Restrict SSH to key-based auth only; disable password login if you have
access to do so.
@ -241,6 +246,7 @@ instead. Apache example:
<VirtualHost *:443>
ServerName daybook.example.com
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
ProxyPass / http://127.0.0.1:8000/
ProxyPassReverse / http://127.0.0.1:8000/
SSLEngine on
@ -249,14 +255,18 @@ instead. Apache example:
</VirtualHost>
```
(`certbot --apache` will provision and wire up the certificate for you.)
`RequestHeader set X-Forwarded-Proto "https"` requires `a2enmod headers`; it's
not optional (see the CSRF note above). (`certbot --apache` will provision and
wire up the certificate for you.)
Only reach for a standalone proxy like Caddy if the target genuinely has
nothing on 80/443 yet:
nothing on 80/443 yet — it still needs the same header:
```text
daybook.example.com {
reverse_proxy 127.0.0.1:8000
reverse_proxy 127.0.0.1:8000 {
header_up X-Forwarded-Proto https
}
}
```
@ -284,6 +294,10 @@ Before reporting the deployment complete, confirm all of these:
- [ ] Cloned via git (not copied), with a working `git pull`.
- [ ] `deploy/.env` has a real, non-placeholder `DJANGO_SECRET_KEY`.
- [ ] `DJANGO_ALLOWED_HOSTS` includes the actual hostname/IP being used.
- [ ] If behind a reverse proxy: `DJANGO_CSRF_TRUSTED_ORIGINS` is set to the
public HTTPS origin, and the proxy forwards `X-Forwarded-Proto: https`.
Verify by actually logging in through the proxy, not just curling
`/api/capabilities` — CSRF only bites on POST (login, artifact writes).
- [ ] `docker compose -f deploy/docker-compose.yml ps` shows `daybook` healthy.
- [ ] `/api/capabilities` returns `403` unauthenticated, `200` with a token.
- [ ] At least one superuser exists with a working personal API token.

View file

@ -1,6 +1,11 @@
DJANGO_DEBUG=0
DJANGO_SECRET_KEY=replace-with-a-long-random-secret
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,your-vm-ip,your-domain.example
# Required if Daybook sits behind a TLS-terminating reverse proxy (Apache,
# Nginx, Traefik, a cloud load balancer). Without it, login/POST fails with
# "CSRF verification failed" because Django can't tell the original request
# was HTTPS. Set to the public HTTPS origin(s) users actually visit:
DJANGO_CSRF_TRUSTED_ORIGINS=https://your-domain.example
DAYBOOK_DATA_DIR=/data
DATABASE_PATH=/data/db.sqlite3
ARTIFACTS_ROOT=/data/artifacts_store