From 5f9f6a470310b7ab9fd3e0050c9e97dcb9984d1a Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Sun, 9 Nov 2025 14:07:49 +1100 Subject: [PATCH] feat: add SMTP submission listener and enhance stalwart configuration - add SMTP submission listener on port 587 with TLS requirement - configure HAProxy frontend/backend for submission with send-proxy-v2 support - add dynamic HAProxy node discovery for proxy trusted networks - use service hostname instead of node FQDN for autoconfig/autodiscover - remove redundant IMAP/IMAPS/SMTP alt-names from TLS certificates - update VRRP CNAME configuration to use mail.main.unkin.net --- .../au/region/syd1/infra/halb/haproxy2.yaml | 17 ++++++++++++++-- hieradata/roles/infra/halb/haproxy2.yaml | 11 ++++++++++ hieradata/roles/infra/mail/backend.yaml | 4 +--- modules/stalwart/manifests/config.pp | 3 +++ modules/stalwart/manifests/init.pp | 20 +++++++++++++++++++ modules/stalwart/templates/config.toml.epp | 15 +++++++++++++- site/profiles/manifests/stalwart/haproxy.pp | 17 ++++++++++++++++ 7 files changed, 81 insertions(+), 6 deletions(-) diff --git a/hieradata/country/au/region/syd1/infra/halb/haproxy2.yaml b/hieradata/country/au/region/syd1/infra/halb/haproxy2.yaml index eaff76b..8f37f95 100644 --- a/hieradata/country/au/region/syd1/infra/halb/haproxy2.yaml +++ b/hieradata/country/au/region/syd1/infra/halb/haproxy2.yaml @@ -13,8 +13,7 @@ profiles::haproxy::dns::vrrp_cnames: - dashboard.ceph.unkin.net - mail-webadmin.main.unkin.net - mail-in.main.unkin.net - - imap.main.unkin.net - - imaps.main.unkin.net + - mail.main.unkin.net - autoconfig.main.unkin.net - autodiscover.main.unkin.net @@ -365,6 +364,20 @@ profiles::haproxy::backends: tcp-check: - connect port 25 - expect string "220 " + be_stalwart_submission: + description: Backend for Stalwart SMTP Submission + collect_exported: false + options: + mode: tcp + balance: roundrobin + option: + - tcp-check + - prefer-last-server + stick-table: 'type ip size 200k expire 30m' + stick: 'on src' + tcp-check: + - connect port 587 + - expect string "220 " profiles::haproxy::certlist::enabled: true profiles::haproxy::certlist::certificates: diff --git a/hieradata/roles/infra/halb/haproxy2.yaml b/hieradata/roles/infra/halb/haproxy2.yaml index f58be10..10d19c6 100644 --- a/hieradata/roles/infra/halb/haproxy2.yaml +++ b/hieradata/roles/infra/halb/haproxy2.yaml @@ -196,6 +196,17 @@ profiles::haproxy::frontends: tcp-request: - inspect-delay 5s - content accept if { req_len 0 } + fe_submission: + description: 'Frontend for Stalwart SMTP Submission' + bind: + 0.0.0.0:587: [] + mode: 'tcp' + options: + log: global + default_backend: be_stalwart_submission + tcp-request: + - inspect-delay 5s + - content accept if { req_len 0 } profiles::haproxy::backends: be_letsencrypt: diff --git a/hieradata/roles/infra/mail/backend.yaml b/hieradata/roles/infra/mail/backend.yaml index ae937b0..c26c200 100644 --- a/hieradata/roles/infra/mail/backend.yaml +++ b/hieradata/roles/infra/mail/backend.yaml @@ -8,9 +8,6 @@ hiera_include: profiles::pki::vault::alt_names: - mail.main.unkin.net - mail-webadmin.main.unkin.net - - imap.main.unkin.net - - imaps.main.unkin.net - - smtp.main.unkin.net - main-in.main.unkin.net - autoconfig.main.unkin.net - autodiscovery.main.unkin.net @@ -41,6 +38,7 @@ stalwart::s3_region: "%{facts.region}" stalwart::domains: - 'mail.unkin.net' stalwart::postfix_relay_host: 'out-mta.main.unkin.net' +stalwart::service_hostname: 'mail.main.unkin.net' stalwart::manage_dns_records: false ## With load balancer: diff --git a/modules/stalwart/manifests/config.pp b/modules/stalwart/manifests/config.pp index 2b43ea5..95aa763 100644 --- a/modules/stalwart/manifests/config.pp +++ b/modules/stalwart/manifests/config.pp @@ -29,6 +29,7 @@ class stalwart::config { content => epp('stalwart/config.toml.epp', { 'cluster_size' => $stalwart::cluster_size, 'other_cluster_members' => $stalwart::other_cluster_members, + 'haproxy_ips' => $stalwart::haproxy_ips, 'effective_node_id' => $stalwart::effective_node_id, 'bind_address' => $stalwart::bind_address, 'advertise_address' => $stalwart::advertise_address, @@ -49,10 +50,12 @@ class stalwart::config { 'enable_imap' => $stalwart::enable_imap, 'enable_imap_tls' => $stalwart::enable_imap_tls, 'enable_http' => $stalwart::enable_http, + 'enable_smtp_submission' => $stalwart::enable_smtp_submission, 'data_dir' => $stalwart::data_dir, 'tls_cert' => $stalwart::tls_cert, 'tls_key' => $stalwart::tls_key, 'log_level' => $stalwart::log_level, + 'service_hostname' => $stalwart::service_hostname, 'fallback_admin_user' => $stalwart::fallback_admin_user, 'fallback_admin_password' => $stalwart::fallback_admin_password, 'webadmin_unpack_path' => $stalwart::webadmin_unpack_path, diff --git a/modules/stalwart/manifests/init.pp b/modules/stalwart/manifests/init.pp index 27b2671..058d533 100644 --- a/modules/stalwart/manifests/init.pp +++ b/modules/stalwart/manifests/init.pp @@ -86,6 +86,15 @@ # @param enable_smtp_relay # Enable SMTP for postfix relay communication # +# @param enable_smtp_submission +# Enable SMTP submission listener on port 587 +# +# @param haproxy_role +# Role name for HAProxy nodes to include in proxy trusted networks +# +# @param service_hostname +# Service hostname used for autoconfig/autodiscover and SMTP greeting +# # @param package_ensure # Package version to install # @@ -133,6 +142,9 @@ class stalwart ( Boolean $enable_imap_tls = true, Boolean $enable_http = true, Boolean $enable_smtp_relay = true, + Boolean $enable_smtp_submission = true, + String $haproxy_role = 'roles::infra::halb::haproxy2', + Stdlib::Fqdn $service_hostname = $facts['networking']['fqdn'], String $package_ensure = 'present', Stdlib::Absolutepath $config_dir = '/opt/stalwart/etc', Stdlib::Absolutepath $data_dir = '/var/lib/stalwart', @@ -166,6 +178,14 @@ class stalwart ( $other_cluster_members = $sorted_cluster_members.filter |$member| { $member != $my_fqdn } $cluster_size = length($sorted_cluster_members) + # Query HAProxy nodes for proxy trusted networks + $haproxy_query = "enc_role='${haproxy_role}' and country='${facts['country']}' and region='${facts['region']}'" + $haproxy_members_raw = query_nodes($haproxy_query, 'networking.ip') + $haproxy_ips = $haproxy_members_raw ? { + undef => [], + default => $haproxy_members_raw, + } + # Extract last 4 digits from hostname (e.g., ausyd1nxvm1234 -> 1234) if $hostname =~ /^.*(\d{4})$/ { $hostname_digits = $1 diff --git a/modules/stalwart/templates/config.toml.epp b/modules/stalwart/templates/config.toml.epp index 83c2c14..d801da2 100644 --- a/modules/stalwart/templates/config.toml.epp +++ b/modules/stalwart/templates/config.toml.epp @@ -2,7 +2,7 @@ # Generated by Puppet - DO NOT EDIT MANUALLY [server] -hostname = "<%= $node_facts['networking']['fqdn'] %>" +hostname = "<%= $service_hostname %>" greeting = "Stalwart ESMTP" [server.listener."smtp-relay"] @@ -10,6 +10,14 @@ bind = ["<%= $bind_address %>:25"] protocol = "smtp" greeting = "Stalwart SMTP Relay" +<% if $enable_smtp_submission { -%> +[server.listener."submission"] +bind = ["<%= $bind_address %>:587"] +protocol = "smtp" +greeting = "Stalwart SMTP Submission" +tls.require = true +<% } -%> + <% if $enable_imap { -%> [server.listener."imap"] bind = ["<%= $bind_address %>:143"] @@ -35,6 +43,11 @@ enable = true implicit = false certificate = "default" +<% if !$haproxy_ips.empty { -%> +[server.proxy] +trusted-networks = ["127.0.0.0/8", "::1"<% $haproxy_ips.each |$ip| { %>, "<%= $ip %>"<% } %>] +<% } -%> + [webadmin] path = "<%= $webadmin_unpack_path %>" auto-update = <%= $webadmin_auto_update %> diff --git a/site/profiles/manifests/stalwart/haproxy.pp b/site/profiles/manifests/stalwart/haproxy.pp index e8375e9..82a3734 100644 --- a/site/profiles/manifests/stalwart/haproxy.pp +++ b/site/profiles/manifests/stalwart/haproxy.pp @@ -15,6 +15,7 @@ class profiles::stalwart::haproxy ( 'inter 2s', 'rise 3', 'fall 2', + 'send-proxy-v2', ] } @@ -27,6 +28,7 @@ class profiles::stalwart::haproxy ( 'inter 3s', 'rise 2', 'fall 3', + 'send-proxy-v2', ] } @@ -41,6 +43,7 @@ class profiles::stalwart::haproxy ( 'inter 3s', 'rise 2', 'fall 3', + 'send-proxy-v2', ] } @@ -53,6 +56,20 @@ class profiles::stalwart::haproxy ( 'inter 3s', 'rise 2', 'fall 3', + 'send-proxy-v2', + ] + } + + # smtp submission + profiles::haproxy::balancemember { "${facts['networking']['fqdn']}_587": + service => 'be_stalwart_submission', + ports => [587], + options => [ + 'check', + 'inter 3s', + 'rise 2', + 'fall 3', + 'send-proxy-v2', ] }