fleetanalytics_mcp/scripts/app_roles_tracksolid_db.sql

96 lines
5.7 KiB
MySQL
Raw Permalink Normal View History

-- app_roles_tracksolid_db.sql — get the tracksolid_db apps off the postgres SUPERUSER.
2026-06-19 20:51:52 +00:00
-- ─────────────────────────────────────────────────────────────────────────────
-- DESIGN (validated against the live DB, 2026-06-20):
-- * webhook_receiver, ingest_worker, worker each run `run_migrations.py` (DDL) and
-- write telemetry. `worker` is a second copy of the ingest_worker image. Because
-- they run migrations, they need to OWN the objects they ALTER. They therefore
-- connect as the shared, NON-SUPERUSER **tracksolid_owner** (the role the repo
-- already intends to own these schemas — see analytics_ro_role.sql default privs).
-- * the prod dashboard_api backend only reads → its own read role `dashboard_app`
-- (or reuse the existing dashboard_ro).
2026-06-19 20:51:52 +00:00
--
-- This file is idempotent. Section 2 (ownership reassignment) is Timescale-aware:
-- it skips table-linked sequences, uses ALTER MATERIALIZED VIEW for continuous
-- aggregates, and leaves reporting.v_trips with reporting_refresher. Reassigning
-- while the apps still run as postgres is SAFE — superuser bypasses ownership, so
-- nothing breaks until you flip each app's DATABASE_URL (see the runbook).
2026-06-19 20:51:52 +00:00
--
-- Run as the postgres SUPERUSER, on tracksolid_db:
2026-06-19 20:51:52 +00:00
-- docker exec -i <timescale_db> psql -U postgres -d tracksolid_db -v ON_ERROR_STOP=1 \
-- -v owner_pw="$(cat ~/.tracksolid_owner.pw)" \
2026-06-19 20:51:52 +00:00
-- -v dash_pw="$(cat ~/.dashboard_app.pw)" \
-- < scripts/app_roles_tracksolid_db.sql
\set ON_ERROR_STOP on
-- ── 1. tracksolid_owner: the shared owner/migrator login for the ingestion apps ──
2026-06-19 20:51:52 +00:00
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='tracksolid_owner') THEN
CREATE ROLE tracksolid_owner LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE;
2026-06-19 20:51:52 +00:00
END IF; END $$;
-- LOGIN + password + a HARD connection cap (the real budget control). No
-- statement_timeout: migrations (e.g. CREATE INDEX on a hypertable) can run long.
ALTER ROLE tracksolid_owner WITH LOGIN PASSWORD :'owner_pw' CONNECTION LIMIT 30;
ALTER ROLE tracksolid_owner SET idle_in_transaction_session_timeout = '5min';
ALTER ROLE tracksolid_owner SET idle_session_timeout = '10min';
ALTER ROLE tracksolid_owner SET lock_timeout = '10s';
GRANT CONNECT ON DATABASE tracksolid_db TO tracksolid_owner;
GRANT USAGE, CREATE ON SCHEMA tracksolid, reporting, tickets, fuel TO tracksolid_owner;
2026-06-19 20:51:52 +00:00
-- ── 2. Reassign the app objects to tracksolid_owner (Timescale-aware, idempotent) ─
DO $reassign$
DECLARE r record; k text;
BEGIN
FOR r IN
SELECT n.nspname, c.relname, c.relkind,
EXISTS (SELECT 1 FROM timescaledb_information.continuous_aggregates ca
WHERE ca.view_schema=n.nspname AND ca.view_name=c.relname) AS is_cagg
FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
WHERE n.nspname IN ('tracksolid','reporting','tickets','fuel')
AND c.relkind IN ('r','p','v','m','S')
AND pg_get_userbyid(c.relowner) <> 'tracksolid_owner' -- idempotent
AND NOT (n.nspname='reporting' AND c.relname='v_trips') -- keep with refresher
AND NOT (c.relkind='S' AND EXISTS ( -- skip linked seqs
SELECT 1 FROM pg_depend d WHERE d.objid=c.oid AND d.deptype IN ('a','i')))
LOOP
k := CASE WHEN r.is_cagg OR r.relkind='m' THEN 'MATERIALIZED VIEW'
WHEN r.relkind='v' THEN 'VIEW' WHEN r.relkind='S' THEN 'SEQUENCE' ELSE 'TABLE' END;
EXECUTE format('ALTER %s %I.%I OWNER TO tracksolid_owner', k, r.nspname, r.relname);
END LOOP;
END $reassign$;
2026-06-19 20:51:52 +00:00
DO $fns$
DECLARE r record;
BEGIN
FOR r IN SELECT p.oid::regprocedure AS sig
FROM pg_proc p JOIN pg_namespace n ON n.oid=p.pronamespace
WHERE n.nspname IN ('tracksolid','reporting','tickets','fuel')
AND pg_get_userbyid(p.proowner) <> 'tracksolid_owner'
LOOP EXECUTE format('ALTER FUNCTION %s OWNER TO tracksolid_owner', r.sig); END LOOP;
END $fns$;
2026-06-19 20:51:52 +00:00
-- ── 3. dashboard_app: read-only role for the prod dashboard_api backend ──────────
-- (If that backend turns out to also WRITE app state, widen via a write group like
-- the fleet_platform file; start read-only.)
2026-06-19 20:51:52 +00:00
DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname='dashboard_app') THEN
CREATE ROLE dashboard_app LOGIN INHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE;
END IF; END $$;
ALTER ROLE dashboard_app WITH LOGIN PASSWORD :'dash_pw' CONNECTION LIMIT 8;
GRANT CONNECT ON DATABASE tracksolid_db TO dashboard_app;
GRANT USAGE ON SCHEMA tracksolid, reporting, tickets, fuel TO dashboard_app;
GRANT SELECT ON ALL TABLES IN SCHEMA tracksolid, reporting, tickets, fuel TO dashboard_app;
GRANT SELECT ON reporting.v_trips TO dashboard_app;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA reporting TO dashboard_app;
ALTER DEFAULT PRIVILEGES FOR ROLE tracksolid_owner IN SCHEMA tracksolid, reporting, tickets, fuel
GRANT SELECT ON TABLES TO dashboard_app; -- future objects (now owned by tracksolid_owner)
2026-06-19 20:51:52 +00:00
ALTER ROLE dashboard_app SET statement_timeout = '30s';
ALTER ROLE dashboard_app SET idle_in_transaction_session_timeout = '60s';
ALTER ROLE dashboard_app SET idle_session_timeout = '5min';
ALTER ROLE dashboard_app SET lock_timeout = '5s';
-- ── 4. Verify ────────────────────────────────────────────────────────────────────
-- \du+ tracksolid_owner -- LOGIN + CONNECTION LIMIT 30
-- SELECT pg_get_userbyid(relowner), count(*) FROM pg_class
-- WHERE relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname='tracksolid') GROUP BY 1;