diff --git a/backend/src/main/resources/db/migration/R__grafana_reader_password.sql b/backend/src/main/resources/db/migration/R__grafana_reader_password.sql new file mode 100644 index 00000000..a4e63037 --- /dev/null +++ b/backend/src/main/resources/db/migration/R__grafana_reader_password.sql @@ -0,0 +1,14 @@ +-- Repeatable migration: sets the grafana_reader role's password from the +-- ${grafanaDbPassword} placeholder (resolved by FlywayConfig from the +-- GRAFANA_DB_PASSWORD environment variable). Flyway computes the checksum on +-- the resolved migration content, so any change to GRAFANA_DB_PASSWORD changes +-- the checksum and re-applies this migration on the next boot. That makes +-- password rotation a "change env var + restart" operation — no manual psql. +-- +-- V68 created the role itself (without a usable password). This file owns the +-- password lifecycle; nothing else writes it. +DO $$ +BEGIN + EXECUTE format('ALTER ROLE grafana_reader WITH PASSWORD %L', '${grafanaDbPassword}'); +END +$$; diff --git a/backend/src/main/resources/db/migration/V68__add_grafana_reader_role.sql b/backend/src/main/resources/db/migration/V68__add_grafana_reader_role.sql index ffb185fa..eb276b77 100644 --- a/backend/src/main/resources/db/migration/V68__add_grafana_reader_role.sql +++ b/backend/src/main/resources/db/migration/V68__add_grafana_reader_role.sql @@ -1,13 +1,13 @@ -- Read-only role used by the Grafana PostgreSQL datasource for the PO Overview --- dashboard (issue #651). Password is injected at migration time via the Flyway --- placeholder ${grafanaDbPassword}, supplied by FlywayConfig from the --- GRAFANA_DB_PASSWORD environment variable. +-- dashboard (issue #651). The role is created here without a usable password +-- (LOGIN-capable but no password set); R__grafana_reader_password.sql sets the +-- password from GRAFANA_DB_PASSWORD on every boot, so rotation is just "bump +-- the env var and restart the backend" — see docs/adr/024-* and the rotation +-- runbook in docs/DEPLOYMENT.md. DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'grafana_reader') THEN - EXECUTE format('CREATE ROLE grafana_reader WITH LOGIN PASSWORD %L', '${grafanaDbPassword}'); - ELSE - EXECUTE format('ALTER ROLE grafana_reader WITH LOGIN PASSWORD %L', '${grafanaDbPassword}'); + CREATE ROLE grafana_reader WITH LOGIN; END IF; END $$;