From 8ddbd7b745899aa1ebe7aaa6e2146af90726ec6b Mon Sep 17 00:00:00 2001 From: david kiania Date: Wed, 10 Jun 2026 12:24:55 +0300 Subject: [PATCH] feat(db): grant grafana_ro read access to reporting.* (Phase 0 role) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit migrations/18_grant_reporting_ro.sql — grants USAGE + SELECT on the reporting.* layer to grafana_ro, with DEFAULT PRIVILEGES so future reporting views are auto-readable. grafana_ro is the read-only role the staging dashboard_api connects as; it read tracksolid.* but never reporting.* (the prod dashboard_api uses the app role), surfacing as "permission denied for view v_filter_drivers / v_daily_summary" on the staging /analytics/* endpoints. Read-only only — no write/REFRESH. Registered 18 in run_migrations.py. Co-Authored-By: Claude Opus 4.8 --- migrations/18_grant_reporting_ro.sql | 24 ++++++++++++++++++++++++ run_migrations.py | 1 + 2 files changed, 25 insertions(+) create mode 100644 migrations/18_grant_reporting_ro.sql diff --git a/migrations/18_grant_reporting_ro.sql b/migrations/18_grant_reporting_ro.sql new file mode 100644 index 0000000..ee16da3 --- /dev/null +++ b/migrations/18_grant_reporting_ro.sql @@ -0,0 +1,24 @@ +-- 18_grant_reporting_ro.sql +-- Read-only access to the reporting.* layer for grafana_ro. +-- +-- grafana_ro is the read-only role the STAGING dashboard_api connects as (it reads +-- the prod DB but must be physically unable to write — see +-- docs/STAGING_FLEETOPS_ARCHITECTURE.md §6). It already reads tracksolid.* (Grafana +-- + the migration-07 analytics views), but was never granted SELECT on the +-- reporting.* map/analytics layer (migration 11) — the prod dashboard_api connects +-- as the app/superuser role, so the gap went unnoticed until the read-only staging +-- instance hit "permission denied for view v_filter_drivers / v_daily_summary". +-- +-- This grants USAGE + SELECT across reporting.* and sets DEFAULT PRIVILEGES so any +-- future reporting view/table is auto-readable by grafana_ro (no re-grant needed). +-- Read-only only: no INSERT/UPDATE/DELETE, so grafana_ro still cannot write or +-- REFRESH. Guarded + idempotent -> safe to re-apply. + +DO $grants$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro') THEN + GRANT USAGE ON SCHEMA reporting TO grafana_ro; + GRANT SELECT ON ALL TABLES IN SCHEMA reporting TO grafana_ro; -- includes views + ALTER DEFAULT PRIVILEGES IN SCHEMA reporting GRANT SELECT ON TABLES TO grafana_ro; + END IF; +END $grants$; diff --git a/run_migrations.py b/run_migrations.py index 41a7b27..3514363 100644 --- a/run_migrations.py +++ b/run_migrations.py @@ -41,6 +41,7 @@ MIGRATIONS = [ "15_map_exclude_cost_centres.sql", # hide personal/management/mtn vehicles from the live map "16_live_feed_vehicle_type.sql", # add vehicle_type + fleet_segment to fn_live_positions feed "17_fleetops_fuel_view.sql", # reporting.v_fuel_daily — FleetOps GET /analytics/fuel source + "18_grant_reporting_ro.sql", # grant SELECT on reporting.* to grafana_ro (staging read-only role) ] # ── Tables that must exist before the service is allowed to start ─────────────