From 95e61bc1108a7e935fb0c7b4271c600aafffae2f Mon Sep 17 00:00:00 2001 From: Joshua Tauberer Date: Thu, 19 Jun 2014 01:39:27 +0000 Subject: [PATCH] add DANE TLSA records to the PUBLIC_HOSTNAME's DNS Postfix has a tls_security_level called "dane" which uses DNS-Based Authentication of Named Entities (DANE) to require, if specified in the DNS of the MX host, an encrpyted connection with a known certificate. This commit adds TLSA records. --- management/dns_update.py | 34 ++++++++++++++++++++++++++++++++-- management/utils.py | 4 ++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index 5204f3e1..4149aa98 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -2,7 +2,7 @@ # and mail aliases and restarts nsd. ######################################################################## -import os, os.path, urllib.parse, datetime, re +import os, os.path, urllib.parse, datetime, re, hashlib import rtyaml from mailconfig import get_mail_domains @@ -126,11 +126,15 @@ def build_zone(domain, zonefile, env, with_ns=True): child_qname += "." + ph records.append((child_qname, child_rtype, child_value)) - # In PUBLIC_HOSTNAME, also define ns1 and ns2. + # In PUBLIC_HOSTNAME... if domain == env["PUBLIC_HOSTNAME"]: + # Define ns1 and ns2. records.append(("ns1", "A", env["PUBLIC_IP"])) records.append(("ns2", "A", env["PUBLIC_IP"])) + # Add a TLSA record for SMTP. + records.append(("_25._tcp", "TLSA", build_tlsa_record(env))) + def has_rec(qname, rtype): for rec in records: if rec[0] == qname and rec[1] == rtype: @@ -175,6 +179,32 @@ def build_zone(domain, zonefile, env, with_ns=True): ######################################################################## +def build_tlsa_record(env): + # A TLSA record in DNS specifies that connections on a port, e.g. + # the SMTP port, must use TLS and the certificate must match a + # particular certificate. + # + # Thanks to http://blog.huque.com/2012/10/dnssec-and-certificates.html + # for explaining all of this! + + # Get the hex SHA256 of the DER-encoded server certificate: + certder = shell("check_output", [ + "/usr/bin/openssl", + "x509", + "-in", os.path.join(env["STORAGE_ROOT"], "ssl", "ssl_certificate.pem"), + "-outform", "DER" + ], + return_bytes=True) + certhash = hashlib.sha256(certder).hexdigest() + + # Specify the TLSA parameters: + # 3: This is the certificate that the client should trust. No CA is needed. + # 0: The whole certificate is matched. + # 1: The certificate is SHA256'd here. + return "3 0 1 " + certhash + +######################################################################## + def write_nsd_zone(domain, zonefile, records, env): # We set the administrative email address for every domain to domain_contact@[domain.com]. # You should probably create an alias to your email address. diff --git a/management/utils.py b/management/utils.py index 737db99c..1697f0b4 100644 --- a/management/utils.py +++ b/management/utils.py @@ -81,12 +81,12 @@ def is_pid_valid(pid): else: return True -def shell(method, cmd_args, env={}, capture_stderr=False): +def shell(method, cmd_args, env={}, capture_stderr=False, return_bytes=False): # A safe way to execute processes. # Some processes like apt-get require being given a sane PATH. import subprocess env.update({ "PATH": "/sbin:/bin:/usr/sbin:/usr/bin" }) stderr = None if not capture_stderr else subprocess.STDOUT ret = getattr(subprocess, method)(cmd_args, env=env, stderr=stderr) - if isinstance(ret, bytes): ret = ret.decode("utf8") + if not return_bytes and isinstance(ret, bytes): ret = ret.decode("utf8") return ret