fix(fire-planner): grafana fire-planner-pg datasource survives pw rotation
Some checks failed
ci/woodpecker/push/default Pipeline failed

The fire-planner-pg Grafana datasource baked the rotating fire_planner DB
password into its provisioning ConfigMap at terraform plan-time, so on every
7-day static-role rotation the password went stale and ALL fire-planner-pg
dashboards (fire-planner, cost-of-living, and the new wealth FIRE Countdown)
silently failed with "password authentication failed for user fire_planner"
until the next stack apply.

Switch to the same live-env pattern wealth-pg / payslips-pg already use:
- new ExternalSecret grafana-fire-planner-pg-creds (monitoring ns, Reloader
  match) mirrors the rotating Vault static-creds/pg-fire-planner password
- datasource ConfigMap now references $__env{FIRE_PLANNER_PG_PASSWORD}
- Grafana mounts it via envFromSecrets; reloader (auto) restarts Grafana on
  rotation so the provisioned datasource never goes stale

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-06-28 16:14:42 +00:00
parent 35c0057d83
commit 6f042ee239
2 changed files with 52 additions and 10 deletions

View file

@ -660,16 +660,53 @@ module "ingress_api" {
auth = "none" auth = "none"
} }
# Plan-time read of the ESO-created K8s Secret for Grafana datasource # ExternalSecret in the monitoring namespace mirroring the rotating
# password. First-apply gotcha: must # fire_planner DB password. Grafana mounts this via envFromSecrets in
# `terragrunt apply -target=kubernetes_manifest.db_external_secret` so # monitoring/grafana_chart_values.yaml; the datasource ConfigMap below
# the Secret exists before this data source plans. # references it as $__env{FIRE_PLANNER_PG_PASSWORD}. Reloader restarts
data "kubernetes_secret" "fire_planner_db_creds" { # Grafana whenever ESO updates this secret (on the 7d static-role
metadata { # rotation), so the provisioned datasource never goes stale replaces
name = "fire-planner-db-creds" # the old plan-time `data.kubernetes_secret` bake that broke weekly.
namespace = kubernetes_namespace.fire_planner.metadata[0].name # Mirrors the wealth-pg / payslips-pg pattern.
resource "kubernetes_manifest" "grafana_fire_planner_pg_creds" {
field_manager {
force_conflicts = true
}
manifest = {
apiVersion = "external-secrets.io/v1"
kind = "ExternalSecret"
metadata = {
name = "grafana-fire-planner-pg-creds"
namespace = "monitoring"
}
spec = {
refreshInterval = "15m"
secretStoreRef = {
name = "vault-database"
kind = "ClusterSecretStore"
}
target = {
name = "grafana-fire-planner-pg-creds"
template = {
metadata = {
annotations = {
"reloader.stakater.com/match" = "true"
}
}
data = {
FIRE_PLANNER_PG_PASSWORD = "{{ .password }}"
}
}
}
data = [{
secretKey = "password"
remoteRef = {
key = "static-creds/pg-fire-planner"
property = "password"
}
}]
}
} }
depends_on = [kubernetes_manifest.db_external_secret]
} }
# Grafana datasource for fire_planner PostgreSQL DB. # Grafana datasource for fire_planner PostgreSQL DB.
@ -706,12 +743,15 @@ resource "kubernetes_config_map" "grafana_fire_planner_datasource" {
timescaledb = false timescaledb = false
} }
secureJsonData = { secureJsonData = {
password = data.kubernetes_secret.fire_planner_db_creds.data["DB_PASSWORD"] # Live env from grafana-fire-planner-pg-creds (above), injected into
# Grafana via envFromSecrets; reloader refreshes it on rotation.
password = "$__env{FIRE_PLANNER_PG_PASSWORD}"
} }
editable = true editable = true
}] }]
}) })
} }
depends_on = [kubernetes_manifest.grafana_fire_planner_pg_creds]
} }
# CI retrigger 2026-05-16T13:42:57+00:00 bulk enrollment apply (pipeline #689 killed) # CI retrigger 2026-05-16T13:42:57+00:00 bulk enrollment apply (pipeline #689 killed)

View file

@ -86,6 +86,8 @@ envFromSecrets:
optional: true optional: true
- name: grafana-job-hunter-pg-creds - name: grafana-job-hunter-pg-creds
optional: true optional: true
- name: grafana-fire-planner-pg-creds
optional: true
env: env:
GF_SERVER_ROOT_URL: https://grafana.viktorbarzin.me GF_SERVER_ROOT_URL: https://grafana.viktorbarzin.me