Add local rspamd and redis to the mail edge gateway (#553)
ci/woodpecker/pr/puppet-lint Pipeline was successful
ci/woodpecker/pr/erb-validate Pipeline was successful
ci/woodpecker/pr/ruby-validate Pipeline was successful
ci/woodpecker/pr/yamllint Pipeline was successful
ci/woodpecker/pr/bolt-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

Edge gateways need cheap deterministic in-session filtering behind the postfix milter, with no AI and no ClamAV on 1GB VMs.

- add a signature-checked rspamd yum repo via artifactapi, enabled for the gateway role
- add profiles::rspamd::edge managing the package and local.d files from hiera
- fetch rule files without blocking the service: empty placeholder, swap only on a successful changed fetch, then reload
- run a self-scanning milter proxy on 11332; reject at 13, header at 6, no greylisting, subject rewrite or gpt
- add a local 128MB redis for bayes autolearn; load SpamAssassin rules from rspamd-rules
- bypass rspamd for mail relayed out by the smarthost

Stacked on #552; requires terraform-artifactapi #63 applied first.

Reviewed-on: #553
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
This commit was merged in pull request #553.
This commit is contained in:
2026-10-13 21:14:16 +11:00
committed by BenVincent
parent a7c2b40b72
commit 9484e8b77b
4 changed files with 135 additions and 0 deletions
+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
+54
View File
@@ -1,4 +1,58 @@
---
hiera_include:
- redis
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'
+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,7 @@ class roles::infra::mail::gateway {
}else{
include profiles::defaults
include profiles::base
include profiles::rspamd::edge
include profiles::postfix::gateway
}
}