summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuilhem Moulin <guilhem@fripost.org>2021-01-26 12:39:10 +0100
committerGuilhem Moulin <guilhem@fripost.org>2021-01-26 13:35:40 +0100
commit44100bab38d32596392a3bc7199b4daa202b4032 (patch)
tree3007929247752cb2957caaa4cd3d8938f39e0f34
parentdb5431e95fc6bc998169b272b30b5998798b56c1 (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.
-rw-r--r--roles/MSA/tasks/main.yml13
l---------roles/MSA/templates/etc/postfix/smtp_tls_policy.j21
-rw-r--r--roles/common/templates/etc/postfix/master.cf.j22
-rw-r--r--roles/out/tasks/main.yml13
-rw-r--r--roles/out/templates/etc/postfix/main.cf.j25
-rw-r--r--roles/out/templates/etc/postfix/smtp_tls_policy.j212
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 %}