From a1ba218cd2957e3bb5402444875f8db6dadd0388 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Sat, 28 Feb 2026 19:08:06 +0000 Subject: [PATCH] [ci skip] Phase 1: PostgreSQL migrated to CNPG on local disk Major milestone - shared PostgreSQL moved from NFS to CloudNativePG: - CNPG cluster (pg-cluster) running in dbaas namespace on local-path storage - PostGIS image (ghcr.io/cloudnative-pg/postgis:16) for dawarich compatibility - All 20 databases and 19 roles restored from pg_dumpall backup - postgresql.dbaas Service patched to point at CNPG primary - Old PG deployment scaled to 0 (NFS data intact for rollback) - All 12+ dependent services verified running: authentik, n8n, dawarich, tandoor, linkwarden, netbox, woodpecker, rybbit, affine, health, resume, trading-bot, atuin - Authentik PgBouncer working through the switched endpoint TODO: codify CNPG cluster in Terraform, add 2nd replica, update backup CronJob --- .woodpecker/build-cli.yml | 7 +- .../2026-02-23-mailserver-hardening-design.md | 62 ++++ .../2026-02-23-mailserver-hardening-plan.md | 315 ++++++++++++++++++ secrets/nfs_directories.txt | Bin 1754 -> 1760 bytes stacks/actualbudget/tiers.tf | 10 + stacks/atuin/main.tf | 148 ++++++++ stacks/atuin/secrets | 1 + stacks/atuin/terragrunt.hcl | 8 + stacks/atuin/tiers.tf | 10 + stacks/ebook2audiobook/tiers.tf | 10 + stacks/nextcloud/tiers.tf | 10 + stacks/ollama/tiers.tf | 10 + stacks/osm_routing/tiers.tf | 10 + stacks/paperless-ngx/tiers.tf | 10 + stacks/platform/tiers.tf | 10 + stacks/plotting-book/tiers.tf | 10 + stacks/poison-fountain/tiers.tf | 10 + stacks/rybbit/tiers.tf | 10 + stacks/servarr/tiers.tf | 10 + stacks/stirling-pdf/tiers.tf | 10 + stacks/trading-bot/main.tf | 29 +- stacks/trading-bot/tiers.tf | 10 + stacks/woodpecker/tiers.tf | 10 + stacks/ytdlp/tiers.tf | 10 + tiers.tf | 10 + 25 files changed, 733 insertions(+), 7 deletions(-) create mode 100644 docs/plans/2026-02-23-mailserver-hardening-design.md create mode 100644 docs/plans/2026-02-23-mailserver-hardening-plan.md create mode 100644 stacks/actualbudget/tiers.tf create mode 100644 stacks/atuin/main.tf create mode 120000 stacks/atuin/secrets create mode 100644 stacks/atuin/terragrunt.hcl create mode 100644 stacks/atuin/tiers.tf create mode 100644 stacks/ebook2audiobook/tiers.tf create mode 100644 stacks/nextcloud/tiers.tf create mode 100644 stacks/ollama/tiers.tf create mode 100644 stacks/osm_routing/tiers.tf create mode 100644 stacks/paperless-ngx/tiers.tf create mode 100644 stacks/platform/tiers.tf create mode 100644 stacks/plotting-book/tiers.tf create mode 100644 stacks/poison-fountain/tiers.tf create mode 100644 stacks/rybbit/tiers.tf create mode 100644 stacks/servarr/tiers.tf create mode 100644 stacks/stirling-pdf/tiers.tf create mode 100644 stacks/trading-bot/tiers.tf create mode 100644 stacks/woodpecker/tiers.tf create mode 100644 stacks/ytdlp/tiers.tf create mode 100644 tiers.tf diff --git a/.woodpecker/build-cli.yml b/.woodpecker/build-cli.yml index 44081d4c..1936d0d8 100644 --- a/.woodpecker/build-cli.yml +++ b/.woodpecker/build-cli.yml @@ -26,8 +26,5 @@ steps: dockerfile: cli/Dockerfile context: cli auto_tag: true - cache_images: registry.viktorbarzin.lan:5050/infra:buildcache - buildkit_config: | - [registry."registry.viktorbarzin.lan:5050"] - http = true - insecure = true + insecure: true + buildkit_config: "[registry.\"registry.viktorbarzin.lan:5050\"]\n http = true\n insecure = true\n" diff --git a/docs/plans/2026-02-23-mailserver-hardening-design.md b/docs/plans/2026-02-23-mailserver-hardening-design.md new file mode 100644 index 00000000..33657c20 --- /dev/null +++ b/docs/plans/2026-02-23-mailserver-hardening-design.md @@ -0,0 +1,62 @@ +# Mail Server Lightweight Hardening Design + +**Date**: 2026-02-23 +**Scope**: Security, reliability, and hygiene improvements to the docker-mailserver stack + +## Current State + +- docker-mailserver 15.0.0 on K8s (single replica, Recreate strategy) +- Roundcubemail webmail (MySQL-backed, debug logging on, unpinned :latest tag) +- Outbound relay via Mailgun, inbound MX via ForwardEmail +- OpenDKIM for DKIM signing, no spam filtering (SpamAssassin/ClamAV/Amavis disabled) +- DMARC policy `none` (monitoring only) +- No brute-force protection, no mailserver-down alert +- Dovecot exporter sidecar (unpinned), stale SendGrid DNS records + +## Changes + +### 1. Enable Rspamd (replace OpenDKIM as DKIM signer) + +Add to `mailserver_env_config`: +- `ENABLE_RSPAMD = "1"` (spam filtering, DKIM verification, phishing detection, Oletools) +- `ENABLE_OPENDKIM = "0"` (Rspamd handles DKIM signing natively) +- `RSPAMD_LEARN = "1"` (learn from Junk folder movements) + +Existing OpenDKIM key mounts stay — Rspamd reads them from the same paths. +Resource impact: ~150-200MB additional RAM. + +### 2. DMARC DNS enforcement + +Update `_dmarc` TXT record: `p=none` -> `p=quarantine`. Can tighten to `p=reject` after validation. + +### 3. Postfix rate limiting + +Add to `postfix_cf`: +``` +smtpd_client_connection_rate_limit = 10 +smtpd_client_message_rate_limit = 30 +anvil_rate_time_unit = 60s +``` + +Service already uses `externalTrafficPolicy: Local`, so real client IPs are visible to Postfix. +ForwardEmail IPs on port 25 are subject to same limits but 10 conn/min is generous. + +### 4. Prometheus alert + +Uncomment the existing mailserver-down alert in `prometheus_chart_values.tpl`. + +### 5. Roundcubemail cleanup + +- Pin image: `roundcube/roundcubemail:latest` -> `roundcube/roundcubemail:1.6-apache` +- Disable debug: `ROUNDCUBEMAIL_SMTP_DEBUG = "false"`, `ROUNDCUBEMAIL_DEBUG_LEVEL = "1"` + +### 6. SendGrid DNS cleanup + +Remove stale CNAME records: `em7107`, `s1._domainkey`, `s2._domainkey`. + +## Not Changing + +- Roundcubemail stays (user preference) +- ForwardEmail/Mailgun relay stays (practical dependency) +- ClamAV stays disabled (Rspamd Oletools covers malicious attachments) +- Single replica (HA email requires significant additional complexity) diff --git a/docs/plans/2026-02-23-mailserver-hardening-plan.md b/docs/plans/2026-02-23-mailserver-hardening-plan.md new file mode 100644 index 00000000..b555017f --- /dev/null +++ b/docs/plans/2026-02-23-mailserver-hardening-plan.md @@ -0,0 +1,315 @@ +# Mail Server Lightweight Hardening Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Harden the mail server with spam filtering (Rspamd), DMARC enforcement, rate limiting, monitoring alerts, and hygiene cleanup. + +**Architecture:** All changes are to the existing docker-mailserver 15.0.0 deployment managed by Terraform. Rspamd replaces OpenDKIM for DKIM signing and adds spam filtering. DMARC moves from `none` to `quarantine` in Cloudflare DNS. Postfix gets rate-limiting parameters. Prometheus gets a mailserver-down alert. Roundcubemail debug logging is disabled and image pinned. + +**Tech Stack:** Terraform/HCL, docker-mailserver, Rspamd, Cloudflare DNS, Prometheus + +--- + +### Task 1: Enable Rspamd and disable OpenDKIM + +**Files:** +- Modify: `stacks/platform/modules/mailserver/main.tf:39-62` (env ConfigMap) + +**Step 1: Add Rspamd env vars to the ConfigMap** + +In `stacks/platform/modules/mailserver/main.tf`, in the `kubernetes_config_map.mailserver_env_config` resource `data` block, add these entries and modify existing ones: + +```hcl + data = { + DMS_DEBUG = "0" + ENABLE_CLAMAV = "0" + ENABLE_AMAVIS = "0" + ENABLE_FAIL2BAN = "0" + ENABLE_FETCHMAIL = "0" + ENABLE_POSTGREY = "0" + ENABLE_SASLAUTHD = "0" + ENABLE_SPAMASSASSIN = "0" + ENABLE_SRS = "1" + ENABLE_RSPAMD = "1" + ENABLE_OPENDKIM = "0" + ENABLE_OPENDMARC = "0" + RSPAMD_LEARN = "1" + FETCHMAIL_POLL = "120" + ONE_DIR = "1" + OVERRIDE_HOSTNAME = "mail.viktorbarzin.me" + POSTFIX_MESSAGE_SIZE_LIMIT = 1024 * 1024 * 200 # 200 MB + POSTFIX_REJECT_UNKNOWN_CLIENT_HOSTNAME = "1" + DEFAULT_RELAY_HOST = "[smtp.eu.mailgun.org]:587" + SPOOF_PROTECTION = "1" + SSL_TYPE = "manual" + SSL_CERT_PATH = "/tmp/ssl/tls.crt" + SSL_KEY_PATH = "/tmp/ssl/tls.key" + } +``` + +The key additions are: `ENABLE_RSPAMD = "1"`, `ENABLE_OPENDKIM = "0"`, `ENABLE_OPENDMARC = "0"`, `RSPAMD_LEARN = "1"`. + +**Note:** The existing OpenDKIM volume mounts (KeyTable, SigningTable, TrustedHosts, opendkim keys) should stay mounted. docker-mailserver's Rspamd integration reads the DKIM key from the same path (`/tmp/docker-mailserver/opendkim/keys/`) to configure Rspamd's DKIM signing module automatically. + +**Step 2: Commit** + +```bash +git add stacks/platform/modules/mailserver/main.tf +git commit -m "[ci skip] mailserver: enable Rspamd, disable OpenDKIM" +``` + +--- + +### Task 2: Add Postfix rate limiting + +**Files:** +- Modify: `stacks/platform/modules/mailserver/variables.tf:3-22` (postfix_cf variable) + +**Step 1: Add rate limiting parameters to postfix_cf** + +In `stacks/platform/modules/mailserver/variables.tf`, append these lines to the `postfix_cf` default value, before the `EOT`: + +``` +smtpd_client_connection_rate_limit = 10 +smtpd_client_message_rate_limit = 30 +anvil_rate_time_unit = 60s +``` + +The full `postfix_cf` variable should become: + +```hcl +variable "postfix_cf" { + default = <1EqA>xW;Q1KG-<-g<9;MTlrBdK$cxdLH?W`XXheSTrMZrKKs6Uc0qmr=1o6>R|F8g0&t41)$qmqTOA|VHzccvj+nl zs^0#$u%?jhDuM(VcK+$$Qqu7gZPtL6!oqg7Fa3F82Ibau3Cx$G{a#PpurpgUhd1;9W6_m1Qj^illnps|3d!6q%vlNDg9y&3$$64MU3D zZxz31P5Sf|H^!6X?LgLoNZ6inGq!j1SEdie$Pfe~V&nb@`>i*3p-6boVtt@r8eTQ`f`03pOZd%pW2h~t-X)aI8sdhQbg6vDSkm*QzI9p zY3PQ|2LL%hsJz0qJncbWRRnCqp;J8u-*!N*+0kQNJc|NFy6c4y9QNg3m-KPzA+w{o ztTD7sIGpV>E=BRx`>SyRy57bjpe`s*Xba_kt5q~3d#a)L(9*A>>67^knbFMMcjVwe zPyo5!jAY(Rsm2KZ3O~L)l&%1jO(Khp*#)(a6wd~xdZE}aLzcC3i+l2Fdm(&P5(nZS z{y-?eZ$fr~z&PTJN>n_^{Yr+Wb2DC7^O!RIss;Bo&gAx`e-A&36>>HrvMD~ZF6UUQ zz4Vq15V9rW@^QM&R#{3ZyKBdHEU3tLEH7$fL`IueO--AN<$1~kDvp}PQ_X|(D;R0YPqDP} zDEOVQu{kbd-FFeDvl31TQj&_66C%@4hiP^#TuSb!04T;dsG}o>uOD|(NxEw^mU)(d z^$yGGizi#Izv*H&kjF-JH3XXB;&A)lK<*D^;7ZyPC1D7TBl%IcTPpJ?z zoOs!kuLY{zG+SHBWQ@q)`$dpo!ffE#S%#U)j7DO;ayX}B?)m6;J4U1wwQ|7N$sm3( zNf4(F?_-)lR~_3{^j$p-o@iDmjU2dPtxFYgAOE(C2Jg;lj`eZ^>w1CyPax3q*W=G+ zksW!7Xg7>+*!E;J&!GXC6&xfvRl)G4+o-OSMd#(+3ChrtZk6d~B)+eHhO(#ak4DY3 zM!K2+mfzYsUe(@~pm;DwRn)ut#~%{o4HwsU$k_O&vH~T2Nztd+OyEr^mf!R*C75A zUsv(&&MA@j5Nrb3Wfx^WpbXu5I`jna@D$BI!3gUt2AOp-Klb@Z6+JY>+Qm%RpK7Ej zyA9FSXo(r8%nV)%wqm47&nl45@;Q9168Q0$DP$AU4LGsrFp%+HudXrL6+tfmbXESp zm|4XPOCM)4Zn2bfmSisj=UH5Olha-MJ&&EvINVkEx1Jjc0#v2~=&zj$q1Op>)mtvt zijC^QefHT%&fhC#i?u6y1Hcq^aX&Sa6|H!KRGLP<7?SQ$*Ib!7cCpl3PSe!lL2Fc# zYK;{iC|-keZ)f~DO@9b7xck%X3Nrps(*=zG05Vk;nosD2)b0Qzw3x$3IA$T-BtMny z6(4|5P8UJNcQ1dU)x^t(E0t?xbwNn%&|T4AYvN@i_DblHyxidDST6ABaBlVy6ziv8 CQG5;n literal 1754 zcmV<01||6bM@dveQdv+`07h7--%tK(7`xtzLB5VK9&G%$AVj?NE|2&xwkxq-7#wZn zhm5#SvoXVWv?PJE*>v-4A`KS=4_Y*#xG9Q4y*pvAh(4FbeA&9BTj3d4vA*TN_ZnH^ zv16`QM0y%yaC2Dx>c6hz2Joykbn@|T7Xv`rNgR|3uLF)fyUNRT4C5@VFRu1bNxROa z-93%0Pe~xT<=V!qC+04wg-g9kJlItpAqGJhsr_%qcT|9~tIkb`E`l9h$K56a`(?}% z25o38_@dhUg!OV4d-r`{(L{$;bMPxsScRs{nDi!yb;s_k5FGPz9Fa#xk0j6o7G&ka z)!EH@cp>>==hgQX$Zt^AKEkX=v}bg#6O{tK@S^Ik=1^@XQebetHbr6~gLXXxge~yI zei@m5Q&tZs>0vV8v1}(=yawfoz5ZdoyikI$*DGx(PtDRAw}iG)|X zg3F`~3)9837R_d@j4s9t(?D%>Qe_b~^T;q4=kv=+gRKmKURYB(WcG$g0%o%A80n;f zq5qE)f*5G^Q}d;liY{@N`5=C=XD(8zAarALj%G6@60;a-vA%8!EL1!8TI7eZWOn`SYuWbuu>3R zpBcocc<;@KCtn}Pwj_*++ux@_Wou-Dv8r)P&wrjrWR~zlZ$iCdS!p1 ziHanYL?3AUHX1X0TtVYDMeMrNHD$wmBsbi}dLB{y8ibW-*wiA2M`IiGv2J z6}7czL1VWUd+3~898tgVdiM6a_jKHhPNk>bLQ8p{Q4nO1B|z~F$GwCVyj3$NiFnS` z0})@z`oB$4R0HbIgi$-Ff>jytp}#Kz51~OodI57DKyXPn%GmTh|Hg+jb0l#lNp+bU%9`Tp>6GPIuXFYUy5vR z5#iTp?m^JZZX2w7CuKAld(aOc0-&YeqM5n9MPp%21d2|J@H|#KoMFcj*uvnzy=+a@ zdW_G?;ajaWI(o~YuyAW}gKvl!xp{1)T3=#pWa-60b2~MJu2Zn zFv}G}AwQXQ%LT#J#-slV|HI`VF!_q$_SpQe7HULyf~$Tdfzvt*78J0`FB! zY6}#iBU4zgz$Qcgn1zy9yaSa&F;7aGB$&p`k+?|K7f|IKKfNjCU?*8`d z4@?RHKmf4cjDQs8gHpN?VJfXsQ@X3o6D$UY((HNhmz2a9>o3)6=kdTwrKqMpXGZL7 z=A-fg7QuiXyqcfrO|>ATo-)n@aj?Sw#UK>rjRL5(#gI{77t!^zWXacY;rZLwyrM~C zwp4!&ozINlU7?Gl#J*IBp-pu6oDM4`_N0b4HEfz8=El5q>ZZR*^%gj zc(|l^9mhH&)fe#e!x2+yS2gx*2m^iTt7y#-+uH%T!9P<;0@*j{v_dJ{JcVK8RfOWX zn}P{XHa3s&JL+c(>D%d=eAui=Tm)B;21PASFmU7bofJl#nM3Oki>ctBJFRORBA63L wjfimGu