-- ============================================================================ -- PRODUCTION DATABASE ROLES AND SECURITY SETUP -- ============================================================================ -- Migration: 999_production_roles_setup.sql -- Purpose: Create production database roles and enable Row Level Security (RLS) -- Created: Wave 79 Agent 8 -- Status: Production Critical -- ============================================================================ -- ============================================================================ -- STEP 1: CREATE PRODUCTION ROLES -- ============================================================================ -- Create authenticated_users role (used by RLS policies) DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated_users') THEN CREATE ROLE authenticated_users; RAISE NOTICE 'Created role: authenticated_users'; ELSE RAISE NOTICE 'Role authenticated_users already exists'; END IF; END $$; -- Create foxhunt_user role (application user with login) DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'foxhunt_user') THEN -- NOTE: Password should be changed in production via: -- ALTER ROLE foxhunt_user WITH PASSWORD 'strong_production_password'; CREATE ROLE foxhunt_user LOGIN PASSWORD 'change_me_in_production'; RAISE NOTICE 'Created role: foxhunt_user'; ELSE RAISE NOTICE 'Role foxhunt_user already exists'; END IF; END $$; -- Grant authenticated_users role to foxhunt_user GRANT authenticated_users TO foxhunt_user; -- Create additional operational roles DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'admin') THEN CREATE ROLE admin; RAISE NOTICE 'Created role: admin'; ELSE RAISE NOTICE 'Role admin already exists'; END IF; IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'compliance_officer') THEN CREATE ROLE compliance_officer; RAISE NOTICE 'Created role: compliance_officer'; ELSE RAISE NOTICE 'Role compliance_officer already exists'; END IF; IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'risk_manager') THEN CREATE ROLE risk_manager; RAISE NOTICE 'Created role: risk_manager'; ELSE RAISE NOTICE 'Role risk_manager already exists'; END IF; IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'trader') THEN CREATE ROLE trader; RAISE NOTICE 'Created role: trader'; ELSE RAISE NOTICE 'Role trader already exists'; END IF; IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'system') THEN CREATE ROLE system; RAISE NOTICE 'Created role: system'; ELSE RAISE NOTICE 'Role system already exists'; END IF; END $$; -- ============================================================================ -- STEP 2: VERIFY REQUIRED FUNCTIONS EXIST -- ============================================================================ -- Check if has_role function exists (used by RLS policies) DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_proc WHERE proname = 'has_role' ) THEN -- Create has_role function if it doesn't exist CREATE OR REPLACE FUNCTION has_role(role_name TEXT) RETURNS BOOLEAN AS $func$ BEGIN -- Check if current user has the specified role RETURN pg_has_role(CURRENT_USER, role_name, 'MEMBER'); EXCEPTION WHEN undefined_object THEN RETURN FALSE; END; $func$ LANGUAGE plpgsql SECURITY DEFINER; RAISE NOTICE 'Created function: has_role'; ELSE RAISE NOTICE 'Function has_role already exists'; END IF; END $$; -- Check if current_user_id function exists (used by RLS policies) DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_proc WHERE proname = 'current_user_id' ) THEN -- Create current_user_id function if it doesn't exist CREATE OR REPLACE FUNCTION current_user_id() RETURNS UUID AS $func$ BEGIN -- Get user_id from session variable RETURN current_setting('app.current_user_id', true)::uuid; EXCEPTION WHEN OTHERS THEN RETURN NULL; END; $func$ LANGUAGE plpgsql SECURITY DEFINER; RAISE NOTICE 'Created function: current_user_id'; ELSE RAISE NOTICE 'Function current_user_id already exists'; END IF; END $$; -- ============================================================================ -- STEP 3: ENABLE ROW LEVEL SECURITY ON AUDIT TABLES -- ============================================================================ -- Enable RLS on compliance audit tables (from 010_compliance_audit_trails.sql) ALTER TABLE sox_trade_audit ENABLE ROW LEVEL SECURITY; ALTER TABLE mifid_transaction_report ENABLE ROW LEVEL SECURITY; ALTER TABLE position_limits_audit ENABLE ROW LEVEL SECURITY; ALTER TABLE kill_switch_audit ENABLE ROW LEVEL SECURITY; ALTER TABLE best_execution_analysis ENABLE ROW LEVEL SECURITY; -- Enable RLS on transaction audit events (from 020_transaction_audit_events.sql) ALTER TABLE transaction_audit_events ENABLE ROW LEVEL SECURITY; -- Enable RLS on MFA tables if they exist (from 017_mfa_totp_implementation.sql) DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'mfa_config') THEN ALTER TABLE mfa_config ENABLE ROW LEVEL SECURITY; ALTER TABLE mfa_backup_codes ENABLE ROW LEVEL SECURITY; ALTER TABLE mfa_verification_log ENABLE ROW LEVEL SECURITY; ALTER TABLE mfa_enrollment_sessions ENABLE ROW LEVEL SECURITY; RAISE NOTICE 'Enabled RLS on MFA tables'; ELSE RAISE NOTICE 'MFA tables not found, skipping RLS for MFA'; END IF; END $$; -- ============================================================================ -- STEP 4: CREATE RLS POLICIES -- ============================================================================ -- Drop existing policies if they exist (idempotent) DROP POLICY IF EXISTS sox_trade_audit_user_policy ON sox_trade_audit; DROP POLICY IF EXISTS mifid_transaction_user_policy ON mifid_transaction_report; DROP POLICY IF EXISTS position_limits_user_policy ON position_limits_audit; DROP POLICY IF EXISTS kill_switch_restricted_policy ON kill_switch_audit; DROP POLICY IF EXISTS best_execution_policy ON best_execution_analysis; DROP POLICY IF EXISTS audit_events_user_policy ON transaction_audit_events; DROP POLICY IF EXISTS audit_events_insert_policy ON transaction_audit_events; -- RLS Policies for SOX Trade Audit CREATE POLICY sox_trade_audit_user_policy ON sox_trade_audit FOR ALL TO authenticated_users USING (user_id = current_user_id() OR has_role('admin') OR has_role('compliance_officer') OR has_role('risk_manager')); -- RLS Policies for MiFID II Transaction Report CREATE POLICY mifid_transaction_user_policy ON mifid_transaction_report FOR ALL TO authenticated_users USING (has_role('admin') OR has_role('compliance_officer') OR has_role('trader')); -- RLS Policies for Position Limits CREATE POLICY position_limits_user_policy ON position_limits_audit FOR ALL TO authenticated_users USING (user_id = current_user_id() OR has_role('admin') OR has_role('risk_manager')); -- RLS Policies for Kill Switch (Admin and Risk Managers only) CREATE POLICY kill_switch_restricted_policy ON kill_switch_audit FOR ALL TO authenticated_users USING (has_role('admin') OR has_role('risk_manager')); -- RLS Policies for Best Execution (Compliance officers can see all) CREATE POLICY best_execution_policy ON best_execution_analysis FOR ALL TO authenticated_users USING (has_role('admin') OR has_role('compliance_officer') OR has_role('trader')); -- RLS Policy: Users can only see their own audit events, unless they're admin/compliance CREATE POLICY audit_events_user_policy ON transaction_audit_events FOR SELECT USING ( actor = current_user OR has_role('admin') OR has_role('compliance_officer') OR has_role('risk_manager') ); -- RLS Policy: Only system can INSERT audit events (prevents tampering) CREATE POLICY audit_events_insert_policy ON transaction_audit_events FOR INSERT WITH CHECK (has_role('admin') OR has_role('system')); -- RLS Policies for MFA tables (if they exist) DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'mfa_config') THEN DROP POLICY IF EXISTS mfa_config_self_access ON mfa_config; DROP POLICY IF EXISTS mfa_backup_codes_self_access ON mfa_backup_codes; DROP POLICY IF EXISTS mfa_verification_log_self_access ON mfa_verification_log; DROP POLICY IF EXISTS mfa_enrollment_sessions_self_access ON mfa_enrollment_sessions; CREATE POLICY mfa_config_self_access ON mfa_config FOR ALL TO foxhunt_user USING (user_id = current_setting('app.current_user_id')::uuid OR current_setting('app.current_user_role') = 'admin'); CREATE POLICY mfa_backup_codes_self_access ON mfa_backup_codes FOR ALL TO foxhunt_user USING (user_id = current_setting('app.current_user_id')::uuid OR current_setting('app.current_user_role') = 'admin'); CREATE POLICY mfa_verification_log_self_access ON mfa_verification_log FOR SELECT TO foxhunt_user USING (user_id = current_setting('app.current_user_id')::uuid OR current_setting('app.current_user_role') = 'admin'); CREATE POLICY mfa_enrollment_sessions_self_access ON mfa_enrollment_sessions FOR ALL TO foxhunt_user USING (user_id = current_setting('app.current_user_id')::uuid); RAISE NOTICE 'Created RLS policies for MFA tables'; END IF; END $$; -- ============================================================================ -- STEP 5: GRANT TABLE PERMISSIONS -- ============================================================================ -- Grants for transaction audit events (from 020_transaction_audit_events.sql) GRANT SELECT ON transaction_audit_events TO authenticated_users; GRANT INSERT ON transaction_audit_events TO authenticated_users; REVOKE UPDATE, DELETE ON transaction_audit_events FROM authenticated_users; REVOKE UPDATE, DELETE ON transaction_audit_events FROM PUBLIC; -- Grants for compliance audit trails (from 010_compliance_audit_trails.sql) GRANT SELECT, INSERT ON sox_trade_audit TO authenticated_users; GRANT SELECT, INSERT ON mifid_transaction_report TO authenticated_users; GRANT SELECT, INSERT ON position_limits_audit TO authenticated_users; GRANT SELECT, INSERT ON kill_switch_audit TO authenticated_users; GRANT SELECT, INSERT ON best_execution_analysis TO authenticated_users; -- Grants for compliance rules (from 011_compliance_rules_dynamic.sql) GRANT SELECT ON compliance_rules TO authenticated_users; GRANT SELECT ON compliance_rule_versions TO authenticated_users; GRANT INSERT ON compliance_rule_executions TO authenticated_users; -- Grants for MFA tables (from 017_mfa_totp_implementation.sql) DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'mfa_config') THEN GRANT SELECT, INSERT, UPDATE ON mfa_config TO foxhunt_user; GRANT SELECT, INSERT, UPDATE ON mfa_backup_codes TO foxhunt_user; GRANT SELECT, INSERT ON mfa_verification_log TO foxhunt_user; GRANT SELECT, INSERT, UPDATE, DELETE ON mfa_enrollment_sessions TO foxhunt_user; RAISE NOTICE 'Granted permissions on MFA tables to foxhunt_user'; END IF; END $$; -- ============================================================================ -- STEP 6: GRANT FUNCTION EXECUTE PERMISSIONS -- ============================================================================ -- Grants for transaction audit functions (from 020_transaction_audit_events.sql) DO $$ BEGIN -- Grant execute on functions that exist IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'verify_audit_event_integrity') THEN GRANT EXECUTE ON FUNCTION verify_audit_event_integrity TO authenticated_users; RAISE NOTICE 'Granted EXECUTE on verify_audit_event_integrity'; END IF; IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'query_audit_events') THEN GRANT EXECUTE ON FUNCTION query_audit_events TO authenticated_users; RAISE NOTICE 'Granted EXECUTE on query_audit_events'; ELSE RAISE NOTICE 'Function query_audit_events does not exist, skipping grant'; END IF; IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'get_audit_event_statistics') THEN GRANT EXECUTE ON FUNCTION get_audit_event_statistics TO authenticated_users; RAISE NOTICE 'Granted EXECUTE on get_audit_event_statistics'; END IF; END $$; -- Grants for compliance audit functions (from 010_compliance_audit_trails.sql) GRANT EXECUTE ON FUNCTION log_sox_trade_activity TO authenticated_users; GRANT EXECUTE ON FUNCTION create_mifid_transaction_report TO authenticated_users; GRANT EXECUTE ON FUNCTION check_position_limits TO authenticated_users; GRANT EXECUTE ON FUNCTION activate_kill_switch TO authenticated_users; GRANT EXECUTE ON FUNCTION analyze_best_execution TO authenticated_users; -- Grants for compliance rule functions (from 011_compliance_rules_dynamic.sql) GRANT EXECUTE ON FUNCTION get_active_compliance_rules TO authenticated_users; GRANT EXECUTE ON FUNCTION get_compliance_rules_by_type TO authenticated_users; GRANT EXECUTE ON FUNCTION record_compliance_rule_execution TO authenticated_users; -- ============================================================================ -- STEP 7: PRODUCTION SECURITY HARDENING -- ============================================================================ -- Revoke public access to sensitive tables REVOKE ALL ON sox_trade_audit FROM PUBLIC; REVOKE ALL ON mifid_transaction_report FROM PUBLIC; REVOKE ALL ON position_limits_audit FROM PUBLIC; REVOKE ALL ON kill_switch_audit FROM PUBLIC; REVOKE ALL ON best_execution_analysis FROM PUBLIC; REVOKE ALL ON transaction_audit_events FROM PUBLIC; -- Revoke public access to MFA tables if they exist DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'mfa_config') THEN REVOKE ALL ON mfa_config FROM PUBLIC; REVOKE ALL ON mfa_backup_codes FROM PUBLIC; REVOKE ALL ON mfa_verification_log FROM PUBLIC; REVOKE ALL ON mfa_enrollment_sessions FROM PUBLIC; RAISE NOTICE 'Revoked public access from MFA tables'; END IF; END $$; -- ============================================================================ -- VERIFICATION QUERIES -- ============================================================================ -- List all roles SELECT 'ROLES CREATED:' as status; SELECT rolname, rolcanlogin FROM pg_roles WHERE rolname IN ('authenticated_users', 'foxhunt_user', 'admin', 'compliance_officer', 'risk_manager', 'trader', 'system') ORDER BY rolname; -- List tables with RLS enabled SELECT 'TABLES WITH RLS ENABLED:' as status; SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public' AND rowsecurity = true ORDER BY tablename; -- List RLS policies SELECT 'RLS POLICIES CREATED:' as status; SELECT schemaname, tablename, policyname, permissive, roles, cmd FROM pg_policies WHERE schemaname = 'public' ORDER BY tablename, policyname; -- ============================================================================ -- PRODUCTION DEPLOYMENT NOTES -- ============================================================================ -- IMPORTANT: Before deploying to production: -- -- 1. Change foxhunt_user password: -- ALTER ROLE foxhunt_user WITH PASSWORD 'strong_production_password'; -- -- 2. Create user-specific accounts with appropriate role grants: -- CREATE USER prod_admin LOGIN PASSWORD 'secure_password'; -- GRANT admin TO prod_admin; -- GRANT authenticated_users TO prod_admin; -- -- 3. Set up SSL/TLS for database connections -- -- 4. Configure connection pooling with appropriate limits -- -- 5. Set up database backup and recovery procedures -- -- 6. Enable audit logging in postgresql.conf: -- log_statement = 'all' -- log_connections = on -- log_disconnections = on -- -- 7. Configure pg_hba.conf for secure authentication -- -- 8. Set up monitoring for failed login attempts -- -- ============================================================================ -- END OF MIGRATION -- ============================================================================