Compare commits

...

1 Commits

Author SHA1 Message Date
noroot
b0d6b00045 fix: Migration 0021 crashte in Produktion — falsche Rollen-Annahme
v0.38.0 verursachte einen ~3-minütigen Ausfall: Migration 0021 nahm an,
die Anwendung verbinde überall als Postgres-Superuser "postgres" (nur
lokal via sudo geprüft, nicht die tatsächliche Produktions-DATABASE_URL)
und versuchte dort eine neue Rolle anzulegen - production verbindet
aber über die nicht-privilegierte, tabellenbesitzende Rolle "deklarix"
ohne CREATEROLE, das INSERT/CREATE ROLE schlug fehl und der Dienst
crash-loopte im "dirty migration"-Zustand. Kein Datenverlust (Postgres
hat die DDL-Transaktion selbst zurückgerollt), Dienst wurde auf v0.37.0
zurückgestuft und stabilisiert.

Fix: Rollen-Anlage ist jetzt an eine CREATEROLE-Prüfung gekoppelt und
wird bei fehlender Berechtigung übersprungen statt zu scheitern.
Zusätzlich FORCE ROW LEVEL SECURITY auf jeder Tabelle - das bindet auch
den Tabellenbesitzer (wie Productions "deklarix"), ganz ohne die
zusätzliche Rolle. Produktivbetrieb braucht dadurch jetzt gar keinen
manuellen Schritt mehr. Erneut end-to-end verifiziert, diesmal
zusätzlich produktionsgetreu simuliert (temporäre nicht-privilegierte,
tabellenbesitzende Rolle lokal).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 09:38:51 +02:00
3 changed files with 137 additions and 53 deletions

View File

@@ -635,16 +635,22 @@ und live per curl gegen echten Server + Postgres verifiziert.
## Row-Level-Security (2026-09-01, Migration 0021) ## Row-Level-Security (2026-09-01, Migration 0021)
**Kritischer Fund vor der Umsetzung:** Sowohl lokal als auch auf dem **Ausgangslage:** RLS-Policies wirken nie bei Postgres-Superusern, und
Produktivserver verbindet sich die Anwendung als `postgres`-Rolle — ein nie beim Tabellenbesitzer ohne `FORCE ROW LEVEL SECURITY` (beides harte,
echter Postgres-**Superuser**. Superuser umgehen RLS-Policies *immer*, nicht überschreibbare Postgres-Regeln). Lokal verbindet die Anwendung
unabhängig von `FORCE ROW LEVEL SECURITY` (das wirkt nur beim als echter Superuser `postgres` (Docker-Testumgebung) — dort hätte
Tabellenbesitzer, nicht bei Superusern — eine harte, nicht `FORCE` allein nichts bewirkt. Auf dem Produktivserver verbindet sie
überschreibbare Postgres-Regel). Policies allein hätten also nichts dagegen über eine eigene, **nicht-privilegierte** Rolle `deklarix`
bewirkt. Migration 0021 legt deshalb zusätzlich eine neue, (kein Superuser), die zugleich Eigentümerin der Tabellen ist — dort
eingeschränkte Rolle **`deklarix_app`** an (kein Superuser, kein reicht `FORCE` aus. **Diese Erkenntnis kam erst nach einem
Tabellenbesitzer, `NOBYPASSRLS`, zunächst `NOLOGIN`) — nur für diese Fehlversuch** (siehe "Incident" weiter unten) — die ursprüngliche
Rolle greifen die Policies tatsächlich. 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)` **Architektur:** `internal/store/tenant_scope.go``Store.db(ctx)`
liefert entweder die aktive Transaktion (falls `WithTenantScope` sie 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 Transaktion, ein halb fehlgeschlagener Handler konnte einen verwaisten
Account ohne Nutzer hinterlassen). 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`, `tenant_isolation`): `antrag`, `registereintrag`, `abteilung`,
`werkzeug_sperre`, `genehmiger_rolle`, `freigabe_regel`, `werkzeug_sperre`, `genehmiger_rolle`, `freigabe_regel`,
`loeschfrist_einstellung` (direkte `account_id`-Prüfung), `werkzeug` `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_APP` nicht gesetzt ist (analog zum bestehenden
`DATABASE_URL`-Skip-Muster). `DATABASE_URL`-Skip-Muster).
**Produktivbetrieb — noch ausstehender manueller Schritt.** Die **Incident 2026-09-01 (~3 Minuten Downtime) und Korrektur.** Die erste
Migration allein bewirkt in Produktion noch NICHTS (die App verbindet Fassung der Migration ging fälschlich davon aus, dass die Anwendung
weiterhin als `postgres`-Superuser, `DATABASE_URL_APP` ist nicht überall als Postgres-**Superuser** `postgres` verbindet (das hatte ich
gesetzt). Um RLS tatsächlich scharf zu schalten: nur lokal und via `sudo -u postgres psql` auf dem Server geprüft — das
1. `ALTER ROLE deklarix_app WITH LOGIN PASSWORD '<neu generiertes Secret>';` ist aber ein SSH/OS-Login-Check, nicht die tatsächliche
einmalig auf dem Zielserver ausführen (das Passwort steht nicht im `DATABASE_URL`-Rolle der Anwendung). Tatsächlich verbindet Produktion
Code/in Migrationen — Secrets gehören nicht in ein versioniertes über eine eigene, **nicht-privilegierte** Rolle `deklarix` (kein
Repo). Superuser, kein `CREATEROLE`), die zugleich Eigentümerin aller Tabellen
2. `DATABASE_URL_APP=postgres://deklarix_app:<secret>@<host>/deklarix?...` ist. Die Migration versuchte dort `CREATE ROLE deklarix_app`
in `/etc/deklarix/deklarix.env` eintragen. auszuführen, scheiterte mit "permission denied to create role", blieb
3. Dienst neu starten. im `dirty`-Zustand hängen und der Dienst crash-loopte beim Start
`DATABASE_URL` (Migrationen, DDL-Rechte) bleibt unverändert auf der (09:2709:30 Uhr). Behoben durch: `schema_migrations` manuell auf
bisherigen, privilegierten Verbindung. 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 00010021 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).
--- ---

View File

@@ -1,40 +1,58 @@
ALTER TABLE antrag NO FORCE ROW LEVEL SECURITY;
ALTER TABLE antrag DISABLE ROW LEVEL SECURITY; ALTER TABLE antrag DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON antrag; DROP POLICY IF EXISTS tenant_isolation ON antrag;
ALTER TABLE registereintrag NO FORCE ROW LEVEL SECURITY;
ALTER TABLE registereintrag DISABLE ROW LEVEL SECURITY; ALTER TABLE registereintrag DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON registereintrag; DROP POLICY IF EXISTS tenant_isolation ON registereintrag;
ALTER TABLE abteilung NO FORCE ROW LEVEL SECURITY;
ALTER TABLE abteilung DISABLE ROW LEVEL SECURITY; ALTER TABLE abteilung DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON abteilung; 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; ALTER TABLE werkzeug_sperre DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON werkzeug_sperre; 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; ALTER TABLE genehmiger_rolle DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON genehmiger_rolle; 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; ALTER TABLE freigabe_regel DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON freigabe_regel; 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; ALTER TABLE loeschfrist_einstellung DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON loeschfrist_einstellung; DROP POLICY IF EXISTS tenant_isolation ON loeschfrist_einstellung;
ALTER TABLE werkzeug NO FORCE ROW LEVEL SECURITY;
ALTER TABLE werkzeug DISABLE ROW LEVEL SECURITY; ALTER TABLE werkzeug DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON werkzeug; DROP POLICY IF EXISTS tenant_isolation ON werkzeug;
ALTER TABLE bewertung NO FORCE ROW LEVEL SECURITY;
ALTER TABLE bewertung DISABLE ROW LEVEL SECURITY; ALTER TABLE bewertung DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON bewertung; DROP POLICY IF EXISTS tenant_isolation ON bewertung;
ALTER TABLE entscheidung NO FORCE ROW LEVEL SECURITY;
ALTER TABLE entscheidung DISABLE ROW LEVEL SECURITY; ALTER TABLE entscheidung DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON entscheidung; DROP POLICY IF EXISTS tenant_isolation ON entscheidung;
ALTER TABLE freigabeschritt NO FORCE ROW LEVEL SECURITY;
ALTER TABLE freigabeschritt DISABLE ROW LEVEL SECURITY; ALTER TABLE freigabeschritt DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON freigabeschritt; 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; ALTER TABLE nutzer_genehmiger_rolle DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON nutzer_genehmiger_rolle; DROP POLICY IF EXISTS tenant_isolation ON nutzer_genehmiger_rolle;
REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM deklarix_app; DO $$
ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM deklarix_app; BEGIN
REVOKE USAGE ON SCHEMA public FROM deklarix_app; IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'deklarix_app') THEN
DROP ROLE IF EXISTS deklarix_app; 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
$$;

View File

@@ -1,36 +1,47 @@
-- Mandantenisolation auf Datenbankebene (Postgres Row-Level Security). -- 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 -- WICHTIG, per Incident am 2026-09-01 gelernt: RLS-Policies wirken NIE
-- Klartext-Passwort gehört nicht in eine versionierte, für jeden mit -- bei Postgres-Superusern, und NIE beim Tabellenbesitzer ohne FORCE ROW
-- Repo-Zugriff lesbare Migrationsdatei. Login-Fähigkeit + Passwort -- LEVEL SECURITY. Welcher Fall zutrifft, hängt von der Umgebung ab:
-- werden einmalig manuell je Umgebung gesetzt: -- - Produktion verbindet als eigene, nicht-privilegierte Rolle (z. B.
-- ALTER ROLE deklarix_app WITH LOGIN PASSWORD '<generiertes Secret>'; -- "deklarix"), die zugleich Eigentümerin der Tabellen ist (sie hat
-- Danach DATABASE_URL_APP in der jeweiligen deklarix.env eintragen. -- 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 $$ DO $$
BEGIN BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'deklarix_app') THEN IF EXISTS (SELECT FROM pg_roles WHERE rolname = current_user AND rolcreaterole) THEN
CREATE ROLE deklarix_app NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS NOLOGIN; 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 IF;
END END
$$; $$;
GRANT USAGE ON SCHEMA public TO deklarix_app; -- Login-Fähigkeit + Passwort für deklarix_app (falls angelegt) werden
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO deklarix_app; -- einmalig manuell je Umgebung gesetzt, NIE in einer versionierten
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO deklarix_app; -- 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 ─────────────────────────── -- ─── Tabellen MIT direkter account_id-Spalte ───────────────────────────
-- Eine einzelne Policy (FOR ALL) pro Tabelle deckt SELECT/UPDATE/DELETE -- 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' OR current_setting('app.is_betreiber', true) = 'true'
); );
ALTER TABLE antrag ENABLE ROW LEVEL SECURITY; ALTER TABLE antrag ENABLE ROW LEVEL SECURITY;
ALTER TABLE antrag FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON registereintrag FOR ALL USING ( CREATE POLICY tenant_isolation ON registereintrag FOR ALL USING (
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid 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' OR current_setting('app.is_betreiber', true) = 'true'
); );
ALTER TABLE registereintrag ENABLE ROW LEVEL SECURITY; ALTER TABLE registereintrag ENABLE ROW LEVEL SECURITY;
ALTER TABLE registereintrag FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON abteilung FOR ALL USING ( CREATE POLICY tenant_isolation ON abteilung FOR ALL USING (
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid 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' OR current_setting('app.is_betreiber', true) = 'true'
); );
ALTER TABLE abteilung ENABLE ROW LEVEL SECURITY; ALTER TABLE abteilung ENABLE ROW LEVEL SECURITY;
ALTER TABLE abteilung FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON werkzeug_sperre FOR ALL USING ( CREATE POLICY tenant_isolation ON werkzeug_sperre FOR ALL USING (
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid 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' OR current_setting('app.is_betreiber', true) = 'true'
); );
ALTER TABLE werkzeug_sperre ENABLE ROW LEVEL SECURITY; 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 ( CREATE POLICY tenant_isolation ON genehmiger_rolle FOR ALL USING (
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid 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' OR current_setting('app.is_betreiber', true) = 'true'
); );
ALTER TABLE genehmiger_rolle ENABLE ROW LEVEL SECURITY; 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 ( CREATE POLICY tenant_isolation ON freigabe_regel FOR ALL USING (
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid 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' OR current_setting('app.is_betreiber', true) = 'true'
); );
ALTER TABLE freigabe_regel ENABLE ROW LEVEL SECURITY; 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 ( CREATE POLICY tenant_isolation ON loeschfrist_einstellung FOR ALL USING (
account_id = NULLIF(current_setting('app.account_id', true), '')::uuid 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' OR current_setting('app.is_betreiber', true) = 'true'
); );
ALTER TABLE loeschfrist_einstellung ENABLE ROW LEVEL SECURITY; ALTER TABLE loeschfrist_einstellung ENABLE ROW LEVEL SECURITY;
ALTER TABLE loeschfrist_einstellung FORCE ROW LEVEL SECURITY;
-- ─── werkzeug: account_id NULLABLE (NULL = zentraler Katalog) ────────── -- ─── werkzeug: account_id NULLABLE (NULL = zentraler Katalog) ──────────
-- Lesen: jeder sieht zentrale (NULL) Einträge plus die eigenen. NUR der -- 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 OR account_id = NULLIF(current_setting('app.account_id', true), '')::uuid
); );
ALTER TABLE werkzeug ENABLE ROW LEVEL SECURITY; ALTER TABLE werkzeug ENABLE ROW LEVEL SECURITY;
ALTER TABLE werkzeug FORCE ROW LEVEL SECURITY;
-- ─── Tabellen OHNE eigene account_id, über Fremdschlüssel abgeleitet ─── -- ─── Tabellen OHNE eigene account_id, über Fremdschlüssel abgeleitet ───
-- antrag/genehmiger_rolle sind selbst schon RLS-geschützt (s. o.) — eine -- 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) antrag_id IN (SELECT id FROM antrag)
); );
ALTER TABLE bewertung ENABLE ROW LEVEL SECURITY; ALTER TABLE bewertung ENABLE ROW LEVEL SECURITY;
ALTER TABLE bewertung FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON entscheidung FOR ALL USING ( CREATE POLICY tenant_isolation ON entscheidung FOR ALL USING (
antrag_id IN (SELECT id FROM antrag) 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) antrag_id IN (SELECT id FROM antrag)
); );
ALTER TABLE entscheidung ENABLE ROW LEVEL SECURITY; ALTER TABLE entscheidung ENABLE ROW LEVEL SECURITY;
ALTER TABLE entscheidung FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON freigabeschritt FOR ALL USING ( CREATE POLICY tenant_isolation ON freigabeschritt FOR ALL USING (
antrag_id IN (SELECT id FROM antrag) 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) antrag_id IN (SELECT id FROM antrag)
); );
ALTER TABLE freigabeschritt ENABLE ROW LEVEL SECURITY; 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 ( CREATE POLICY tenant_isolation ON nutzer_genehmiger_rolle FOR ALL USING (
genehmiger_rolle_id IN (SELECT id FROM genehmiger_rolle) 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) genehmiger_rolle_id IN (SELECT id FROM genehmiger_rolle)
); );
ALTER TABLE nutzer_genehmiger_rolle ENABLE ROW LEVEL SECURITY; ALTER TABLE nutzer_genehmiger_rolle ENABLE ROW LEVEL SECURITY;
ALTER TABLE nutzer_genehmiger_rolle FORCE ROW LEVEL SECURITY;
-- ─── Bewusst OHNE RLS ─────────────────────────────────────────────────── -- ─── Bewusst OHNE RLS ───────────────────────────────────────────────────
-- account: hat keine account_id-Spalte (ist selbst der Mandant) und -- account: hat keine account_id-Spalte (ist selbst der Mandant) und