diff options
author | Guilhem Moulin <guilhem@fripost.org> | 2021-01-26 12:39:10 +0100 |
---|---|---|
committer | Guilhem Moulin <guilhem@fripost.org> | 2021-01-26 13:35:40 +0100 |
commit | 44100bab38d32596392a3bc7199b4daa202b4032 (patch) | |
tree | 3007929247752cb2957caaa4cd3d8938f39e0f34 /roles | |
parent | db5431e95fc6bc998169b272b30b5998798b56c1 (diff) |
Postfix: pin key material to our MX:es for fripost.org and its subdomains.
This solves an issue where an attacker would strip the STARTTLS keyword
from the EHLO response, thereby preventing connection upgrade; or spoof
DNS responses to route outgoing messages to an attacker-controlled
SMTPd, thereby allowing message MiTM'ing. With key material pinning in
place, smtp(8postfix) immediately aborts the connection (before the MAIL
command) and places the message into the deferred queue instead:
postfix-out/smtp[NNN]: … dsn=4.7.5, status=undeliverable (Server certificate not verified)
This applies to the smarthost as well as for verification probes on the
Mail Submission Agent. Placing message into the deferred queue might
yield denial of service, but we argue that it's better than a privacy
leak.
This only covers *internal messages* (from Fripost to Fripost) though:
only messages with ‘fripost.org’ (or a subdomain of such) as recipient
domain. Other domains, even those using mx[12].fripost.org as MX, are
not covered. A scalable solution for arbitrary domains would involve
either DANE and TLSA records, or MTA-STS [RFC8461]. Regardless, there
is some merit in hardcoding our internal policy (when the client and
server are both under our control) in the configuration. It for
instance enables us to harden TLS ciphers and protocols, and makes the
verification logic independent of DNS.
Diffstat (limited to 'roles')
-rw-r--r-- | roles/MSA/tasks/main.yml | 13 | ||||
l--------- | roles/MSA/templates/etc/postfix/smtp_tls_policy.j2 | 1 | ||||
-rw-r--r-- | roles/common/templates/etc/postfix/master.cf.j2 | 2 | ||||
-rw-r--r-- | roles/out/tasks/main.yml | 13 | ||||
-rw-r--r-- | roles/out/templates/etc/postfix/main.cf.j2 | 5 | ||||
-rw-r--r-- | roles/out/templates/etc/postfix/smtp_tls_policy.j2 | 12 |
6 files changed, 45 insertions, 1 deletions
diff --git a/roles/MSA/tasks/main.yml b/roles/MSA/tasks/main.yml index 4b38974..bf17702 100644 --- a/roles/MSA/tasks/main.yml +++ b/roles/MSA/tasks/main.yml @@ -23,40 +23,53 @@ - name: Create '_postfix-sender-login' user user: name=_postfix-sender-login system=yes group=nogroup createhome=no home=/nonexistent shell=/usr/sbin/nologin password=! state=present - name: Copy Postfix sender login socketmap systemd unit files copy: src=etc/systemd/system/{{ item }} dest=/etc/systemd/system/{{ item }} owner=root group=root mode=0644 with_items: - postfix-sender-login.service - postfix-sender-login.socket notify: - systemctl daemon-reload +- name: Copy the SMTP TLS policy maps + template: src=etc/postfix/smtp_tls_policy.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/smtp_tls_policy + owner=root group=root + mode=0644 + +- name: Compile the SMTP TLS policy maps + postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/smtp_tls_policy db=lmdb + owner=root group=root + mode=0644 + notify: + - Reload Postfix + - meta: flush_handlers - name: Enable Postfix sender login socketmap service: name=postfix-sender-login.socket state=started enabled=yes - name: Configure Postfix template: src=etc/postfix/{{ item }}.j2 dest=/etc/postfix-{{ postfix_instance[inst].name }}/{{ item }} owner=root group=root mode=0644 with_items: - main.cf - master.cf notify: - Reload Postfix - name: Copy the Regex to anonymize senders # no need to reload upon change, as cleanup(8) is short-running copy: src=etc/postfix/anonymize_sender.pcre dest=/etc/postfix-{{ postfix_instance[inst].name }}/anonymize_sender.pcre diff --git a/roles/MSA/templates/etc/postfix/smtp_tls_policy.j2 b/roles/MSA/templates/etc/postfix/smtp_tls_policy.j2 new file mode 120000 index 0000000..b40876f --- /dev/null +++ b/roles/MSA/templates/etc/postfix/smtp_tls_policy.j2 @@ -0,0 +1 @@ +../../../../out/templates/etc/postfix/smtp_tls_policy.j2
\ No newline at end of file diff --git a/roles/common/templates/etc/postfix/master.cf.j2 b/roles/common/templates/etc/postfix/master.cf.j2 index f199ed0..3954085 100644 --- a/roles/common/templates/etc/postfix/master.cf.j2 +++ b/roles/common/templates/etc/postfix/master.cf.j2 @@ -40,40 +40,42 @@ cleanup unix n - y - 0 cleanup qmgr unix n - n 300 1 qmgr tlsmgr unix - - y 1000? 1 tlsmgr rewrite unix - - y - - trivial-rewrite bounce unix - - y - 0 bounce defer unix - - y - 0 bounce trace unix - - y - 0 bounce verify unix - - y - 1 verify flush unix n - y 1000? 0 flush proxymap unix - - n - - proxymap proxywrite unix - - n - 1 proxymap smtp unix - - y - - smtp # -o smtp_helo_timeout=5 -o smtp_connect_timeout=5 {% if inst is defined and inst == 'MSA' %} smtp_verify unix - - y - - smtp -o smtp_helo_name=noreply.$mydomain -o smtp_tls_security_level=may -o smtp_tls_ciphers=medium -o smtp_tls_protocols=!SSLv2,!SSLv3 -o smtp_tls_note_starttls_offer=yes -o smtp_tls_session_cache_database=lmdb:$data_directory/smtp_tls_session_cache + -o smtp_tls_fingerprint_digest=sha256 + -o smtp_tls_policy_maps=lmdb:$config_directory/smtp_tls_policy {% endif %} relay unix - - y - - smtp showq unix n - y - - showq error unix - - y - - error retry unix - - y - - error discard unix - - y - - discard local unix - n n - - local virtual unix - n n - - virtual lmtp unix - - y - - lmtp anvil unix - - y - 1 anvil scache unix - - y - 1 scache {% if inst is defined and inst == 'MSA' %} policyd-spf unix - n n - 0 spawn user=policyd-spf argv=/usr/bin/policyd-spf {% endif %} {% if inst is defined and inst == 'MX' %} reserved-alias unix - n n - - pipe flags=Rhu user=nobody argv=/usr/local/bin/reserved-alias.pl ${sender} ${original_recipient} @fripost.org {% endif %} {% if inst is defined and inst == 'lists' %} diff --git a/roles/out/tasks/main.yml b/roles/out/tasks/main.yml index 48c162a..7a297f1 100644 --- a/roles/out/tasks/main.yml +++ b/roles/out/tasks/main.yml @@ -11,40 +11,53 @@ owner=root group=root mode=0644 with_items: - main.cf - master.cf notify: - Reload Postfix - name: Copy the canonical maps template: src=etc/postfix/canonical.j2 dest=/etc/postfix-{{ postfix_instance[inst].name }}/canonical owner=root group=root mode=0644 - name: Compile the canonical maps # no need to reload upon change, as cleanup(8) is short-running postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/canonical db=lmdb owner=root group=root mode=0644 +- name: Copy the SMTP TLS policy maps + template: src=etc/postfix/smtp_tls_policy.j2 + dest=/etc/postfix-{{ postfix_instance[inst].name }}/smtp_tls_policy + owner=root group=root + mode=0644 + +- name: Compile the SMTP TLS policy maps + postmap: cmd=postmap src=/etc/postfix-{{ postfix_instance[inst].name }}/smtp_tls_policy db=lmdb + owner=root group=root + mode=0644 + notify: + - Reload Postfix + - meta: flush_handlers - name: Start Postfix service: name=postfix state=started - name: Install 'postfix_mailqueue_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_mailqueue_ dest=/etc/munin/plugins/postfix_mailqueue_postfix-{{ postfix_instance[inst].name }} owner=root group=root state=link force=yes tags: - munin - munin-node notify: - Restart munin-node - name: Install 'postfix_stats_' Munin wildcard plugin file: src=/usr/local/share/munin/plugins/postfix_stats_ dest=/etc/munin/plugins/postfix_stats_{{ item }}_postfix-{{ postfix_instance[inst].name }} diff --git a/roles/out/templates/etc/postfix/main.cf.j2 b/roles/out/templates/etc/postfix/main.cf.j2 index c05d9a5..f8aa55a 100644 --- a/roles/out/templates/etc/postfix/main.cf.j2 +++ b/roles/out/templates/etc/postfix/main.cf.j2 @@ -39,41 +39,44 @@ local_recipient_maps = message_size_limit = 0 recipient_delimiter = + relay_domains = relay_transport = error:5.3.2 Relay Transport unavailable # Replace internal system addresses under $myhostname with a valid address canonical_maps = lmdb:$config_directory/canonical canonical_classes = envelope_sender, envelope_recipient # All header rewriting happens upstream local_header_rewrite_clients = smtp_tls_security_level = may smtp_tls_ciphers = medium smtp_tls_protocols = !SSLv2, !SSLv3 smtp_tls_note_starttls_offer = yes smtp_tls_session_cache_database = lmdb:$data_directory/smtp_tls_session_cache -smtpd_tls_security_level = none +smtp_tls_fingerprint_digest = sha256 +smtp_tls_policy_maps = lmdb:$config_directory/smtp_tls_policy + +smtpd_tls_security_level = none strict_rfc821_envelopes = yes smtpd_delay_reject = yes disable_vrfy_command = yes smtpd_client_restrictions = permit_mynetworks # We are the only ones using this proxy, but if things go wrong we # want to know why defer smtpd_helo_required = yes smtpd_helo_restrictions = reject_invalid_helo_hostname smtpd_sender_restrictions = reject_non_fqdn_sender smtpd_relay_restrictions = reject_non_fqdn_recipient diff --git a/roles/out/templates/etc/postfix/smtp_tls_policy.j2 b/roles/out/templates/etc/postfix/smtp_tls_policy.j2 new file mode 100644 index 0000000..7722dc8 --- /dev/null +++ b/roles/out/templates/etc/postfix/smtp_tls_policy.j2 @@ -0,0 +1,12 @@ +# Lookup table matching next-hop destinations to TLS security policies; +# this allows pining the key material for chosen recipient domains. +# +# {{ ansible_managed }} +# Do NOT edit this file directly! +{% for nexthop in ['fripost.org','.fripost.org'] %} + +{{ nexthop }} fingerprint ciphers=high protocols=!SSLv2:!SSLv3:!TLSv1:!TLSv1.1 +{% for h in groups.MX | sort %} + match={{ lookup('pipe', 'openssl pkey -pubin -outform DER <"certs/public/mx'+(hostvars[h].mxno | default('') | string)+'.fripost.org.pub" | openssl dgst -sha256 -c | sed "s/[^=]*=\s*//"') }} +{% endfor %} +{% endfor %} |