Compare commits

...

8 Commits

Author SHA1 Message Date
unkin-agent 27071d21ae Add the wgvpn role for the BinaryLane VPN hosts
ci/woodpecker/pr/ruby-validate Pipeline was successful
ci/woodpecker/pr/bolt-validate Pipeline was successful
ci/woodpecker/pr/puppet-lint Pipeline was successful
ci/woodpecker/pr/yamllint Pipeline was successful
ci/woodpecker/pr/erb-validate Pipeline was successful
ci/woodpecker/pr/epp-validate Pipeline was successful
ci/woodpecker/pr/ruby-check Pipeline was successful
ci/woodpecker/pr/puppet-validate Pipeline was successful
2026-10-16 23:40:35 +11:00
unkin-agent e6a06f4845 Add dnsmasq resolver and edge base-class changes to the mail gateway (#554)
The edge gateways sit in BinaryLane VPCs with no consul datacentre and a public interface, and postfix and rspamd need a caching resolver that reaches the lab over the VPN route.

- run dnsmasq as the local resolver, forwarding to 198.18.200.7, and point resolv.conf at it
- add an enable toggle to profiles::consul::client, off for the gateway role
- disable node_exporter on the gateway role, as it registers via consul
- restrict sshd AllowUsers to the edge VPCs (172.18.30.0/24, 172.18.31.0/24, 172.18.32.0/24) and the lab (198.18.0.0/16, 10.10.12.200/32)

Reviewed-on: #554
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-10-16 23:17:56 +11:00
unkin-agent a14cdbb55a Add the bne dmz vpc and move the mta2 edge to bne (#559)
BinaryLane mel has no stock, so the second mail edge site moves to Brisbane (VPC 172.18.32.0/24); syd and mel stay.

- add 172.18.32.0/24 as region bne1 dmz in subnet facts
- rename the mta2 edge node hiera to aubne1nxvm2132

Reviewed-on: #559
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-10-16 23:08:56 +11:00
unkin-agent b338c8420a Move mail DMZ VPC subnet facts to RFC 1918 ranges (#558)
BinaryLane only accepts RFC 1918 VPC ranges, so the syd and mel mail DMZ VPCs move from 198.18.30.0/24 and 198.18.31.0/24 to 172.18.30.0/24 and 172.18.31.0/24.

- map 172.18.30.0/24 to syd2 dmz and 172.18.31.0/24 to mel1 dmz, replacing the 198.18.30-31 entries

Reviewed-on: #558
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-10-16 21:53:13 +11:00
unkin-agent b1f2fff045 Resolve subnet facts from secondary interfaces (#557)
BinaryLane edge hosts have a public primary IP, so region/environment/country/zone resolve to unknown even though their VPC NIC sits in a known subnet.

- match the primary IP first, unchanged for existing hosts
- otherwise scan interface bindings in name order, skipping lo, dum* and wg*
- add modules/libs/test/subnet_facts_check.rb (`ruby modules/libs/test/subnet_facts_check.rb`)

Reviewed-on: #557
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-10-13 21:46:53 +11:00
unkin-agent 4313b7db49 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: #552
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-10-13 21:42:38 +11:00
unkin-agent 693452b4f8 Map the binarylane DMZ VPCs in subnet facts (#556)
The BinaryLane mail edge DMZ VPCs need environment, region, country and zone facts like every other lab subnet.

- map 198.18.30.0/24 to prod/syd2/au/dmz (binarylane syd dmz vpc)
- map 198.18.31.0/24 to prod/mel1/au/dmz (binarylane mel dmz vpc)

Reviewed-on: #556
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-10-13 21:37:57 +11:00
unkin-agent 6c880d9794 Add consul server federation SANs to consul server certs (#547)
WAN federation through mesh gateways makes Consul verify server certs against `server.<dc>.consul` and `<node_name>.server.<dc>.consul`. Node names are FQDNs, so each server needs its exact SAN. This adds the SANs ahead of Consul server TLS, which a stacked follow-up enables once the reissued certs are verified.

- add `server.<country>-<region>.consul` to consul server alt_names
- add `<fqdn>.server.<country>-<region>.consul` to consul server alt_names

Reviewed-on: #547
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
2026-10-09 21:51:43 +11:00
19 changed files with 415 additions and 48 deletions
@@ -0,0 +1,4 @@
---
profiles::postfix::gateway::myhostname: 'mta2.edge.unkin.net'
profiles::pki::vault::alt_names:
- mta2.edge.unkin.net
@@ -0,0 +1,25 @@
---
networking_loopback0_ip: 198.18.2.162
networking_wg_link: 198.18.2.178/31
networking_vpc_iface: eth1
networking_vpc_cidr: 172.18.32.0/24
# wg0 p2p to prodnxsr0020; lab routes stay pinned to wg0 sourced from the /31 until ospf learns them
wireguard::interfaces:
wg0:
addresses:
- 198.18.2.179/31
listen_port: 51820
table: 'off'
post_up:
- ip route replace 198.18.0.0/16 dev wg0 src 198.18.2.179
- ip route replace 10.10.12.200/32 dev wg0 src 198.18.2.179
private_key: ''
peers:
- public_key: ''
endpoint: 103.216.191.185:51822
allowed_ips:
- 224.0.0.5/32
- 198.18.0.0/16
- 10.10.12.200/32
persistent_keepalive: 25
@@ -0,0 +1,4 @@
---
profiles::postfix::gateway::myhostname: 'mta1.edge.unkin.net'
profiles::pki::vault::alt_names:
- mta1.edge.unkin.net
@@ -0,0 +1,25 @@
---
networking_loopback0_ip: 198.18.2.161
networking_wg_link: 198.18.2.176/31
networking_vpc_iface: eth1
networking_vpc_cidr: 172.18.30.0/24
# wg0 p2p to prodnxsr0020; lab routes stay pinned to wg0 sourced from the /31 until ospf learns them
wireguard::interfaces:
wg0:
addresses:
- 198.18.2.177/31
listen_port: 51820
table: 'off'
post_up:
- ip route replace 198.18.0.0/16 dev wg0 src 198.18.2.177
- ip route replace 10.10.12.200/32 dev wg0 src 198.18.2.177
private_key: ''
peers:
- public_key: ''
endpoint: 103.216.191.185:51821
allowed_ips:
- 224.0.0.5/32
- 198.18.0.0/16
- 10.10.12.200/32
persistent_keepalive: 25
+12
View File
@@ -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
+84 -12
View File
@@ -1,25 +1,90 @@
---
hiera_include:
- redis
# additional altnames
profiles::pki::vault::alt_names:
- in-mta.main.unkin.net
# no consul datacentre at the edge, so nothing to register or scrape via consul
profiles::consul::client::enable: false
exporters::node_exporter::enable: false
# ssh only from the edge vpcs (syd, mel, bne) and the lab over the vpn route
ssh::server::options:
AllowUsers: '*@172.18.30.0/24 *@172.18.31.0/24 *@172.18.32.0/24 *@198.18.0.0/16 *@10.10.12.200/32'
# local caching resolver for postfix and rspamd, forwarding to the lab resolver over the vpn route
dnsmasq::no_resolv: true
dnsmasq::cache_size: 10000
dnsmasq::servers:
- 198.18.200.7
profiles::dns::base::nameservers:
- 127.0.0.1
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 +97,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'
+41
View File
@@ -0,0 +1,41 @@
---
hiera_include:
- wireguard
- frrouting
# no consul datacentre at the edge, so nothing to register or scrape via consul
profiles::consul::client::enable: false
exporters::node_exporter::enable: false
# ssh only from the lab over the vpn
ssh::server::options:
AllowUsers: '*@198.18.0.0/16 *@10.10.12.200/32'
# routing
sysctl::base::values:
net.ipv4.ip_forward:
value: '1'
# frrouting: p2p ospf over wg0; loopback /32, wg /31 and vpc /24 only, no default route in or out
frrouting::daemons:
ospfd: true
frrouting::ospfd_router_id: "%{hiera('networking_loopback0_ip')}"
frrouting::ospfd_interfaces:
wg0:
area: 0.0.0.0
network: point-to-point
dum0:
area: 0.0.0.0
passive: true
"%{hiera('networking_vpc_iface')}":
area: 0.0.0.0
passive: true
frrouting::ospfd_redistribute:
- connected
frrouting::ospfd_redistribute_connected_permit:
- "%{hiera('networking_loopback0_ip')}/32"
- "%{hiera('networking_wg_link')}"
- "%{hiera('networking_vpc_cidr')}"
frrouting::ospfd_deny_default_in: true
frrouting::ospf_preferred_source_enable: true
frrouting::ospf_preferred_source: "%{hiera('networking_loopback0_ip')}"
@@ -31,6 +31,8 @@ profiles::pki::vault::alt_names:
- consul.service.consul
- "consul.service.%{facts.country}-%{facts.region}.consul"
- consul
- "server.%{facts.country}-%{facts.region}.consul"
- "%{facts.networking.fqdn}.server.%{facts.country}-%{facts.region}.consul"
# manage a simple nginx reverse proxy
profiles::nginx::simpleproxy::nginx_vhost: 'consul.service.consul'
+2
View File
@@ -10,6 +10,8 @@ class frrouting (
Array[String] $ospfd_redistribute = [],
Array[String] $ospfd_networks = [],
Boolean $ospfd_default_originate_always = false,
Array[Stdlib::IP::Address::V4::CIDR] $ospfd_redistribute_connected_permit = [], # only these exact connected prefixes are redistributed
Boolean $ospfd_deny_default_in = false, # keep a learned default route out of the routing table
Boolean $mpls_te_enabled = false,
Optional[String] $mpls_ldp_router_id = undef,
Optional[String] $mpls_ldp_transport_addr = undef,
+21
View File
@@ -8,6 +8,9 @@ interface <%= iface %>
<% if params['area'] -%>
ip ospf area <%= params['area'] %>
<% end -%>
<% if params['network'] -%>
ip ospf network <%= params['network'] %>
<% end -%>
<% if params['passive'] == true -%>
ip ospf passive
<% end -%>
@@ -28,12 +31,27 @@ route-map CONNECTED-NON-K8S permit 100
exit
<% end -%>
<% if @ospfd_redistribute_connected_permit.any? -%>
<% @ospfd_redistribute_connected_permit.each_with_index do |prefix, i| -%>
ip prefix-list CONNECTED-PERMIT seq <%= (i + 1) * 5 %> permit <%= prefix %>
<% end -%>
route-map CONNECTED-PERMIT permit 10
match ip address prefix-list CONNECTED-PERMIT
exit
<% end -%>
<% if @ospfd_deny_default_in -%>
access-list NO-DEFAULT seq 5 deny 0.0.0.0/0 exact-match
access-list NO-DEFAULT seq 10 permit any
<% end -%>
router ospf
ospf router-id <%= @ospfd_router_id %>
log-adjacency-changes detail
<% @ospfd_redistribute.each do |type| -%>
<% if @ospf_exclude_k8s_enable && type == 'connected' && (@k8s_cluster_cidr || @k8s_service_cidr) -%>
redistribute connected route-map CONNECTED-NON-K8S
<% elsif type == 'connected' && @ospfd_redistribute_connected_permit.any? -%>
redistribute connected route-map CONNECTED-PERMIT
<% else -%>
redistribute <%= type %>
<% end -%>
@@ -41,6 +59,9 @@ router ospf
<% @ospfd_networks.each do |network| -%>
network <%= network %>
<% end -%>
<% if @ospfd_deny_default_in -%>
distribute-list NO-DEFAULT in
<% end -%>
<% if @ospfd_default_originate_always -%>
default-information originate always
<% end -%>
+31 -12
View File
@@ -22,27 +22,46 @@ class SubnetAttributes
'198.18.26.0/24' => { environment: 'prod', region: 'syd1', country: 'au', zone: 'common' }, # common node0010
'198.18.27.0/24' => { environment: 'prod', region: 'syd1', country: 'au', zone: 'common' }, # common node0011
'198.18.28.0/24' => { environment: 'prod', region: 'syd1', country: 'au', zone: 'common' }, # common node0012
'198.18.29.0/24' => { environment: 'prod', region: 'syd1', country: 'au', zone: 'common' } # common node0013
'198.18.29.0/24' => { environment: 'prod', region: 'syd1', country: 'au', zone: 'common' }, # common node0013
'172.18.30.0/24' => { environment: 'prod', region: 'syd2', country: 'au', zone: 'dmz' }, # binarylane syd dmz vpc
'172.18.31.0/24' => { environment: 'prod', region: 'mel1', country: 'au', zone: 'dmz' }, # binarylane mel dmz vpc
'172.18.32.0/24' => { environment: 'prod', region: 'bne1', country: 'au', zone: 'dmz' } # binarylane bne dmz vpc
}.freeze
# Default attributes if no subnet matches, also defined as a constant
DEFAULT_ATTRIBUTES = { environment: 'unknown', region: 'unknown', country: 'unknown' }.freeze
# provide ip to return attributes
def self.attributes(ip)
SUBNET_TO_ATTRIBUTES.each do |subnet, attrs|
return attrs if IPAddr.new(subnet).include?(IPAddr.new(ip))
end
# loopbacks and wg /31s sit in syd1 ranges, so they must not decide where a host lives
SKIPPED_INTERFACES = /\A(lo\z|dum|wg)/.freeze
DEFAULT_ATTRIBUTES
# primary ip first, then the remaining interfaces in name order
def self.from_networking(networking)
match(networking['ip']) || interface_match(networking['interfaces'] || {}) || DEFAULT_ATTRIBUTES
end
def self.interface_match(interfaces)
interfaces.sort
.reject { |name, _interface| name.match?(SKIPPED_INTERFACES) }
.flat_map { |_name, interface| addresses(interface) }
.lazy.filter_map { |address| match(address) }.first
end
def self.addresses(interface)
(interface['bindings'] || []).map { |binding| binding['address'] }
end
def self.match(ip)
return unless ip
addr = IPAddr.new(ip)
SUBNET_TO_ATTRIBUTES.each do |subnet, attrs|
return attrs if IPAddr.new(subnet).include?(addr)
end
nil
end
end
# Use the primary IP address from facts
ip = Facter.value(:networking)['ip']
# Call the class method directly without creating an instance
subnet_attributes = SubnetAttributes.attributes(ip)
subnet_attributes = SubnetAttributes.from_networking(Facter.value(:networking))
# Add separate facts for environment, region, and country
Facter.add('environment') { setcode { subnet_attributes[:environment] } }
+36
View File
@@ -0,0 +1,36 @@
# frozen_string_literal: true
# ruby modules/libs/test/subnet_facts_check.rb
module Facter
def self.value(_name)
{}
end
def self.add(*); end
end
load File.expand_path('../lib/facter/subnet_facts.rb', __dir__)
iface = ->(*addresses) { { 'bindings' => addresses.map { |address| { 'address' => address } } } }
checks = {
'primary ip wins over other interfaces' => [
{ 'ip' => '198.18.17.5', 'interfaces' => { 'eth1' => iface.call('198.18.13.5') } }, 'drw1'
],
'unmatched primary ip falls back to an interface' => [
{ 'ip' => '203.0.113.5', 'interfaces' => { 'eth0' => iface.call('203.0.113.5'), 'eth1' => iface.call('198.18.24.11') } }, 'syd1'
],
'lo, dum and wg interfaces are skipped' => [
{ 'ip' => '203.0.113.5',
'interfaces' => { 'dum0' => iface.call('198.18.2.161'), 'lo' => iface.call('198.18.19.1'),
'wg0' => iface.call('198.18.2.177') } }, 'unknown'
],
'interfaces are scanned in name order' => [
{ 'ip' => '203.0.113.5', 'interfaces' => { 'eth2' => iface.call('198.18.24.1'), 'eth1' => iface.call('198.18.17.1') } }, 'drw1'
],
'missing ip and interfaces resolve to unknown' => [{}, 'unknown']
}
failures = checks.reject { |_name, (networking, expected)| SubnetAttributes.from_networking(networking)[:region] == expected }
failures.each_key { |name| warn "FAIL: #{name}" }
abort if failures.any?
puts "ok (#{checks.size} checks)"
+2
View File
@@ -4,6 +4,8 @@ define wireguard::interface (
Optional[Stdlib::Port] $listen_port = undef,
Optional[Integer[1280, 9000]] $mtu = undef,
Optional[Sensitive[String[1]]] $private_key = undef,
Optional[String[1]] $table = undef,
Array[String[1]] $post_up = [],
Array[Struct[{
public_key => String[1],
allowed_ips => Variant[String[1], Array[String[1], 1]],
+6
View File
@@ -9,11 +9,17 @@ ListenPort = <%= @listen_port %>
<% if @mtu -%>
MTU = <%= @mtu %>
<% end -%>
<% if @table -%>
Table = <%= @table %>
<% end -%>
<% if @key -%>
PrivateKey = <%= @key %>
<% else -%>
PostUp = wg set %i private-key /etc/wireguard/%i.key
<% end -%>
<% @post_up.each do |cmd| -%>
PostUp = <%= cmd %>
<% end -%>
<% @peers.each do |peer| -%>
[Peer]
+5
View File
@@ -12,8 +12,13 @@ class profiles::consul::client (
Array[Hash] $node_rules = [],
Hash $ports = {},
Stdlib::IP::Address $host_addr = $facts['networking']['ip'],
Boolean $enable = true,
) {
unless $enable {
return()
}
if $facts['enc_role'] != $members_role {
# set a datacentre/cluster name
+30 -24
View File
@@ -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' => {
+68
View File
@@ -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'],
}
}
@@ -6,6 +6,11 @@ class roles::infra::mail::gateway {
}else{
include profiles::defaults
include profiles::base
include dnsmasq
include profiles::rspamd::edge
include profiles::postfix::gateway
# resolv.conf points at dnsmasq, so it must be running first
Class['dnsmasq'] -> Class['profiles::dns::resolvconf']
}
}
@@ -0,0 +1,12 @@
# roles::infra::network::wgvpn
# a wireguard vpn edge host; routes its vpc to the lab over wg0 with ospf
#
class roles::infra::network::wgvpn {
if $facts['firstrun'] {
include profiles::defaults
include profiles::firstrun::init
}else{
include profiles::defaults
include profiles::base
}
}