Merge ~tcuthbert/mailman3-core-charm:mail-filtering into mailman3-core-charm:master

Proposed by Thomas Cuthbert
Status: Merged
Approved by: Thomas Cuthbert
Approved revision: aede9ce56eeb56c3ddf2b7c4b5d4b4a2ebcacb92
Merged at revision: 7f909904566fae6fa882de28d08c638efefe99b5
Proposed branch: ~tcuthbert/mailman3-core-charm:mail-filtering
Merge into: mailman3-core-charm:master
Diff against target: 188 lines (+124/-0)
6 files modified
config.yaml (+30/-0)
files/spamassassin_master.cf (+2/-0)
files/spf_master.cf (+2/-0)
layer.yaml (+3/-0)
reactive/mailman3_core.py (+83/-0)
templates/sa-local.cf.tmpl (+4/-0)
Reviewer Review Type Date Requested Status
Tom Haddon Approve
Review via email: mp+350264@code.launchpad.net

Commit message

Integrate Spamassassin and SPF with Postfix

Description of the change

This is currently working in lists.staging.c.c

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Tom Haddon (mthaddon) wrote :

LGTM in terms of the charm changes. I'm not familiar with our spamassassin or SPF set up, but I'm assuming this is copied from existing implementations.

review: Approve
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote :

Change successfully merged at revision 7f909904566fae6fa882de28d08c638efefe99b5

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/config.yaml b/config.yaml
2index 8265ce4..90a4ce5 100644
3--- a/config.yaml
4+++ b/config.yaml
5@@ -24,6 +24,36 @@ options:
6 type: string
7 description: |
8 Policy map to enforce TLS/SSL
9+ enable_spamassassin:
10+ default: true
11+ type: boolean
12+ description: |
13+ Enable Spamassassin mail filter
14+ enable_spf:
15+ default: true
16+ type: boolean
17+ description: |
18+ Enable Postfix SPF extensions
19+ spamassassin_policy:
20+ default: ""
21+ type: string
22+ description: |
23+ Custom Spamassassin policies
24+ spf_config:
25+ default: |
26+ debugLevel = 1
27+ defaultSeedOnly = 1
28+
29+ HELO_reject = SPF_Not_Pass
30+ Mail_From_reject = Fail
31+
32+ PermError_reject = False
33+ TempError_Defer = False
34+
35+ skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1/128
36+ type: string
37+ description: |
38+ Custom SPF configuration
39 nagios_context:
40 default: "juju"
41 type: string
42diff --git a/files/spamassassin_master.cf b/files/spamassassin_master.cf
43new file mode 100644
44index 0000000..41a5ca9
45--- /dev/null
46+++ b/files/spamassassin_master.cf
47@@ -0,0 +1,2 @@
48+spamassassin unix - n n - - pipe
49+ flags=FR user=spamd argv=/usr/bin/spamc -e /usr/sbin/sendmail -G -i -f ${sender} -- ${recipient}
50diff --git a/files/spf_master.cf b/files/spf_master.cf
51new file mode 100644
52index 0000000..fc349ca
53--- /dev/null
54+++ b/files/spf_master.cf
55@@ -0,0 +1,2 @@
56+policy-spf unix - n n - - spawn
57+ user=nobody argv=/usr/bin/policyd-spf
58diff --git a/layer.yaml b/layer.yaml
59index d297df3..c6e1d2c 100644
60--- a/layer.yaml
61+++ b/layer.yaml
62@@ -10,3 +10,6 @@ options:
63 packages:
64 - mailman3
65 - postfix
66+ - postfix-policyd-spf-python
67+ - spamassassin
68+ - spamc
69diff --git a/reactive/mailman3_core.py b/reactive/mailman3_core.py
70index df82b1e..1eddf2a 100644
71--- a/reactive/mailman3_core.py
72+++ b/reactive/mailman3_core.py
73@@ -1,4 +1,5 @@
74 import os
75+import pwd
76 import random
77 import string
78 import subprocess
79@@ -94,6 +95,11 @@ def configure_postfix():
80 subprocess.check_output(['postconf', 'always_add_missing_headers=yes',
81 'local_header_rewrite_clients=static:all'])
82
83+ if config["enable_spamassassin"]:
84+ configure_spamassassin()
85+ if config["enable_spf"]:
86+ configure_postfix_spf()
87+
88 host.service_reload('postfix')
89 # Open the SMTP port
90 hookenv.open_port(25)
91@@ -101,6 +107,52 @@ def configure_postfix():
92 hookenv.status_set('active', 'ready')
93
94
95+def configure_spamassassin():
96+ config = hookenv.config()
97+ default_config = ('ENABLED=0\n'
98+ 'OPTIONS="--create-prefs --max-children 5 --pidfile=/var/run/spamassassin.pid --username spamd --helper-home-dir /var/lib/spamd -s /var/log/spamd.log"\n'
99+ 'CRON=1\n')
100+ sa_policy = config["spamassassin_policy"]
101+ render(source="sa-local.cf.tmpl",
102+ target="/etc/spamassassin/local.cf",
103+ owner="spamd",
104+ group="spamd",
105+ perms=0o640,
106+ context={'policy': sa_policy})
107+
108+ host.write_file("/etc/default/spamassassin", default_config)
109+ if not postconf(key="spamassassin", args=("-F",)): # Check to see if Spamassassin is already configured.
110+ postfix_master_cf = os.path.join(hookenv.charm_dir(), "files/spamassassin_master.cf")
111+ with open(postfix_master_cf) as f:
112+ append_to_file("/etc/postfix/master.cf", f.read())
113+ postconf(key="smtp/inet/content_filter", value="spamassassin:dummy", args=("-P",))
114+
115+ useradd("spamd")
116+ systemctl("reload", "postfix.service")
117+ systemctl("enable", "spamassassin.service")
118+ systemctl("start", "spamassassin.service")
119+ set_flag('mailman.postfix.spamassassin.configured')
120+
121+
122+def configure_postfix_spf():
123+ config = hookenv.config()
124+ postconf(key="policy-spf_time_limit", value="3600s")
125+ postconf(key="smtpd_recipient_restrictions", value=(
126+ "permit_sasl_authenticated "
127+ "permit_mynetworks "
128+ "reject_unauth_destination "
129+ "check_policy_service unix:private/policy-spf"
130+ ))
131+ if not postconf(key="policy-spf", args=("-F",)): # Check to see if spf is already configured.
132+ spf_master_cf = os.path.join(hookenv.charm_dir(), "files/spf_master.cf")
133+ with open(spf_master_cf) as f:
134+ append_to_file("/etc/postfix/master.cf", f.read())
135+
136+ host.write_file("/etc/postfix-policyd-spf-python/policyd-spf.conf", config["spf_config"])
137+ systemctl("reload", "postfix.service")
138+ set_flag('mailman.postfix.spf.configured')
139+
140+
141 # HyperKitty's recommended dependencies include, basically, all of Django so
142 # we go ahead and purge that, we just want the mailman plugin, not the whole
143 # thing.
144@@ -234,3 +286,34 @@ def setup_nagios(nagios):
145
146 nrpe_setup.write()
147 hookenv.status_set('active', 'ready')
148+
149+
150+def systemctl(action, service_name):
151+ return subprocess.check_call(["systemctl", action, service_name])
152+
153+
154+def useradd(username):
155+ cmd = ["useradd",
156+ "-r",
157+ "-k", "/dev/null",
158+ "-md", "/var/lib/spamd",
159+ "-s", "/bin/false",
160+ username]
161+ try:
162+ pwd.getpwnam(username)
163+ except KeyError:
164+ return subprocess.check_call(cmd)
165+
166+
167+def postconf(key, value=None, args=None):
168+ cmd = ['postconf', '{k}'.format(k=key)]
169+ if value:
170+ cmd = ['postconf', '{k}={v}'.format(k=key, v=value)]
171+ if args:
172+ [cmd.insert(1, a) for a in reversed(args)]
173+ return subprocess.check_output(cmd)
174+
175+
176+def append_to_file(filep, content):
177+ with open(filep, "a") as f:
178+ f.write(content)
179diff --git a/templates/sa-local.cf.tmpl b/templates/sa-local.cf.tmpl
180new file mode 100644
181index 0000000..682699c
182--- /dev/null
183+++ b/templates/sa-local.cf.tmpl
184@@ -0,0 +1,4 @@
185+# This file is managed by Juju
186+{{ policy }}
187+ifplugin Mail::SpamAssassin::Plugin::Shortcircuit
188+endif # Mail::SpamAssassin::Plugin::Shortcircuit

Subscribers

People subscribed via source and target branches