-- 007_enable_rls.sql -- Enable Row-Level Security for Findings Ledger tenant isolation (LEDGER-TEN-48-001-DEV) -- Based on Evidence Locker pattern per CONTRACT-FINDINGS-LEDGER-RLS-011 BEGIN; -- ============================================ -- 1. Create app schema and tenant function -- ============================================ CREATE SCHEMA IF NOT EXISTS findings_ledger_app; CREATE OR REPLACE FUNCTION findings_ledger_app.require_current_tenant() RETURNS TEXT LANGUAGE plpgsql STABLE AS $$ DECLARE tenant_text TEXT; BEGIN tenant_text := current_setting('app.current_tenant', true); IF tenant_text IS NULL OR length(trim(tenant_text)) = 0 THEN RAISE EXCEPTION 'app.current_tenant is not set for the current session' USING ERRCODE = 'P0001'; END IF; RETURN tenant_text; END; $$; COMMENT ON FUNCTION findings_ledger_app.require_current_tenant() IS 'Returns the current tenant ID from session variable, raises exception if not set'; -- ============================================ -- 2. Enable RLS on ledger_events -- ============================================ ALTER TABLE ledger_events ENABLE ROW LEVEL SECURITY; ALTER TABLE ledger_events FORCE ROW LEVEL SECURITY; DROP POLICY IF EXISTS ledger_events_tenant_isolation ON ledger_events; CREATE POLICY ledger_events_tenant_isolation ON ledger_events FOR ALL USING (tenant_id = findings_ledger_app.require_current_tenant()) WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant()); -- ============================================ -- 3. Enable RLS on ledger_merkle_roots -- ============================================ ALTER TABLE ledger_merkle_roots ENABLE ROW LEVEL SECURITY; ALTER TABLE ledger_merkle_roots FORCE ROW LEVEL SECURITY; DROP POLICY IF EXISTS ledger_merkle_roots_tenant_isolation ON ledger_merkle_roots; CREATE POLICY ledger_merkle_roots_tenant_isolation ON ledger_merkle_roots FOR ALL USING (tenant_id = findings_ledger_app.require_current_tenant()) WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant()); -- ============================================ -- 4. Enable RLS on findings_projection -- ============================================ ALTER TABLE findings_projection ENABLE ROW LEVEL SECURITY; ALTER TABLE findings_projection FORCE ROW LEVEL SECURITY; DROP POLICY IF EXISTS findings_projection_tenant_isolation ON findings_projection; CREATE POLICY findings_projection_tenant_isolation ON findings_projection FOR ALL USING (tenant_id = findings_ledger_app.require_current_tenant()) WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant()); -- ============================================ -- 5. Enable RLS on finding_history -- ============================================ ALTER TABLE finding_history ENABLE ROW LEVEL SECURITY; ALTER TABLE finding_history FORCE ROW LEVEL SECURITY; DROP POLICY IF EXISTS finding_history_tenant_isolation ON finding_history; CREATE POLICY finding_history_tenant_isolation ON finding_history FOR ALL USING (tenant_id = findings_ledger_app.require_current_tenant()) WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant()); -- ============================================ -- 6. Enable RLS on triage_actions -- ============================================ ALTER TABLE triage_actions ENABLE ROW LEVEL SECURITY; ALTER TABLE triage_actions FORCE ROW LEVEL SECURITY; DROP POLICY IF EXISTS triage_actions_tenant_isolation ON triage_actions; CREATE POLICY triage_actions_tenant_isolation ON triage_actions FOR ALL USING (tenant_id = findings_ledger_app.require_current_tenant()) WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant()); -- ============================================ -- 7. Enable RLS on ledger_attestations -- ============================================ ALTER TABLE ledger_attestations ENABLE ROW LEVEL SECURITY; ALTER TABLE ledger_attestations FORCE ROW LEVEL SECURITY; DROP POLICY IF EXISTS ledger_attestations_tenant_isolation ON ledger_attestations; CREATE POLICY ledger_attestations_tenant_isolation ON ledger_attestations FOR ALL USING (tenant_id = findings_ledger_app.require_current_tenant()) WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant()); -- ============================================ -- 8. Enable RLS on orchestrator_exports -- ============================================ ALTER TABLE orchestrator_exports ENABLE ROW LEVEL SECURITY; ALTER TABLE orchestrator_exports FORCE ROW LEVEL SECURITY; DROP POLICY IF EXISTS orchestrator_exports_tenant_isolation ON orchestrator_exports; CREATE POLICY orchestrator_exports_tenant_isolation ON orchestrator_exports FOR ALL USING (tenant_id = findings_ledger_app.require_current_tenant()) WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant()); -- ============================================ -- 9. Enable RLS on airgap_imports -- ============================================ ALTER TABLE airgap_imports ENABLE ROW LEVEL SECURITY; ALTER TABLE airgap_imports FORCE ROW LEVEL SECURITY; DROP POLICY IF EXISTS airgap_imports_tenant_isolation ON airgap_imports; CREATE POLICY airgap_imports_tenant_isolation ON airgap_imports FOR ALL USING (tenant_id = findings_ledger_app.require_current_tenant()) WITH CHECK (tenant_id = findings_ledger_app.require_current_tenant()); -- ============================================ -- 10. Create admin bypass role -- ============================================ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'findings_ledger_admin') THEN CREATE ROLE findings_ledger_admin NOLOGIN BYPASSRLS; END IF; END; $$; COMMENT ON ROLE findings_ledger_admin IS 'Admin role that bypasses RLS for migrations and cross-tenant operations'; COMMIT;