From 7502e0db216db1ace40dad862760e201d0e98eac Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sun, 19 Apr 2026 10:33:57 +0000 Subject: [PATCH] [mailserver] Document postfix-accounts.cf hash-drift invariant [ci skip] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context The `postfix-accounts.cf` ConfigMap renders `bcrypt(pass, 6)` for each user in `var.mailserver_accounts`. bcrypt generates a fresh salt on every evaluation → the ConfigMap `data` hash line differs every plan run. `ignore_changes = [data["postfix-accounts.cf"]]` was the pragmatic workaround, but the side-effect wasn't documented: a Vault rotation of a mailserver password would be MASKED by ignore_changes — TF would never push the new hash and the pod would keep accepting the old password until manual taint/replace. Addresses bd code-7ns. ## This change Inline comment on the lifecycle block spelling out: - Why ignore_changes exists (non-deterministic bcrypt) - What the invariant costs (masks automatic rotation) - Why it's acceptable TODAY (no automatic rotation for mailserver_accounts — verified in Vault; manual password change is a manual TF run anyway) - Two concrete alternatives if rotation is ever added: (a) deterministic bcrypt with stable per-user salt (b) render from an ESO-synced K8s Secret No code change, no apply needed — this is a comment-only commit. The decision (live-with + document) is one of the three options in the plan. ## What is NOT in this change - Deterministic hashing (not needed until automatic rotation exists) - ESO-driven Secret (same reason) - Removal of ignore_changes (would cause the original drift flap) ## Test Plan ### Automated ``` $ cd stacks/mailserver && /home/wizard/code/infra/scripts/tg plan # no diff expected on this comment-only change; other drift remains # but is pre-existing and out of scope. ``` ### Manual Verification Read the new comment block at `stacks/mailserver/modules/mailserver/ main.tf` around the postfix-accounts-cf lifecycle — comprehensible without session context. Closes: code-7ns Co-Authored-By: Claude Opus 4.7 (1M context) --- stacks/mailserver/modules/mailserver/main.tf | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/stacks/mailserver/modules/mailserver/main.tf b/stacks/mailserver/modules/mailserver/main.tf index 95b2a134..4adcd50f 100644 --- a/stacks/mailserver/modules/mailserver/main.tf +++ b/stacks/mailserver/modules/mailserver/main.tf @@ -147,8 +147,24 @@ resource "kubernetes_config_map" "mailserver_config" { logtarget = SYSOUT EOF } - # Password hashes are different each time and avoid changing secret constantly. - # Either 1.Create consistent hashes or 2.Find a way to ignore_changes on per password + # bcrypt() generates a fresh salt on every evaluation, so the hash line + # differs each plan run. ignore_changes is the pragmatic workaround. + # + # INVARIANT (code-7ns, decision 2026-04-19): if a password in Vault + # (secret/platform.mailserver_accounts) is rotated, ignore_changes WILL + # mask that rotation — TF will not re-render the ConfigMap and the pod + # will keep accepting the old password until the ConfigMap is force- + # taintned (`terraform taint module.mailserver.kubernetes_config_map + # .postfix-accounts-cf`) or the resource is addressed explicitly on + # apply (`-replace=...`). Currently there is NO automatic Vault + # rotation for mailserver_accounts, so this is acceptable. If automatic + # rotation is ever added, replace this ignore_changes with either: + # (a) deterministic hashing (bcrypt with a stable salt derived from + # the user string — loses per-user salt uniqueness but keeps TF + # convergent), or + # (b) render postfix-accounts.cf from a K8s Secret synced by ESO + # (CRD consumed by a dedicated volume mount; docker-mailserver + # loads it at pod start). lifecycle { # DRIFT_WORKAROUND: postfix-accounts.cf password hashes non-deterministic; would flap on every apply. Reviewed 2026-04-18. ignore_changes = [data["postfix-accounts.cf"]]