Replaces the grafana_ro reuse with a purpose-built least-privilege login role
that can serve the FULL dashboard_api read surface — so it backs the staging
instance now and can take over the live prod connection later (stage 2).
scripts/dashboard_ro_role.sql (run as postgres, password-free in repo):
- CREATE ROLE dashboard_ro LOGIN, read-only
- SELECT on reporting.* + tracksolid.*; explicit SELECT on the
reporting.v_trips MATERIALIZED VIEW (not covered by GRANT ON ALL TABLES)
- EXECUTE on reporting.fn_* map functions
- ALTER DEFAULT PRIVILEGES so future objects are auto-readable ("dynamic")
scripts/bootstrap_dashboard_ro.sh:
- generates the password into ~/.dashboard_ro.pw (0600), never printed
- applies the DDL via docker exec psql -U postgres -v ro_pw=...
deploy_dashboard_api_staging.sh: build DATABASE_URL from dashboard_ro +
~/.dashboard_ro.pw instead of grafana_ro.
Migrations 17/18 (already applied) are left intact. Not yet executed on host.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
45 lines
2.5 KiB
SQL
45 lines
2.5 KiB
SQL
-- dashboard_ro_role.sql — dedicated read-only LOGIN role for dashboard_api.
|
|
--
|
|
-- Run as the postgres SUPERUSER (CREATE ROLE), NOT via run_migrations.py (which
|
|
-- connects as the app role and may lack CREATEROLE). Apply with
|
|
-- scripts/bootstrap_dashboard_ro.sh, which supplies the password as the psql
|
|
-- variable :ro_pw from a host-only 0600 file — so no secret lives in this repo.
|
|
--
|
|
-- Purpose: a least-privilege role that can serve the FULL dashboard_api read
|
|
-- surface, so it backs BOTH the staging instance now (stage 1) AND the live prod
|
|
-- connection later (stage 2 — migrate fleetapi.rahamafresh.com off the app role).
|
|
-- It therefore grants exactly what the API reads:
|
|
-- * SELECT on reporting.* and tracksolid.* (tables + views)
|
|
-- * SELECT on the reporting.v_trips MATERIALIZED VIEW — matviews are NOT
|
|
-- covered by GRANT ... ON ALL TABLES, so it must be named explicitly
|
|
-- * EXECUTE on the reporting.fn_* map functions (fn_live_positions, etc.)
|
|
-- * DEFAULT PRIVILEGES so future objects created by the migration role are
|
|
-- auto-readable ("dynamic" — no re-grant when we add views)
|
|
-- Read-only: no INSERT/UPDATE/DELETE and not the matview owner, so dashboard_ro
|
|
-- can never write or REFRESH. Idempotent -> safe to re-apply (also rotates pw).
|
|
|
|
\set ON_ERROR_STOP on
|
|
|
|
DO $role$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'dashboard_ro') THEN
|
|
CREATE ROLE dashboard_ro LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE;
|
|
END IF;
|
|
END $role$;
|
|
|
|
ALTER ROLE dashboard_ro WITH LOGIN PASSWORD :'ro_pw';
|
|
|
|
GRANT CONNECT ON DATABASE tracksolid_db TO dashboard_ro;
|
|
GRANT USAGE ON SCHEMA reporting, tracksolid TO dashboard_ro;
|
|
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA reporting TO dashboard_ro; -- tables + views
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA tracksolid TO dashboard_ro; -- tables + views
|
|
GRANT SELECT ON reporting.v_trips TO dashboard_ro; -- MATERIALIZED VIEW (not in ALL TABLES)
|
|
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA reporting TO dashboard_ro;
|
|
|
|
-- "dynamic": future objects created by the migration role (tracksolid_owner)
|
|
-- are auto-granted. NOTE: matviews are still never covered — a new matview needs
|
|
-- its own explicit GRANT SELECT (as above for v_trips).
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE tracksolid_owner IN SCHEMA reporting GRANT SELECT ON TABLES TO dashboard_ro;
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE tracksolid_owner IN SCHEMA tracksolid GRANT SELECT ON TABLES TO dashboard_ro;
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE tracksolid_owner IN SCHEMA reporting GRANT EXECUTE ON FUNCTIONS TO dashboard_ro;
|