From a0d26686a4edce431eaf9b31b35eb02ff9d5c744 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Tue, 21 Oct 2025 22:10:22 +1100 Subject: [PATCH] feat: implement comprehensive postfix gateway with eFa5 configuration - add voxpupuli-postfix module to Puppetfile - create profiles::postfix::gateway class with config based on efa5 - add master.cf entries for postscreen, smtpd, dnsblog, and tlsproxy services - create postfix hash files: aliases, access controls, canonical maps - configure TLS with system PKI certificates and strong cipher suites - add transport and virtual alias mappings for mail routing --- Puppetfile | 3 + hieradata/roles/infra/mail/gateway.yaml | 5 + site/profiles/files/postfix/gateway/aliases | 54 ++++ .../files/postfix/gateway/helo_access | 11 + .../files/postfix/gateway/postscreen_access | 4 + .../files/postfix/gateway/recipient_access | 9 + .../files/postfix/gateway/recipient_canonical | 8 + .../files/postfix/gateway/relay_domains | 3 + .../files/postfix/gateway/relay_recipients | 3 + .../files/postfix/gateway/sender_access | 10 + .../files/postfix/gateway/sender_canonical | 8 + site/profiles/manifests/postfix/gateway.pp | 250 ++++++++++++++++++ site/roles/manifests/infra/mail/gateway.pp | 1 + 13 files changed, 369 insertions(+) create mode 100644 hieradata/roles/infra/mail/gateway.yaml create mode 100644 site/profiles/files/postfix/gateway/aliases create mode 100644 site/profiles/files/postfix/gateway/helo_access create mode 100644 site/profiles/files/postfix/gateway/postscreen_access create mode 100644 site/profiles/files/postfix/gateway/recipient_access create mode 100644 site/profiles/files/postfix/gateway/recipient_canonical create mode 100644 site/profiles/files/postfix/gateway/relay_domains create mode 100644 site/profiles/files/postfix/gateway/relay_recipients create mode 100644 site/profiles/files/postfix/gateway/sender_access create mode 100644 site/profiles/files/postfix/gateway/sender_canonical create mode 100644 site/profiles/manifests/postfix/gateway.pp diff --git a/Puppetfile b/Puppetfile index b559a1b..f02f52b 100644 --- a/Puppetfile +++ b/Puppetfile @@ -19,6 +19,7 @@ mod 'puppetlabs-haproxy', '8.2.0' mod 'puppetlabs-java', '11.1.0' mod 'puppetlabs-reboot', '5.1.0' mod 'puppetlabs-docker', '10.2.0' +mod 'puppetlabs-mailalias_core', '1.2.0' # puppet mod 'puppet-python', '7.4.0' @@ -43,6 +44,8 @@ mod 'puppet-letsencrypt', '11.1.0' mod 'puppet-rundeck', '9.2.0' mod 'puppet-redis', '11.1.0' mod 'puppet-nodejs', '11.0.0' +mod 'puppet-postfix', '5.1.0' +mod 'puppet-alternatives', '6.0.0' # other mod 'saz-sudo', '9.0.2' diff --git a/hieradata/roles/infra/mail/gateway.yaml b/hieradata/roles/infra/mail/gateway.yaml new file mode 100644 index 0000000..f3069bd --- /dev/null +++ b/hieradata/roles/infra/mail/gateway.yaml @@ -0,0 +1,5 @@ +--- + +# additional altnames +profiles::pki::vault::alt_names: + - in-mta.main.unkin.net diff --git a/site/profiles/files/postfix/gateway/aliases b/site/profiles/files/postfix/gateway/aliases new file mode 100644 index 0000000..977e660 --- /dev/null +++ b/site/profiles/files/postfix/gateway/aliases @@ -0,0 +1,54 @@ +# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED + +postmaster: root + +# Many mailers use this address to represent the empty SMTP return path +MAILER-DAEMON: postmaster + +# Common aliases for system accounts. +bin: root +daemon: root +games: root +ingres: root +nobody: root +system: root +toor: root +foo: root +falken: root + +# Well-known aliases. +admin: root +manager: root +dumper: root +operator: root + +# traps to catch security attacks +decode: root +moof: root +moog: root + +# Standard aliases also defined by RFC 2142 +abuse: postmaster + +# reports of network infrastructure difficulties +noc: root + +# address to report secuirty problems +security: root + +# DNS administrator (DNS soa records should use this) +hostmaster: root + +# Usenet news service administrator +news: usenet +usenet: root + +# http/web service administrator +www: webmaster +webmaster: root + +# UUCP service administrator +uucp: root + +# FTP administrator (especially anon FTP) +ftp: root diff --git a/site/profiles/files/postfix/gateway/helo_access b/site/profiles/files/postfix/gateway/helo_access new file mode 100644 index 0000000..7ece2c6 --- /dev/null +++ b/site/profiles/files/postfix/gateway/helo_access @@ -0,0 +1,11 @@ +# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED + +# HELO/EHLO access controls +# Format: pattern action +# Actions: REJECT, OK, WARN, etc. + +# Block common spam patterns +.dynamic. REJECT +.dialup. REJECT +unknown REJECT +localhost REJECT You are not localhost \ No newline at end of file diff --git a/site/profiles/files/postfix/gateway/postscreen_access b/site/profiles/files/postfix/gateway/postscreen_access new file mode 100644 index 0000000..2e0743c --- /dev/null +++ b/site/profiles/files/postfix/gateway/postscreen_access @@ -0,0 +1,4 @@ +# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED + +127.0.0.1/32 permit +10.10.12.200/32 permit diff --git a/site/profiles/files/postfix/gateway/recipient_access b/site/profiles/files/postfix/gateway/recipient_access new file mode 100644 index 0000000..9a09e9f --- /dev/null +++ b/site/profiles/files/postfix/gateway/recipient_access @@ -0,0 +1,9 @@ +# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED + +# Recipient access controls +# Format: recipient_pattern action +# Actions: REJECT, OK, WARN, DISCARD, etc. + +# Protected recipients that require special handling +# Example entries: +# @main.unkin.net OK \ No newline at end of file diff --git a/site/profiles/files/postfix/gateway/recipient_canonical b/site/profiles/files/postfix/gateway/recipient_canonical new file mode 100644 index 0000000..63fe6d7 --- /dev/null +++ b/site/profiles/files/postfix/gateway/recipient_canonical @@ -0,0 +1,8 @@ +# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED + +# Recipient canonical address mapping +# Format: original_address canonical_address +# Used to rewrite recipient addresses + +# Example mappings: +# user@olddomain.com user@main.unkin.net \ No newline at end of file diff --git a/site/profiles/files/postfix/gateway/relay_domains b/site/profiles/files/postfix/gateway/relay_domains new file mode 100644 index 0000000..c779905 --- /dev/null +++ b/site/profiles/files/postfix/gateway/relay_domains @@ -0,0 +1,3 @@ +# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED + +main.unkin.net OK diff --git a/site/profiles/files/postfix/gateway/relay_recipients b/site/profiles/files/postfix/gateway/relay_recipients new file mode 100644 index 0000000..209fc19 --- /dev/null +++ b/site/profiles/files/postfix/gateway/relay_recipients @@ -0,0 +1,3 @@ +# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED + +@main.unkin.net OK diff --git a/site/profiles/files/postfix/gateway/sender_access b/site/profiles/files/postfix/gateway/sender_access new file mode 100644 index 0000000..ba803bd --- /dev/null +++ b/site/profiles/files/postfix/gateway/sender_access @@ -0,0 +1,10 @@ +# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED + +# Sender access controls +# Format: sender_pattern action +# Actions: REJECT, OK, WARN, DISCARD, etc. + +# Block known spam domains +# Example entries: +# spammer@example.com REJECT +# @badspammer.com REJECT \ No newline at end of file diff --git a/site/profiles/files/postfix/gateway/sender_canonical b/site/profiles/files/postfix/gateway/sender_canonical new file mode 100644 index 0000000..d470b0a --- /dev/null +++ b/site/profiles/files/postfix/gateway/sender_canonical @@ -0,0 +1,8 @@ +# FILE MANAGED BY PUPPET, CHANGES WILL BE REPLACED + +# Sender canonical address mapping +# Format: original_address canonical_address +# Used to rewrite sender addresses + +# Example mappings: +# user@internal.local user@main.unkin.net \ No newline at end of file diff --git a/site/profiles/manifests/postfix/gateway.pp b/site/profiles/manifests/postfix/gateway.pp new file mode 100644 index 0000000..8b78be2 --- /dev/null +++ b/site/profiles/manifests/postfix/gateway.pp @@ -0,0 +1,250 @@ +class profiles::postfix::gateway ( + $tls_cert_file = '/etc/pki/tls/vault/certificate.pem', + $tls_key_file = '/etc/pki/tls/vault/certificate.pem', + $tls_ca_file = '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', +) { + + $alias_maps = 'hash:/etc/aliases, hash:/etc/postfix/aliases' + + class { 'postfix': + relayhost => 'direct', + myorigin => 'main.unkin.net', + mydestination => 'blank', + mynetworks => '127.0.0.0/8 [::1]/128', + alias_maps => $alias_maps, + mta => true, + manage_aliases => true, + master_smtp => 'smtp inet n - n - 1 postscreen', + master_entries => [ + # Postscreen backend services + 'smtpd pass - - n - - smtpd', + 'dnsblog unix - - n - 0 dnsblog', + 'tlsproxy unix - - n - 0 tlsproxy', + ], + } + + postfix::config { + 'alias_database': + value => $alias_maps; + 'default_destination_recipient_limit': + value => '1'; + 'disable_vrfy_command': + value => 'yes'; + 'enable_long_queue_ids': + value => 'yes'; + 'error_notice_recipient': + value => 'root'; + 'header_checks': + value => 'regexp:/etc/postfix/header_checks'; + 'local_recipient_maps': + ensure => 'blank'; # no local mailboxes + 'local_transport': + value => 'error:No local mail delivery'; + 'mailbox_size_limit': + value => '133169152'; # ~127MB + 'message_size_limit': + value => '133169152'; # ~127MB + 'myhostname': + value => 'in-mta.main.unkin.net'; + 'non_smtpd_milters': + ensure => 'blank'; + 'postscreen_access_list': + value => 'permit_mynetworks, cidr:/etc/postfix/postscreen_access'; + 'postscreen_blacklist_action': + value => 'enforce'; + 'postscreen_cache_map': + value => 'btree:$data_directory/postscreen_cache'; + 'postscreen_dnsbl_action': + value => 'enforce'; + 'postscreen_dnsbl_sites': + value => join([ + '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', + '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' + ], ', '); + 'postscreen_dnsbl_threshold': + value => '2'; + 'postscreen_greet_action': + value => 'enforce'; + 'postscreen_greet_banner': + value => '$smtpd_banner'; + 'postscreen_greet_wait': + value => "\${stress?2}\${stress:6}s"; + 'qmqpd_authorized_clients': + value => '127.0.0.1 [::1]'; + 'recipient_canonical_maps': + value => 'hash:/etc/postfix/recipient_canonical'; + 'recipient_delimiter': + value => '+'; + 'relay_domains': + value => 'hash:/etc/postfix/relay_domains'; + 'relay_recipient_maps': + value => 'hash:/etc/postfix/relay_recipients'; + 'sender_canonical_maps': + value => 'hash:/etc/postfix/sender_canonical'; + 'smtp_tls_CAfile': + value => $tls_ca_file; + 'smtp_tls_mandatory_protocols': + value => '!SSLv2,!SSLv3'; + 'smtp_tls_note_starttls_offer': + value => 'yes'; + 'smtp_tls_protocols': + value => '!SSLv2,!SSLv3'; + 'smtp_tls_security_level': + value => 'may'; + 'smtp_tls_session_cache_database': + value => 'btree:/var/lib/postfix/smtp_tls_session_cache'; + 'smtp_use_tls': + value => 'yes'; + 'smtpd_banner': + value => '$myhostname ESMTP $mail_name'; + 'smtpd_client_restrictions': + value => 'permit_sasl_authenticated, permit_mynetworks, reject_rbl_client zen.spamhaus.org'; + 'smtpd_data_restrictions': + value => 'reject_unauth_pipelining'; + 'smtpd_delay_reject': + value => 'yes'; + 'smtpd_discard_ehlo_keywords': + value => 'chunking, silent-discard'; + 'smtpd_forbid_bare_newline': + value => 'yes'; + 'smtpd_forbid_bare_newline_exclusions': + value => '$mynetworks'; + 'smtpd_forbid_unauth_pipelining': + value => 'yes'; + 'smtpd_helo_required': + value => 'yes'; + 'smtpd_helo_restrictions': + value => 'check_helo_access hash:/etc/postfix/helo_access, reject_invalid_hostname'; + 'smtpd_milters': + value => 'inet:127.0.0.1:33333'; + 'smtpd_recipient_restrictions': + value => join([ + 'permit_sasl_authenticated', + 'permit_mynetworks', + 'reject_unauth_destination', + 'reject_non_fqdn_recipient', + 'reject_unknown_recipient_domain', + 'check_recipient_access hash:/etc/postfix/recipient_access', + 'check_policy_service inet:127.0.0.1:2501', + 'reject_unverified_recipient' + ], ', '); + 'smtpd_relay_restrictions': + value => 'permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination'; + 'smtpd_sender_restrictions': + value => join([ + 'permit_sasl_authenticated', + 'check_sender_access hash:/etc/postfix/sender_access', + 'reject_non_fqdn_sender', + 'reject_unknown_sender_domain' + ], ', '); + 'smtpd_tls_CAfile': + value => $tls_ca_file; + 'smtpd_tls_cert_file': + value => $tls_cert_file; + 'smtpd_tls_ciphers': + value => 'medium'; + 'smtpd_tls_key_file': + value => $tls_key_file; + 'smtpd_tls_loglevel': + value => '1'; + 'smtpd_tls_mandatory_protocols': + value => '!SSLv2,!SSLv3'; + 'smtpd_tls_protocols': + value => '!SSLv2,!SSLv3'; + 'smtpd_tls_received_header': + value => 'yes'; + 'smtpd_tls_security_level': + value => 'may'; + 'smtpd_tls_session_cache_database': + value => 'btree:/var/lib/postfix/smtpd_tls_session_cache'; + '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' + ], ':'); + 'tls_preempt_cipherlist': + value => 'yes'; + 'tls_random_source': + value => 'dev:/dev/urandom'; + 'unverified_recipient_reject_code': + value => '550'; + 'unverified_recipient_reject_reason': + value => 'No user at this address'; + } + + postfix::map { 'postscreen_access': + ensure => present, + type => 'cidr', + source => 'puppet:///modules/profiles/postfix/gateway/postscreen_access' + } + postfix::map { 'relay_recipients': + ensure => present, + type => 'hash', + source => 'puppet:///modules/profiles/postfix/gateway/relay_recipients' + } + postfix::map { 'relay_domains': + ensure => present, + type => 'hash', + source => 'puppet:///modules/profiles/postfix/gateway/relay_domains' + } + postfix::map { 'aliases': + ensure => present, + type => 'hash', + source => 'puppet:///modules/profiles/postfix/gateway/aliases' + } + postfix::map { 'helo_access': + ensure => present, + type => 'hash', + source => 'puppet:///modules/profiles/postfix/gateway/helo_access' + } + postfix::map { 'sender_access': + ensure => present, + type => 'hash', + source => 'puppet:///modules/profiles/postfix/gateway/sender_access' + } + postfix::map { 'recipient_access': + ensure => present, + type => 'hash', + source => 'puppet:///modules/profiles/postfix/gateway/recipient_access' + } + postfix::map { 'recipient_canonical': + ensure => present, + type => 'hash', + source => 'puppet:///modules/profiles/postfix/gateway/recipient_canonical' + } + postfix::map { 'sender_canonical': + ensure => present, + type => 'hash', + source => 'puppet:///modules/profiles/postfix/gateway/sender_canonical' + } + + postfix::transport { + 'main.unkin.net': + ensure => present, + destination => 'relay', + nexthop => 'ausyd1nxvm2120.main.unkin.net:25'; + } + postfix::virtual { + 'root': + ensure => present, + destination => 'ben@main.unkin.net'; + 'postmaster': + ensure => present, + destination => 'ben@main.unkin.net'; + 'abuse': + ensure => present, + destination => 'ben@main.unkin.net'; + } + +} diff --git a/site/roles/manifests/infra/mail/gateway.pp b/site/roles/manifests/infra/mail/gateway.pp index a2bd0b8..e046920 100644 --- a/site/roles/manifests/infra/mail/gateway.pp +++ b/site/roles/manifests/infra/mail/gateway.pp @@ -6,5 +6,6 @@ class roles::infra::mail::gateway { }else{ include profiles::defaults include profiles::base + include profiles::postfix::gateway } }