Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0d6b00045 |
91
CLAUDE.md
91
CLAUDE.md
@@ -635,16 +635,22 @@ und live per curl gegen echten Server + Postgres verifiziert.
|
||||
|
||||
## Row-Level-Security (2026-09-01, Migration 0021)
|
||||
|
||||
**Kritischer Fund vor der Umsetzung:** Sowohl lokal als auch auf dem
|
||||
Produktivserver verbindet sich die Anwendung als `postgres`-Rolle — ein
|
||||
echter Postgres-**Superuser**. Superuser umgehen RLS-Policies *immer*,
|
||||
unabhängig von `FORCE ROW LEVEL SECURITY` (das wirkt nur beim
|
||||
Tabellenbesitzer, nicht bei Superusern — eine harte, nicht
|
||||
überschreibbare Postgres-Regel). Policies allein hätten also nichts
|
||||
bewirkt. Migration 0021 legt deshalb zusätzlich eine neue,
|
||||
eingeschränkte Rolle **`deklarix_app`** an (kein Superuser, kein
|
||||
Tabellenbesitzer, `NOBYPASSRLS`, zunächst `NOLOGIN`) — nur für diese
|
||||
Rolle greifen die Policies tatsächlich.
|
||||
**Ausgangslage:** RLS-Policies wirken nie bei Postgres-Superusern, und
|
||||
nie beim Tabellenbesitzer ohne `FORCE ROW LEVEL SECURITY` (beides harte,
|
||||
nicht überschreibbare Postgres-Regeln). Lokal verbindet die Anwendung
|
||||
als echter Superuser `postgres` (Docker-Testumgebung) — dort hätte
|
||||
`FORCE` allein nichts bewirkt. Auf dem Produktivserver verbindet sie
|
||||
dagegen über eine eigene, **nicht-privilegierte** Rolle `deklarix`
|
||||
(kein Superuser), die zugleich Eigentümerin der Tabellen ist — dort
|
||||
reicht `FORCE` aus. **Diese Erkenntnis kam erst nach einem
|
||||
Fehlversuch** (siehe "Incident" weiter unten) — die ursprüngliche
|
||||
Annahme, auch Produktion verbinde als Superuser, war falsch und beruhte
|
||||
auf einer Prüfung der falschen Rolle. Migration 0021 deckt seit der
|
||||
Korrektur beide Fälle ab: `FORCE ROW LEVEL SECURITY` auf jeder Tabelle
|
||||
(reicht für Produktion), plus optional eine neue, eingeschränkte Rolle
|
||||
**`deklarix_app`** (kein Superuser, kein Tabellenbesitzer,
|
||||
`NOBYPASSRLS`, zunächst `NOLOGIN`) für Umgebungen mit einer
|
||||
Superuser-Verbindung wie lokal.
|
||||
|
||||
**Architektur:** `internal/store/tenant_scope.go` — `Store.db(ctx)`
|
||||
liefert entweder die aktive Transaktion (falls `WithTenantScope` sie
|
||||
@@ -668,7 +674,7 @@ werden frühere Schritte desselben Requests zurückgerollt (vorher: keine
|
||||
Transaktion, ein halb fehlgeschlagener Handler konnte einen verwaisten
|
||||
Account ohne Nutzer hinterlassen).
|
||||
|
||||
**Geschützte Tabellen** (`ENABLE ROW LEVEL SECURITY` + Policy
|
||||
**Geschützte Tabellen** (`ENABLE` + `FORCE ROW LEVEL SECURITY` + Policy
|
||||
`tenant_isolation`): `antrag`, `registereintrag`, `abteilung`,
|
||||
`werkzeug_sperre`, `genehmiger_rolle`, `freigabe_regel`,
|
||||
`loeschfrist_einstellung` (direkte `account_id`-Prüfung), `werkzeug`
|
||||
@@ -710,19 +716,56 @@ eingeschränkte Rolle und überspringen sich selbst sauber, wenn
|
||||
`DATABASE_URL_APP` nicht gesetzt ist (analog zum bestehenden
|
||||
`DATABASE_URL`-Skip-Muster).
|
||||
|
||||
**Produktivbetrieb — noch ausstehender manueller Schritt.** Die
|
||||
Migration allein bewirkt in Produktion noch NICHTS (die App verbindet
|
||||
weiterhin als `postgres`-Superuser, `DATABASE_URL_APP` ist nicht
|
||||
gesetzt). Um RLS tatsächlich scharf zu schalten:
|
||||
1. `ALTER ROLE deklarix_app WITH LOGIN PASSWORD '<neu generiertes Secret>';`
|
||||
einmalig auf dem Zielserver ausführen (das Passwort steht nicht im
|
||||
Code/in Migrationen — Secrets gehören nicht in ein versioniertes
|
||||
Repo).
|
||||
2. `DATABASE_URL_APP=postgres://deklarix_app:<secret>@<host>/deklarix?...`
|
||||
in `/etc/deklarix/deklarix.env` eintragen.
|
||||
3. Dienst neu starten.
|
||||
`DATABASE_URL` (Migrationen, DDL-Rechte) bleibt unverändert auf der
|
||||
bisherigen, privilegierten Verbindung.
|
||||
**Incident 2026-09-01 (~3 Minuten Downtime) und Korrektur.** Die erste
|
||||
Fassung der Migration ging fälschlich davon aus, dass die Anwendung
|
||||
überall als Postgres-**Superuser** `postgres` verbindet (das hatte ich
|
||||
nur lokal und via `sudo -u postgres psql` auf dem Server geprüft — das
|
||||
ist aber ein SSH/OS-Login-Check, nicht die tatsächliche
|
||||
`DATABASE_URL`-Rolle der Anwendung). Tatsächlich verbindet Produktion
|
||||
über eine eigene, **nicht-privilegierte** Rolle `deklarix` (kein
|
||||
Superuser, kein `CREATEROLE`), die zugleich Eigentümerin aller Tabellen
|
||||
ist. Die Migration versuchte dort `CREATE ROLE deklarix_app`
|
||||
auszuführen, scheiterte mit "permission denied to create role", blieb
|
||||
im `dirty`-Zustand hängen und der Dienst crash-loopte beim Start
|
||||
(09:27–09:30 Uhr). Behoben durch: `schema_migrations` manuell auf
|
||||
Version 20 zurückgesetzt, Paket auf v0.37.0 zurückgestuft, Dienst
|
||||
stabilisiert — kein Datenverlust, Postgres hatte die fehlgeschlagene
|
||||
Migration als DDL-Transaktion bereits sauber selbst zurückgerollt, nur
|
||||
golang-migrates eigene Versions-Buchführung musste von Hand korrigiert
|
||||
werden.
|
||||
|
||||
**Korrigierte, robustere Migration:** das Anlegen von `deklarix_app` ist
|
||||
jetzt an eine Prüfung gekoppelt (`SELECT ... WHERE rolname = current_user
|
||||
AND rolcreaterole`) und wird bei fehlendem `CREATEROLE` übersprungen
|
||||
(`RAISE NOTICE`, kein Fehler) statt die ganze Migration scheitern zu
|
||||
lassen. Zusätzlich bekommt jede Tabelle jetzt **`FORCE ROW LEVEL
|
||||
SECURITY`** (vorher nur `ENABLE`) — das bindet auch den **Tabellen-
|
||||
besitzer** an die Policies, sofern er kein Superuser ist. Damit deckt
|
||||
eine einzige Migration beide Fälle ab:
|
||||
- **Produktion** (`deklarix`, Tabellenbesitzer, kein Superuser): `FORCE`
|
||||
allein reicht bereits aus. **Kein manueller Schritt nötig** — nach dem
|
||||
Deploy dieser Migration ist RLS dort sofort aktiv.
|
||||
- **Lokales Docker-Postgres** (`postgres`-Superuser, für den `FORCE`
|
||||
wirkungslos bleibt): `deklarix_app` wird zusätzlich angelegt, für
|
||||
lokale Tests weiterhin per `DATABASE_URL_APP` nutzbar.
|
||||
|
||||
**Erneut end-to-end verifiziert nach der Korrektur**, diesmal
|
||||
zusätzlich produktionsgetreu: eine zweite, temporäre lokale Rolle
|
||||
(`deklarix_sim`, kein Superuser, kein `CREATEROLE`, Eigentümerin einer
|
||||
frischen Test-Datenbank — exakt Produktions-Rechtemodell) durchlief die
|
||||
komplette Migrationskette 0001–0021 fehlerfrei, **und** die Isolation
|
||||
griff nachweislich auch für sie als Tabellenbesitzerin (Kontext A sah
|
||||
ausschließlich Zeile A, trotz voller Tabellen-Eigentümerschaft). Vorher
|
||||
war nur der `deklarix_app`-Pfad (Nicht-Eigentümer-Rolle) bewiesen, nicht
|
||||
der tatsächliche Produktions-Pfad (Eigentümer-Rolle + FORCE) — genau die
|
||||
Lücke, die den Incident verursachte.
|
||||
|
||||
**Lehre für künftige Prüfungen dieser Art:** "welche DB-Rolle verwendet
|
||||
die Anwendung" per `sudo -u postgres psql` zu beantworten prüft die
|
||||
falsche Sache — maßgeblich ist ausschließlich die Rolle **in der
|
||||
tatsächlichen `DATABASE_URL`** (hier: `cat /etc/deklarix/deklarix.env`
|
||||
bzw. `SELECT rolname, rolsuper, rolcreaterole FROM pg_roles WHERE
|
||||
rolname = 'deklarix'`, nicht `current_user` über einen andere Anmeldung).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,40 +1,58 @@
|
||||
ALTER TABLE antrag NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE antrag DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON antrag;
|
||||
|
||||
ALTER TABLE registereintrag NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE registereintrag DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON registereintrag;
|
||||
|
||||
ALTER TABLE abteilung NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE abteilung DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON abteilung;
|
||||
|
||||
ALTER TABLE werkzeug_sperre NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE werkzeug_sperre DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON werkzeug_sperre;
|
||||
|
||||
ALTER TABLE genehmiger_rolle NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE genehmiger_rolle DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON genehmiger_rolle;
|
||||
|
||||
ALTER TABLE freigabe_regel NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE freigabe_regel DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON freigabe_regel;
|
||||
|
||||
ALTER TABLE loeschfrist_einstellung NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE loeschfrist_einstellung DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON loeschfrist_einstellung;
|
||||
|
||||
ALTER TABLE werkzeug NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE werkzeug DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON werkzeug;
|
||||
|
||||
ALTER TABLE bewertung NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE bewertung DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON bewertung;
|
||||
|
||||
ALTER TABLE entscheidung NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE entscheidung DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON entscheidung;
|
||||
|
||||
ALTER TABLE freigabeschritt NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE freigabeschritt DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON freigabeschritt;
|
||||
|
||||
ALTER TABLE nutzer_genehmiger_rolle NO FORCE ROW LEVEL SECURITY;
|
||||
ALTER TABLE nutzer_genehmiger_rolle DISABLE ROW LEVEL SECURITY;
|
||||
DROP POLICY IF EXISTS tenant_isolation ON nutzer_genehmiger_rolle;
|
||||
|
||||
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM deklarix_app;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM deklarix_app;
|
||||
REVOKE USAGE ON SCHEMA public FROM deklarix_app;
|
||||
DROP ROLE IF EXISTS deklarix_app;
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'deklarix_app') THEN
|
||||
EXECUTE 'REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM deklarix_app';
|
||||
EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM deklarix_app';
|
||||
EXECUTE 'REVOKE USAGE ON SCHEMA public FROM deklarix_app';
|
||||
DROP ROLE deklarix_app;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
@@ -1,36 +1,47 @@
|
||||
-- Mandantenisolation auf Datenbankebene (Postgres Row-Level Security).
|
||||
-- WICHTIG: RLS-Policies wirken NIE bei Postgres-Superusern und NIE beim
|
||||
-- Tabellenbesitzer ohne FORCE ROW LEVEL SECURITY — und FORCE wirkt
|
||||
-- seinerseits NICHT bei Superusern (das ist eine harte, nicht
|
||||
-- überschreibbare Postgres-Regel). Migrationen und die bisherige
|
||||
-- Anwendungs-DATABASE_URL laufen als Superuser "postgres" (Tabellen-
|
||||
-- besitzer) — für diese Rolle ist RLS wirkungslos, ganz gleich wie die
|
||||
-- Policies aussehen. Deshalb legt diese Migration zusätzlich eine neue,
|
||||
-- eingeschränkte Rolle "deklarix_app" an (kein Superuser, kein
|
||||
-- Tabellenbesitzer, NOBYPASSRLS) — NUR für diese Rolle greifen die
|
||||
-- Policies unten tatsächlich. Migrationen laufen weiterhin über die
|
||||
-- bisherige privilegierte DATABASE_URL; die laufende Anwendung muss auf
|
||||
-- die neue, eingeschränkte Rolle umgestellt werden (neue Umgebungs-
|
||||
-- variable DATABASE_URL_APP, siehe cmd/deklarix/main.go) — ohne diesen
|
||||
-- Wechsel ist diese Migration reine Dokumentation ohne Wirkung.
|
||||
--
|
||||
-- Die Rolle wird bewusst OHNE Passwort angelegt (NOLOGIN) — ein
|
||||
-- Klartext-Passwort gehört nicht in eine versionierte, für jeden mit
|
||||
-- Repo-Zugriff lesbare Migrationsdatei. Login-Fähigkeit + Passwort
|
||||
-- werden einmalig manuell je Umgebung gesetzt:
|
||||
-- ALTER ROLE deklarix_app WITH LOGIN PASSWORD '<generiertes Secret>';
|
||||
-- Danach DATABASE_URL_APP in der jeweiligen deklarix.env eintragen.
|
||||
-- WICHTIG, per Incident am 2026-09-01 gelernt: RLS-Policies wirken NIE
|
||||
-- bei Postgres-Superusern, und NIE beim Tabellenbesitzer ohne FORCE ROW
|
||||
-- LEVEL SECURITY. Welcher Fall zutrifft, hängt von der Umgebung ab:
|
||||
-- - Produktion verbindet als eigene, nicht-privilegierte Rolle (z. B.
|
||||
-- "deklarix"), die zugleich Eigentümerin der Tabellen ist (sie hat
|
||||
-- sie über die Migrationen selbst angelegt) — für sie reicht FORCE
|
||||
-- ROW LEVEL SECURITY völlig aus, keine weitere Rolle nötig.
|
||||
-- - Manche Entwicklungs-/Testumgebungen verbinden dagegen als
|
||||
-- echter Postgres-Superuser (z. B. lokales Docker-Postgres mit
|
||||
-- "postgres") — für den wirkt FORCE nicht (Superuser sind davon
|
||||
-- laut Postgres-Dokumentation ausdrücklich ausgenommen). Dort kann
|
||||
-- zusätzlich eine eingeschränkte Rolle "deklarix_app" angelegt
|
||||
-- werden, für die die Policies unabhängig von FORCE gelten.
|
||||
--
|
||||
-- Diese Migration deckt BEIDE Fälle ab, ohne bei fehlendem CREATEROLE
|
||||
-- fehlzuschlagen (das brachte den Dienst am 2026-09-01 für ~3 Minuten
|
||||
-- zum Absturz, siehe CLAUDE.md) — das Anlegen von "deklarix_app" ist
|
||||
-- rein optional und wird übersprungen, wenn die aktuelle Rolle dafür
|
||||
-- keine Berechtigung hat.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'deklarix_app') THEN
|
||||
CREATE ROLE deklarix_app NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOLOGIN;
|
||||
IF EXISTS (SELECT FROM pg_roles WHERE rolname = current_user AND rolcreaterole) THEN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'deklarix_app') THEN
|
||||
CREATE ROLE deklarix_app NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOLOGIN;
|
||||
END IF;
|
||||
EXECUTE 'GRANT USAGE ON SCHEMA public TO deklarix_app';
|
||||
EXECUTE 'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO deklarix_app';
|
||||
EXECUTE 'ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO deklarix_app';
|
||||
ELSE
|
||||
RAISE NOTICE 'Rolle % hat kein CREATEROLE — deklarix_app wird übersprungen, FORCE ROW LEVEL SECURITY schützt stattdessen direkt die bestehende (Tabellenbesitzer-)Rolle.', current_user;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
GRANT USAGE ON SCHEMA public TO deklarix_app;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO deklarix_app;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO deklarix_app;
|
||||
-- Login-Fähigkeit + Passwort für deklarix_app (falls angelegt) werden
|
||||
-- einmalig manuell je Umgebung gesetzt, NIE in einer versionierten
|
||||
-- Migration (Klartext-Secret gehört nicht ins Repo):
|
||||
-- ALTER ROLE deklarix_app WITH LOGIN PASSWORD '<generiertes Secret>';
|
||||
-- Danach optional DATABASE_URL_APP in der jeweiligen deklarix.env
|
||||
-- eintragen. Für Umgebungen, in denen die Anwendung bereits als
|
||||
-- Tabellenbesitzer (nicht-Superuser) verbindet, ist das NICHT nötig —
|
||||
-- FORCE ROW LEVEL SECURITY unten reicht dort aus.
|
||||
|
||||
-- ─── Tabellen MIT direkter account_id-Spalte ───────────────────────────
|
||||
-- Eine einzelne Policy (FOR ALL) pro Tabelle deckt SELECT/UPDATE/DELETE
|
||||
@@ -46,6 +57,7 @@ CREATE POLICY tenant_isolation ON antrag FOR ALL USING (
|
||||
OR current_setting('app.is_betreiber', true) = 'true'
|
||||
);
|
||||
ALTER TABLE antrag ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE antrag FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_isolation ON registereintrag FOR ALL USING (
|
||||
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid
|
||||
@@ -55,6 +67,7 @@ CREATE POLICY tenant_isolation ON registereintrag FOR ALL USING (
|
||||
OR current_setting('app.is_betreiber', true) = 'true'
|
||||
);
|
||||
ALTER TABLE registereintrag ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE registereintrag FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_isolation ON abteilung FOR ALL USING (
|
||||
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid
|
||||
@@ -64,6 +77,7 @@ CREATE POLICY tenant_isolation ON abteilung FOR ALL USING (
|
||||
OR current_setting('app.is_betreiber', true) = 'true'
|
||||
);
|
||||
ALTER TABLE abteilung ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE abteilung FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_isolation ON werkzeug_sperre FOR ALL USING (
|
||||
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid
|
||||
@@ -73,6 +87,7 @@ CREATE POLICY tenant_isolation ON werkzeug_sperre FOR ALL USING (
|
||||
OR current_setting('app.is_betreiber', true) = 'true'
|
||||
);
|
||||
ALTER TABLE werkzeug_sperre ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE werkzeug_sperre FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_isolation ON genehmiger_rolle FOR ALL USING (
|
||||
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid
|
||||
@@ -82,6 +97,7 @@ CREATE POLICY tenant_isolation ON genehmiger_rolle FOR ALL USING (
|
||||
OR current_setting('app.is_betreiber', true) = 'true'
|
||||
);
|
||||
ALTER TABLE genehmiger_rolle ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE genehmiger_rolle FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_isolation ON freigabe_regel FOR ALL USING (
|
||||
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid
|
||||
@@ -91,6 +107,7 @@ CREATE POLICY tenant_isolation ON freigabe_regel FOR ALL USING (
|
||||
OR current_setting('app.is_betreiber', true) = 'true'
|
||||
);
|
||||
ALTER TABLE freigabe_regel ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE freigabe_regel FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_isolation ON loeschfrist_einstellung FOR ALL USING (
|
||||
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid
|
||||
@@ -100,6 +117,7 @@ CREATE POLICY tenant_isolation ON loeschfrist_einstellung FOR ALL USING (
|
||||
OR current_setting('app.is_betreiber', true) = 'true'
|
||||
);
|
||||
ALTER TABLE loeschfrist_einstellung ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE loeschfrist_einstellung FORCE ROW LEVEL SECURITY;
|
||||
|
||||
-- ─── werkzeug: account_id NULLABLE (NULL = zentraler Katalog) ──────────
|
||||
-- Lesen: jeder sieht zentrale (NULL) Einträge plus die eigenen. NUR der
|
||||
@@ -115,6 +133,7 @@ CREATE POLICY tenant_isolation ON werkzeug FOR ALL USING (
|
||||
OR account_id = NULLIF(current_setting('app.account_id', true), '')::uuid
|
||||
);
|
||||
ALTER TABLE werkzeug ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE werkzeug FORCE ROW LEVEL SECURITY;
|
||||
|
||||
-- ─── Tabellen OHNE eigene account_id, über Fremdschlüssel abgeleitet ───
|
||||
-- antrag/genehmiger_rolle sind selbst schon RLS-geschützt (s. o.) — eine
|
||||
@@ -126,6 +145,7 @@ CREATE POLICY tenant_isolation ON bewertung FOR ALL USING (
|
||||
antrag_id IN (SELECT id FROM antrag)
|
||||
);
|
||||
ALTER TABLE bewertung ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE bewertung FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_isolation ON entscheidung FOR ALL USING (
|
||||
antrag_id IN (SELECT id FROM antrag)
|
||||
@@ -133,6 +153,7 @@ CREATE POLICY tenant_isolation ON entscheidung FOR ALL USING (
|
||||
antrag_id IN (SELECT id FROM antrag)
|
||||
);
|
||||
ALTER TABLE entscheidung ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE entscheidung FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_isolation ON freigabeschritt FOR ALL USING (
|
||||
antrag_id IN (SELECT id FROM antrag)
|
||||
@@ -140,6 +161,7 @@ CREATE POLICY tenant_isolation ON freigabeschritt FOR ALL USING (
|
||||
antrag_id IN (SELECT id FROM antrag)
|
||||
);
|
||||
ALTER TABLE freigabeschritt ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE freigabeschritt FORCE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY tenant_isolation ON nutzer_genehmiger_rolle FOR ALL USING (
|
||||
genehmiger_rolle_id IN (SELECT id FROM genehmiger_rolle)
|
||||
@@ -147,6 +169,7 @@ CREATE POLICY tenant_isolation ON nutzer_genehmiger_rolle FOR ALL USING (
|
||||
genehmiger_rolle_id IN (SELECT id FROM genehmiger_rolle)
|
||||
);
|
||||
ALTER TABLE nutzer_genehmiger_rolle ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE nutzer_genehmiger_rolle FORCE ROW LEVEL SECURITY;
|
||||
|
||||
-- ─── Bewusst OHNE RLS ───────────────────────────────────────────────────
|
||||
-- account: hat keine account_id-Spalte (ist selbst der Mandant) und
|
||||
|
||||
Reference in New Issue
Block a user