diff --git a/stacks/dbaas/main.tf b/stacks/dbaas/main.tf index 9825da78..2902a48c 100644 --- a/stacks/dbaas/main.tf +++ b/stacks/dbaas/main.tf @@ -14,14 +14,24 @@ data "vault_kv_secret_v2" "secrets" { name = "platform" } -module "dbaas" { - source = "./modules/dbaas" - prod = var.prod - tls_secret_name = var.tls_secret_name - nfs_server = var.nfs_server - dbaas_root_password = data.vault_kv_secret_v2.secrets.data["dbaas_root_password"] - postgresql_root_password = data.vault_kv_secret_v2.secrets.data["dbaas_postgresql_root_password"] - pgadmin_password = data.vault_kv_secret_v2.secrets.data["dbaas_pgadmin_password"] - kube_config_path = var.kube_config_path - tier = local.tiers.cluster +# Personal/app-user secrets (forgejo + roundcubemail MySQL passwords live here, +# not under secret/platform, to match the "secret/viktor as the go-to personal +# vault" convention documented in .claude/CLAUDE.md). +data "vault_kv_secret_v2" "viktor" { + mount = "secret" + name = "viktor" +} + +module "dbaas" { + source = "./modules/dbaas" + prod = var.prod + tls_secret_name = var.tls_secret_name + nfs_server = var.nfs_server + dbaas_root_password = data.vault_kv_secret_v2.secrets.data["dbaas_root_password"] + postgresql_root_password = data.vault_kv_secret_v2.secrets.data["dbaas_postgresql_root_password"] + pgadmin_password = data.vault_kv_secret_v2.secrets.data["dbaas_pgadmin_password"] + mysql_forgejo_password = data.vault_kv_secret_v2.viktor.data["mysql_forgejo_password"] + mysql_roundcubemail_password = data.vault_kv_secret_v2.viktor.data["mysql_roundcubemail_password"] + kube_config_path = var.kube_config_path + tier = local.tiers.cluster } diff --git a/stacks/dbaas/modules/dbaas/main.tf b/stacks/dbaas/modules/dbaas/main.tf index df68afbd..7b69a01c 100644 --- a/stacks/dbaas/modules/dbaas/main.tf +++ b/stacks/dbaas/modules/dbaas/main.tf @@ -17,6 +17,18 @@ variable "kube_config_path" { sensitive = true } +# MySQL static application users (not rotated by Vault DB engine; baked into +# each app's config). Codified here so future MySQL rebuilds cannot silently +# drop them. +variable "mysql_forgejo_password" { + type = string + sensitive = true +} +variable "mysql_roundcubemail_password" { + type = string + sensitive = true +} + resource "kubernetes_namespace" "dbaas" { metadata { name = "dbaas" @@ -562,6 +574,59 @@ resource "kubernetes_service" "mysql" { depends_on = [kubernetes_stateful_set_v1.mysql_standalone] } +# MySQL static application users — not rotated by Vault DB engine. +# Each app stores its password in its own config (forgejo app.ini, roundcube +# ROUNDCUBEMAIL_DB_PASSWORD env). During the 2026-04-16 InnoDB Cluster → +# standalone migration these users were accidentally dropped and recreated with +# mismatched passwords; this block codifies them so a future rebuild cannot +# silently break the apps. +# +# Pattern matches `null_resource.pg_terraform_state_db` below (local-exec into +# the DB pod). We CREATE IF NOT EXISTS + ALTER USER on every apply so a +# password rotation in Vault is re-synced on the next `scripts/tg apply`. The +# `password_hash` trigger re-runs the provisioner when the Vault password +# changes; the namespace/user triggers re-run if identifiers change. +locals { + mysql_static_users = { + forgejo = { + database = "forgejo" + password = var.mysql_forgejo_password + } + roundcubemail = { + database = "roundcubemail" + password = var.mysql_roundcubemail_password + } + } +} + +resource "null_resource" "mysql_static_user" { + for_each = local.mysql_static_users + + depends_on = [kubernetes_stateful_set_v1.mysql_standalone] + + triggers = { + username = each.key + database = each.value.database + password_hash = sha256(each.value.password) + } + + provisioner "local-exec" { + command = <<-EOT + kubectl --kubeconfig ${var.kube_config_path} exec -n dbaas mysql-standalone-0 -c mysql -- \ + env USER='${each.key}' DB='${each.value.database}' PW='${each.value.password}' \ + bash -c ' + mysql -uroot -p"$MYSQL_ROOT_PASSWORD" <