Oppdaterer dokumentasjon basert på tre eksterne arkitekturvurderinger: - RLS Leak Hunter med CI-test og audit-trigger (migration_safety.md) - pgvector-migrasjon flyttet til Lag 2, WAL-arkivering med pgBackRest (ARCHITECTURE.md, produksjon.md) - Off-site backup med rclone, Docker cgroups for workers (ARCHITECTURE.md, produksjon.md) - Kostnadskontroll i AI Gateway: workspace-budsjett, auto-fallback (ai_gateway.md) - Gjeste-token sikkerhetsdybde: ClamAV, rate limiting, auto-revoke (den_asynkrone_gjesten.md) - SpacetimeDB fase 1-vurdering: PG LISTEN/NOTIFY som mellomsteg (synkronisering.md) - Kritiske events (Aha-markører) flushes umiddelbart (synkronisering.md) - Ekstern helsesjekk, observability-utvidelser (ARCHITECTURE.md) - Tre nye forslag: Contradiction Detector, Auto-Highlight Reel, Audience Voice Memo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
148 lines
5.4 KiB
Markdown
148 lines
5.4 KiB
Markdown
# Migration Safety Checklist
|
|
|
|
Sjekkliste for alle som kjører PostgreSQL-migrasjoner — lokalt eller i prod.
|
|
|
|
## Før migrering
|
|
|
|
- [ ] Les migrasjonfilen og forstå hva den gjør
|
|
- [ ] Har migrasjonen en tilhørende **down-migrering**? (påkrevd for skjema-endringer)
|
|
- [ ] Tar migrasjonen backup-hensyn? (dropper den kolonner/tabeller med data?)
|
|
|
|
## Etter migrering
|
|
|
|
### RLS-verifisering (KRITISK)
|
|
Etter *enhver* migrering som oppretter eller endrer tabeller med `workspace_id`:
|
|
|
|
```sql
|
|
-- 1. Verifiser at RLS er aktivert på alle workspace-tabeller
|
|
SELECT tablename, rowsecurity
|
|
FROM pg_tables
|
|
WHERE schemaname = 'public'
|
|
AND tablename IN ('nodes', 'graph_edges', 'messages', 'channels',
|
|
'media_files', 'job_queue', 'message_attachments')
|
|
ORDER BY tablename;
|
|
-- Forventet: rowsecurity = true for alle
|
|
|
|
-- 2. Verifiser at policies eksisterer
|
|
SELECT tablename, policyname, cmd, qual
|
|
FROM pg_policies
|
|
WHERE schemaname = 'public'
|
|
ORDER BY tablename;
|
|
-- Forventet: workspace_isolation_* policy for hver tabell
|
|
|
|
-- 3. Test isolasjon: sett workspace A, forsøk å lese workspace B
|
|
SET app.current_workspace_id = '<workspace_a_uuid>';
|
|
SELECT count(*) FROM nodes WHERE workspace_id = '<workspace_b_uuid>';
|
|
-- Forventet: 0 rader (RLS blokkerer)
|
|
|
|
-- 4. Verifiser at superuser IKKE blokkeres (Rust workers trenger dette)
|
|
RESET app.current_workspace_id;
|
|
SET ROLE postgres;
|
|
SELECT count(*) FROM nodes;
|
|
-- Forventet: alle rader synlige
|
|
```
|
|
|
|
### Indeksverifisering
|
|
```sql
|
|
-- Sjekk at viktige indekser finnes
|
|
SELECT indexname, tablename
|
|
FROM pg_indexes
|
|
WHERE schemaname = 'public'
|
|
AND tablename IN ('nodes', 'graph_edges', 'messages')
|
|
ORDER BY tablename;
|
|
```
|
|
|
|
### Constraint-verifisering
|
|
```sql
|
|
-- Sjekk at foreign keys er intakte
|
|
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)
|
|
|
|
`SET app.current_workspace_id` er en skjult single point of failure — en glemt SET i en ny feature, en feil i connection-pool, eller en ny tjeneste som kobler til PG uten middleware kan føre til cross-workspace datalekkasje. Denne testen fanger det opp.
|
|
|
|
### Automatisk CI-test (to-workspace leak detection)
|
|
Kjøres i migrasjonstester og som egen CI-steg:
|
|
|
|
```sql
|
|
-- Opprett to test-workspaces
|
|
INSERT INTO workspaces (id, name, slug) VALUES
|
|
('aaaaaaaa-0000-0000-0000-000000000001', 'Workspace A', 'ws-a'),
|
|
('aaaaaaaa-0000-0000-0000-000000000002', 'Workspace B', 'ws-b');
|
|
|
|
-- Seed testdata i begge
|
|
INSERT INTO nodes (id, node_type, workspace_id) VALUES
|
|
('bbbbbbbb-0000-0000-0000-000000000001', 'tema', 'aaaaaaaa-0000-0000-0000-000000000001'),
|
|
('bbbbbbbb-0000-0000-0000-000000000002', 'tema', 'aaaaaaaa-0000-0000-0000-000000000002');
|
|
|
|
-- TEST 1: Sett workspace A, forsøk å lese workspace B
|
|
SET app.current_workspace_id = 'aaaaaaaa-0000-0000-0000-000000000001';
|
|
DO $$
|
|
BEGIN
|
|
IF (SELECT count(*) FROM nodes WHERE workspace_id = 'aaaaaaaa-0000-0000-0000-000000000002') > 0 THEN
|
|
RAISE EXCEPTION 'RLS LEAK: Workspace A kan lese Workspace B sine noder!';
|
|
END IF;
|
|
END $$;
|
|
|
|
-- TEST 2: Uten SET (tom current_setting) skal returnere 0 rader
|
|
RESET app.current_workspace_id;
|
|
DO $$
|
|
BEGIN
|
|
-- For vanlig bruker (ikke superuser) bør dette returnere 0
|
|
IF (SELECT count(*) FROM nodes) > 0 AND current_setting('is_superuser') = 'off' THEN
|
|
RAISE EXCEPTION 'RLS LEAK: Uautentisert tilkobling kan lese data!';
|
|
END IF;
|
|
END $$;
|
|
```
|
|
|
|
### Audit-trigger (produksjon)
|
|
Valgfri trigger som logger mistenkelige queries i prod:
|
|
|
|
```sql
|
|
-- Tabell for RLS-audit
|
|
CREATE TABLE IF NOT EXISTS rls_audit_log (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
table_name TEXT NOT NULL,
|
|
operation TEXT NOT NULL,
|
|
current_workspace TEXT,
|
|
session_user TEXT NOT NULL,
|
|
query_timestamp TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- Funksjon som logger når current_workspace_id ikke er satt
|
|
CREATE OR REPLACE FUNCTION audit_rls_context() RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF current_setting('app.current_workspace_id', true) IS NULL
|
|
OR current_setting('app.current_workspace_id', true) = '' THEN
|
|
IF current_setting('is_superuser') = 'off' THEN
|
|
INSERT INTO rls_audit_log (table_name, operation, current_workspace, session_user)
|
|
VALUES (TG_TABLE_NAME, TG_OP, current_setting('app.current_workspace_id', true), session_user);
|
|
END IF;
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
**Kjør leak hunter mot ALLE tabeller med workspace_id — ikke bare de som er listet over.** Nye tabeller legges til i listen automatisk via introspeksjon:
|
|
|
|
```sql
|
|
-- Finn alle tabeller med workspace_id-kolonne (bør alle ha RLS)
|
|
SELECT t.tablename
|
|
FROM pg_tables t
|
|
JOIN information_schema.columns c ON c.table_name = t.tablename
|
|
WHERE c.column_name = 'workspace_id'
|
|
AND t.schemaname = 'public'
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM pg_policies p WHERE p.tablename = t.tablename
|
|
);
|
|
-- Forventet: 0 rader. Enhver rad her = tabell med workspace_id UTEN RLS-policy.
|
|
```
|
|
|
|
## Automatisering
|
|
Disse sjekkene kjøres automatisk i migrasjonstestene (se `ARCHITECTURE.md` §10.2). Manuell kjøring er kun nødvendig ved prod-migrasjoner til automatiserte tester er på plass. **RLS Leak Hunter bør prioriteres som første CI-steg — den beskytter mot den mest alvorlige feilkategorien (cross-workspace datalekkasje).**
|