Publish MTA-STS policy for incoming mail (#1731)

Co-authored-by: Daniel Mabbett <triumph_2500@hotmail.com>
This commit is contained in:
A. Schippers 2020-05-29 21:30:07 +02:00 committed by GitHub
parent 7de8fc9bc0
commit afc9f9686a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 81 additions and 3 deletions

View file

@ -24,6 +24,12 @@ Web:
* Add a new hidden feature to set nginx alias in www/custom.yaml.
MTA-STS:
* Added support for client side MTA-STS when there is a valid SSL Certificate on the primary domain
* Automatically adds reporting when alias "tlsrpt@<primary-domain>" is added.
* Starts default on 'testing', but changes will be kept between MiaB Upgrades.
Setup:
* Improved error handling.

4
conf/mta-sts.txt Normal file
View file

@ -0,0 +1,4 @@
version: STSv1
mode: MODE
mx: PRIMARY_HOSTNAME
max_age: 86400

View file

@ -21,6 +21,9 @@
location = /mail/config-v1.1.xml {
alias /var/lib/mailinabox/mozilla-autoconfig.xml;
}
location = /.well-known/mta-sts.txt {
alias /var/lib/mailinabox/mta-sts.txt;
}
# Roundcube Webmail configuration.
rewrite ^/mail$ /mail/ redirect;

View file

@ -9,8 +9,9 @@ import ipaddress
import rtyaml
import dns.resolver
from mailconfig import get_mail_domains
from mailconfig import get_mail_domains, get_mail_aliases
from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains
from ssl_certificates import get_ssl_certificates, check_certificate
# From https://stackoverflow.com/questions/3026957/how-to-validate-a-domain-name-using-regex-php/16491074#16491074
# This regular expression matches domain names according to RFCs, it also accepts fqdn with an leading dot,
@ -303,6 +304,42 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
if not has_rec(qname, rtype):
records.append((qname, rtype, value, explanation))
# If this is a domain name that there are email addresses configured for, i.e. "something@"
# this domain name, then the domain name is a MTA-STS (https://tools.ietf.org/html/rfc8461)
# Policy Domain.
#
# A "_mta-sts" TXT record signals the presence of a MTA-STS policy, and an effectively random policy
# ID is used to signal that a new policy may (or may not) be deployed any time the DNS is
# updated.
#
# The policy itself is served at the "mta-sts" (no underscore) subdomain over HTTPS. The
# TLS certificate used by Postfix for STARTTLS must be a valid certificate for the MX
# name (PRIMARY_HOSTNAME), so we do not set an MTA-STS policy if the certificate is not
# valid (e.g. because it is self-signed and a valid certificate has not yet been provisioned).
get_prim_cert = get_ssl_certificates(env)[env['PRIMARY_HOSTNAME']]
response = check_certificate(env['PRIMARY_HOSTNAME'], get_prim_cert['certificate'],get_prim_cert['private-key'])
if response[0] == 'OK' and domain in get_mail_domains(env):
mta_sts_records = [
("mta-sts", "A", env["PUBLIC_IP"], "Provides MTA-STS support"),
("mta-sts", "AAAA", env.get('PUBLIC_IPV6'), "Provides MTA-STS support"),
("_mta-sts", "TXT", "v=STSv1; id=%sZ" % datetime.datetime.now().strftime("%Y%m%d%H%M%S"), "Enables MTA-STS support")
]
# Rules can be custom configured accoring to https://tools.ietf.org/html/rfc8460.
# Skip if the rules below if the user has set a custom _smtp._tls record.
if not has_rec("_smtp._tls", "TXT", prefix="v=TLSRPTv1;"):
# if the alias 'tlsrpt@PRIMARY_HOSTNAME' is configured, automaticly, reporting will be enabled to this email address
tls_rpt_email = "tlsrpt@%s" % env['PRIMARY_HOSTNAME']
tls_rpt_string = ""
for alias in get_mail_aliases(env):
if alias[0] == tls_rpt_email:
tls_rpt_string = " rua=mailto:%s" % tls_rpt_email
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"])))
for qname, rtype, value, explanation in mta_sts_records:
if value is None or value.strip() == "": continue # skip IPV6 if not set
if not has_rec(qname, rtype):
records.append((qname, rtype, value, explanation))
# Sort the records. The None records *must* go first in the nsd zone file. Otherwise it doesn't matter.
records.sort(key = lambda rec : list(reversed(rec[0].split(".")) if rec[0] is not None else ""))

View file

@ -30,6 +30,9 @@ def get_web_domains(env, include_www_redirects=True, exclude_dns_elsewhere=True)
domains |= set('autoconfig.' + maildomain for maildomain in get_mail_domains(env))
domains |= set('autodiscover.' + maildomain for maildomain in get_mail_domains(env))
# 'mta-sts.' for MTA-STS support.
domains |= set('mta-sts.' + maildomain for maildomain in get_mail_domains(env))
if exclude_dns_elsewhere:
# ...Unless the domain has an A/AAAA record that maps it to a different
# IP address than this box. Remove those domains from our list.

View file

@ -109,6 +109,12 @@ As discussed above, there is no way to require on-the-wire encryption of mail. W
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))
### MTA-STS
SMTP MTA Strict Transport Security ([SMTP MTA-STS for short](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security)).
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.
### Filters
Incoming mail is run through several filters. Email is bounced if the sender's IP address is listed in the [Spamhaus Zen blacklist](http://www.spamhaus.org/zen/) or if the sender's domain is listed in the [Spamhaus Domain Block List](http://www.spamhaus.org/dbl/). Greylisting (with [postgrey](http://postgrey.schweikert.ch/)) is also used to cut down on spam. ([source](setup/mail-postfix.sh))

View file

@ -82,6 +82,11 @@ if [ ! -f $STORAGE_ROOT/mailinabox.version ]; then
chown $STORAGE_USER.$STORAGE_USER $STORAGE_ROOT/mailinabox.version
fi
# Default policy (initial) for MTA_STS = testing in the current state of inclusion.
# it can be changed to "none", "testing" or "enforce". With this extention, this is preserved by
# future upgrades
MTA_STS="${DEFAULT_MTA_STS:-testing}"
# Save the global options in /etc/mailinabox.conf so that standalone
# tools know where to look for data.
@ -93,6 +98,7 @@ PUBLIC_IP=$PUBLIC_IP
PUBLIC_IPV6=$PUBLIC_IPV6
PRIVATE_IP=$PRIVATE_IP
PRIVATE_IPV6=$PRIVATE_IPV6
MTA_STS=$MTA_STS
EOF
# Start service configuration.

View file

@ -19,7 +19,7 @@ fi
echo "Installing Nginx (web server)..."
apt_install nginx php-cli php-fpm
apt_install nginx php-cli php-fpm idn2
rm -f /etc/nginx/sites-enabled/default
@ -122,6 +122,20 @@ cat conf/mozilla-autoconfig.xml \
> /var/lib/mailinabox/mozilla-autoconfig.xml
chmod a+r /var/lib/mailinabox/mozilla-autoconfig.xml
# Create a generic mta-sts.txt file which is exposed via the
# nginx configuration at /.well-known/mta-sts.txt
# more documentation is available on:
# https://www.uriports.com/blog/mta-sts-explained/
# default mode is "testing", which means: "Messages will be delivered as
# though there was no failure but a report will be sent if TLS-RPT is configured"
# other valid modes are: "enforce" and "none".
PUNY_PRIMARY_HOSTNAME=$(echo "$PRIMARY_HOSTNAME" | idn2)
cat conf/mta-sts.txt \
| sed "s/MODE/$MTA_STS/" \
| sed "s/PRIMARY_HOSTNAME/$PUNY_PRIMARY_HOSTNAME/" \
> /var/lib/mailinabox/mta-sts.txt
chmod a+r /var/lib/mailinabox/mta-sts.txt
# make a default homepage
if [ -d $STORAGE_ROOT/www/static ]; then mv $STORAGE_ROOT/www/static $STORAGE_ROOT/www/default; fi # migration #NODOC
mkdir -p $STORAGE_ROOT/www/default
@ -137,4 +151,3 @@ restart_service php7.2-fpm
# Open ports.
ufw_allow http
ufw_allow https