Merge ~hloeung/smtp-relay-charm:mx into smtp-relay-charm:master

Proposed by Haw Loeung
Status: Merged
Approved by: Haw Loeung
Approved revision: 123e54e62417f3dad87ae94310795763b41e8b18
Merged at revision: c6c92aa26414b5c6f3f34f1daf5853f7af24ee73
Proposed branch: ~hloeung/smtp-relay-charm:mx
Merge into: smtp-relay-charm:master
Diff against target: 401 lines (+296/-5)
7 files modified
config.yaml (+18/-0)
reactive/smtp_relay.py (+12/-4)
templates/postfix_main_cf.tmpl (+9/-1)
tests/unit/files/postfix_main_transport_maps.cf (+59/-0)
tests/unit/files/postfix_main_transport_maps_with_header_checks.cf (+61/-0)
tests/unit/files/postfix_main_transport_maps_with_virtual_alias_maps.cf (+61/-0)
tests/unit/test_smtp_relay.py (+76/-0)
Reviewer Review Type Date Requested Status
Colin Misare Approve
Canonical IS Reviewers Pending
Review via email: mp+416357@code.launchpad.net

Commit message

Add support for running as MX by allowing transport and virtual alias maps

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
Colin Misare (cmisare) wrote :

One nitpick but LGTM either way

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

Change successfully merged at revision c6c92aa26414b5c6f3f34f1daf5853f7af24ee73

Revision history for this message
Joel Sing (jsing) :
Revision history for this message
Haw Loeung (hloeung) wrote :

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 19eab05..b3d6470 100644
3--- a/config.yaml
4+++ b/config.yaml
5@@ -159,3 +159,21 @@ options:
6 outbound to other MTAs as it may cause deliverability issues.
7
8 http://www.postfix.org/postconf.5.html#smtpd_tls_security_level
9+ transport_maps:
10+ type: string
11+ default: ''
12+ description: |
13+ Optional lookup tables with mappings from recipient address to
14+ message delivery transport or next-hop destination (use 'MANUAL'
15+ to indicate it is managed outside of juju configs).
16+
17+ http://www.postfix.org/postconf.5.html#transport_maps
18+ virtual_alias_maps:
19+ type: string
20+ default: ''
21+ description: |
22+ Optional lookup tables that alias specific mail addresses or
23+ domains to other local or remote addresses (use 'MANUAL' to
24+ indicate it is managed outside of juju configs).
25+
26+ http://www.postfix.org/postconf.5.html#virtual_alias_maps
27diff --git a/reactive/smtp_relay.py b/reactive/smtp_relay.py
28index 8fb59c3..58ec8b3 100644
29--- a/reactive/smtp_relay.py
30+++ b/reactive/smtp_relay.py
31@@ -122,6 +122,8 @@ def configure_smtp_auth(dovecot_config='/etc/dovecot/dovecot.conf', dovecot_user
32 'config.changed.tls_policy_maps',
33 'config.changed.tls_protocols',
34 'config.changed.tls_security_level',
35+ 'config.changed.transport_maps',
36+ 'config.changed.virtual_alias_maps',
37 )
38 def config_changed():
39 reactive.clear_flag('smtp-relay.configured')
40@@ -152,7 +154,9 @@ def _create_update_map(content, postmap):
41 os.utime(pmfname, None)
42 changed = True
43
44- if not content.startswith('MANUAL'):
45+ if content.startswith('MANUAL'):
46+ hookenv.log('Map {} manually managed'.format(pmfname))
47+ else:
48 contents = JUJU_HEADER + content + '\n'
49 changed = _write_file(contents, pmfname) or changed
50
51@@ -220,6 +224,8 @@ def configure_smtp_relay(postfix_conf_dir='/etc/postfix', tls_dh_params='/etc/ss
52 'tls_exclude_ciphers': config['tls_exclude_ciphers'],
53 'tls_protocols': config['tls_protocols'],
54 'tls_security_level': config['tls_security_level'],
55+ 'transport_maps': True if config['transport_maps'] else False,
56+ 'virtual_alias_maps': True if config['virtual_alias_maps'] else False,
57 }
58 base = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
59 env = jinja2.Environment(loader=jinja2.FileSystemLoader(base))
60@@ -237,6 +243,8 @@ def configure_smtp_relay(postfix_conf_dir='/etc/postfix', tls_dh_params='/etc/ss
61 'sender_access': 'hash:{}'.format(os.path.join(postfix_conf_dir, 'access')),
62 'sender_login_maps': 'hash:{}'.format(os.path.join(postfix_conf_dir, 'sender_login')),
63 'tls_policy_maps': 'hash:{}'.format(os.path.join(postfix_conf_dir, 'tls_policy')),
64+ 'transport_maps': 'hash:{}'.format(os.path.join(postfix_conf_dir, 'transport')),
65+ 'virtual_alias_maps': 'hash:{}'.format(os.path.join(postfix_conf_dir, 'virtual_alias')),
66 }
67 sender_access_content = config['restrict_sender_access']
68 if sender_access_content and not sender_access_content.startswith('MANUAL'):
69@@ -251,6 +259,8 @@ def configure_smtp_relay(postfix_conf_dir='/etc/postfix', tls_dh_params='/etc/ss
70 'sender_access': sender_access_content,
71 'sender_login_maps': config['sender_login_maps'],
72 'tls_policy_maps': config['tls_policy_maps'],
73+ 'transport_maps': config['transport_maps'],
74+ 'virtual_alias_maps': config['virtual_alias_maps'],
75 }
76
77 # Ensure various maps exists before starting/restarting postfix.
78@@ -365,9 +375,7 @@ def _write_file(source, dest_path, perms=0o644, owner=None, group=None):
79 """Write file only on changes and return True if changes written."""
80 # Compare and only write out file on change.
81 dest = ''
82- if not os.path.exists(dest_path):
83- with open(dest_path, 'a') as f:
84- os.utime(dest_path, None)
85+
86 try:
87 with open(dest_path, 'r') as f:
88 dest = f.read()
89diff --git a/templates/postfix_main_cf.tmpl b/templates/postfix_main_cf.tmpl
90index a555c4b..c92ee6f 100644
91--- a/templates/postfix_main_cf.tmpl
92+++ b/templates/postfix_main_cf.tmpl
93@@ -98,6 +98,14 @@ restricted = check_recipient_access hash:/etc/postfix/restricted_recipients, rej
94 smtpd_milters = {{milter}}
95 non_smtpd_milters = {{milter}}
96 {% endif -%}
97-{% if header_checks %}
98+
99+{%- if header_checks %}
100 header_checks = regexp:/etc/postfix/header_checks
101+{% endif -%}
102+
103+{%- if transport_maps %}
104+transport_maps = hash:/etc/postfix/transport
105+{% endif %}
106+{%- if virtual_alias_maps %}
107+virtual_alias_maps = hash:/etc/postfix/virtual_alias
108 {% endif %}
109diff --git a/tests/unit/files/postfix_main_transport_maps.cf b/tests/unit/files/postfix_main_transport_maps.cf
110new file mode 100644
111index 0000000..a80d6b8
112--- /dev/null
113+++ b/tests/unit/files/postfix_main_transport_maps.cf
114@@ -0,0 +1,59 @@
115+## This file is Juju managed - do not edit by hand #
116+
117+
118+smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
119+biff = no
120+
121+append_dot_mydomain = no
122+
123+readme_directory = no
124+
125+# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
126+# fresh installs.
127+compatibility_level = 2
128+
129+# TLS parameters
130+tls_preempt_cipherlist = yes
131+smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem
132+smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key
133+smtpd_tls_dh1024_param_file = /etc/ssl/private/dhparams.pem
134+smtpd_tls_ciphers = HIGH
135+smtpd_tls_exclude_ciphers = aNULL, eNULL, DES, 3DES, MD5, RC4, CAMELLIA
136+smtpd_tls_loglevel = 1
137+smtpd_tls_mandatory_ciphers = HIGH
138+smtpd_tls_mandatory_protocols = !SSLv2 !SSLv3
139+smtpd_tls_protocols = !SSLv2 !SSLv3
140+smtpd_tls_received_header = yes
141+smtpd_tls_security_level = may
142+smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
143+
144+smtp_tls_CApath = /etc/ssl/certs
145+smtp_tls_loglevel = 1
146+smtp_tls_security_level = may
147+smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
148+
149+message_size_limit = 61440000
150+strict_rfc821_envelopes = yes
151+smtpd_client_connection_count_limit = 100
152+smtpd_helo_required = yes
153+smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination
154+myhostname = juju-87625f-hloeung-94.openstacklocal
155+alias_maps = hash:/etc/aliases
156+alias_database = hash:/etc/aliases
157+mydestination = $myhostname, juju-87625f-hloeung-94, localhost.localdomain, localhost
158+relayhost =
159+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
160+mailbox_size_limit = 0
161+recipient_delimiter = +
162+inet_interfaces = all
163+inet_protocols = all
164+
165+broken_sasl_auth_clients = yes
166+smtpd_sasl_auth_enable = yes
167+smtpd_sasl_security_options = noanonymous
168+smtpd_sasl_type = dovecot
169+smtpd_sasl_path = private/auth
170+smtpd_tls_auth_only = yes
171+smtpd_sender_restrictions = reject_unknown_sender_domain, check_sender_access hash:/etc/postfix/access
172+
173+transport_maps = hash:/etc/postfix/transport
174diff --git a/tests/unit/files/postfix_main_transport_maps_with_header_checks.cf b/tests/unit/files/postfix_main_transport_maps_with_header_checks.cf
175new file mode 100644
176index 0000000..34e055c
177--- /dev/null
178+++ b/tests/unit/files/postfix_main_transport_maps_with_header_checks.cf
179@@ -0,0 +1,61 @@
180+## This file is Juju managed - do not edit by hand #
181+
182+
183+smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
184+biff = no
185+
186+append_dot_mydomain = no
187+
188+readme_directory = no
189+
190+# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
191+# fresh installs.
192+compatibility_level = 2
193+
194+# TLS parameters
195+tls_preempt_cipherlist = yes
196+smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem
197+smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key
198+smtpd_tls_dh1024_param_file = /etc/ssl/private/dhparams.pem
199+smtpd_tls_ciphers = HIGH
200+smtpd_tls_exclude_ciphers = aNULL, eNULL, DES, 3DES, MD5, RC4, CAMELLIA
201+smtpd_tls_loglevel = 1
202+smtpd_tls_mandatory_ciphers = HIGH
203+smtpd_tls_mandatory_protocols = !SSLv2 !SSLv3
204+smtpd_tls_protocols = !SSLv2 !SSLv3
205+smtpd_tls_received_header = yes
206+smtpd_tls_security_level = may
207+smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
208+
209+smtp_tls_CApath = /etc/ssl/certs
210+smtp_tls_loglevel = 1
211+smtp_tls_security_level = may
212+smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
213+
214+message_size_limit = 61440000
215+strict_rfc821_envelopes = yes
216+smtpd_client_connection_count_limit = 100
217+smtpd_helo_required = yes
218+smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination
219+myhostname = juju-87625f-hloeung-94.openstacklocal
220+alias_maps = hash:/etc/aliases
221+alias_database = hash:/etc/aliases
222+mydestination = $myhostname, juju-87625f-hloeung-94, localhost.localdomain, localhost
223+relayhost =
224+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
225+mailbox_size_limit = 0
226+recipient_delimiter = +
227+inet_interfaces = all
228+inet_protocols = all
229+
230+broken_sasl_auth_clients = yes
231+smtpd_sasl_auth_enable = yes
232+smtpd_sasl_security_options = noanonymous
233+smtpd_sasl_type = dovecot
234+smtpd_sasl_path = private/auth
235+smtpd_tls_auth_only = yes
236+smtpd_sender_restrictions = reject_unknown_sender_domain, check_sender_access hash:/etc/postfix/access
237+
238+header_checks = regexp:/etc/postfix/header_checks
239+
240+transport_maps = hash:/etc/postfix/transport
241diff --git a/tests/unit/files/postfix_main_transport_maps_with_virtual_alias_maps.cf b/tests/unit/files/postfix_main_transport_maps_with_virtual_alias_maps.cf
242new file mode 100644
243index 0000000..6049e7c
244--- /dev/null
245+++ b/tests/unit/files/postfix_main_transport_maps_with_virtual_alias_maps.cf
246@@ -0,0 +1,61 @@
247+## This file is Juju managed - do not edit by hand #
248+
249+
250+smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
251+biff = no
252+
253+append_dot_mydomain = no
254+
255+readme_directory = no
256+
257+# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on
258+# fresh installs.
259+compatibility_level = 2
260+
261+# TLS parameters
262+tls_preempt_cipherlist = yes
263+smtpd_tls_cert_file = /etc/ssl/certs/ssl-cert-snakeoil.pem
264+smtpd_tls_key_file = /etc/ssl/private/ssl-cert-snakeoil.key
265+smtpd_tls_dh1024_param_file = /etc/ssl/private/dhparams.pem
266+smtpd_tls_ciphers = HIGH
267+smtpd_tls_exclude_ciphers = aNULL, eNULL, DES, 3DES, MD5, RC4, CAMELLIA
268+smtpd_tls_loglevel = 1
269+smtpd_tls_mandatory_ciphers = HIGH
270+smtpd_tls_mandatory_protocols = !SSLv2 !SSLv3
271+smtpd_tls_protocols = !SSLv2 !SSLv3
272+smtpd_tls_received_header = yes
273+smtpd_tls_security_level = may
274+smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
275+
276+smtp_tls_CApath = /etc/ssl/certs
277+smtp_tls_loglevel = 1
278+smtp_tls_security_level = may
279+smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
280+
281+message_size_limit = 61440000
282+strict_rfc821_envelopes = yes
283+smtpd_client_connection_count_limit = 100
284+smtpd_helo_required = yes
285+smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination
286+myhostname = juju-87625f-hloeung-94.openstacklocal
287+alias_maps = hash:/etc/aliases
288+alias_database = hash:/etc/aliases
289+mydestination = $myhostname, juju-87625f-hloeung-94, localhost.localdomain, localhost
290+relayhost =
291+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
292+mailbox_size_limit = 0
293+recipient_delimiter = +
294+inet_interfaces = all
295+inet_protocols = all
296+
297+broken_sasl_auth_clients = yes
298+smtpd_sasl_auth_enable = yes
299+smtpd_sasl_security_options = noanonymous
300+smtpd_sasl_type = dovecot
301+smtpd_sasl_path = private/auth
302+smtpd_tls_auth_only = yes
303+smtpd_sender_restrictions = reject_unknown_sender_domain, check_sender_access hash:/etc/postfix/access
304+
305+transport_maps = hash:/etc/postfix/transport
306+
307+virtual_alias_maps = hash:/etc/postfix/virtual_alias
308diff --git a/tests/unit/test_smtp_relay.py b/tests/unit/test_smtp_relay.py
309index 366a24b..f9cdd61 100644
310--- a/tests/unit/test_smtp_relay.py
311+++ b/tests/unit/test_smtp_relay.py
312@@ -77,6 +77,8 @@ class TestCharm(unittest.TestCase):
313 'tls_policy_maps': '',
314 'tls_protocols': '!SSLv2 !SSLv3',
315 'tls_security_level': 'may',
316+ 'transport_maps': '',
317+ 'virtual_alias_maps': '',
318 }
319
320 patcher = mock.patch('charmhelpers.core.hookenv.close_port')
321@@ -905,6 +907,80 @@ someplace.local encrypt
322 @mock.patch('charms.reactive.set_flag')
323 @mock.patch('reactive.smtp_relay._get_autocert_cn')
324 @mock.patch('reactive.smtp_relay._get_milters')
325+ @mock.patch('subprocess.call')
326+ def test_configure_smtp_relay_config_transport_maps(self, call, get_milters, get_cn, set_flag, clear_flag):
327+ postfix_main_cf = os.path.join(self.tmpdir, 'main.cf')
328+ postfix_transport_maps = os.path.join(self.tmpdir, 'transport')
329+ get_cn.return_value = ''
330+ get_milters.return_value = ''
331+ self.mock_config.return_value['transport_maps'] = '.mydomain.local smtp:[smtp.mydomain.local]'
332+ smtp_relay.configure_smtp_relay(self.tmpdir)
333+ with open('tests/unit/files/postfix_main_transport_maps.cf', 'r', encoding='utf-8') as f:
334+ want = f.read()
335+ with open(postfix_main_cf, 'r', encoding='utf-8') as f:
336+ got = f.read()
337+ self.assertEqual(want, got)
338+ want = smtp_relay.JUJU_HEADER + '.mydomain.local smtp:[smtp.mydomain.local]' + "\n"
339+ with open(postfix_transport_maps, 'r', encoding='utf-8') as f:
340+ got = f.read()
341+ self.assertEqual(want, got)
342+
343+ @mock.patch('charms.reactive.clear_flag')
344+ @mock.patch('charms.reactive.set_flag')
345+ @mock.patch('reactive.smtp_relay._get_autocert_cn')
346+ @mock.patch('reactive.smtp_relay._get_milters')
347+ @mock.patch('subprocess.call')
348+ def test_configure_smtp_relay_config_transport_maps_with_header_checks(
349+ self, call, get_milters, get_cn, set_flag, clear_flag
350+ ):
351+ postfix_main_cf = os.path.join(self.tmpdir, 'main.cf')
352+ postfix_transport_maps = os.path.join(self.tmpdir, 'transport')
353+ get_cn.return_value = ''
354+ get_milters.return_value = ''
355+ self.mock_config.return_value['header_checks'] = '/^Received:/ HOLD'
356+ self.mock_config.return_value['transport_maps'] = '.mydomain.local smtp:[smtp.mydomain.local]'
357+ smtp_relay.configure_smtp_relay(self.tmpdir)
358+ with open('tests/unit/files/postfix_main_transport_maps_with_header_checks.cf', 'r', encoding='utf-8') as f:
359+ want = f.read()
360+ with open(postfix_main_cf, 'r', encoding='utf-8') as f:
361+ got = f.read()
362+ self.assertEqual(want, got)
363+ want = smtp_relay.JUJU_HEADER + '.mydomain.local smtp:[smtp.mydomain.local]' + "\n"
364+ with open(postfix_transport_maps, 'r', encoding='utf-8') as f:
365+ got = f.read()
366+ self.assertEqual(want, got)
367+
368+ @mock.patch('charms.reactive.clear_flag')
369+ @mock.patch('charms.reactive.set_flag')
370+ @mock.patch('reactive.smtp_relay._get_autocert_cn')
371+ @mock.patch('reactive.smtp_relay._get_milters')
372+ @mock.patch('subprocess.call')
373+ def test_configure_smtp_relay_config_transport_maps_with_virtual_alias_maps(
374+ self, call, get_milters, get_cn, set_flag, clear_flag
375+ ):
376+ postfix_main_cf = os.path.join(self.tmpdir, 'main.cf')
377+ postfix_virtual_alias_maps = os.path.join(self.tmpdir, 'virtual_alias')
378+ get_cn.return_value = ''
379+ get_milters.return_value = ''
380+ self.mock_config.return_value['transport_maps'] = '.mydomain.local smtp:[smtp.mydomain.local]'
381+ self.mock_config.return_value['virtual_alias_maps'] = 'abuse@mydomain.local sysadmin@mydomain.local'
382+ smtp_relay.configure_smtp_relay(self.tmpdir)
383+ with open(
384+ 'tests/unit/files/postfix_main_transport_maps_with_virtual_alias_maps.cf', 'r', encoding='utf-8'
385+ ) as f:
386+ want = f.read()
387+ with open(postfix_main_cf, 'r', encoding='utf-8') as f:
388+ got = f.read()
389+ self.assertEqual(want, got)
390+ want = smtp_relay.JUJU_HEADER + 'abuse@mydomain.local sysadmin@mydomain.local' + "\n"
391+ with open(postfix_virtual_alias_maps, 'r', encoding='utf-8') as f:
392+ got = f.read()
393+ self.assertEqual(want, got)
394+
395+ @mock.patch('charms.reactive.clear_flag')
396+ @mock.patch('charms.reactive.set_flag')
397+ @mock.patch('reactive.smtp_relay._get_autocert_cn')
398+ @mock.patch('reactive.smtp_relay._get_milters')
399 @mock.patch('reactive.smtp_relay._write_file')
400 @mock.patch('subprocess.call')
401 def test_configure_smtp_relay_flags(self, call, write_file, get_milters, get_cn, set_flag, clear_flag):

Subscribers

People subscribed via source and target branches