mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-04 15:54:48 +01:00
MTA-STS tweaks, add status check using postfix-mta-sts-resolver, change to enforce
This commit is contained in:
@@ -304,37 +304,47 @@ 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.
|
||||
# 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):
|
||||
# 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. Therefore
|
||||
# the TLS certificate used by Postfix for STARTTLS must be a valid certificate for the MX
|
||||
# domain name (PRIMARY_HOSTNAME) *and* the TLS certificate used by nginx for HTTPS on the mta-sts
|
||||
# subdomain must be valid certificate for that domain. Do not set an MTA-STS policy if either
|
||||
# certificate in use is not valid (e.g. because it is self-signed and a valid certificate has not
|
||||
# yet been provisioned).
|
||||
mta_sts_enabled = False
|
||||
if domain in get_mail_domains(env):
|
||||
# Check that PRIMARY_HOSTNAME and the mta_sts domain both have valid certificates.
|
||||
for d in (env['PRIMARY_HOSTNAME'], "mta-sts." + domain):
|
||||
cert = get_ssl_certificates(env).get(d)
|
||||
if not cert:
|
||||
break # no certificate provisioned for this domain
|
||||
cert_status = check_certificate(d, cert['certificate'], cert['private-key'])
|
||||
if cert_status[0] != 'OK':
|
||||
break # certificate is not valid
|
||||
else:
|
||||
# 'break' was not encountered above, so both domains are good
|
||||
mta_sts_enabled = True
|
||||
if mta_sts_enabled:
|
||||
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")
|
||||
("mta-sts", "A", env["PUBLIC_IP"], "Optional. MTA-STS Policy Host serving /.well-known/mta-sts.txt."),
|
||||
("mta-sts", "AAAA", env.get('PUBLIC_IPV6'), "Optional. MTA-STS Policy Host serving /.well-known/mta-sts.txt."),
|
||||
("_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.")
|
||||
]
|
||||
# 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"])))
|
||||
tls_rpt_email = env.get("MTA_STS_TLSRPT_EMAIL", "postmaster@%s" % env['PRIMARY_HOSTNAME'])
|
||||
if tls_rpt_email: # if a reporting address is not cleared
|
||||
tls_rpt_string = " rua=mailto:%s" % tls_rpt_email
|
||||
mta_sts_records.append(("_smtp._tls", "TXT", "v=TLSRPTv1;%s" % tls_rpt_string, "Optional. Enables MTA-STS reporting."))
|
||||
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):
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
# what to do next.
|
||||
|
||||
import sys, os, os.path, re, subprocess, datetime, multiprocessing.pool
|
||||
import asyncio
|
||||
|
||||
import dns.reversename, dns.resolver
|
||||
import dateutil.parser, dateutil.tz
|
||||
import idna
|
||||
import psutil
|
||||
import postfix_mta_sts_resolver.resolver
|
||||
|
||||
from dns_update import get_dns_zones, build_tlsa_record, get_custom_dns_config, get_secondary_dns, get_custom_dns_records
|
||||
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):
|
||||
def run_domain_checks_on_domain(domain, rounded_time, env, dns_domains, dns_zonefiles, mail_domains, web_domains, domains_with_a_records):
|
||||
output = BufferedOutput()
|
||||
|
||||
# When running inside Flask, the worker threads don't get a thread pool automatically.
|
||||
# Also this method is called in a forked worker pool, so creating a new loop is probably
|
||||
# a good idea.
|
||||
asyncio.set_event_loop(asyncio.new_event_loop())
|
||||
|
||||
# we'd move this up, but this returns non-pickleable values
|
||||
ssl_certificates = get_ssl_certificates(env)
|
||||
|
||||
@@ -611,6 +618,19 @@ def check_mail_domain(domain, env, output):
|
||||
if mx != recommended_mx:
|
||||
good_news += " This configuration is non-standard. The recommended configuration is '%s'." % (recommended_mx,)
|
||||
output.print_ok(good_news)
|
||||
|
||||
# Check MTA-STS policy.
|
||||
loop = asyncio.get_event_loop()
|
||||
sts_resolver = postfix_mta_sts_resolver.resolver.STSResolver(loop=loop)
|
||||
valid, policy = loop.run_until_complete(sts_resolver.resolve(domain))
|
||||
if valid == postfix_mta_sts_resolver.resolver.STSFetchResult.VALID:
|
||||
if policy[1].get("mx") == [env['PRIMARY_HOSTNAME']] and policy[1].get("mode") == "enforce": # policy[0] is the policyid
|
||||
output.print_ok("MTA-STS policy is present.")
|
||||
else:
|
||||
output.print_error("MTA-STS policy is present but has unexpected settings. [{}]".format(policy[1]))
|
||||
else:
|
||||
output.print_error("MTA-STS policy is missing: {}".format(valid))
|
||||
|
||||
else:
|
||||
output.print_error("""This domain's DNS MX record is incorrect. It is currently set to '%s' but should be '%s'. Mail will not
|
||||
be delivered to this box. It may take several hours for public DNS to update after a change. This problem may result from
|
||||
|
||||
Reference in New Issue
Block a user