|
|
|
|
@@ -0,0 +1,163 @@
|
|
|
|
|
-- Authority Schema Migration 003: Row-Level Security
|
|
|
|
|
-- Sprint: SPRINT_3421_0001_0001 - RLS Expansion
|
|
|
|
|
-- Category: B (release migration, requires coordination)
|
|
|
|
|
--
|
|
|
|
|
-- Purpose: Enable Row-Level Security on all tenant-scoped tables in the authority
|
|
|
|
|
-- schema to provide database-level tenant isolation as defense-in-depth.
|
|
|
|
|
--
|
|
|
|
|
-- Note: The tenants table is EXCLUDED from RLS as it defines tenant boundaries.
|
|
|
|
|
-- All other tables reference tenants(tenant_id) and are RLS-protected.
|
|
|
|
|
|
|
|
|
|
BEGIN;
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Step 1: Create helper schema and function for tenant context
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
CREATE SCHEMA IF NOT EXISTS authority_app;
|
|
|
|
|
|
|
|
|
|
-- Tenant context helper function
|
|
|
|
|
CREATE OR REPLACE FUNCTION authority_app.require_current_tenant()
|
|
|
|
|
RETURNS TEXT
|
|
|
|
|
LANGUAGE plpgsql STABLE SECURITY DEFINER
|
|
|
|
|
AS $$
|
|
|
|
|
DECLARE
|
|
|
|
|
v_tenant TEXT;
|
|
|
|
|
BEGIN
|
|
|
|
|
v_tenant := current_setting('app.tenant_id', true);
|
|
|
|
|
IF v_tenant IS NULL OR v_tenant = '' THEN
|
|
|
|
|
RAISE EXCEPTION 'app.tenant_id session variable not set'
|
|
|
|
|
USING HINT = 'Set via: SELECT set_config(''app.tenant_id'', ''<tenant>'', false)',
|
|
|
|
|
ERRCODE = 'P0001';
|
|
|
|
|
END IF;
|
|
|
|
|
RETURN v_tenant;
|
|
|
|
|
END;
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
REVOKE ALL ON FUNCTION authority_app.require_current_tenant() FROM PUBLIC;
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Step 2: Enable RLS on tenant-scoped tables (NOT on tenants table itself)
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- authority.users
|
|
|
|
|
ALTER TABLE authority.users ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
ALTER TABLE authority.users FORCE ROW LEVEL SECURITY;
|
|
|
|
|
DROP POLICY IF EXISTS users_tenant_isolation ON authority.users;
|
|
|
|
|
CREATE POLICY users_tenant_isolation ON authority.users
|
|
|
|
|
FOR ALL
|
|
|
|
|
USING (tenant_id = authority_app.require_current_tenant())
|
|
|
|
|
WITH CHECK (tenant_id = authority_app.require_current_tenant());
|
|
|
|
|
|
|
|
|
|
-- authority.roles
|
|
|
|
|
ALTER TABLE authority.roles ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
ALTER TABLE authority.roles FORCE ROW LEVEL SECURITY;
|
|
|
|
|
DROP POLICY IF EXISTS roles_tenant_isolation ON authority.roles;
|
|
|
|
|
CREATE POLICY roles_tenant_isolation ON authority.roles
|
|
|
|
|
FOR ALL
|
|
|
|
|
USING (tenant_id = authority_app.require_current_tenant())
|
|
|
|
|
WITH CHECK (tenant_id = authority_app.require_current_tenant());
|
|
|
|
|
|
|
|
|
|
-- authority.permissions
|
|
|
|
|
ALTER TABLE authority.permissions ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
ALTER TABLE authority.permissions FORCE ROW LEVEL SECURITY;
|
|
|
|
|
DROP POLICY IF EXISTS permissions_tenant_isolation ON authority.permissions;
|
|
|
|
|
CREATE POLICY permissions_tenant_isolation ON authority.permissions
|
|
|
|
|
FOR ALL
|
|
|
|
|
USING (tenant_id = authority_app.require_current_tenant())
|
|
|
|
|
WITH CHECK (tenant_id = authority_app.require_current_tenant());
|
|
|
|
|
|
|
|
|
|
-- authority.role_permissions (FK-based, inherits from roles)
|
|
|
|
|
ALTER TABLE authority.role_permissions ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
ALTER TABLE authority.role_permissions FORCE ROW LEVEL SECURITY;
|
|
|
|
|
DROP POLICY IF EXISTS role_permissions_tenant_isolation ON authority.role_permissions;
|
|
|
|
|
CREATE POLICY role_permissions_tenant_isolation ON authority.role_permissions
|
|
|
|
|
FOR ALL
|
|
|
|
|
USING (
|
|
|
|
|
role_id IN (
|
|
|
|
|
SELECT id FROM authority.roles
|
|
|
|
|
WHERE tenant_id = authority_app.require_current_tenant()
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- authority.user_roles (FK-based, inherits from users)
|
|
|
|
|
ALTER TABLE authority.user_roles ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
ALTER TABLE authority.user_roles FORCE ROW LEVEL SECURITY;
|
|
|
|
|
DROP POLICY IF EXISTS user_roles_tenant_isolation ON authority.user_roles;
|
|
|
|
|
CREATE POLICY user_roles_tenant_isolation ON authority.user_roles
|
|
|
|
|
FOR ALL
|
|
|
|
|
USING (
|
|
|
|
|
user_id IN (
|
|
|
|
|
SELECT id FROM authority.users
|
|
|
|
|
WHERE tenant_id = authority_app.require_current_tenant()
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
-- authority.api_keys
|
|
|
|
|
ALTER TABLE authority.api_keys ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
ALTER TABLE authority.api_keys FORCE ROW LEVEL SECURITY;
|
|
|
|
|
DROP POLICY IF EXISTS api_keys_tenant_isolation ON authority.api_keys;
|
|
|
|
|
CREATE POLICY api_keys_tenant_isolation ON authority.api_keys
|
|
|
|
|
FOR ALL
|
|
|
|
|
USING (tenant_id = authority_app.require_current_tenant())
|
|
|
|
|
WITH CHECK (tenant_id = authority_app.require_current_tenant());
|
|
|
|
|
|
|
|
|
|
-- authority.tokens
|
|
|
|
|
ALTER TABLE authority.tokens ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
ALTER TABLE authority.tokens FORCE ROW LEVEL SECURITY;
|
|
|
|
|
DROP POLICY IF EXISTS tokens_tenant_isolation ON authority.tokens;
|
|
|
|
|
CREATE POLICY tokens_tenant_isolation ON authority.tokens
|
|
|
|
|
FOR ALL
|
|
|
|
|
USING (tenant_id = authority_app.require_current_tenant())
|
|
|
|
|
WITH CHECK (tenant_id = authority_app.require_current_tenant());
|
|
|
|
|
|
|
|
|
|
-- authority.refresh_tokens
|
|
|
|
|
ALTER TABLE authority.refresh_tokens ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
ALTER TABLE authority.refresh_tokens FORCE ROW LEVEL SECURITY;
|
|
|
|
|
DROP POLICY IF EXISTS refresh_tokens_tenant_isolation ON authority.refresh_tokens;
|
|
|
|
|
CREATE POLICY refresh_tokens_tenant_isolation ON authority.refresh_tokens
|
|
|
|
|
FOR ALL
|
|
|
|
|
USING (tenant_id = authority_app.require_current_tenant())
|
|
|
|
|
WITH CHECK (tenant_id = authority_app.require_current_tenant());
|
|
|
|
|
|
|
|
|
|
-- authority.sessions
|
|
|
|
|
ALTER TABLE authority.sessions ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
ALTER TABLE authority.sessions FORCE ROW LEVEL SECURITY;
|
|
|
|
|
DROP POLICY IF EXISTS sessions_tenant_isolation ON authority.sessions;
|
|
|
|
|
CREATE POLICY sessions_tenant_isolation ON authority.sessions
|
|
|
|
|
FOR ALL
|
|
|
|
|
USING (tenant_id = authority_app.require_current_tenant())
|
|
|
|
|
WITH CHECK (tenant_id = authority_app.require_current_tenant());
|
|
|
|
|
|
|
|
|
|
-- authority.audit
|
|
|
|
|
ALTER TABLE authority.audit ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
ALTER TABLE authority.audit FORCE ROW LEVEL SECURITY;
|
|
|
|
|
DROP POLICY IF EXISTS audit_tenant_isolation ON authority.audit;
|
|
|
|
|
CREATE POLICY audit_tenant_isolation ON authority.audit
|
|
|
|
|
FOR ALL
|
|
|
|
|
USING (tenant_id = authority_app.require_current_tenant())
|
|
|
|
|
WITH CHECK (tenant_id = authority_app.require_current_tenant());
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Step 3: Note on tenants table
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
-- The authority.tenants table is intentionally NOT RLS-protected.
|
|
|
|
|
-- It defines tenant boundaries and must be accessible for tenant resolution.
|
|
|
|
|
-- Access control is handled at the application layer.
|
|
|
|
|
COMMENT ON TABLE authority.tenants IS
|
|
|
|
|
'Tenant registry. Not RLS-protected - defines tenant boundaries for the system.';
|
|
|
|
|
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
-- Step 4: Create admin bypass role
|
|
|
|
|
-- ============================================================================
|
|
|
|
|
|
|
|
|
|
DO $$
|
|
|
|
|
BEGIN
|
|
|
|
|
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authority_admin') THEN
|
|
|
|
|
CREATE ROLE authority_admin WITH NOLOGIN BYPASSRLS;
|
|
|
|
|
END IF;
|
|
|
|
|
END
|
|
|
|
|
$$;
|
|
|
|
|
|
|
|
|
|
COMMIT;
|