From c8bbd10d37ba0ed7cc49986abcb76b2991e8a0c7 Mon Sep 17 00:00:00 2001 From: cschlaefke Date: Wed, 4 Nov 2026 10:06:46 +0100 Subject: [PATCH] test(db): pin runbook 8.2 queries and QUE-04 log assumptions in pgTAP (KAT-01, 3a) Adds the unchanged runbook queries (rolconfig, pg_available_extensions, pg_settings) and asserts the settings that keep search terms out of logs. katalog-pflege 8.2 now states that the three log settings depend on each other. Refs #3 Co-Authored-By: Claude Sonnet 5.5 --- docs/katalog-pflege.md | 2 +- .../tests/database/09_catalog_search.test.sql | 24 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/katalog-pflege.md b/docs/katalog-pflege.md index f9991bc..dad11e0 100644 --- a/docs/katalog-pflege.md +++ b/docs/katalog-pflege.md @@ -568,5 +568,5 @@ Bei einer Abweichung die betroffenen Spalten **per neuer Migration** entfernen u - **Ohne Anmeldung aufrufbar:** `search_catalog_games` ist für `anon` ausführbar. Die Eingabegrenzen der Funktion (100 Zeichen, 10 Wörter, höchstens 20 IDs je Filter, Seitengröße 50) begrenzen die Last je Aufruf; zusätzlich gelten die `statement_timeout`-Werte der Rollen `anon` und `authenticated` (lokal 3 s und 8 s; im Self-Hosting prüfen, `select rolname, rolconfig from pg_roles where rolname in ('anon', 'authenticated')`). Ein `set statement_timeout` an der Funktion wirkt nicht. - **Mengenbegrenzung je IP (Entscheidung vor dem Go-live, Aufgabe D1):** Das eigentliche Risiko sind viele parallele Aufrufe ohne Anmeldung. Für R1.1 ist eine Begrenzung kein Muss; vor dem Go-live entscheidet devops, ob das Kong-Plugin `rate-limiting` auf `/rest/v1/rpc/search_catalog_games` oder `limit_req` im vorgelagerten Proxy gesetzt wird, und trägt das in das Hosting-Runbook ein. -- **Suchbegriffe nicht protokollieren (QUE-04):** Die App sendet den Suchbegriff per `POST` im Anfragekörper (nicht in der URL). In den Datenbank-Einstellungen dürfen `log_min_duration_statement`, `log_statement` und `log_parameter_max_length*` Parameterwerte nicht in die Protokolle schreiben; `auto_explain.log_min_duration` und `pgaudit.log_parameter` ebenfalls nicht. Lokal geprüfte Werte und die Abweichungen stehen in der Beschreibung von PR 3a; auf Staging/Produktion mit `select name, setting from pg_settings where name in ('log_min_duration_statement', 'log_statement', 'log_parameter_max_length', 'log_parameter_max_length_on_error', 'auto_explain.log_min_duration', 'pgaudit.log', 'pgaudit.log_parameter')` prüfen. +- **Suchbegriffe nicht protokollieren (QUE-04):** Die App sendet den Suchbegriff per `POST` im Anfragekörper (nicht in der URL); PostgREST reicht ihn als JSON-Parameter `$1` an die Datenbank. Drei Einstellungen müssen **zusammen** geprüft werden, weil sie voneinander abhängen: (1) `log_parameter_max_length_on_error = 0`, sonst stünde der Begriff im Fehlerprotokoll (Fehler wie `search_query_too_long` erscheinen lokal nur mit dem Platzhalter `$1`); (2) `log_min_duration_statement = -1`, `log_statement` höchstens `ddl`, `pgaudit.log = none`; (3) `auto_explain.log_min_duration` (lokal 10 s, mit `auto_explain.log_parameter_max_length = -1`, also **mit** Parameterwerten) muss **größer** sein als das längste `statement_timeout` der Rollen `anon` und `authenticated` (lokal 3 s und 8 s): Eine Suche, die das Zeitlimit erreicht, wird abgebrochen und erzeugt keine auto_explain-Ausgabe. Wird das Zeitlimit von `authenticated` über 10 s angehoben (oder `auto_explain.log_min_duration` gesenkt), landen Suchbegriffe im Protokoll. Prüfung auf Staging/Produktion: `select name, setting from pg_settings where name in ('log_min_duration_statement', 'log_statement', 'log_parameter_max_length', 'log_parameter_max_length_on_error', 'auto_explain.log_min_duration', 'pgaudit.log', 'pgaudit.log_parameter')`. Lokal bestätigt (Aufruf per `curl` gegen Kong, Datenbank- und PostgREST-Protokoll enthalten den Begriff nicht); Hetzner ist **nicht** geprüft. - **Erweiterungen:** `select * from pg_available_extensions where name in ('pg_trgm', 'unaccent')` muss auf Staging/Produktion beide Zeilen liefern, bevor die Migration `catalog_search` eingespielt wird. diff --git a/supabase/tests/database/09_catalog_search.test.sql b/supabase/tests/database/09_catalog_search.test.sql index bb24238..2d293d6 100644 --- a/supabase/tests/database/09_catalog_search.test.sql +++ b/supabase/tests/database/09_catalog_search.test.sql @@ -6,7 +6,7 @@ -- Ohne Suchbegriff wird nur auf Fixture-IDs geprüft (Hilfsfunktion pg_temp.hits blättert durch). -- Die Include-Dateien in ../support/ (\ir) erzeugt supabase/catalog/build_startbestand.ts. begin; -select plan(211); +select plan(217); insert into auth.users (id, email, raw_user_meta_data) values ('11111111-1111-1111-1111-111111111111', 'anna@example.com', '{"age_confirmed":"true","terms_accepted":"true","terms_version":"test","display_name":"Anna"}'), @@ -796,6 +796,28 @@ union all select 'publishers', count(*) from public.publishers $$values ('games'::text, 0::bigint), ('game_alt_titles', 0), ('publishers', 0)$$, 'Runbook 8: die Prüfabfrage meldet bei unverändertem Stand überall 0 Abweichungen'); +-- --------------------------------------------------------------------------------------------- +-- Abschnitt 8.2 des Runbooks: Betrieb (Prüfabfragen unverändert, dazu die Annahmen für QUE-04) +-- --------------------------------------------------------------------------------------------- +select set_eq($$select rolname, rolconfig from pg_roles where rolname in ('anon', 'authenticated')$$, + $$select rolname, rolconfig from pg_roles where rolname in ('anon', 'authenticated') and exists (select 1 from unnest(rolconfig) c where c like 'statement_timeout=%')$$, + 'Runbook 8.2: anon und authenticated haben ein statement_timeout in rolconfig'); +select is((select count(*) from pg_available_extensions where name in ('pg_trgm', 'unaccent')), 2::bigint, + 'Runbook 8.2: pg_trgm und unaccent sind verfügbar (select * from pg_available_extensions …)'); +select is((select count(*) from pg_settings where name in ('log_min_duration_statement', 'log_statement', 'log_parameter_max_length', 'log_parameter_max_length_on_error', 'auto_explain.log_min_duration', 'pgaudit.log', 'pgaudit.log_parameter')), 7::bigint, + 'Runbook 8.2: die sieben Protokoll-Einstellungen der Prüfabfrage sind abfragbar'); +select is((select setting from pg_settings where name = 'log_parameter_max_length_on_error'), '0', + 'QUE-04: bei Fehlern werden keine Parameterwerte protokolliert (Suchbegriffe im JSON-Parameter von PostgREST)'); +select ok((select setting from pg_settings where name = 'log_statement') in ('none', 'ddl') + and (select setting from pg_settings where name = 'log_min_duration_statement') = '-1' + and (select setting from pg_settings where name = 'pgaudit.log') = 'none' + and (select setting from pg_settings where name = 'pgaudit.log_parameter') = 'off', + 'QUE-04: weder Anweisungen mit Parametern (log_statement, log_min_duration_statement, pgaudit) noch langsame Anweisungen werden protokolliert'); +select ok((select max((regexp_match(c, '^statement_timeout=(\d+)s$'))[1]::int) * 1000 + from pg_roles r, unnest(r.rolconfig) c where r.rolname in ('anon', 'authenticated') and c like 'statement_timeout=%') + < (select setting::int from pg_settings where name = 'auto_explain.log_min_duration'), + 'QUE-04: auto_explain (mit Parametern) greift erst nach dem längsten Zeitlimit der API-Rollen, eine abgebrochene Suche erscheint also nie im Protokoll'); + -- Drift simulieren: Die Funktion wird geändert, die gespeicherten Spalten bleiben (wie nach einem Image-Upgrade) create or replace function public.search_normalize(p text) returns text language sql immutable strict parallel safe set search_path = '' as $$ select upper(p) $$;