diff --git a/cms/README.md b/cms/README.md index bf30c19..0305dca 100644 --- a/cms/README.md +++ b/cms/README.md @@ -161,6 +161,14 @@ Eingebaute Schutzmaßnahmen (Stand: Härtungs-Pass): - **Non-root**: der CMS-Container läuft als `node` (uid 1000). - **Port-Binding** über `BIND_ADDR` (Standard `127.0.0.1`), DB nur auf localhost. - **CORS** am Kong-Gateway auf die eigene `SITE_URL`-Origin beschränkt (kein `*`). +- **Reverse-Proxy nur `/auth/*`**: bei einem Domain-Deploy gehört nur das Login + (GoTrue) public — `/rest`, `/storage`, `/realtime` nicht durchreichen (PostgREST + `/rest/v1/` würde sonst die DB-Schema-Beschreibung preisgeben). Siehe Caddy-Block + in [`../proxmox/README.md`](../proxmox/README.md). +- **Login-Rate-Limit** an GoTrue (`GOTRUE_RATE_LIMIT_TOKEN_REFRESH`), weil das + öffentliche Login direkt aufs `/token` geht (nicht übers Node-Limit). +- **Keine Tabellenrechte für `anon`/`authenticated`** (`revoke` in `db/schema.sql`): + RLS bleibt so auch bei künftigen Policies dicht; nur `service_role` (Node) liest. ### Migration eines bestehenden Containers diff --git a/cms/db/schema.sql b/cms/db/schema.sql index 764fcbd..8e7581d 100644 --- a/cms/db/schema.sql +++ b/cms/db/schema.sql @@ -31,6 +31,7 @@ create index if not exists posts_section_idx on public.posts (section); -- RLS aktivieren; die api nutzt den Service-Key (umgeht RLS). Wenn das -- Frontend später direkt liest, hier gezielte Policies ergänzen. alter table public.posts enable row level security; +revoke all on public.posts from anon, authenticated; -- ── Dialog / Diskussionen ─────────────────────────────────────────────── -- Thread = Pfad des Beitrags (z.B. /library/software/stack/). Flache Wortmeldungen @@ -50,7 +51,12 @@ create table if not exists public.comments ( alter table public.comments add column if not exists author_role text; create index if not exists comments_thread_idx on public.comments (thread, created_at); alter table public.comments enable row level security; -grant all on public.comments to anon, authenticated, service_role; +-- Nur die Node-API (service_role) greift auf die Tabellen zu; der Browser geht +-- ausschliesslich über /api/*. anon/authenticated bekommen KEINE Tabellenrechte, +-- damit das öffentlich erreichbare /rest/v1 auch bei künftigen RLS-Policies dicht +-- bleibt (Defense-in-Depth, nicht nur "RLS ohne Policy"). +grant all on public.comments to service_role; +revoke all on public.comments from anon, authenticated; -- Aggregat je Thread (Anzahl + letzte Aktivität). Spart der API den Full-Table- -- Scan + JS-Aggregation bei jedem Forum-Aufruf; Postgres zählt direkt. @@ -75,7 +81,8 @@ create table if not exists public.forums ( created_at timestamptz not null default now() ); alter table public.forums enable row level security; -grant all on public.forums to anon, authenticated, service_role; +grant all on public.forums to service_role; +revoke all on public.forums from anon, authenticated; -- ── Threads (Diskussionen) ────────────────────────────────────────────── -- key = stabiler Bezeichner, den comments.thread referenziert: @@ -96,7 +103,8 @@ create table if not exists public.threads ( ); create index if not exists threads_forum_idx on public.threads (forum_id); alter table public.threads enable row level security; -grant all on public.threads to anon, authenticated, service_role; +grant all on public.threads to service_role; +revoke all on public.threads from anon, authenticated; -- Seed-Kategorien (idempotent; im Admin umbenenn-/erweiterbar). insert into public.forums (slug, name, sort, kind) values diff --git a/cms/docker-compose.yml b/cms/docker-compose.yml index 398aea6..94b04a0 100644 --- a/cms/docker-compose.yml +++ b/cms/docker-compose.yml @@ -89,6 +89,10 @@ services: # Single-Author: Self-Signup aus. User wird per Admin-API angelegt # (Kommando steht im README / LXC-Output). GOTRUE_DISABLE_SIGNUP: "true" + # Brute-Force-Bremse aufs /token: das öffentliche Login läuft direkt gegen + # GoTrue (nicht über das Node-Rate-Limit), daher hier kappen — max. 100 + # Token-Anfragen / 5 Min. Reichlich für einen Autor, bremst Rateversuche. + GOTRUE_RATE_LIMIT_TOKEN_REFRESH: "100" GOTRUE_JWT_ADMIN_ROLES: service_role GOTRUE_JWT_AUD: authenticated GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated diff --git a/cms/proxmox/create-openbureau-lxc.sh b/cms/proxmox/create-openbureau-lxc.sh index fcb85fd..427a3be 100755 --- a/cms/proxmox/create-openbureau-lxc.sh +++ b/cms/proxmox/create-openbureau-lxc.sh @@ -192,8 +192,11 @@ cat <} zeigt) Caddy-Block: ${SITE_DOMAIN} { - @sb path /auth/* /rest/* /storage/* /realtime/* - reverse_proxy @sb ${IPADDR:-}:8000 + # Nur /auth/* muss public ans Gateway (Browser-Login). Daten + # laufen über /api/* (Node spricht kong intern an). /rest, /storage, + # /realtime NICHT exponieren — unnötige Angriffsfläche. + @auth path /auth/* + reverse_proxy @auth ${IPADDR:-}:8000 reverse_proxy ${IPADDR:-}:8080 } EOF diff --git a/proxmox/README.md b/proxmox/README.md index 4cf9c53..505caa2 100644 --- a/proxmox/README.md +++ b/proxmox/README.md @@ -69,8 +69,12 @@ Den passenden Reverse-Proxy-Eintrag gibt das Skript am Ende selbst aus. Für ```caddy dev.openbureau.ch { - @sb path /auth/* /rest/* /storage/* /realtime/* - reverse_proxy @sb 192.168.1.134:8000 + # Nur /auth/* muss public ans Supabase-Gateway (Browser-Login). Alle Daten + # laufen über /api/* (Node spricht kong intern an). /rest, /storage, + # /realtime bewusst NICHT exponieren — sonst gibt /rest/v1/ die ganze + # DB-Schema-Beschreibung preis (PostgREST-OpenAPI). + @auth path /auth/* + reverse_proxy @auth 192.168.1.134:8000 reverse_proxy 192.168.1.134:8080 } ```