Build spec and host brief carried in from C:\Claude and WRPS/02-env; the plant model (equipment, tags, alarm bitmask, enums, unit conversions) is derived from WRPS/04-plc/register-map.csv, WRPS/05-scada/modbus/scada-points.csv and WRPS-CTL-003. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
74 lines
2.9 KiB
SQL
74 lines
2.9 KiB
SQL
-- =============================================================================
|
|
-- 003_roles.sql — least privilege inside pg-ai.
|
|
--
|
|
-- psql -h pg-ai -U postgres -d plant -f 003_roles.sql
|
|
--
|
|
-- Passwords are NOT in this file. Set them from the 0600 env files:
|
|
-- \set agent_pw `echo "$AGENT_DB_PASSWORD"`
|
|
-- or ALTER ROLE ... PASSWORD after creation, from a shell that reads ~/ai/*.env.
|
|
--
|
|
-- Two roles, deliberately different:
|
|
-- agent_ro the API. SELECT only, everywhere. It must not be able to write.
|
|
-- cube_rw Cube. SELECT on reference data, full rights on cube_preagg only,
|
|
-- because pre-aggregation refresh creates and drops tables there.
|
|
-- =============================================================================
|
|
|
|
-- --- agent_ro — the application role. SELECT and nothing else. ---------------
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'agent_ro') THEN
|
|
CREATE ROLE agent_ro LOGIN;
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
REVOKE ALL ON DATABASE plant FROM agent_ro;
|
|
GRANT CONNECT ON DATABASE plant TO agent_ro;
|
|
|
|
REVOKE ALL ON SCHEMA public FROM agent_ro;
|
|
GRANT USAGE ON SCHEMA public TO agent_ro;
|
|
|
|
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM agent_ro;
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA public TO agent_ro;
|
|
|
|
-- Applies to tables created later, including the fixture tables.
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO agent_ro;
|
|
|
|
-- No sequences, no functions, no temp tables, no schema creation.
|
|
REVOKE ALL ON ALL SEQUENCES IN SCHEMA public FROM agent_ro;
|
|
REVOKE TEMPORARY ON DATABASE plant FROM agent_ro;
|
|
REVOKE CREATE ON SCHEMA public FROM agent_ro;
|
|
|
|
-- --- cube_rw — Cube. Read reference data, own cube_preagg. -------------------
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'cube_rw') THEN
|
|
CREATE ROLE cube_rw LOGIN;
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
GRANT CONNECT ON DATABASE plant TO cube_rw;
|
|
GRANT USAGE ON SCHEMA public TO cube_rw;
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA public TO cube_rw;
|
|
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO cube_rw;
|
|
|
|
GRANT ALL ON SCHEMA cube_preagg TO cube_rw;
|
|
ALTER SCHEMA cube_preagg OWNER TO cube_rw;
|
|
|
|
-- agent_ro reads pre-aggregations but never writes them.
|
|
GRANT USAGE ON SCHEMA cube_preagg TO agent_ro;
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE cube_rw IN SCHEMA cube_preagg
|
|
GRANT SELECT ON TABLES TO agent_ro;
|
|
|
|
-- =============================================================================
|
|
-- Phase 1 gate — verify, do not assume. As agent_ro:
|
|
--
|
|
-- SELECT count(*) FROM equipment; -- must work
|
|
-- INSERT INTO equipment VALUES ('X'); -- must be REJECTED
|
|
-- CREATE TABLE t (i int); -- must be REJECTED
|
|
--
|
|
-- An INSERT that succeeds here is a Phase 1 failure, not a detail to fix later.
|
|
-- The API's SQL allow-list in guardrails.py is the second line of defence, not
|
|
-- the first; this role is the first.
|
|
-- =============================================================================
|