diff --git a/deploy/petal.env.example b/deploy/petal.env.example index 1f2f06e..578cd9e 100644 --- a/deploy/petal.env.example +++ b/deploy/petal.env.example @@ -20,6 +20,14 @@ TZ=Europe/Lisbon PETAL_UID=1001 PETAL_GID=1001 +# --- Interim edge gate (delete when Phase 16 auth lands) --------------------- +# Petal has no authentication of its own yet — StaticResolver hands every +# request the same local user — so Traefik holds the door with basic auth until +# the OIDC flow exists. user:bcrypt-hash, as produced by: +# htpasswd -nbB petal 'your-password' +# /api/health is deliberately exempt (its own router) so monitoring still works. +PETAL_BASIC_AUTH= + # --- LLM (millenia, over headscale) ------------------------------------------ # The only cross-VPN dependency. Petal degrades warmly when it's unreachable: # spell check, gloss, garden, search, export and read-aloud all keep working and diff --git a/docker-compose.yml b/docker-compose.yml index 5ac8743..dbd7df3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,8 +65,23 @@ services: traefik.http.routers.petal.tls: "true" traefik.http.routers.petal.tls.certResolver: default traefik.http.routers.petal.service: petal - traefik.http.routers.petal.middlewares: compression@file,petal-headers + traefik.http.routers.petal.middlewares: compression@file,petal-headers,petal-auth traefik.http.services.petal.loadbalancer.server.port: "8080" + # INTERIM — delete this middleware and the petal-health router when Phase + # 16's OIDC login lands. Petal has no authentication of its own yet + # (StaticResolver hands every request the same local user), so without a + # gate at the edge anyone who finds the hostname can read and write + # documents and upload images. PETAL_BASIC_AUTH is a user:bcrypt-hash + # pair; see deploy/README.md for generating it. + traefik.http.middlewares.petal-auth.basicauth.users: ${PETAL_BASIC_AUTH:?set PETAL_BASIC_AUTH in .env} + # /api/health stays open on its own higher-priority router: a monitoring + # probe must not need a credential, and the endpoint carries no user data. + traefik.http.routers.petal-health.rule: Host(`${PETAL_HOST:-petal.parodia.dev}`) && Path(`/api/health`) + traefik.http.routers.petal-health.priority: "100" + traefik.http.routers.petal-health.entrypoints: web-secure + traefik.http.routers.petal-health.tls: "true" + traefik.http.routers.petal-health.tls.certResolver: default + traefik.http.routers.petal-health.service: petal # Petal is a private writing space: no framing, no sniffing, HSTS on. traefik.http.middlewares.petal-headers.headers.customresponseheaders.Content-Security-Policy: frame-ancestors 'self' traefik.http.middlewares.petal-headers.headers.customresponseheaders.Strict-Transport-Security: max-age=31536000; includeSubDomains