feat: add SMTP submission listener and enhance stalwart configuration #425

Merged
unkinben merged 1 commits from benvin/stalwart_submission into develop 2025-11-09 18:48:06 +11:00
7 changed files with 112 additions and 9 deletions

View File

@ -13,8 +13,7 @@ profiles::haproxy::dns::vrrp_cnames:
- dashboard.ceph.unkin.net - dashboard.ceph.unkin.net
- mail-webadmin.main.unkin.net - mail-webadmin.main.unkin.net
- mail-in.main.unkin.net - mail-in.main.unkin.net
- imap.main.unkin.net - mail.main.unkin.net
- imaps.main.unkin.net
- autoconfig.main.unkin.net - autoconfig.main.unkin.net
- autodiscover.main.unkin.net - autodiscover.main.unkin.net
@ -333,7 +332,7 @@ profiles::haproxy::backends:
stick-table: 'type ip size 200k expire 30m' stick-table: 'type ip size 200k expire 30m'
stick: 'on src' stick: 'on src'
tcp-check: tcp-check:
- connect port 143 - connect port 143 send-proxy
- expect string "* OK" - expect string "* OK"
- send "A001 STARTTLS\r\n" - send "A001 STARTTLS\r\n"
- expect rstring "A001 (OK|2.0.0)" - expect rstring "A001 (OK|2.0.0)"
@ -349,7 +348,7 @@ profiles::haproxy::backends:
stick-table: 'type ip size 200k expire 30m' stick-table: 'type ip size 200k expire 30m'
stick: 'on src' stick: 'on src'
tcp-check: tcp-check:
- connect ssl - connect ssl send-proxy
- expect string "* OK" - expect string "* OK"
be_stalwart_smtp: be_stalwart_smtp:
description: Backend for Stalwart SMTP description: Backend for Stalwart SMTP
@ -363,7 +362,21 @@ profiles::haproxy::backends:
stick-table: 'type ip size 200k expire 30m' stick-table: 'type ip size 200k expire 30m'
stick: 'on src' stick: 'on src'
tcp-check: tcp-check:
- connect port 25 - connect port 25 send-proxy
- 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 send-proxy
- expect string "220 " - expect string "220 "
profiles::haproxy::certlist::enabled: true profiles::haproxy::certlist::enabled: true

View File

@ -196,6 +196,17 @@ profiles::haproxy::frontends:
tcp-request: tcp-request:
- inspect-delay 5s - inspect-delay 5s
- content accept if { req_len 0 } - 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: profiles::haproxy::backends:
be_letsencrypt: be_letsencrypt:

View File

@ -8,9 +8,6 @@ hiera_include:
profiles::pki::vault::alt_names: profiles::pki::vault::alt_names:
- mail.main.unkin.net - mail.main.unkin.net
- mail-webadmin.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 - main-in.main.unkin.net
- autoconfig.main.unkin.net - autoconfig.main.unkin.net
- autodiscovery.main.unkin.net - autodiscovery.main.unkin.net
@ -41,6 +38,7 @@ stalwart::s3_region: "%{facts.region}"
stalwart::domains: stalwart::domains:
- 'mail.unkin.net' - 'mail.unkin.net'
stalwart::postfix_relay_host: 'out-mta.main.unkin.net' stalwart::postfix_relay_host: 'out-mta.main.unkin.net'
stalwart::service_hostname: 'mail.main.unkin.net'
stalwart::manage_dns_records: false stalwart::manage_dns_records: false
## With load balancer: ## With load balancer:

View File

@ -29,6 +29,7 @@ class stalwart::config {
content => epp('stalwart/config.toml.epp', { content => epp('stalwart/config.toml.epp', {
'cluster_size' => $stalwart::cluster_size, 'cluster_size' => $stalwart::cluster_size,
'other_cluster_members' => $stalwart::other_cluster_members, 'other_cluster_members' => $stalwart::other_cluster_members,
'haproxy_ips' => $stalwart::haproxy_ips,
'effective_node_id' => $stalwart::effective_node_id, 'effective_node_id' => $stalwart::effective_node_id,
'bind_address' => $stalwart::bind_address, 'bind_address' => $stalwart::bind_address,
'advertise_address' => $stalwart::advertise_address, 'advertise_address' => $stalwart::advertise_address,
@ -49,10 +50,12 @@ class stalwart::config {
'enable_imap' => $stalwart::enable_imap, 'enable_imap' => $stalwart::enable_imap,
'enable_imap_tls' => $stalwart::enable_imap_tls, 'enable_imap_tls' => $stalwart::enable_imap_tls,
'enable_http' => $stalwart::enable_http, 'enable_http' => $stalwart::enable_http,
'enable_smtp_submission' => $stalwart::enable_smtp_submission,
'data_dir' => $stalwart::data_dir, 'data_dir' => $stalwart::data_dir,
'tls_cert' => $stalwart::tls_cert, 'tls_cert' => $stalwart::tls_cert,
'tls_key' => $stalwart::tls_key, 'tls_key' => $stalwart::tls_key,
'log_level' => $stalwart::log_level, 'log_level' => $stalwart::log_level,
'service_hostname' => $stalwart::service_hostname,
'fallback_admin_user' => $stalwart::fallback_admin_user, 'fallback_admin_user' => $stalwart::fallback_admin_user,
'fallback_admin_password' => $stalwart::fallback_admin_password, 'fallback_admin_password' => $stalwart::fallback_admin_password,
'webadmin_unpack_path' => $stalwart::webadmin_unpack_path, 'webadmin_unpack_path' => $stalwart::webadmin_unpack_path,

View File

@ -86,6 +86,15 @@
# @param enable_smtp_relay # @param enable_smtp_relay
# Enable SMTP for postfix relay communication # 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 # @param package_ensure
# Package version to install # Package version to install
# #
@ -133,6 +142,9 @@ class stalwart (
Boolean $enable_imap_tls = true, Boolean $enable_imap_tls = true,
Boolean $enable_http = true, Boolean $enable_http = true,
Boolean $enable_smtp_relay = 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', String $package_ensure = 'present',
Stdlib::Absolutepath $config_dir = '/opt/stalwart/etc', Stdlib::Absolutepath $config_dir = '/opt/stalwart/etc',
Stdlib::Absolutepath $data_dir = '/var/lib/stalwart', Stdlib::Absolutepath $data_dir = '/var/lib/stalwart',
@ -166,6 +178,14 @@ class stalwart (
$other_cluster_members = $sorted_cluster_members.filter |$member| { $member != $my_fqdn } $other_cluster_members = $sorted_cluster_members.filter |$member| { $member != $my_fqdn }
$cluster_size = length($sorted_cluster_members) $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 => sort($haproxy_members_raw),
}
# Extract last 4 digits from hostname (e.g., ausyd1nxvm1234 -> 1234) # Extract last 4 digits from hostname (e.g., ausyd1nxvm1234 -> 1234)
if $hostname =~ /^.*(\d{4})$/ { if $hostname =~ /^.*(\d{4})$/ {
$hostname_digits = $1 $hostname_digits = $1

View File

@ -2,7 +2,7 @@
# Generated by Puppet - DO NOT EDIT MANUALLY # Generated by Puppet - DO NOT EDIT MANUALLY
[server] [server]
hostname = "<%= $node_facts['networking']['fqdn'] %>" hostname = "<%= $service_hostname %>"
greeting = "Stalwart ESMTP" greeting = "Stalwart ESMTP"
[server.listener."smtp-relay"] [server.listener."smtp-relay"]
@ -10,10 +10,33 @@ bind = ["<%= $bind_address %>:25"]
protocol = "smtp" protocol = "smtp"
greeting = "Stalwart SMTP Relay" greeting = "Stalwart SMTP Relay"
<% if !$haproxy_ips.empty { -%>
[server.listener."smtp-relay".proxy]
trusted-networks = ["127.0.0.0/8", "::1"<% $haproxy_ips.each |$ip| { %>, "<%= $ip %>"<% } %>]
<% } -%>
<% if $enable_smtp_submission { -%>
[server.listener."submission"]
bind = ["<%= $bind_address %>:587"]
protocol = "smtp"
greeting = "Stalwart SMTP Submission"
tls.require = true
<% if !$haproxy_ips.empty { -%>
[server.listener."submission".proxy]
trusted-networks = ["127.0.0.0/8", "::1"<% $haproxy_ips.each |$ip| { %>, "<%= $ip %>"<% } %>]
<% } -%>
<% } -%>
<% if $enable_imap { -%> <% if $enable_imap { -%>
[server.listener."imap"] [server.listener."imap"]
bind = ["<%= $bind_address %>:143"] bind = ["<%= $bind_address %>:143"]
protocol = "imap" protocol = "imap"
<% if !$haproxy_ips.empty { -%>
[server.listener."imap".proxy]
trusted-networks = ["127.0.0.0/8", "::1"<% $haproxy_ips.each |$ip| { %>, "<%= $ip %>"<% } %>]
<% } -%>
<% } -%> <% } -%>
<% if $enable_imap_tls { -%> <% if $enable_imap_tls { -%>
@ -21,6 +44,11 @@ protocol = "imap"
bind = ["<%= $bind_address %>:993"] bind = ["<%= $bind_address %>:993"]
protocol = "imap" protocol = "imap"
tls.implicit = true tls.implicit = true
<% if !$haproxy_ips.empty { -%>
[server.listener."imaps".proxy]
trusted-networks = ["127.0.0.0/8", "::1"<% $haproxy_ips.each |$ip| { %>, "<%= $ip %>"<% } %>]
<% } -%>
<% } -%> <% } -%>
<% if $enable_http { -%> <% if $enable_http { -%>
@ -28,6 +56,11 @@ tls.implicit = true
bind = ["<%= $bind_address %>:443"] bind = ["<%= $bind_address %>:443"]
protocol = "http" protocol = "http"
tls.implicit = true tls.implicit = true
<% if !$haproxy_ips.empty { -%>
[server.listener."https".proxy]
trusted-networks = ["127.0.0.0/8", "::1"<% $haproxy_ips.each |$ip| { %>, "<%= $ip %>"<% } %>]
<% } -%>
<% } -%> <% } -%>
[server.tls] [server.tls]
@ -35,6 +68,7 @@ enable = true
implicit = false implicit = false
certificate = "default" certificate = "default"
[webadmin] [webadmin]
path = "<%= $webadmin_unpack_path %>" path = "<%= $webadmin_unpack_path %>"
auto-update = <%= $webadmin_auto_update %> auto-update = <%= $webadmin_auto_update %>
@ -167,6 +201,12 @@ directory = "internal"
[imap.protocol] [imap.protocol]
max-requests = 64 max-requests = 64
# Inbound rate limiting
[[queue.limiter.inbound]]
key = ["remote_ip"]
rate = "500/1s"
enable = true
# SMTP configuration for postfix relay # SMTP configuration for postfix relay
[session.data] [session.data]
pipe.command = "sendmail" pipe.command = "sendmail"
@ -212,6 +252,7 @@ max-message-size = 52428800
[certificate."default"] [certificate."default"]
cert = "%{file:<%= $tls_cert %>}%" cert = "%{file:<%= $tls_cert %>}%"
private-key = "%{file:<%= $tls_key %>}%" private-key = "%{file:<%= $tls_key %>}%"
default = true
# Logging configuration # Logging configuration
[tracer] [tracer]

View File

@ -15,6 +15,7 @@ class profiles::stalwart::haproxy (
'inter 2s', 'inter 2s',
'rise 3', 'rise 3',
'fall 2', 'fall 2',
'send-proxy-v2',
] ]
} }
@ -27,6 +28,7 @@ class profiles::stalwart::haproxy (
'inter 3s', 'inter 3s',
'rise 2', 'rise 2',
'fall 3', 'fall 3',
'send-proxy-v2',
] ]
} }
@ -41,6 +43,7 @@ class profiles::stalwart::haproxy (
'inter 3s', 'inter 3s',
'rise 2', 'rise 2',
'fall 3', 'fall 3',
'send-proxy-v2',
] ]
} }
@ -53,6 +56,20 @@ class profiles::stalwart::haproxy (
'inter 3s', 'inter 3s',
'rise 2', 'rise 2',
'fall 3', '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',
] ]
} }