From e1a545d9d4ed95c952a28b03e707dd3311e35257 Mon Sep 17 00:00:00 2001 From: Brian Bustin Date: Wed, 1 Jul 2015 15:02:40 -0400 Subject: [PATCH 1/7] Initial backend changes to make it possible to have one or more secondary name servers --- management/dns_update.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index 5a3ca1dc..4ed11157 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -144,8 +144,13 @@ 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"]) - records.append((None, "NS", secondary_ns+'.', False)) + # User may provide one or more additional nameservers + if get_secondary_dns(additional_records).len > 0: + for secondary_ns in get_secondary_dns(additional_records): + records.append((None, "NS", secondary_ns+'.', False)) + else: + secondary_ns = get_secondary_dns(additional_records) or ("ns2." + env["PRIMARY_HOSTNAME"]) + records.append((None, "NS", secondary_ns+'.', False)) # In PRIMARY_HOSTNAME... @@ -462,9 +467,9 @@ 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): + # If a custom secondary nameservers have been set, allow zone transfers + # and notifies to th. + for secondary_ns in 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() @@ -793,7 +798,11 @@ def set_custom_dns_record(qname, rtype, value, action, env): def get_secondary_dns(custom_dns): for qname, rtype, value in custom_dns: if qname == "_secondary_nameserver": - return value + # always return a list so other parts of code path can iterate + if isinstance(value, str): + return [value] + else: + return value return None def set_secondary_dns(hostname, env): From ac5921051cdb24dc2a252569501158df705293b5 Mon Sep 17 00:00:00 2001 From: Brian Bustin Date: Wed, 1 Jul 2015 15:06:28 -0400 Subject: [PATCH 2/7] no message --- management/dns_update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/dns_update.py b/management/dns_update.py index 4ed11157..6b4b5092 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -145,7 +145,7 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en # Define ns2.PRIMARY_HOSTNAME or whatever the user overrides. # User may provide one or more additional nameservers - if get_secondary_dns(additional_records).len > 0: + if length(get_secondary_dns(additional_records)) > 0: for secondary_ns in get_secondary_dns(additional_records): records.append((None, "NS", secondary_ns+'.', False)) else: From c23c1c311e4858551ae5adb9da932ac528997753 Mon Sep 17 00:00:00 2001 From: Brian Bustin Date: Wed, 1 Jul 2015 15:10:12 -0400 Subject: [PATCH 3/7] no message --- management/dns_update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/management/dns_update.py b/management/dns_update.py index 6b4b5092..2a5426b5 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -145,7 +145,7 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en # Define ns2.PRIMARY_HOSTNAME or whatever the user overrides. # User may provide one or more additional nameservers - if length(get_secondary_dns(additional_records)) > 0: + if len(get_secondary_dns(additional_records)) > 0: for secondary_ns in get_secondary_dns(additional_records): records.append((None, "NS", secondary_ns+'.', False)) else: From 32a6ebe2cad86793517bbe9e795e33b224a2b63f Mon Sep 17 00:00:00 2001 From: Brian Bustin Date: Wed, 1 Jul 2015 21:16:28 -0400 Subject: [PATCH 4/7] Now accepts one or more secondary nameservers. Works from the web interface. --- management/daemon.py | 2 +- management/dns_update.py | 66 +++++++++++++--------------- management/templates/custom-dns.html | 2 +- 3 files changed, 32 insertions(+), 38 deletions(-) diff --git a/management/daemon.py b/management/daemon.py index 88dd9a42..2d14b4e6 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -229,7 +229,7 @@ def dns_get_secondary_nameserver(): 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(request.form.get('hostname').split(","), env) except ValueError as e: return (str(e), 400) diff --git a/management/dns_update.py b/management/dns_update.py index 2a5426b5..92fd1ada 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -145,12 +145,11 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en # Define ns2.PRIMARY_HOSTNAME or whatever the user overrides. # User may provide one or more additional nameservers - if len(get_secondary_dns(additional_records)) > 0: - for secondary_ns in get_secondary_dns(additional_records): - records.append((None, "NS", secondary_ns+'.', False)) + if get_secondary_dns(additional_records): + [records.append((None, "NS", secondary_ns+'.', False)) for secondary_ns in get_secondary_dns(additional_records)] else: - secondary_ns = get_secondary_dns(additional_records) or ("ns2." + env["PRIMARY_HOSTNAME"]) - records.append((None, "NS", secondary_ns+'.', False)) + secondary_ns = get_secondary_dns(additional_records) or () + records.append((None, "NS", "ns2." + env["PRIMARY_HOSTNAME"] + '.', False)) # In PRIMARY_HOSTNAME... @@ -467,17 +466,15 @@ zone: zonefile: %s """ % (domain, zonefile) - # If a custom secondary nameservers have been set, allow zone transfers - # and notifies to th. - for secondary_ns in 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. + if get_secondary_dns(additional_records): + for hostname in get_secondary_dns(additional_records): + # Get the IP address of the nameserver by resolving it. + resolver = dns.resolver.get_default_resolver() + response = dns.resolver.query(hostname+'.', "A") + ipaddr = str(response[0]) + nsdconf += "\n\tnotify: %s NOKEY\n\tprovide-xfr: %s NOKEY" % (ipaddr, ipaddr) # Check if the file is changing. If it isn't changing, # return False to flag that no change was made. @@ -790,37 +787,34 @@ 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): + values = [] for qname, rtype, value in custom_dns: if qname == "_secondary_nameserver": - # always return a list so other parts of code path can iterate if isinstance(value, str): - return [value] - else: - return value + values.append(value) + if len(values) > 0: + return values return None def set_secondary_dns(hostname, env): - - if hostname in (None, ""): - # Clear. - set_custom_dns_record("_secondary_nameserver", "A", None, "set", env) - else: - # Validate. - hostname = hostname.strip().lower() + hostnames = [item.strip().lower() for item in hostname] + if len(hostnames) > 0: resolver = dns.resolver.get_default_resolver() - try: - response = dns.resolver.query(hostname, "A") - except (dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): - raise ValueError("Could not resolve the IP address of %s." % hostname) - - # Set. - set_custom_dns_record("_secondary_nameserver", "A", hostname, "set", env) + for item in hostnames: + try: + 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." % item) + # Set. + set_custom_dns_record("_secondary_nameserver", "A", {"A":hostnames}, "set", env) + else: + # Clear. + set_custom_dns_record("_secondary_nameserver", "A", None, "set", env) # Apply. return do_dns_update(env) @@ -912,5 +906,5 @@ if __name__ == "__main__": for zone, records in build_recommended_dns(env): for record in records: print("; " + record['explanation']) - print(record['qname'], record['rtype'], record['value'], sep="\t") + print(record['qname'], record['rtype'], record['value']) print() diff --git a/management/templates/custom-dns.html b/management/templates/custom-dns.html index 711bc384..ddc1153f 100644 --- a/management/templates/custom-dns.html +++ b/management/templates/custom-dns.html @@ -67,7 +67,7 @@

Using a Secondary Nameserver

-

If your TLD requires you to have two separate nameservers, you can either set up a secondary (aka “slave”) nameserver or, alternatively, set up external DNS 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 their secondary nameserver:

+

If your TLD requires you to have two separate nameservers, you can either set up a secondary (aka “slave”) nameserver or, alternatively, set up external DNS 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 their secondary nameserver (multiple secondaries can be added separated with commas i.e. ns2.hostingcompany.com,ns3.hostingcompany.com):

From f9c078c3aad2a58b0e12bf9bdcbd896c264982eb Mon Sep 17 00:00:00 2001 From: Brian Bustin Date: Wed, 1 Jul 2015 21:55:54 -0400 Subject: [PATCH 5/7] Some secondary DNS hosting providers like DNS Made Easy provide different IP addresses for notifies and zone transfers. Added the ability to add these in the custom.yaml, but purposely did not expose it in the UI as it would probably serve to confuse the majority of people. The format in the custom.yaml file for the IPs to send notifies and allow zone transfers to (but that we do not want NS records created for) is like this: _secondary_notify_xfr: A: - 1.1.1.1 - 2.2.2.2 --- management/dns_update.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index 92fd1ada..0e754ddd 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -468,13 +468,21 @@ zone: # If custom secondary nameservers have been set, allow zone transfers # and notifies to them. - if get_secondary_dns(additional_records): + if get_secondary_dns(additional_records, ['_secondary_nameserver']): for hostname in get_secondary_dns(additional_records): # Get the IP address of the nameserver by resolving it. resolver = dns.resolver.get_default_resolver() response = dns.resolver.query(hostname+'.', "A") ipaddr = str(response[0]) nsdconf += "\n\tnotify: %s NOKEY\n\tprovide-xfr: %s NOKEY" % (ipaddr, ipaddr) + # Some providers use different servers for zone transfers and notifies + # such as DNS Made Easy. This allows us to set these IP addresses as well manually + # in custom.yaml + if get_secondary_dns(additional_records, ["_secondary_notify_xfr"]): + for ipaddr in get_secondary_dns(additional_records, ["_secondary_notify_xfr"]): + nsdconf += "\n\tnotify: %s NOKEY\n\tprovide-xfr: %s NOKEY" % (ipaddr, ipaddr) + + # Check if the file is changing. If it isn't changing, # return False to flag that no change was made. @@ -791,10 +799,14 @@ def set_custom_dns_record(qname, rtype, value, action, env): ######################################################################## -def get_secondary_dns(custom_dns): +def get_secondary_dns(custom_dns, dns_type=['_secondary_nameserver']): + valid_types = set(['_secondary_nameserver', '_secondary_notify_xfr']) + if not valid_types.issuperset(set(dns_type)): + raise ValueError("Valid types are one or more of the following: %s" % ", ".join(valid_types)) + values = [] for qname, rtype, value in custom_dns: - if qname == "_secondary_nameserver": + if qname in dns_type: if isinstance(value, str): values.append(value) if len(values) > 0: From be586e4006aeff3bdb41c2028c0ced28a0c8bf63 Mon Sep 17 00:00:00 2001 From: Brian Bustin Date: Wed, 1 Jul 2015 22:12:02 -0400 Subject: [PATCH 6/7] Fixed issue with domain tests caused by get_secondary_ns now returning a list. --- management/status_checks.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/management/status_checks.py b/management/status_checks.py index 61bd7d2d..cd92435e 100755 --- a/management/status_checks.py +++ b/management/status_checks.py @@ -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)) 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']: From 8d79945812958803d65f29320d6191137781a3b7 Mon Sep 17 00:00:00 2001 From: Brian Bustin Date: Thu, 2 Jul 2015 08:56:58 -0400 Subject: [PATCH 7/7] Made changes recommended by JoshData. --- management/dns_update.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/management/dns_update.py b/management/dns_update.py index 0e754ddd..5321b4f7 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -145,10 +145,11 @@ def build_zone(domain, all_domains, additional_records, www_redirect_domains, en # Define ns2.PRIMARY_HOSTNAME or whatever the user overrides. # User may provide one or more additional nameservers - if get_secondary_dns(additional_records): - [records.append((None, "NS", secondary_ns+'.', False)) for secondary_ns in get_secondary_dns(additional_records)] + secondary_dns_records = get_secondary_dns(additional_records) + if len(secondary_dns_records) > 0: + for secondary_ns in secondary_dns_records: + records.append((None, "NS", secondary_ns+'.', False)) else: - secondary_ns = get_secondary_dns(additional_records) or () records.append((None, "NS", "ns2." + env["PRIMARY_HOSTNAME"] + '.', False)) @@ -809,9 +810,7 @@ def get_secondary_dns(custom_dns, dns_type=['_secondary_nameserver']): if qname in dns_type: if isinstance(value, str): values.append(value) - if len(values) > 0: - return values - return None + return values def set_secondary_dns(hostname, env): hostnames = [item.strip().lower() for item in hostname] @@ -918,5 +917,5 @@ if __name__ == "__main__": for zone, records in build_recommended_dns(env): for record in records: print("; " + record['explanation']) - print(record['qname'], record['rtype'], record['value']) + print(record['qname'], record['rtype'], record['value'], sep="\t") print()