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
diff --git a/config.yaml b/config.yaml
index 8265ce4..90a4ce5 100644
--- a/config.yaml
+++ b/config.yaml
@@ -24,6 +24,36 @@ options:
24 type: string24 type: string
25 description: |25 description: |
26 Policy map to enforce TLS/SSL26 Policy map to enforce TLS/SSL
27 enable_spamassassin:
28 default: true
29 type: boolean
30 description: |
31 Enable Spamassassin mail filter
32 enable_spf:
33 default: true
34 type: boolean
35 description: |
36 Enable Postfix SPF extensions
37 spamassassin_policy:
38 default: ""
39 type: string
40 description: |
41 Custom Spamassassin policies
42 spf_config:
43 default: |
44 debugLevel = 1
45 defaultSeedOnly = 1
46
47 HELO_reject = SPF_Not_Pass
48 Mail_From_reject = Fail
49
50 PermError_reject = False
51 TempError_Defer = False
52
53 skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1/128
54 type: string
55 description: |
56 Custom SPF configuration
27 nagios_context:57 nagios_context:
28 default: "juju"58 default: "juju"
29 type: string59 type: string
diff --git a/files/spamassassin_master.cf b/files/spamassassin_master.cf
30new file mode 10064460new file mode 100644
index 0000000..41a5ca9
--- /dev/null
+++ b/files/spamassassin_master.cf
@@ -0,0 +1,2 @@
1spamassassin unix - n n - - pipe
2 flags=FR user=spamd argv=/usr/bin/spamc -e /usr/sbin/sendmail -G -i -f ${sender} -- ${recipient}
diff --git a/files/spf_master.cf b/files/spf_master.cf
0new file mode 1006443new file mode 100644
index 0000000..fc349ca
--- /dev/null
+++ b/files/spf_master.cf
@@ -0,0 +1,2 @@
1policy-spf unix - n n - - spawn
2 user=nobody argv=/usr/bin/policyd-spf
diff --git a/layer.yaml b/layer.yaml
index d297df3..c6e1d2c 100644
--- a/layer.yaml
+++ b/layer.yaml
@@ -10,3 +10,6 @@ options:
10 packages:10 packages:
11 - mailman311 - mailman3
12 - postfix12 - postfix
13 - postfix-policyd-spf-python
14 - spamassassin
15 - spamc
diff --git a/reactive/mailman3_core.py b/reactive/mailman3_core.py
index df82b1e..1eddf2a 100644
--- a/reactive/mailman3_core.py
+++ b/reactive/mailman3_core.py
@@ -1,4 +1,5 @@
1import os1import os
2import pwd
2import random3import random
3import string4import string
4import subprocess5import subprocess
@@ -94,6 +95,11 @@ def configure_postfix():
94 subprocess.check_output(['postconf', 'always_add_missing_headers=yes',95 subprocess.check_output(['postconf', 'always_add_missing_headers=yes',
95 'local_header_rewrite_clients=static:all'])96 'local_header_rewrite_clients=static:all'])
9697
98 if config["enable_spamassassin"]:
99 configure_spamassassin()
100 if config["enable_spf"]:
101 configure_postfix_spf()
102
97 host.service_reload('postfix')103 host.service_reload('postfix')
98 # Open the SMTP port104 # Open the SMTP port
99 hookenv.open_port(25)105 hookenv.open_port(25)
@@ -101,6 +107,52 @@ def configure_postfix():
101 hookenv.status_set('active', 'ready')107 hookenv.status_set('active', 'ready')
102108
103109
110def configure_spamassassin():
111 config = hookenv.config()
112 default_config = ('ENABLED=0\n'
113 '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'
114 'CRON=1\n')
115 sa_policy = config["spamassassin_policy"]
116 render(source="sa-local.cf.tmpl",
117 target="/etc/spamassassin/local.cf",
118 owner="spamd",
119 group="spamd",
120 perms=0o640,
121 context={'policy': sa_policy})
122
123 host.write_file("/etc/default/spamassassin", default_config)
124 if not postconf(key="spamassassin", args=("-F",)): # Check to see if Spamassassin is already configured.
125 postfix_master_cf = os.path.join(hookenv.charm_dir(), "files/spamassassin_master.cf")
126 with open(postfix_master_cf) as f:
127 append_to_file("/etc/postfix/master.cf", f.read())
128 postconf(key="smtp/inet/content_filter", value="spamassassin:dummy", args=("-P",))
129
130 useradd("spamd")
131 systemctl("reload", "postfix.service")
132 systemctl("enable", "spamassassin.service")
133 systemctl("start", "spamassassin.service")
134 set_flag('mailman.postfix.spamassassin.configured')
135
136
137def configure_postfix_spf():
138 config = hookenv.config()
139 postconf(key="policy-spf_time_limit", value="3600s")
140 postconf(key="smtpd_recipient_restrictions", value=(
141 "permit_sasl_authenticated "
142 "permit_mynetworks "
143 "reject_unauth_destination "
144 "check_policy_service unix:private/policy-spf"
145 ))
146 if not postconf(key="policy-spf", args=("-F",)): # Check to see if spf is already configured.
147 spf_master_cf = os.path.join(hookenv.charm_dir(), "files/spf_master.cf")
148 with open(spf_master_cf) as f:
149 append_to_file("/etc/postfix/master.cf", f.read())
150
151 host.write_file("/etc/postfix-policyd-spf-python/policyd-spf.conf", config["spf_config"])
152 systemctl("reload", "postfix.service")
153 set_flag('mailman.postfix.spf.configured')
154
155
104# HyperKitty's recommended dependencies include, basically, all of Django so156# HyperKitty's recommended dependencies include, basically, all of Django so
105# we go ahead and purge that, we just want the mailman plugin, not the whole157# we go ahead and purge that, we just want the mailman plugin, not the whole
106# thing.158# thing.
@@ -234,3 +286,34 @@ def setup_nagios(nagios):
234286
235 nrpe_setup.write()287 nrpe_setup.write()
236 hookenv.status_set('active', 'ready')288 hookenv.status_set('active', 'ready')
289
290
291def systemctl(action, service_name):
292 return subprocess.check_call(["systemctl", action, service_name])
293
294
295def useradd(username):
296 cmd = ["useradd",
297 "-r",
298 "-k", "/dev/null",
299 "-md", "/var/lib/spamd",
300 "-s", "/bin/false",
301 username]
302 try:
303 pwd.getpwnam(username)
304 except KeyError:
305 return subprocess.check_call(cmd)
306
307
308def postconf(key, value=None, args=None):
309 cmd = ['postconf', '{k}'.format(k=key)]
310 if value:
311 cmd = ['postconf', '{k}={v}'.format(k=key, v=value)]
312 if args:
313 [cmd.insert(1, a) for a in reversed(args)]
314 return subprocess.check_output(cmd)
315
316
317def append_to_file(filep, content):
318 with open(filep, "a") as f:
319 f.write(content)
diff --git a/templates/sa-local.cf.tmpl b/templates/sa-local.cf.tmpl
237new file mode 100644320new file mode 100644
index 0000000..682699c
--- /dev/null
+++ b/templates/sa-local.cf.tmpl
@@ -0,0 +1,4 @@
1# This file is managed by Juju
2{{ policy }}
3ifplugin Mail::SpamAssassin::Plugin::Shortcircuit
4endif # Mail::SpamAssassin::Plugin::Shortcircuit

Subscribers

People subscribed via source and target branches