From 4313b7db499497f33590de76c298857987361478 Mon Sep 17 00:00:00 2001 From: unkin-agent Date: Tue, 13 Oct 2026 21:42:38 +1100 Subject: [PATCH] Fix edge postfix gateway config for unkin.net (#552) The mail gateway config was copied from an eFa appliance and predates the edge design: unfiltered/dead DNSBLs, a MailScanner milter, no local filtering, and no edge identity. - filter postscreen DNSBL codes, add mailspike/psbl, drop sorbs/swl; dnswl clients skip greet tests - run local rspamd (deterministic checks, redis bayes, edge.cf rules fetched each puppet run) as a tempfail milter - set a 50MB size limit; drop qmqpd, 3DES and eFa leftovers - relay unkin.net and main.unkin.net to [mail-in.main.unkin.net]:25 with verified TLS and recipient probing - set myhostname and vault cert SANs per node (mta1/mta2.edge.unkin.net) Reviewed-on: https://git.unkin.net/unkin/puppet-prod/pulls/552 Co-authored-by: unkin-agent Co-committed-by: unkin-agent --- .../nodes/aumel1nxvm2132.main.unkin.net.yaml | 4 + .../nodes/ausyd2nxvm2130.main.unkin.net.yaml | 4 + hieradata/os/AlmaLinux/all_releases.yaml | 12 +++ hieradata/roles/infra/mail/gateway.yaml | 80 ++++++++++++++++--- site/profiles/manifests/postfix/gateway.pp | 54 +++++++------ site/profiles/manifests/rspamd/edge.pp | 68 ++++++++++++++++ site/roles/manifests/infra/mail/gateway.pp | 1 + 7 files changed, 187 insertions(+), 36 deletions(-) create mode 100644 hieradata/nodes/aumel1nxvm2132.main.unkin.net.yaml create mode 100644 hieradata/nodes/ausyd2nxvm2130.main.unkin.net.yaml create mode 100644 site/profiles/manifests/rspamd/edge.pp diff --git a/hieradata/nodes/aumel1nxvm2132.main.unkin.net.yaml b/hieradata/nodes/aumel1nxvm2132.main.unkin.net.yaml new file mode 100644 index 0000000..de887ee --- /dev/null +++ b/hieradata/nodes/aumel1nxvm2132.main.unkin.net.yaml @@ -0,0 +1,4 @@ +--- +profiles::postfix::gateway::myhostname: 'mta2.edge.unkin.net' +profiles::pki::vault::alt_names: + - mta2.edge.unkin.net diff --git a/hieradata/nodes/ausyd2nxvm2130.main.unkin.net.yaml b/hieradata/nodes/ausyd2nxvm2130.main.unkin.net.yaml new file mode 100644 index 0000000..9203cff --- /dev/null +++ b/hieradata/nodes/ausyd2nxvm2130.main.unkin.net.yaml @@ -0,0 +1,4 @@ +--- +profiles::postfix::gateway::myhostname: 'mta1.edge.unkin.net' +profiles::pki::vault::alt_names: + - mta1.edge.unkin.net diff --git a/hieradata/os/AlmaLinux/all_releases.yaml b/hieradata/os/AlmaLinux/all_releases.yaml index 3ff5da7..c09e0ee 100644 --- a/hieradata/os/AlmaLinux/all_releases.yaml +++ b/hieradata/os/AlmaLinux/all_releases.yaml @@ -147,6 +147,18 @@ profiles::yum::global::repos: baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/postgresql/common/redhat/rhel-%{facts.os.release.major}-%{facts.os.architecture} gpgkey: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/postgresql/keys/PGDG-RPM-GPG-KEY-RHEL + # Rspamd repositories + rspamd: + ensure: absent + name: rspamd + descr: rspamd repository + target: /etc/yum.repos.d/rspamd.repo + baseurl: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/rspamd/rpm-stable/centos-%{facts.os.release.major}/x86_64 + gpgkey: https://artifactapi.k8s.syd1.au.unkin.net/api/v1/remote/rspamd/rpm/rspamd.asc + gpgcheck: 1 + repo_gpgcheck: 1 + mirrorlist: absent + # Ceph repositories ceph: ensure: absent diff --git a/hieradata/roles/infra/mail/gateway.yaml b/hieradata/roles/infra/mail/gateway.yaml index ead70e4..f9644cd 100644 --- a/hieradata/roles/infra/mail/gateway.yaml +++ b/hieradata/roles/infra/mail/gateway.yaml @@ -1,25 +1,74 @@ --- +hiera_include: + - redis -# additional altnames -profiles::pki::vault::alt_names: - - in-mta.main.unkin.net +profiles::yum::global::repos: + rspamd: + ensure: present + +# local redis for rspamd bayes and history; bayes tokens carry a ttl so volatile-ttl can evict +redis::maxmemory: '128mb' +redis::maxmemory_policy: 'volatile-ttl' + +# local rspamd, deterministic checks only; module defaults cover spf/dkim/dmarc/arc/rbl/phishing/mime_types/fuzzy_check +profiles::rspamd::edge::rules: + edge.cf: 'https://git.unkin.net/unkin/rspamd-rules/raw/branch/main/edge.cf' +profiles::rspamd::edge::local_d: + worker-proxy.inc: | + bind_socket = "127.0.0.1:11332"; + milter = yes; + timeout = 120s; + count = 1; + upstream "local" { + default = yes; + self_scan = yes; + } + worker-normal.inc: | + enabled = false; + redis.conf: | + servers = "127.0.0.1:6379"; + actions.conf: | + reject = 13; + add_header = 6; + rewrite_subject = null; + greylist = null; + greylist.conf: | + enabled = false; + gpt.conf: | + enabled = false; + classifier-bayes.conf: | + backend = "redis"; + expire = 8640000; + autolearn { + spam_threshold = 6.0; + ham_threshold = -0.5; + check_balance = true; + } + spamassassin.conf: | + ruleset = "/etc/rspamd/rules/*.cf"; + alpha = 0.1; + settings.conf: | + smarthost_outbound { + priority = high; + ip = "103.216.191.185"; + want_spam = yes; + } # base postfix configuration (passed to postfix class) postfix::relayhost: 'direct' -postfix::myorigin: 'main.unkin.net' +postfix::myorigin: 'unkin.net' postfix::mydestination: 'blank' -postfix::mynetworks: '127.0.0.0/8 [::1]/128' +postfix::mynetworks: '127.0.0.0/8 [::1]/128 103.216.191.185/32' postfix::mta: true postfix::manage_aliases: true -# profile parameters for customization -profiles::postfix::gateway::myhostname: 'in-mta.main.unkin.net' - # postfix map content (templates) profiles::postfix::gateway::relay_recipients_maps: + '@unkin.net': 'OK' '@main.unkin.net': 'OK' profiles::postfix::gateway::relay_domains_maps: + 'unkin.net': 'OK' 'main.unkin.net': 'OK' profiles::postfix::gateway::postscreen_access_maps: @@ -32,21 +81,28 @@ profiles::postfix::gateway::helo_access_maps: 'unknown': 'REJECT' 'localhost': 'REJECT You are not localhost' +profiles::postfix::gateway::smtp_tls_policy_maps: + '[mail-in.main.unkin.net]:25': 'secure match=hostname' + # postfix transports postfix::transports: + 'unkin.net': + ensure: present + destination: 'relay' + nexthop: '[mail-in.main.unkin.net]:25' 'main.unkin.net': ensure: present destination: 'relay' - nexthop: 'mail-in.main.unkin.net:25' + nexthop: '[mail-in.main.unkin.net]:25' # postfix virtuals postfix::virtuals: 'root': ensure: present - destination: 'ben@main.unkin.net' + destination: 'ben@unkin.net' 'postmaster': ensure: present - destination: 'ben@main.unkin.net' + destination: 'ben@unkin.net' 'abuse': ensure: present - destination: 'ben@main.unkin.net' + destination: 'ben@unkin.net' diff --git a/site/profiles/manifests/postfix/gateway.pp b/site/profiles/manifests/postfix/gateway.pp index d86cbff..1254184 100644 --- a/site/profiles/manifests/postfix/gateway.pp +++ b/site/profiles/manifests/postfix/gateway.pp @@ -3,8 +3,7 @@ class profiles::postfix::gateway ( Stdlib::Absolutepath $tls_key_file = '/etc/pki/tls/vault/certificate.pem', Stdlib::Absolutepath $tls_ca_file = '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', String $myhostname = $trusted['certname'], - String $message_size_limit = '133169152', - String $mailbox_size_limit = '133169152', + String $message_size_limit = '52428800', String $local_transport = 'error:No local mail delivery', Boolean $enable_postscreen = true, Array[String] $alias_maps = [ @@ -12,12 +11,12 @@ class profiles::postfix::gateway ( 'hash:/etc/postfix/aliases', ], Array[String] $postscreen_dnsbl_sites = [ - 'zen.spamhaus.org*3', - 'b.barracudacentral.org=127.0.0.[2..11]*2', - 'bl.spameatingmonkey.net*2', - 'bl.spamcop.net', - 'dnsbl.sorbs.net', - 'swl.spamhaus.org*-4', + 'zen.spamhaus.org=127.0.0.[2..11]*3', + 'b.barracudacentral.org=127.0.0.2*2', + 'bl.mailspike.net=127.0.0.2*1', + 'psbl.surriel.com=127.0.0.2*1', + 'bl.spameatingmonkey.net=127.0.0.2*2', + 'bl.spamcop.net=127.0.0.2', 'list.dnswl.org=127.[0..255].[0..255].0*-2', 'list.dnswl.org=127.[0..255].[0..255].1*-4', 'list.dnswl.org=127.[0..255].[0..255].[2..3]*-6', @@ -25,7 +24,6 @@ class profiles::postfix::gateway ( Array[String] $smtpd_client_restrictions = [ 'permit_sasl_authenticated', 'permit_mynetworks', - 'reject_rbl_client zen.spamhaus.org', ], Array[String] $smtpd_sender_restrictions = [ 'permit_sasl_authenticated', @@ -47,6 +45,7 @@ class profiles::postfix::gateway ( 'permit_mynetworks', 'reject_unauth_destination', ], + String $smtpd_milters = 'inet:127.0.0.1:11332', Hash[String, String] $smtp_tls_policy_maps = {}, Hash[String, String] $sender_canonical_maps = {}, Hash[String, String] $sender_access_maps = {}, @@ -73,7 +72,7 @@ class profiles::postfix::gateway ( 'value' => 'permit_mynetworks, cidr:/etc/postfix/postscreen_access' }, 'postscreen_blacklist_action' => { - 'value' => 'enforce' + 'value' => 'drop' }, 'postscreen_cache_map' => { 'value' => 'btree:$data_directory/postscreen_cache' @@ -87,6 +86,10 @@ class profiles::postfix::gateway ( 'postscreen_dnsbl_threshold' => { 'value' => '2' }, + # dnswl-listed clients (net score <= -1) skip the slow greet tests; el9 postfix 3.5 predates the allowlist rename + 'postscreen_dnsbl_whitelist_threshold' => { + 'value' => '-1' + }, 'postscreen_greet_action' => { 'value' => 'enforce' }, @@ -108,9 +111,6 @@ class profiles::postfix::gateway ( 'alias_database' => { 'value' => $alias_maps_string }, - 'default_destination_recipient_limit' => { - 'value' => '1' - }, 'disable_vrfy_command' => { 'value' => 'yes' }, @@ -129,9 +129,6 @@ class profiles::postfix::gateway ( 'local_transport' => { 'value' => $local_transport }, - 'mailbox_size_limit' => { - 'value' => $mailbox_size_limit - }, 'message_size_limit' => { 'value' => $message_size_limit }, @@ -141,8 +138,20 @@ class profiles::postfix::gateway ( 'non_smtpd_milters' => { 'ensure' => 'blank' }, - 'qmqpd_authorized_clients' => { - 'value' => '127.0.0.1 [::1]' + 'milter_connect_timeout' => { + 'value' => '10s' + }, + 'milter_content_timeout' => { + 'value' => '60s' + }, + 'milter_default_action' => { + 'value' => 'tempfail' + }, + 'milter_mail_macros' => { + 'value' => 'i {mail_addr} {client_addr} {client_name} {auth_authen}' + }, + 'milter_protocol' => { + 'value' => '6' }, 'recipient_canonical_maps' => { 'value' => 'hash:/etc/postfix/recipient_canonical' @@ -177,7 +186,7 @@ class profiles::postfix::gateway ( 'smtp_tls_session_cache_database' => { 'value' => 'btree:/var/lib/postfix/smtp_tls_session_cache' }, - 'smtp_use_tls' => { + 'smtp_send_xforward_command' => { 'value' => 'yes' }, 'smtpd_banner' => { @@ -211,7 +220,7 @@ class profiles::postfix::gateway ( 'value' => 'check_helo_access hash:/etc/postfix/helo_access, reject_invalid_hostname' }, 'smtpd_milters' => { - 'value' => 'inet:127.0.0.1:33333' + 'value' => $smtpd_milters }, 'smtpd_recipient_restrictions' => { 'value' => join($smtpd_recipient_restrictions, ', ') @@ -255,13 +264,10 @@ class profiles::postfix::gateway ( 'smtpd_tls_session_cache_timeout' => { 'value' => '3600s' }, - 'smtpd_use_tls' => { - 'value' => 'yes' - }, 'tls_medium_cipherlist' => { 'value' => join([ 'ECDSA+AESGCM:ECDH+AESGCM:DH+AESGCM:ECDSA+AES:ECDH+AES:DH+AES', - 'ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS' + 'RSA+AESGCM:RSA+AES:!aNULL:!MD5:!DSS:!3DES' ], ':') }, 'tls_preempt_cipherlist' => { diff --git a/site/profiles/manifests/rspamd/edge.pp b/site/profiles/manifests/rspamd/edge.pp new file mode 100644 index 0000000..c3a248e --- /dev/null +++ b/site/profiles/manifests/rspamd/edge.pp @@ -0,0 +1,68 @@ +# local rspamd milter for the mail edge gateways +# - local_d: /etc/rspamd/local.d file name => content +# - rules: /etc/rspamd/rules file name => url, re-fetched every puppet run; fetch failures keep the last good copy +class profiles::rspamd::edge ( + Hash[String, String] $local_d = {}, + Hash[String, Stdlib::HTTPSUrl] $rules = {}, +) { + + package { 'rspamd': + ensure => installed, + } + + $local_d.each |$name, $content| { + file { "/etc/rspamd/local.d/${name}": + ensure => file, + owner => 'root', + group => 'root', + mode => '0644', + content => $content, + require => Package['rspamd'], + notify => Service['rspamd'], + } + } + + file { '/etc/rspamd/rules': + ensure => directory, + owner => 'root', + group => 'root', + mode => '0755', + require => Package['rspamd'], + } + + $rules.each |$name, $url| { + $path = "/etc/rspamd/rules/${name}" + + # empty placeholder so rspamd starts before the first successful fetch + file { $path: + ensure => file, + owner => 'root', + group => 'root', + mode => '0644', + content => '', + replace => false, + } + + # a failed or unchanged fetch skips the swap without failing the run + exec { "rspamd_rules_fetch_${name}": + command => "/usr/bin/mv -f ${path}.new ${path}", + onlyif => "/usr/bin/curl -fsS --max-time 30 -o ${path}.new ${url} && ! /usr/bin/cmp -s ${path}.new ${path}", + require => File[$path], + notify => Exec['rspamd_reload'], + } + } + + # reload keeps the milter answering while config changes apply + service { 'rspamd': + ensure => running, + enable => true, + restart => '/usr/bin/systemctl reload rspamd', + require => Package['rspamd'], + } + + exec { 'rspamd_reload': + command => '/usr/bin/systemctl reload rspamd', + refreshonly => true, + require => Service['rspamd'], + } +} diff --git a/site/roles/manifests/infra/mail/gateway.pp b/site/roles/manifests/infra/mail/gateway.pp index e046920..37ac4e4 100644 --- a/site/roles/manifests/infra/mail/gateway.pp +++ b/site/roles/manifests/infra/mail/gateway.pp @@ -6,6 +6,7 @@ class roles::infra::mail::gateway { }else{ include profiles::defaults include profiles::base + include profiles::rspamd::edge include profiles::postfix::gateway } }