From 8ea2dea84cfcc54c413234240705658b1482c20e Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 18 Apr 2026 23:56:25 +0000 Subject: [PATCH] [mailserver] Authentik-gate Roundcube webmail ingress [ci skip] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context mail.viktorbarzin.me exposed the Roundcube login page directly: requests hit Traefik → CrowdSec + anti-AI middleware → Roundcube. The `ingress_factory` call in `roundcubemail.tf` omitted `protected = true`, so the Authentik ForwardAuth middleware was never wired up. Project rule (`infra/.claude/CLAUDE.md`): ingresses should be `protected = true` unless there is a specific reason to leave them open. Credentialed surfaces (login pages) have no reason to skip the OIDC gate — CrowdSec alone is a behavioural signal, not an identity gate. Trade-off accepted by Viktor on 2026-04-18: webmail now requires two logins (Authentik SSO, then Roundcube IMAP auth against dovecot). This is tolerable for a low-volume personal webmail; mail clients (Thunderbird, phone Mail) bypass the webmail entirely and speak IMAPS/SMTP directly against `mail.viktorbarzin.me` on the MetalLB service IP (10.0.20.202), which is a separate path and MUST stay open. ## This change Single-line flip: `protected = true` added to the `ingress_factory` call in `stacks/mailserver/modules/mailserver/roundcubemail.tf`. The factory (`modules/kubernetes/ingress_factory/main.tf`) responds to the flag by: 1. Appending `traefik-authentik-forward-auth@kubernetescrd` to the ingress `router.middlewares` annotation — Traefik then hands each request to the Authentik outpost before forwarding to Roundcube. 2. Flipping `effective_anti_ai` from true → false (logic: `anti_ai_scraping != null ? … : !var.protected`), which removes the two anti-AI middlewares. Rationale in the factory: a login-gated resource is already invisible to unauthenticated scrapers, so the robots/noai middleware chain is redundant. Request path before vs after: Before: Client → Traefik → [retry, error-pages, rate-limit, csp, crowdsec, ai-bot-block, anti-ai-headers] → Roundcube (200 on /) After: Client → Traefik → [retry, error-pages, rate-limit, csp, crowdsec, authentik-forward-auth] → if unauth: 302 to authentik.viktorbarzin.me → if auth: Roundcube (login form) ## What is NOT in this change - The `mailserver` Service (MetalLB IP 10.0.20.202) is untouched. IMAPS (993), SMTPS (465), SMTP-Submission (587) continue to bypass Traefik entirely and speak directly to dovecot/postfix. Mail clients are unaffected. - Pre-existing drift on `kubernetes_deployment.mailserver` (volume_mount ordering) and `kubernetes_service.mailserver` (stale metallb annotation) is left alone — out of scope per bd-bmh. Apply was scoped with `-target=` to the ingress resource only. - No Authentik app/provider Terraform was touched — the `mail.*` ingress is already covered by the existing wildcard Authentik proxy outpost on `*.viktorbarzin.me` (standard pattern). ## Test Plan ### Automated Baseline (before apply): $ curl -sI https://mail.viktorbarzin.me/ | head -2 HTTP/2 200 alt-svc: h3=":443"; ma=2592000 $ openssl s_client -connect mail.viktorbarzin.me:993 < /dev/null 2>&1 \ | grep -E 'CONNECTED|subject=' CONNECTED(00000003) subject=CN = viktorbarzin.me After apply: $ curl -sI https://mail.viktorbarzin.me/ | head -3 HTTP/2 302 alt-svc: h3=":443"; ma=2592000 location: https://authentik.viktorbarzin.me/application/o/authorize/?client_id=… $ openssl s_client -connect mail.viktorbarzin.me:993 < /dev/null 2>&1 \ | grep -E 'CONNECTED|subject=' CONNECTED(00000003) subject=CN = viktorbarzin.me Middleware annotation on the ingress: $ kubectl get ingress -n mailserver mail \ -o jsonpath='{.metadata.annotations.traefik\.ingress\.kubernetes\.io/router\.middlewares}' traefik-retry@kubernetescrd,traefik-error-pages@kubernetescrd, traefik-rate-limit@kubernetescrd,traefik-csp-headers@kubernetescrd, traefik-crowdsec@kubernetescrd,traefik-authentik-forward-auth@kubernetescrd Terraform apply (targeted): $ scripts/tg apply --non-interactive \ -target=module.mailserver.module.ingress.kubernetes_ingress_v1.proxied-ingress … Apply complete! Resources: 0 added, 1 changed, 0 destroyed. ### Manual Verification 1. In a private browser window, navigate to https://mail.viktorbarzin.me/ 2. Expected: redirected to Authentik SSO login (not Roundcube) 3. Authenticate with Authentik credentials 4. Expected: redirected back and shown the Roundcube IMAP login form 5. Enter IMAP credentials (same as before the change) 6. Expected: Roundcube inbox loads normally 7. Separately, verify a mail client (Thunderbird, phone Mail) still connects to IMAPS on mail.viktorbarzin.me:993 and SMTP on :587 without any Authentik prompt — that path hits MetalLB 10.0.20.202 directly. ## Reproduce locally 1. cd infra/stacks/mailserver 2. vault login -method=oidc 3. scripts/tg plan Expected: 0 to add, 3 to change, 0 to destroy. Relevant change is the `router.middlewares` annotation on `module.ingress.kubernetes_ingress_v1.proxied-ingress` swapping the two anti-AI middlewares for `traefik-authentik-forward-auth`. The other 2 changes are pre-existing drift (volume_mounts, metallb annotation) and are out of scope. 4. scripts/tg apply --non-interactive \ -target=module.mailserver.module.ingress.kubernetes_ingress_v1.proxied-ingress 5. curl -sI https://mail.viktorbarzin.me/ — expect HTTP/2 302 to authentik.viktorbarzin.me Closes: code-bmh Co-Authored-By: Claude Opus 4.7 (1M context) --- stacks/mailserver/modules/mailserver/roundcubemail.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/stacks/mailserver/modules/mailserver/roundcubemail.tf b/stacks/mailserver/modules/mailserver/roundcubemail.tf index 83fc1ca8..d1e4c623 100644 --- a/stacks/mailserver/modules/mailserver/roundcubemail.tf +++ b/stacks/mailserver/modules/mailserver/roundcubemail.tf @@ -267,6 +267,7 @@ module "ingress" { name = "mail" service_name = "roundcubemail" tls_secret_name = var.tls_secret_name + protected = true extra_annotations = { "gethomepage.dev/enabled" = "true" "gethomepage.dev/name" = "Roundcube Mail"