diff --git a/management/daemon.py b/management/daemon.py index 13b6693f..d450c639 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -180,6 +180,22 @@ def dns_update(): except Exception as e: return (str(e), 500) +@app.route('/dns/secondary-nameserver') +@authorized_personnel_only +def dns_get_secondary_nameserver(): + from dns_update import get_custom_dns_config + return json_response({ "hostname": get_custom_dns_config(env).get("_secondary_nameserver") }) + +@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) + except ValueError as e: + return (str(e), 400) + + @app.route('/dns/set/', methods=['POST']) @app.route('/dns/set//', methods=['POST']) @app.route('/dns/set///', methods=['POST']) diff --git a/management/dns_update.py b/management/dns_update.py index 43072f59..c96618d3 100755 --- a/management/dns_update.py +++ b/management/dns_update.py @@ -7,6 +7,7 @@ import os, os.path, urllib.parse, datetime, re, hashlib, base64 import ipaddress import rtyaml +import dns.resolver from mailconfig import get_mail_domains from utils import shell, load_env_vars_from_file, safe_domain_name, sort_domains @@ -55,6 +56,11 @@ def get_custom_dns_config(env): except: return { } +def write_custom_dns_config(config, env): + config_yaml = rtyaml.dump(config) + with open(os.path.join(env['STORAGE_ROOT'], 'dns/custom.yaml'), "w") as f: + f.write(config_yaml) + def do_dns_update(env, force=False): # What domains (and their zone filenames) should we build? domains = get_dns_domains(env) @@ -105,7 +111,7 @@ def do_dns_update(env, force=False): zonefiles[i][1] += ".signed" # Write the main nsd.conf file. - if write_nsd_conf(zonefiles, env): + if write_nsd_conf(zonefiles, additional_records, env): # Make sure updated_domains contains *something* if we wrote an updated # nsd.conf so that we know to restart nsd. if len(updated_domains) == 0: @@ -134,12 +140,22 @@ def do_dns_update(env, force=False): def build_zone(domain, all_domains, additional_records, env, is_zone=True): records = [] - # For top-level zones, define ourselves as the authoritative name server. + # For top-level zones, define the authoritative name servers. + # + # Normally we are our own nameservers. Some TLDs require two distinct IP addresses, + # so we allow the user to override the second nameserver definition so that + # secondary DNS can be set up elsewhere. + # # 'False' in the tuple indicates these records would not be used if the zone # is managed outside of the box. if is_zone: + # Obligatory definition of ns1.PRIMARY_HOSTNAME. records.append((None, "NS", "ns1.%s." % env["PRIMARY_HOSTNAME"], False)) - records.append((None, "NS", "ns2.%s." % env["PRIMARY_HOSTNAME"], False)) + + # Define ns2.PRIMARY_HOSTNAME or whatever the user overrides. + secondary_ns = additional_records.get("_secondary_nameserver", "ns2." + env["PRIMARY_HOSTNAME"]) + records.append((None, "NS", secondary_ns+'.', False)) + # In PRIMARY_HOSTNAME... if domain == env["PRIMARY_HOSTNAME"]: @@ -437,7 +453,7 @@ $TTL 1800 ; default time to live ######################################################################## -def write_nsd_conf(zonefiles, env): +def write_nsd_conf(zonefiles, additional_records, env): # Basic header. nsdconf = """ server: @@ -465,6 +481,19 @@ zone: name: %s zonefile: %s """ % (domain, zonefile) + + # If a custom secondary nameserver has been set, allow zone transfers + # and notifies to that nameserver. + if additional_records.get("_secondary_nameserver"): + # Get the IP address of the nameserver by resolving it. + hostname = additional_records.get("_secondary_nameserver") + 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) + # Check if the nsd.conf is changing. If it isn't changing, # return False to flag that no change was made. @@ -688,14 +717,38 @@ def set_custom_dns_record(qname, rtype, value, env): config[qname][rtype] = value # serialize & save - config_yaml = rtyaml.dump(config) - with open(os.path.join(env['STORAGE_ROOT'], 'dns/custom.yaml'), "w") as f: - f.write(config_yaml) + write_custom_dns_config(config, env) return True ######################################################################## +def set_secondary_dns(hostname, env): + config = get_custom_dns_config(env) + + if hostname in (None, ""): + # Clear. + if "_secondary_nameserver" in config: + del config["_secondary_nameserver"] + else: + # Validate. + hostname = hostname.strip().lower() + 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. + config["_secondary_nameserver"] = hostname + + # Save and apply. + write_custom_dns_config(config, env) + return do_dns_update(env) + + +######################################################################## + def justtestingdotemail(domain, records): # If the domain is a subdomain of justtesting.email, which we own, # automatically populate the zone where it is set up on dns4e.com. diff --git a/management/templates/custom-dns.html b/management/templates/custom-dns.html index 23180691..431465d2 100644 --- a/management/templates/custom-dns.html +++ b/management/templates/custom-dns.html @@ -1,16 +1,45 @@ -

Custom DNS (Advanced)

+

Custom DNS

-

It is possible to set custom DNS records on domains hosted here. For instance, you can create your own dynamic DNS service. To do so, you will need to call your box’s DNS API.

+

This is an advanced configuration page.

-

The HTTP POST request

+

It is possible to set custom DNS records on domains hosted here.

+ +

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:

+ +
+
+ +
+ +
+
+
+
+ +
+
+
+
+

Clear the box to use the box itself as secondary DNS, which is the default/normal setup.

+
+
+
+ +

Custom DNS API

+ +

Use your box’s DNS API to set custom DNS records on domains hosted here. For instance, you can create your own dynamic DNS service.

Send a POST request like this:

curl -d "" --user {email}:{password} https://{{hostname}}/admin/dns/set/qname[/rtype[/value]]
+

HTTP POST parameters

+ @@ -41,5 +70,28 @@ curl -d "value=something%20here" --user me@mydomain.com:###### https://{{hostnam diff --git a/management/templates/external-dns.html b/management/templates/external-dns.html index 34cce701..2bad47c4 100644 --- a/management/templates/external-dns.html +++ b/management/templates/external-dns.html @@ -25,9 +25,11 @@ } -

External DNS (Advanced)

+

External DNS

-

Although your box is configured to serve its own DNS, it is possible to host your DNS elsewhere by copying the DNS zone information shown in the table below.

+

This is an advanced configuration page.

+ +

Although your box is configured to serve its own DNS, it is possible to host your DNS elsewhere — such as in the DNS control panel provided by your domain name registrar or virtual cloud provider — by copying the DNS zone information shown in the table below into your external DNS server’s control panel.

If you do so, you are responsible for keeping your DNS entries up to date! If you previously enabled DNSSEC on your domain name by setting a DS record at your registrar, you will likely have to turn it off before changing nameservers.

Parameter Value
email The email address of any administrative user here.