-- WINDOWS #18 — Anwendungsrolle ohne RLS-Umgehungsrecht (T-DGJ-01, T-DGJ-04, -- T-DGJ-05). Erste von zwei Migrationen; die zweite (20260909140000) ergaenzt -- die restlichen Policies. -- -- Befund (gemessen am 2026-09-09, siehe docker-compose.yml:33/:76): Die API -- verbindet als Rolle "tessera". Diese Rolle entsteht aus POSTGRES_USER und -- ist damit Superuser des Postgres-Clusters (auf alpha gemessen: -- rolsuper = t, rolbypassrls = t). PostgreSQL wendet Row-Level Security auf -- Superuser-Rollen und Rollen ohne NOBYPASSRLS grundsaetzlich nicht an; -- FORCE ROW LEVEL SECURITY aendert daran nichts, weil es nur den -- Tabelleneigentuemer erfasst, nicht Rollen mit Umgehungsrecht. Die sieben -- vorhandenen Policies -- (User, Group, GroupMembership, LdapConfig, LdapFieldMapping, ModuleGrant, -- PasswordResetToken) sind unter der Rolle "tessera" deshalb ohne Wirkung. -- -- Diese Migration allein stellt noch nichts um — sie legt lediglich die -- Rolle "tessera_app" an und konvergiert sie bei Wiederholung auf -- NOSUPERUSER/NOBYPASSRLS. Niemand verbindet mit ihr, solange -- DATABASE_URL nicht umgestellt wird. Der volle Ablauf inklusive -- Kennwortvergabe steht in docs/mandantentrennung-datenbankrolle.md — das -- Kennwort wird bewusst NICHT hier gesetzt, sondern vom Betreiber von Hand, -- weil es sonst im Klartext in dieser versionierten Datei laenden wuerde. DO $$ DECLARE can_manage_roles boolean; BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'tessera_app') THEN -- Rolle existiert noch nicht: current_user braucht die Berechtigung, -- Rollen anzulegen. Lautes Scheitern mit Anleitung ist hier richtig — -- ein stilles Ueberspringen wuerde einen Betreiber im Glauben lassen, -- die Rolle existiere bereits. SELECT rolsuper OR rolcreaterole INTO can_manage_roles FROM pg_roles WHERE rolname = current_user; IF NOT can_manage_roles THEN RAISE EXCEPTION 'tessera_app existiert nicht und current_user (%) darf keine Rollen anlegen. ' 'Einmalig als Datenbank-Superuser ausfuehren: ' 'CREATE ROLE tessera_app WITH LOGIN NOSUPERUSER NOBYPASSRLS NOCREATEDB NOCREATEROLE; ' 'Siehe docs/mandantentrennung-datenbankrolle.md.', current_user; END IF; CREATE ROLE tessera_app WITH LOGIN NOSUPERUSER NOBYPASSRLS NOCREATEDB NOCREATEROLE; ELSE -- Rolle existiert bereits: auf denselben Stand konvergieren. Nur ein -- Superuser darf SUPERUSER/NOBYPASSRLS setzen oder entziehen — ist -- current_user keiner, pruefen, ob die Merkmale bereits beide falsch -- sind, statt das ALTER zu versuchen. SELECT rolsuper INTO can_manage_roles FROM pg_roles WHERE rolname = current_user; IF can_manage_roles THEN ALTER ROLE tessera_app WITH LOGIN NOSUPERUSER NOBYPASSRLS NOCREATEDB NOCREATEROLE; ELSE IF EXISTS ( SELECT 1 FROM pg_roles WHERE rolname = 'tessera_app' AND (rolsuper OR rolbypassrls) ) THEN RAISE EXCEPTION 'tessera_app traegt noch rolsuper oder rolbypassrls, und current_user (%) ' 'ist kein Superuser, um das zu korrigieren. Einmalig als ' 'Datenbank-Superuser ausfuehren: ' 'ALTER ROLE tessera_app WITH NOSUPERUSER NOBYPASSRLS; ' 'Siehe docs/mandantentrennung-datenbankrolle.md.', current_user; END IF; -- rolsuper und rolbypassrls sind bereits beide falsch — reiner Durchlauf. END IF; END IF; END $$; -- Rechte, alle idempotent (ein wiederholtes GRANT ist in PostgreSQL -- folgenlos). Datenbankname und Eigentuemer werden dynamisch gebildet, -- damit die Migration auch gegen eine anders benannte Installation bzw. -- eine andere migrierende Rolle laeuft. DO $$ BEGIN EXECUTE format('GRANT CONNECT ON DATABASE %I TO tessera_app', current_database()); END $$; GRANT USAGE ON SCHEMA public TO tessera_app; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO tessera_app; -- Das Schema nutzt derzeit keine Sequenzen (kein einziges autoincrement -- nachgezaehlt) — die Vergabe kostet nichts und verhindert einen spaeteren -- Stolperstein, sollte eine kuenftige Migration eine Sequenz einfuehren. GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO tessera_app; -- Vorgaberechte, damit kuenftige Migrationen (angewendet von current_user, -- also der migrierenden Rolle — nicht fest "tessera" eingetragen, weil die -- auf einer anderen Installation anders heissen kann) nicht jedes Mal -- nachziehen muessen. DO $$ BEGIN EXECUTE format( 'ALTER DEFAULT PRIVILEGES FOR ROLE %I IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO tessera_app', current_user ); EXECUTE format( 'ALTER DEFAULT PRIVILEGES FOR ROLE %I IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO tessera_app', current_user ); END $$;