Commit 216acb0e authored by Joshua Tauberer's avatar Joshua Tauberer

merge #476 - multiple secondary NS servers and zone xfr-only servers

closes #476
parents d08a3095 5dd5fc4a
......@@ -222,14 +222,14 @@ def dns_update():
@authorized_personnel_only
def dns_get_secondary_nameserver():
from dns_update import get_custom_dns_config, get_secondary_dns
return json_response({ "hostname": get_secondary_dns(get_custom_dns_config(env)) })
return json_response({ "hostnames": get_secondary_dns(get_custom_dns_config(env), mode=None) })
@app.route('/dns/secondary-nameserver', methods=['POST'])
@authorized_personnel_only
def dns_set_secondary_nameserver():
from dns_update import set_secondary_dns
try:
return set_secondary_dns(request.form.get('hostname'), env)
return set_secondary_dns([ns.strip() for ns in re.split(r"[, ]+", request.form.get('hostnames') or "") if ns.strip() != ""], env)
except ValueError as e:
return (str(e), 400)
......
......@@ -144,7 +144,10 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en
records.append((None, "NS", "ns1.%s." % env["PRIMARY_HOSTNAME"], False))
# Define ns2.PRIMARY_HOSTNAME or whatever the user overrides.
secondary_ns = get_secondary_dns(additional_records) or ("ns2." + env["PRIMARY_HOSTNAME"])
# User may provide one or more additional nameservers
secondary_ns_list = get_secondary_dns(additional_records, mode="NS") \
or ["ns2." + env["PRIMARY_HOSTNAME"]]
for secondary_ns in secondary_ns_list:
records.append((None, "NS", secondary_ns+'.', False))
......@@ -462,17 +465,10 @@ zone:
zonefile: %s
""" % (domain, zonefile)
# If a custom secondary nameserver has been set, allow zone transfers
# and notifies to that nameserver.
if get_secondary_dns(additional_records):
# Get the IP address of the nameserver by resolving it.
hostname = get_secondary_dns(additional_records)
resolver = dns.resolver.get_default_resolver()
response = dns.resolver.query(hostname+'.', "A")
ipaddr = str(response[0])
nsdconf += """\tnotify: %s NOKEY
provide-xfr: %s NOKEY
""" % (ipaddr, ipaddr)
# If custom secondary nameservers have been set, allow zone transfers
# and notifies to them.
for ipaddr in get_secondary_dns(additional_records, mode="xfr"):
nsdconf += "\n\tnotify: %s NOKEY\n\tprovide-xfr: %s NOKEY\n" % (ipaddr, ipaddr)
# Check if the file is changing. If it isn't changing,
# return False to flag that no change was made.
......@@ -785,33 +781,63 @@ def set_custom_dns_record(qname, rtype, value, action, env):
if made_change:
# serialize & save
write_custom_dns_config(newconfig, env)
return made_change
########################################################################
def get_secondary_dns(custom_dns):
def get_secondary_dns(custom_dns, mode=None):
resolver = dns.resolver.get_default_resolver()
values = []
for qname, rtype, value in custom_dns:
if qname == "_secondary_nameserver":
return value
return None
if qname != '_secondary_nameserver': continue
for hostname in value.split(" "):
hostname = hostname.strip()
if mode == None:
# Just return the setting.
values.append(hostname)
# This is a hostname. Before including in zone xfr lines,
# resolve to an IP address. Otherwise just return the hostname.
if not hostname.startswith("xfr:"):
if mode == "xfr":
response = dns.resolver.query(hostname+'.', "A")
hostname = str(response[0])
values.append(hostname)
def set_secondary_dns(hostname, env):
# This is a zone-xfer-only IP address. Do not return if
# we're querying for NS record hostnames. Only return if
# we're querying for zone xfer IP addresses - return the
# IP address.
elif mode == "xfr":
values.append(hostname[4:])
if hostname in (None, ""):
# Clear.
set_custom_dns_record("_secondary_nameserver", "A", None, "set", env)
else:
# Validate.
hostname = hostname.strip().lower()
return values
def set_secondary_dns(hostnames, env):
if len(hostnames) > 0:
# Validate that all hostnames are valid and that all zone-xfer IP addresses are valid.
resolver = dns.resolver.get_default_resolver()
for item in hostnames:
if not item.startswith("xfr:"):
# Resolve hostname.
try:
response = dns.resolver.query(hostname, "A")
response = dns.resolver.query(item, "A")
except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
raise ValueError("Could not resolve the IP address of %s." % hostname)
raise ValueError("Could not resolve the IP address of %s." % item)
else:
# Validate IP address.
try:
v = ipaddress.ip_address(item[4:]) # raises a ValueError if there's a problem
if not isinstance(v, ipaddress.IPv4Address): raise ValueError("That's an IPv6 address.")
except ValueError:
raise ValueError("'%s' is not an IPv4 address." % item[4:])
# Set.
set_custom_dns_record("_secondary_nameserver", "A", hostname, "set", env)
set_custom_dns_record("_secondary_nameserver", "A", " ".join(hostnames), "set", env)
else:
# Clear.
set_custom_dns_record("_secondary_nameserver", "A", None, "set", env)
# Apply.
return do_dns_update(env)
......
......@@ -370,12 +370,9 @@ def check_dns_zone(domain, env, output, dns_zonefiles):
# the TLD, and so we're not actually checking the TLD. For that we'd need
# to do a DNS trace.
ip = query_dns(domain, "A")
secondary_ns = get_secondary_dns(get_custom_dns_config(env)) or "ns2." + env['PRIMARY_HOSTNAME']
secondary_ns = get_secondary_dns(get_custom_dns_config(env), mode="NS") or ["ns2." + env['PRIMARY_HOSTNAME']]
existing_ns = query_dns(domain, "NS")
correct_ns = "; ".join(sorted([
"ns1." + env['PRIMARY_HOSTNAME'],
secondary_ns,
]))
correct_ns = "; ".join(sorted(["ns1." + env['PRIMARY_HOSTNAME']] + secondary_ns))
if existing_ns.lower() == correct_ns.lower():
output.print_ok("Nameservers are set correctly at registrar. [%s]" % correct_ns)
elif ip == env['PUBLIC_IP']:
......
......@@ -67,7 +67,8 @@
<h3>Using a Secondary Nameserver</h3>
<p>If your TLD requires you to have two separate nameservers, you can either set up a secondary (aka &ldquo;slave&rdquo;) nameserver or, alternatively, set up <a href="#" onclick="return show_panel('external_dns')">external DNS</a> and ignore the DNS server on this box. If you choose to use a seconday/slave nameserver, you must find a seconday/slave nameserver service provider. Your domain name registrar or virtual cloud provider may provide this service for you. Once you set up the seconday/slave nameserver service, enter the hostname of <em>their</em> secondary nameserver:</p>
<p>If your TLD requires you to have two separate nameservers, you can either set up <a href="#" onclick="return show_panel('external_dns')">external DNS</a> and ignore the DNS server on this box entirely, or use the DNS server on this box but add a secondary (aka &ldquo;slave&rdquo;) nameserver.</p>
<p>If you choose to use a seconday nameserver, you must find a seconday nameserver service provider. Your domain name registrar or virtual cloud provider may provide this service for you. Once you set up the seconday nameserver service, enter the hostname (not the IP address) of <em>their</em> secondary nameserver in the box below.</p>
<form class="form-horizontal" role="form" onsubmit="do_set_secondary_dns(); return false;">
<div class="form-group">
......@@ -83,7 +84,11 @@
</div>
<div id="secondarydns-clear-instructions" class="form-group" style="display: none">
<div class="col-sm-offset-1 col-sm-11">
<p class="small">Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup.</p>
<p class="small">
Multiple secondary servers can be separated with commas or spaces (i.e., <code>ns2.hostingcompany.com ns3.hostingcompany.com</code>).
To enable zone transfers to additional servers without listing them as secondary nameservers, add <code>xfr:IPADDRESS</code>.
Clear the input field above and click Update to use this machine itself as secondary DNS, which is the default/normal setup.
</p>
</div>
</div>
</form>
......@@ -152,8 +157,8 @@ function show_custom_dns() {
"GET",
{ },
function(data) {
$('#secondarydnsHostname').val(data.hostname ? data.hostname : '');
$('#secondarydns-clear-instructions').toggle(data.hostname != null);
$('#secondarydnsHostname').val(data.hostnames.join(' '));
$('#secondarydns-clear-instructions').toggle(data.hostnames.length > 0);
});
api(
......@@ -210,7 +215,7 @@ function do_set_secondary_dns() {
"/dns/secondary-nameserver",
"POST",
{
hostname: $('#secondarydnsHostname').val()
hostnames: $('#secondarydnsHostname').val()
},
function(data) {
if (data == "") return; // nothing updated
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment