Zeilenschutz auf vier Tabellen nachziehen, und den Zustand pruefen
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m36s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m6s

Beim Neuaufbau der Datenbank aus den Migrationen am 26.08. blieben
cost_centers, position_cost_centers, onboarding_tasks und
offboarding_tasks ohne Row Level Security: sie verlassen sich auf
den Ereignis-Trigger ensure_rls, den eine spaetere Migration erst
anlegt. Ein Ereignis-Trigger wirkt nur nach vorne.

Die Policies auf diesen Tabellen existieren und wurden nie
ausgewertet — PostgreSQL befragt sie nur bei eingeschaltetem RLS.
In jeder Aufstellung der Policies sah es richtig aus.

Der CI-Job prueft ab jetzt den Zustand nach dem Lauf, nicht nur
dass die Dateien durchlaufen. Genau in diesem Zwischenraum ist der
Fehler durchgekommen.
This commit is contained in:
2026-09-08 15:36:48 +02:00
parent 405d708bc4
commit 206a7c6eba
2 changed files with 221 additions and 0 deletions

View File

@@ -117,6 +117,77 @@ jobs:
echo "$ausgabe" | grep -q "^Nichts anzuwenden" \
|| { echo "Der zweite Lauf wollte erneut anwenden — die Buchführung greift nicht."; exit 1; }
# Dass die Migrationen durchlaufen, heisst nicht, dass das Ergebnis
# stimmt. Bis hierher prüft dieser Job nur, dass keine Datei abbricht —
# und genau daran ist ein Fehler vorbeigekommen: vier Tabellen aus dem
# August (Kostenstellen, On-/Offboarding) standen ohne Zeilenschutz da,
# weil sie sich auf den Ereignis-Trigger `ensure_rls` verliessen, den
# eine spätere Migration erst anlegt. Alle Dateien liefen sauber durch,
# die Policies standen im Katalog, und ausgewertet wurde keine davon.
#
# Deshalb wird ab hier der **Zustand** geprüft, nicht der Durchlauf.
# Nachgezogen hat das 20260908100000; dort steht dieselbe Gegenprobe.
# Sie läuft aber nur einmal, beim Anwenden jener Datei. Der Schritt hier
# läuft bei jedem Push gegen eine frisch aufgebaute Datenbank und fängt
# deshalb auch die *nächste* Tabelle, die es wieder vergisst.
- name: Zugriffsschutz nach dem Lauf
env:
PGPASSWORD: ci
run: |
set -euo pipefail
frage() {
psql -v ON_ERROR_STOP=1 -h postgres -U postgres -d alpenwerk -At -c "$1"
}
# relkind in ('r','p'): gewöhnliche und partitionierte Tabellen.
ohne_rls=$(frage "
select coalesce(string_agg(c.relname, ', ' order by c.relname), '')
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
and c.relkind in ('r', 'p')
and not c.relrowsecurity")
# RLS ohne Policy ist nicht offen, sondern leer — kein Leck, aber
# eine Tabelle, aus der nie etwas zurückkommt.
ohne_policy=$(frage "
select coalesce(string_agg(c.relname, ', ' order by c.relname), '')
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
and c.relkind in ('r', 'p')
and not exists (select 1 from pg_policy p where p.polrelid = c.oid)")
# Das Netz selbst: fehlt oder schläft es, ist die nächste neue
# Tabelle wieder ungeschützt.
netz=$(frage "
select count(*) from pg_event_trigger
where evtname = 'ensure_rls' and evtenabled <> 'D'")
fehler=0
if [ -n "$ohne_rls" ]; then
echo "Ohne Row Level Security: $ohne_rls"
fehler=1
fi
if [ -n "$ohne_policy" ]; then
echo "Mit Row Level Security, aber ohne jede Policy: $ohne_policy"
fehler=1
fi
if [ "$netz" != "1" ]; then
echo "Der Ereignis-Trigger ensure_rls fehlt oder ist abgeschaltet."
fehler=1
fi
if [ "$fehler" -ne 0 ]; then
echo
echo "Jede Tabelle in public braucht RLS und mindestens eine Policy."
echo "Nachziehen in einer neuen Migration, nicht in einer bereits angewendeten."
exit 1
fi
echo "Zugriffsschutz vollstaendig: jede Tabelle mit RLS und Policy, ensure_rls aktiv."
# Die Integrationstests brauchen einen befüllten Bestand; der Seed lag
# in der abgelösten Umgebung und ist noch nicht nachgezogen (siehe
# README, offene Punkte). Bis dahin laufen sie gegen eine erreichbare