Skip to content

Commit 10bedad

Browse files
committed
MTA-STS tweaks, add status check using postfix-mta-sts-resolver, change to enforce
1 parent afc9f96 commit 10bedad

8 files changed

+90
-58
lines changed

CHANGELOG.md

+8-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
CHANGELOG
22
=========
33

4+
In Development
5+
--------------
6+
7+
Mail:
8+
9+
* An MTA-STS policy for incoming mail is now published (in DNS and over HTTPS) when the primary hostname and email address domain both have a signed TLS certificate installed.
10+
* MTA-STS reporting is enabled with reports sent to administrator@ the primary hostname.
11+
412
v0.45 (May 16, 2020)
513
--------------------
614

@@ -24,12 +32,6 @@ Web:
2432

2533
* Add a new hidden feature to set nginx alias in www/custom.yaml.
2634

27-
MTA-STS:
28-
29-
* Added support for client side MTA-STS when there is a valid SSL Certificate on the primary domain
30-
* Automatically adds reporting when alias "tlsrpt@<primary-domain>" is added.
31-
* Starts default on 'testing', but changes will be kept between MiaB Upgrades.
32-
3335
Setup:
3436

3537
* Improved error handling.

README.md

+12-7
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,20 @@ It is a one-click email appliance. There are no user-configurable setup options.
2828

2929
The components installed are:
3030

31-
* SMTP ([postfix](http://www.postfix.org/)), IMAP ([dovecot](http://dovecot.org/)), CardDAV/CalDAV ([Nextcloud](https://nextcloud.com/)), Exchange ActiveSync ([z-push](http://z-push.org/))
32-
* Webmail ([Roundcube](http://roundcube.net/)), static website hosting ([nginx](http://nginx.org/))
33-
* Spam filtering ([spamassassin](https://spamassassin.apache.org/)), greylisting ([postgrey](http://postgrey.schweikert.ch/))
34-
* DNS ([nsd4](https://www.nlnetlabs.nl/projects/nsd/)) with [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework), DKIM ([OpenDKIM](http://www.opendkim.org/)), [DMARC](https://en.wikipedia.org/wiki/DMARC), [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC), [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities), and [SSHFP](https://tools.ietf.org/html/rfc4255) records automatically set
35-
* Backups ([duplicity](http://duplicity.nongnu.org/)), firewall ([ufw](https://launchpad.net/ufw)), intrusion protection ([fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page)), system monitoring ([munin](http://munin-monitoring.org/))
31+
* SMTP ([postfix](http://www.postfix.org/)), IMAP ([dovecot](http://dovecot.org/)), CardDAV/CalDAV ([Nextcloud](https://nextcloud.com/)), and Exchange ActiveSync ([z-push](http://z-push.org/)) servers
32+
* Webmail ([Roundcube](http://roundcube.net/)), mail filter rules (also using dovecot), email client autoconfig settings (served by [nginx](http://nginx.org/))
33+
* Spam filtering ([spamassassin](https://spamassassin.apache.org/)) and greylisting ([postgrey](http://postgrey.schweikert.ch/))
34+
* DNS ([nsd4](https://www.nlnetlabs.nl/projects/nsd/)) with [SPF](https://en.wikipedia.org/wiki/Sender_Policy_Framework), DKIM ([OpenDKIM](http://www.opendkim.org/)), [DMARC](https://en.wikipedia.org/wiki/DMARC), [DNSSEC](https://en.wikipedia.org/wiki/DNSSEC), [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities), [MTA-STS](https://tools.ietf.org/html/rfc8461), and [SSHFP](https://tools.ietf.org/html/rfc4255) policy records automatically set
35+
* TLS certificates automatically provisioned [Let's Encrypt](https://letsencrypt.org/)
36+
* Backups ([duplicity](http://duplicity.nongnu.org/)), firewall ([ufw](https://launchpad.net/ufw)), intrusion protection ([fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page)), and basic system monitoring ([munin](http://munin-monitoring.org/))
3637

37-
It also includes:
38+
It also includes system management tools:
3839

39-
* A control panel and API for adding/removing mail users, aliases, custom DNS records, etc. and detailed system monitoring.
40+
* Comprehensive health monitoring that checks each day that services are running, ports are open, TLS certificates are valid, and DNS records are correct
41+
* A control panel for adding/removing mail users, aliases, custom DNS records, configuring backups, etc.
42+
* An API for all of the actions on the control panel
43+
44+
It also supports static website hosting since the box is serving HTTPS anyway.
4045

4146
For more information on how Mail-in-a-Box handles your privacy, see the [security details page](security.md).
4247

management/dns_update.py

+34-24
Original file line numberDiff line numberDiff line change
@@ -304,37 +304,47 @@ def has_rec(qname, rtype, prefix=None):
304304
if not has_rec(qname, rtype):
305305
records.append((qname, rtype, value, explanation))
306306

307-
# If this is a domain name that there are email addresses configured for, i.e. "something@"
308-
# this domain name, then the domain name is a MTA-STS (https://tools.ietf.org/html/rfc8461)
309-
# Policy Domain.
307+
# If this is a domain name that there are email addresses configured for, i.e. "something@"
308+
# this domain name, then the domain name is a MTA-STS (https://tools.ietf.org/html/rfc8461)
309+
# Policy Domain.
310310
#
311-
# A "_mta-sts" TXT record signals the presence of a MTA-STS policy, and an effectively random policy
312-
# ID is used to signal that a new policy may (or may not) be deployed any time the DNS is
313-
# updated.
314-
#
315-
# The policy itself is served at the "mta-sts" (no underscore) subdomain over HTTPS. The
316-
# TLS certificate used by Postfix for STARTTLS must be a valid certificate for the MX
317-
# name (PRIMARY_HOSTNAME), so we do not set an MTA-STS policy if the certificate is not
318-
# valid (e.g. because it is self-signed and a valid certificate has not yet been provisioned).
319-
get_prim_cert = get_ssl_certificates(env)[env['PRIMARY_HOSTNAME']]
320-
response = check_certificate(env['PRIMARY_HOSTNAME'], get_prim_cert['certificate'],get_prim_cert['private-key'])
321-
322-
if response[0] == 'OK' and domain in get_mail_domains(env):
311+
# A "_mta-sts" TXT record signals the presence of a MTA-STS policy, and an effectively random policy
312+
# ID is used to signal that a new policy may (or may not) be deployed any time the DNS is
313+
# updated.
314+
#
315+
# The policy itself is served at the "mta-sts" (no underscore) subdomain over HTTPS. Therefore
316+
# the TLS certificate used by Postfix for STARTTLS must be a valid certificate for the MX
317+
# domain name (PRIMARY_HOSTNAME) *and* the TLS certificate used by nginx for HTTPS on the mta-sts
318+
# subdomain must be valid certificate for that domain. Do not set an MTA-STS policy if either
319+
# certificate in use is not valid (e.g. because it is self-signed and a valid certificate has not
320+
# yet been provisioned).
321+
mta_sts_enabled = False
322+
if domain in get_mail_domains(env):
323+
# Check that PRIMARY_HOSTNAME and the mta_sts domain both have valid certificates.
324+
for d in (env['PRIMARY_HOSTNAME'], "mta-sts." + domain):
325+
cert = get_ssl_certificates(env).get(d)
326+
if not cert:
327+
break # no certificate provisioned for this domain
328+
cert_status = check_certificate(d, cert['certificate'], cert['private-key'])
329+
if cert_status[0] != 'OK':
330+
break # certificate is not valid
331+
else:
332+
# 'break' was not encountered above, so both domains are good
333+
mta_sts_enabled = True
334+
if mta_sts_enabled:
323335
mta_sts_records = [
324-
("mta-sts", "A", env["PUBLIC_IP"], "Provides MTA-STS support"),
325-
("mta-sts", "AAAA", env.get('PUBLIC_IPV6'), "Provides MTA-STS support"),
326-
("_mta-sts", "TXT", "v=STSv1; id=%sZ" % datetime.datetime.now().strftime("%Y%m%d%H%M%S"), "Enables MTA-STS support")
336+
("mta-sts", "A", env["PUBLIC_IP"], "Optional. MTA-STS Policy Host serving /.well-known/mta-sts.txt."),
337+
("mta-sts", "AAAA", env.get('PUBLIC_IPV6'), "Optional. MTA-STS Policy Host serving /.well-known/mta-sts.txt."),
338+
("_mta-sts", "TXT", "v=STSv1; id=%sZ" % datetime.datetime.now().strftime("%Y%m%d%H%M%S"), "Optional. Part of the MTA-STS policy for incoming mail. If set, a MTA-STS policy must also be published.")
327339
]
328340
# Rules can be custom configured accoring to https://tools.ietf.org/html/rfc8460.
329341
# Skip if the rules below if the user has set a custom _smtp._tls record.
330342
if not has_rec("_smtp._tls", "TXT", prefix="v=TLSRPTv1;"):
331-
# if the alias 'tlsrpt@PRIMARY_HOSTNAME' is configured, automaticly, reporting will be enabled to this email address
332-
tls_rpt_email = "tlsrpt@%s" % env['PRIMARY_HOSTNAME']
333343
tls_rpt_string = ""
334-
for alias in get_mail_aliases(env):
335-
if alias[0] == tls_rpt_email:
336-
tls_rpt_string = " rua=mailto:%s" % tls_rpt_email
337-
mta_sts_records.append(("_smtp._tls", "TXT", "v=TLSRPTv1;%s" % tls_rpt_string, "For reporting, add an email alias: 'tlsrpt@%s' or add a custom TXT record like 'v=TLSRPTv1; rua=mailto:[youremail]@%s' for reporting" % (env["PRIMARY_HOSTNAME"], env["PRIMARY_HOSTNAME"])))
344+
tls_rpt_email = env.get("MTA_STS_TLSRPT_EMAIL", "postmaster@%s" % env['PRIMARY_HOSTNAME'])
345+
if tls_rpt_email: # if a reporting address is not cleared
346+
tls_rpt_string = " rua=mailto:%s" % tls_rpt_email
347+
mta_sts_records.append(("_smtp._tls", "TXT", "v=TLSRPTv1;%s" % tls_rpt_string, "Optional. Enables MTA-STS reporting."))
338348
for qname, rtype, value, explanation in mta_sts_records:
339349
if value is None or value.strip() == "": continue # skip IPV6 if not set
340350
if not has_rec(qname, rtype):

management/status_checks.py

+20
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
# what to do next.
66

77
import sys, os, os.path, re, subprocess, datetime, multiprocessing.pool
8+
import asyncio
89

910
import dns.reversename, dns.resolver
1011
import dateutil.parser, dateutil.tz
1112
import idna
1213
import psutil
14+
import postfix_mta_sts_resolver.resolver
1315

1416
from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_records
1517
from web_update import get_web_domains, get_domains_with_a_records
@@ -327,6 +329,11 @@ def run_domain_checks(rounded_time, env, output, pool):
327329
def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records):
328330
output = BufferedOutput()
329331

332+
# When running inside Flask, the worker threads don't get a thread pool automatically.
333+
# Also this method is called in a forked worker pool, so creating a new loop is probably
334+
# a good idea.
335+
asyncio.set_event_loop(asyncio.new_event_loop())
336+
330337
# we'd move this up, but this returns non-pickleable values
331338
ssl_certificates = get_ssl_certificates(env)
332339

@@ -611,6 +618,19 @@ def check_mail_domain(domain, env, output):
611618
if mx != recommended_mx:
612619
good_news += " This configuration is non-standard. The recommended configuration is '%s'." % (recommended_mx,)
613620
output.print_ok(good_news)
621+
622+
# Check MTA-STS policy.
623+
loop = asyncio.get_event_loop()
624+
sts_resolver = postfix_mta_sts_resolver.resolver.STSResolver(loop=loop)
625+
valid, policy = loop.run_until_complete(sts_resolver.resolve(domain))
626+
if valid == postfix_mta_sts_resolver.resolver.STSFetchResult.VALID:
627+
if policy[1].get("mx") == [env['PRIMARY_HOSTNAME']] and policy[1].get("mode") == "enforce": # policy[0] is the policyid
628+
output.print_ok("MTA-STS policy is present.")
629+
else:
630+
output.print_error("MTA-STS policy is present but has unexpected settings. [{}]".format(policy[1]))
631+
else:
632+
output.print_error("MTA-STS policy is missing: {}".format(valid))
633+
614634
else:
615635
output.print_error("""This domain's DNS MX record is incorrect. It is currently set to '%s' but should be '%s'. Mail will not
616636
be delivered to this box. It may take several hours for public DNS to update after a change. This problem may result from

security.md

+6-8
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,17 @@ The box restricts the envelope sender address (also called the return path or MA
101101
Incoming Mail
102102
-------------
103103

104-
### Encryption
105-
106-
As discussed above, there is no way to require on-the-wire encryption of mail. When the box receives an incoming email (SMTP on port 25), it offers encryption (STARTTLS) but cannot require that senders use it because some senders may not support STARTTLS at all and other senders may support STARTTLS but not with the latest protocols/ciphers. To give senders the best chance at making use of encryption, the box offers protocols back to TLSv1 and ciphers with key lengths as low as 112 bits. Modern clients (senders) will make use of the 256-bit ciphers and Diffie-Hellman ciphers with a 2048-bit key for perfect forward secrecy, however. ([source](setup/mail-postfix.sh))
107-
108-
### DANE
104+
### Encryption Settings
109105

110-
When DNSSEC is enabled at the box's domain name's registrar, [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities) records are automatically published in DNS. Senders supporting DANE will enforce encryption on-the-wire between them and the box --- see the section on DANE for outgoing mail above. ([source](management/dns_update.py))
106+
As with outbound email, there is no way to require on-the-wire encryption of incoming mail from all senders. When the box receives an incoming email (SMTP on port 25), it offers encryption (STARTTLS) but cannot require that senders use it because some senders may not support STARTTLS at all and other senders may support STARTTLS but not with the latest protocols/ciphers. To give senders the best chance at making use of encryption, the box offers protocols back to TLSv1 and ciphers with key lengths as low as 112 bits. Modern clients (senders) will make use of the 256-bit ciphers and Diffie-Hellman ciphers with a 2048-bit key for perfect forward secrecy, however. ([source](setup/mail-postfix.sh))
111107

112108
### MTA-STS
113109

114-
SMTP MTA Strict Transport Security ([SMTP MTA-STS for short](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security)).
110+
The box publishes a SMTP MTA Strict Transport Security ([SMTP MTA-STS](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security)) policy (via DNS and HTTPS) in "enforce" mode. Senders that support MTA-STS will use a secure SMTP connection. (MTA-STS tells senders to connect and expect a signed TLS certificate for the "MX" domain without permitting a fallback to an unencrypted connection.)
111+
112+
### DANE
115113

116-
MTA-STS is a mechanism that instructs an SMTP server that the communication with the other SMTP server MUST be encrypted and that the domain name on the certificate should match the domain in the policy. It uses a combination of DNS and HTTPS to publish a policy that tells the sending party what to do when an encrypted channel can not be negotiated.
114+
When DNSSEC is enabled at the box's domain name's registrar, [DANE TLSA](https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities) records are automatically published in DNS. Senders supporting DANE will enforce encryption on-the-wire between them and the box --- see the section on DANE for outgoing mail above. ([source](management/dns_update.py))
117115

118116
### Filters
119117

setup/management.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ hide_output $venv/bin/pip install --upgrade pip
5050
hide_output $venv/bin/pip install --upgrade \
5151
rtyaml "email_validator>=1.0.0" "exclusiveprocess" \
5252
flask dnspython python-dateutil \
53-
"idna>=2.0.0" "cryptography==2.2.2" boto psutil
53+
"idna>=2.0.0" "cryptography==2.2.2" boto psutil postfix-mta-sts-resolver
5454

5555
# CONFIGURATION
5656

setup/start.sh

+4-8
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,10 @@ if [ ! -f $STORAGE_ROOT/mailinabox.version ]; then
8282
chown $STORAGE_USER.$STORAGE_USER $STORAGE_ROOT/mailinabox.version
8383
fi
8484

85-
# Default policy (initial) for MTA_STS = testing in the current state of inclusion.
86-
# it can be changed to "none", "testing" or "enforce". With this extention, this is preserved by
87-
# future upgrades
88-
89-
MTA_STS="${DEFAULT_MTA_STS:-testing}"
90-
9185
# Save the global options in /etc/mailinabox.conf so that standalone
92-
# tools know where to look for data.
86+
# tools know where to look for data. The default MTA_STS_MODE setting
87+
# is blank unless set by an environment variable, but see web.sh for
88+
# how that is interpreted.
9389
cat > /etc/mailinabox.conf << EOF;
9490
STORAGE_USER=$STORAGE_USER
9591
STORAGE_ROOT=$STORAGE_ROOT
@@ -98,7 +94,7 @@ PUBLIC_IP=$PUBLIC_IP
9894
PUBLIC_IPV6=$PUBLIC_IPV6
9995
PRIVATE_IP=$PRIVATE_IP
10096
PRIVATE_IPV6=$PRIVATE_IPV6
101-
MTA_STS=$MTA_STS
97+
MTA_STS_MODE=${MTA_STS_MODE-}
10298
EOF
10399

104100
# Start service configuration.

setup/web.sh

+5-4
Original file line numberDiff line numberDiff line change
@@ -126,12 +126,13 @@ chmod a+r /var/lib/mailinabox/mozilla-autoconfig.xml
126126
# nginx configuration at /.well-known/mta-sts.txt
127127
# more documentation is available on:
128128
# https://www.uriports.com/blog/mta-sts-explained/
129-
# default mode is "testing", which means: "Messages will be delivered as
130-
# though there was no failure but a report will be sent if TLS-RPT is configured"
131-
# other valid modes are: "enforce" and "none".
129+
# default mode is "enforce". Change to "testing" which means
130+
# "Messages will be delivered as though there was no failure
131+
# but a report will be sent if TLS-RPT is configured" if you
132+
# are not sure you want this yet. Or "none".
132133
PUNY_PRIMARY_HOSTNAME=$(echo "$PRIMARY_HOSTNAME" | idn2)
133134
cat conf/mta-sts.txt \
134-
| sed "s/MODE/$MTA_STS/" \
135+
| sed "s/MODE/${MTA_STS_MODE:-enforce}/" \
135136
| sed "s/PRIMARY_HOSTNAME/$PUNY_PRIMARY_HOSTNAME/" \
136137
> /var/lib/mailinabox/mta-sts.txt
137138
chmod a+r /var/lib/mailinabox/mta-sts.txt

0 commit comments

Comments
 (0)