# Migration Safety Checklist Sjekkliste for alle som kjører PostgreSQL-migrasjoner — lokalt eller i prod. ## RLS-modell Synops bruker `node_access`-matrisen for tilgangskontroll, ikke workspace-scope. Maskinrommet kobler til som superuser (`sidelinja`) for skriveoperasjoner. For tunge lesespørringer brukes `SET LOCAL ROLE synops_reader`, som er underlagt RLS. Sesjonsvariabel: `app.current_node_id` settes til brukerens node-UUID. Funksjon: `current_node_id()` leser denne variabelen. RLS-policies finnes på: `nodes`, `edges`, `node_access`. ## Før migrering - [ ] Les migrasjonfilen og forstå hva den gjør - [ ] Tar migrasjonen backup-hensyn? (dropper den kolonner/tabeller med data?) - [ ] Påvirker den RLS-policies? Sjekk at ingen policy fjernes utilsiktet. ## Etter migrering ### RLS-verifisering (KRITISK) Etter *enhver* migrering som oppretter eller endrer tabeller: ```sql -- 1. Verifiser at RLS er aktivert på relevante tabeller SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public' AND tablename IN ('nodes', 'edges', 'node_access') ORDER BY tablename; -- Forventet: rowsecurity = true for alle -- 2. Verifiser at policies eksisterer SELECT tablename, policyname, cmd FROM pg_policies WHERE schemaname = 'public' ORDER BY tablename; -- Forventet: node_select, edge_select, na_select -- 3. Test isolasjon: sett bruker A, forsøk å lese hidden node opprettet av bruker B BEGIN; SET LOCAL app.current_node_id = ''; SET LOCAL ROLE synops_reader; SELECT count(*) FROM nodes WHERE created_by = '' AND visibility = 'hidden'; -- Forventet: 0 rader (RLS blokkerer) COMMIT; -- 4. Verifiser at superuser IKKE blokkeres (maskinrommet skriver som superuser) SELECT count(*) FROM nodes; -- Forventet: alle rader synlige (superuser bypasser RLS) ``` ### Indeksverifisering ```sql SELECT indexname, tablename FROM pg_indexes WHERE schemaname = 'public' AND tablename IN ('nodes', 'edges', 'node_access') ORDER BY tablename; -- Viktige indekser: idx_na_subject, idx_na_object (for RLS-ytelse) ``` ### Constraint-verifisering ```sql SELECT tc.table_name, tc.constraint_name, tc.constraint_type FROM information_schema.table_constraints tc WHERE tc.table_schema = 'public' AND tc.constraint_type IN ('FOREIGN KEY', 'CHECK', 'UNIQUE') ORDER BY tc.table_name; ``` ## RLS Leak Hunter (CI-test) `app.current_node_id` er en single point of failure — en glemt SET i en ny feature eller en direkte PG-tilkobling uten `SET LOCAL ROLE synops_reader` kan føre til datalekkasje. Denne testen fanger det opp. ### Automatisk test (to-bruker leak detection) ```sql -- Opprett to testbrukere INSERT INTO nodes (id, node_kind, title, visibility, created_by) VALUES ('aaaaaaaa-0000-0000-0000-000000000001', 'person', 'Test A', 'hidden', NULL), ('aaaaaaaa-0000-0000-0000-000000000002', 'person', 'Test B', 'hidden', NULL); -- Opprett hidden noder for hver bruker INSERT INTO nodes (id, node_kind, title, visibility, created_by) VALUES ('bbbbbbbb-0000-0000-0000-000000000001', 'content', 'Hemmelighet A', 'hidden', 'aaaaaaaa-0000-0000-0000-000000000001'), ('bbbbbbbb-0000-0000-0000-000000000002', 'content', 'Hemmelighet B', 'hidden', 'aaaaaaaa-0000-0000-0000-000000000002'); -- TEST 1: Bruker A skal ikke se Bruker B sine hidden noder BEGIN; SET LOCAL app.current_node_id = 'aaaaaaaa-0000-0000-0000-000000000001'; SET LOCAL ROLE synops_reader; DO $$ BEGIN IF (SELECT count(*) FROM nodes WHERE id = 'bbbbbbbb-0000-0000-0000-000000000002') > 0 THEN RAISE EXCEPTION 'RLS LEAK: Bruker A kan lese Bruker B sine hidden noder!'; END IF; END $$; COMMIT; -- TEST 2: Uten satt bruker-id skal hidden noder være usynlige BEGIN; SET LOCAL ROLE synops_reader; DO $$ BEGIN IF (SELECT count(*) FROM nodes WHERE visibility = 'hidden') > 0 THEN RAISE EXCEPTION 'RLS LEAK: Uautentisert tilkobling kan lese hidden data!'; END IF; END $$; COMMIT; -- Rydd opp DELETE FROM nodes WHERE id IN ( 'aaaaaaaa-0000-0000-0000-000000000001', 'aaaaaaaa-0000-0000-0000-000000000002', 'bbbbbbbb-0000-0000-0000-000000000001', 'bbbbbbbb-0000-0000-0000-000000000002' ); ``` ## Nye tabeller Hvis en ny tabell opprettes som inneholder brukerdata: 1. Aktiver RLS: `ALTER TABLE ny_tabell ENABLE ROW LEVEL SECURITY;` 2. Opprett SELECT-policy for `synops_reader` med `current_node_id()`-sjekk 3. Grant SELECT til `synops_reader` 4. Kjør leak hunter