From 01a718e17b10f29c5a7124ad7bf9bc6b5843089c Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 18 Apr 2026 21:29:02 +0000 Subject: [PATCH] =?UTF-8?q?[mailserver]=20Filter=20redundant=20local?= =?UTF-8?q?=E2=86=92local=20aliases=20to=20fix=20Dovecot=20'exists=20more?= =?UTF-8?q?=20than=20once'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context Dovecot auth logs have been steadily spamming `passwd-file /etc/dovecot/userdb: User r730-idrac@viktorbarzin.me exists more than once` (and the same for vaultwarden@) at ~31 occurrences per 500 log lines. Under load this flakes IMAP auth for the e2e email-roundtrip probe (spam@viktorbarzin.me uses the catch-all), which was masquerading as "Brevo or probe timing" noise. ## Root cause docker-mailserver builds Dovecot's `/etc/dovecot/userdb` from two sources: real accounts (`postfix-accounts.cf`) AND virtual-alias entries whose *target* resolves to a local mailbox (`postfix-virtual.cf`). When the same address appears as BOTH a real mailbox AND an alias whose target is another local mailbox, the generated userdb has two lines for that username pointing to different home directories — e.g.: r730-idrac@viktorbarzin.me:...:/var/mail/.../r730-idrac/home r730-idrac@viktorbarzin.me:...:/var/mail/.../spam/home ← from alias Dovecot's passwd-file driver rejects the duplicate, and every subsequent auth lookup logs the error. This affected exactly two addresses: - r730-idrac@viktorbarzin.me (real account + alias → spam@) - vaultwarden@viktorbarzin.me (real account + alias → me@) Other aliases are fine: they either forward to external addresses (gmail etc.) — no local userdb entry generated — or map an address to itself (me@ → me@) which docker-mailserver dedups internally. Note: removing the real accounts is not an option because Vaultwarden uses `vaultwarden@viktorbarzin.me` as its live SMTP_USERNAME (stacks/vaultwarden/modules/vaultwarden/main.tf:121). ## This change Introduces a `local.postfix_virtual` that concatenates the Vault-sourced aliases with `extra/aliases.txt`, then filters out any line matching the exact "LHS RHS" shape where both sides are in `var.mailserver_accounts` and LHS != RHS. That is, only the pure local→local redundant entries are dropped; all forwarding aliases and the catch-all are preserved. The filter is self-healing: if a future alias ever collides with a real account, it gets silently suppressed instead of breaking Dovecot auth. ``` Vault mailserver_aliases ─┐ ├─ concat ─ split \n ─ filter ─ join \n ─► postfix-virtual.cf extra/aliases.txt ─────────┘ │ └── drop if LHS+RHS both in mailserver_accounts and LHS != RHS ``` Filtered entries (confirmed via locally-simulated filter on live data): - r730-idrac@viktorbarzin.me spam@viktorbarzin.me - vaultwarden@viktorbarzin.me me@viktorbarzin.me Preserved (sample): postmaster→me, contact→me, alarm-valchedrym→self+3 ext, lubohristov→gmail, yoana→gmail, @viktorbarzin.me→spam (catch-all), all four disposable `*-generated@` aliases. ## What is NOT in this change - Real accounts in Vault (`secret/platform.mailserver_accounts`) are untouched — vaultwarden SMTP auth keeps working. - Postfix postscreen btree lock contention (separate commit). - Email-roundtrip probe IMAP window (separate commit). ## Test Plan ### Automated `terraform validate` — passes (docker-mailserver module): ``` Success! The configuration is valid, but there were some validation warnings as shown above. ``` `scripts/tg plan -target=module.mailserver.kubernetes_config_map.mailserver_config`: ``` # module.mailserver.kubernetes_config_map.mailserver_config will be updated in-place ~ resource "kubernetes_config_map" "mailserver_config" { ~ data = { ~ "postfix-virtual.cf" = (sensitive value) # (9 unchanged elements hidden) } id = "mailserver/mailserver.config" } Plan: 0 to add, 1 to change, 0 to destroy. ``` `scripts/tg apply` — applied: ``` Apply complete! Resources: 0 added, 1 changed, 0 destroyed. ``` ### Manual Verification Post-apply configmap content (the two lines are gone): ``` $ kubectl -n mailserver get cm mailserver.config -o jsonpath='{.data.postfix-virtual\.cf}' postmaster@viktorbarzin.me me@viktorbarzin.me contact@viktorbarzin.me me@viktorbarzin.me me@viktorbarzin.me me@viktorbarzin.me lubohristov@viktorbarzin.me lyubomir.hristov3@gmail.com alarm-valchedrym@viktorbarzin.me alarm-valchedrym@...,vbarzin@...,emil.barzin@...,me@... yoana@viktorbarzin.me divcheva.yoana@gmail.com @viktorbarzin.me spam@viktorbarzin.me firmly-gerardo-generated@viktorbarzin.me me@viktorbarzin.me closely-keith-generated@viktorbarzin.me vbarzin@gmail.com literally-paolo-generated@viktorbarzin.me viktorbarzin@fb.com hastily-stefanie-generated@viktorbarzin.me elliestamenova@gmail.com ``` Reloader triggers a pod rollout; once new pod is Ready: - `kubectl -n mailserver exec -c docker-mailserver -- cut -d: -f1 /etc/dovecot/userdb | sort | uniq -d` expected output: empty (no duplicate usernames) - `kubectl -n mailserver logs -c docker-mailserver --tail=500 | grep -c "exists more than once"` expected output: 0 (baseline was 31/500 lines) ## Reproduce locally 1. `kubectl -n mailserver get cm mailserver.config -o jsonpath='{.data.postfix-virtual\.cf}'` 2. Expect: no `r730-idrac@viktorbarzin.me spam@viktorbarzin.me` line and no `vaultwarden@viktorbarzin.me me@viktorbarzin.me` line. 3. After pod restart: `kubectl -n mailserver logs -l app=mailserver -c docker-mailserver --tail=500 | grep -c "exists more than once"` → 0. Closes: code-27l Co-Authored-By: Claude Opus 4.7 (1M context) --- stacks/mailserver/modules/mailserver/main.tf | 22 +++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/stacks/mailserver/modules/mailserver/main.tf b/stacks/mailserver/modules/mailserver/main.tf index 4880684c..16cbcc8a 100644 --- a/stacks/mailserver/modules/mailserver/main.tf +++ b/stacks/mailserver/modules/mailserver/main.tf @@ -14,6 +14,26 @@ variable "email_monitor_imap_password" { sensitive = true } +# Build the virtual-alias map, dropping aliases where BOTH the source and +# target are real mailboxes in var.mailserver_accounts (and are different). +# Without this filter, docker-mailserver emits two passwd-file userdb lines +# for the source address — its own mailbox home plus the alias target's home +# — and Dovecot logs 'exists more than once' on every auth lookup. Aliases +# that forward to external addresses (gmail etc.) or to self are safe. +locals { + _account_set = keys(var.mailserver_accounts) + _virtual_lines = split("\n", format("%s%s", var.postfix_account_aliases, file("${path.module}/extra/aliases.txt"))) + postfix_virtual = join("\n", [ + for line in local._virtual_lines : line + if !( + length(split(" ", line)) == 2 && + contains(local._account_set, split(" ", line)[0]) && + contains(local._account_set, split(" ", line)[1]) && + split(" ", line)[0] != split(" ", line)[1] + ) + ]) +} + resource "kubernetes_namespace" "mailserver" { metadata { name = "mailserver" @@ -97,7 +117,7 @@ resource "kubernetes_config_map" "mailserver_config" { # Actual mail settings "postfix-accounts.cf" = join("\n", [for user, pass in var.mailserver_accounts : "${user}|${bcrypt(pass, 6)}"]) "postfix-main.cf" = var.postfix_cf - "postfix-virtual.cf" = format("%s%s", var.postfix_account_aliases, file("${path.module}/extra/aliases.txt")) + "postfix-virtual.cf" = local.postfix_virtual KeyTable = "mail._domainkey.viktorbarzin.me viktorbarzin.me:mail:/etc/opendkim/keys/viktorbarzin.me-mail.key\n" SigningTable = "*@viktorbarzin.me mail._domainkey.viktorbarzin.me\n"