write explanatory text as we build DNS zones so we can help the user manage DNS off of the box
This commit is contained in:
parent
91cf45c843
commit
7803ac9ca4
|
@ -129,36 +129,41 @@ def build_zone(domain, subdomains, additional_records, env, with_ns=True):
|
||||||
records = []
|
records = []
|
||||||
|
|
||||||
# For top-level zones, define ourselves as the authoritative name server.
|
# For top-level zones, define ourselves as the authoritative name server.
|
||||||
|
# 'False' in the tuple indicates these records would not be used if the zone
|
||||||
|
# is managed outside of the box.
|
||||||
if with_ns:
|
if with_ns:
|
||||||
records.append((None, "NS", "ns1.%s." % env["PRIMARY_HOSTNAME"]))
|
records.append((None, "NS", "ns1.%s." % env["PRIMARY_HOSTNAME"], False))
|
||||||
records.append((None, "NS", "ns2.%s." % env["PRIMARY_HOSTNAME"]))
|
records.append((None, "NS", "ns2.%s." % env["PRIMARY_HOSTNAME"], False))
|
||||||
|
|
||||||
# The MX record says where email for the domain should be delivered: Here!
|
# The MX record says where email for the domain should be delivered: Here!
|
||||||
records.append((None, "MX", "10 %s." % env["PRIMARY_HOSTNAME"]))
|
records.append((None, "MX", "10 %s." % env["PRIMARY_HOSTNAME"], "Required. Specifies the hostname of the machine that handles @%s mail." % domain))
|
||||||
|
|
||||||
# SPF record: Permit the box ('mx', see above) to send mail on behalf of
|
# SPF record: Permit the box ('mx', see above) to send mail on behalf of
|
||||||
# the domain, and no one else.
|
# the domain, and no one else.
|
||||||
records.append((None, "TXT", '"v=spf1 mx -all"'))
|
records.append((None, "TXT", '"v=spf1 mx -all"', "Recomended. Specifies that only the box is permitted to send @%s mail." % domain))
|
||||||
|
|
||||||
# If we need to define DNS for any subdomains of this domain, include it
|
# If we need to define DNS for any subdomains of this domain, include it
|
||||||
# in the zone.
|
# in the zone.
|
||||||
for subdomain in subdomains:
|
for subdomain in subdomains:
|
||||||
subdomain_qname = subdomain[0:-len("." + domain)]
|
subdomain_qname = subdomain[0:-len("." + domain)]
|
||||||
for child_qname, child_rtype, child_value in build_zone(subdomain, [], {}, env, with_ns=False):
|
subzone = build_zone(subdomain, [], {}, env, with_ns=False)
|
||||||
|
for child_qname, child_rtype, child_value, child_explanation in subzone:
|
||||||
if child_qname == None:
|
if child_qname == None:
|
||||||
child_qname = subdomain_qname
|
child_qname = subdomain_qname
|
||||||
else:
|
else:
|
||||||
child_qname += "." + subdomain_qname
|
child_qname += "." + subdomain_qname
|
||||||
records.append((child_qname, child_rtype, child_value))
|
records.append((child_qname, child_rtype, child_value, child_explanation))
|
||||||
|
|
||||||
# In PRIMARY_HOSTNAME...
|
# In PRIMARY_HOSTNAME...
|
||||||
if domain == env["PRIMARY_HOSTNAME"]:
|
if domain == env["PRIMARY_HOSTNAME"]:
|
||||||
# Define ns1 and ns2.
|
# Define ns1 and ns2.
|
||||||
records.append(("ns1", "A", env["PUBLIC_IP"]))
|
# 'False' in the tuple indicates these records would not be used if the zone
|
||||||
records.append(("ns2", "A", env["PUBLIC_IP"]))
|
# is managed outside of the box.
|
||||||
|
records.append(("ns1", "A", env["PUBLIC_IP"], False))
|
||||||
|
records.append(("ns2", "A", env["PUBLIC_IP"], False))
|
||||||
|
|
||||||
# Add a DANE TLSA record for SMTP.
|
# Add a DANE TLSA record for SMTP.
|
||||||
records.append(("_25._tcp", "TLSA", build_tlsa_record(env)))
|
records.append(("_25._tcp", "TLSA", build_tlsa_record(env), "Recommended when DNSSEC is enabled. Advertises to mail servers connecting to the box that mandatory encryption should be used."))
|
||||||
|
|
||||||
def has_rec(qname, rtype):
|
def has_rec(qname, rtype):
|
||||||
for rec in records:
|
for rec in records:
|
||||||
|
@ -175,17 +180,26 @@ def build_zone(domain, subdomains, additional_records, env, with_ns=True):
|
||||||
qname = qname[0:len(qname)-len("." + domain)]
|
qname = qname[0:len(qname)-len("." + domain)]
|
||||||
if has_rec(qname, value): continue
|
if has_rec(qname, value): continue
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
records.append((qname, "A", value))
|
values = [("A", value)]
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
for rtype, value2 in value.items():
|
values = value.items()
|
||||||
if rtype == "TXT": value2 = "\"" + value2 + "\""
|
else:
|
||||||
records.append((qname, rtype, value2))
|
raise ValueError()
|
||||||
|
for rtype, value2 in values:
|
||||||
|
if rtype == "TXT": value2 = "\"" + value2 + "\""
|
||||||
|
records.append((qname, rtype, value2, "(Set by user.)"))
|
||||||
|
|
||||||
# Add defaults if not overridden by the user's custom settings.
|
# Add defaults if not overridden by the user's custom settings.
|
||||||
if not has_rec(None, "A"): records.append((None, "A", env["PUBLIC_IP"]))
|
defaults = [
|
||||||
if env.get('PUBLIC_IPV6') and not has_rec(None, "AAAA"): records.append((None, "AAAA", env["PUBLIC_IPV6"]))
|
(None, "A", env["PUBLIC_IP"], "Optional. Sets the IP address that %s resolves to, e.g. for web hosting." % domain),
|
||||||
if not has_rec("www", "A"): records.append(("www", "A", env["PUBLIC_IP"]))
|
("www", "A", env["PUBLIC_IP"], "Optional. Sets the IP address that www.%s resolves to, e.g. for web hosting." % domain),
|
||||||
if env.get('PUBLIC_IPV6') and not has_rec("www", "AAAA"): records.append(("www", "AAAA", env["PUBLIC_IPV6"]))
|
(None, "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that %s resolves to, e.g. for web hosting." % domain),
|
||||||
|
("www", "AAAA", env.get('PUBLIC_IPV6'), "Optional. Sets the IPv6 address that www.%s resolves to, e.g. for web hosting." % domain),
|
||||||
|
]
|
||||||
|
for qname, rtype, value, explanation in defaults:
|
||||||
|
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))
|
||||||
|
|
||||||
# If OpenDKIM is in use..
|
# If OpenDKIM is in use..
|
||||||
opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.txt')
|
opendkim_record_file = os.path.join(env['STORAGE_ROOT'], 'mail/dkim/mail.txt')
|
||||||
|
@ -193,12 +207,12 @@ def build_zone(domain, subdomains, additional_records, env, with_ns=True):
|
||||||
# Append the DKIM TXT record to the zone as generated by OpenDKIM, after string formatting above.
|
# Append the DKIM TXT record to the zone as generated by OpenDKIM, after string formatting above.
|
||||||
with open(opendkim_record_file) as orf:
|
with open(opendkim_record_file) as orf:
|
||||||
m = re.match(r"(\S+)\s+IN\s+TXT\s+(\(.*\))\s*;", orf.read(), re.S)
|
m = re.match(r"(\S+)\s+IN\s+TXT\s+(\(.*\))\s*;", orf.read(), re.S)
|
||||||
records.append((m.group(1), "TXT", m.group(2)))
|
records.append((m.group(1), "TXT", m.group(2), "Recommended. Specifies that only the box is permitted to send mail at this domain."))
|
||||||
|
|
||||||
# Append a DMARC record.
|
# Append a DMARC record.
|
||||||
records.append(("_dmarc", "TXT", '"v=DMARC1; p=quarantine"'))
|
records.append(("_dmarc", "TXT", '"v=DMARC1; p=quarantine"', "Optional. Specifies that mail that does not originate from the box but claims to be from @%s is suspect and should be quarantined by the recipient's mail system." % domain))
|
||||||
|
|
||||||
# Sort the records. The None records *must* go first. Otherwise it doesn't matter.
|
# 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 ""))
|
records.sort(key = lambda rec : list(reversed(rec[0].split(".")) if rec[0] is not None else ""))
|
||||||
|
|
||||||
return records
|
return records
|
||||||
|
@ -255,7 +269,7 @@ $TTL 86400 ; default time to live
|
||||||
zone = zone.format(domain=domain, primary_domain=env["PRIMARY_HOSTNAME"])
|
zone = zone.format(domain=domain, primary_domain=env["PRIMARY_HOSTNAME"])
|
||||||
|
|
||||||
# Add records.
|
# Add records.
|
||||||
for subdomain, querytype, value in records:
|
for subdomain, querytype, value, explanation in records:
|
||||||
if subdomain:
|
if subdomain:
|
||||||
zone += subdomain
|
zone += subdomain
|
||||||
zone += "\tIN\t" + querytype + "\t"
|
zone += "\tIN\t" + querytype + "\t"
|
||||||
|
@ -490,7 +504,7 @@ def justtestingdotemail(domain, records):
|
||||||
if not domain.endswith(".justtesting.email"):
|
if not domain.endswith(".justtesting.email"):
|
||||||
return
|
return
|
||||||
|
|
||||||
for subdomain, querytype, value in records:
|
for subdomain, querytype, value, explanation in records:
|
||||||
if querytype in ("NS",): continue
|
if querytype in ("NS",): continue
|
||||||
if subdomain in ("www", "ns1", "ns2"): continue # don't do unnecessary things
|
if subdomain in ("www", "ns1", "ns2"): continue # don't do unnecessary things
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue