1
0
mirror of https://github.com/mail-in-a-box/mailinabox.git synced 2024-11-22 02:17:26 +00:00

allow overriding the second nameserver with a secondary/slave server

fixes #151
fixes #223
This commit is contained in:
Joshua Tauberer 2014-10-05 14:53:42 +00:00
parent 092c842a87
commit f42a1c5a74
4 changed files with 135 additions and 12 deletions

View File

@ -180,6 +180,22 @@ def dns_update():
except Exception as e: except Exception as e:
return (str(e), 500) 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/<qname>', methods=['POST']) @app.route('/dns/set/<qname>', methods=['POST'])
@app.route('/dns/set/<qname>/<rtype>', methods=['POST']) @app.route('/dns/set/<qname>/<rtype>', methods=['POST'])
@app.route('/dns/set/<qname>/<rtype>/<value>', methods=['POST']) @app.route('/dns/set/<qname>/<rtype>/<value>', methods=['POST'])

View File

@ -7,6 +7,7 @@
import os, os.path, urllib.parse, datetime, re, hashlib, base64 import os, os.path, urllib.parse, datetime, re, hashlib, base64
import ipaddress import ipaddress
import rtyaml import rtyaml
import dns.resolver
from mailconfig import get_mail_domains from mailconfig import get_mail_domains
from utils import shell, load_env_vars_from_file, safe_domain_name, sort_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: except:
return { } 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): def do_dns_update(env, force=False):
# What domains (and their zone filenames) should we build? # What domains (and their zone filenames) should we build?
domains = get_dns_domains(env) domains = get_dns_domains(env)
@ -105,7 +111,7 @@ def do_dns_update(env, force=False):
zonefiles[i][1] += ".signed" zonefiles[i][1] += ".signed"
# Write the main nsd.conf file. # 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 # Make sure updated_domains contains *something* if we wrote an updated
# nsd.conf so that we know to restart nsd. # nsd.conf so that we know to restart nsd.
if len(updated_domains) == 0: 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): def build_zone(domain, all_domains, additional_records, env, is_zone=True):
records = [] 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 # 'False' in the tuple indicates these records would not be used if the zone
# is managed outside of the box. # is managed outside of the box.
if is_zone: if is_zone:
# Obligatory definition of ns1.PRIMARY_HOSTNAME.
records.append((None, "NS", "ns1.%s." % env["PRIMARY_HOSTNAME"], False)) 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... # In PRIMARY_HOSTNAME...
if domain == env["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. # Basic header.
nsdconf = """ nsdconf = """
server: server:
@ -466,6 +482,19 @@ zone:
zonefile: %s zonefile: %s
""" % (domain, zonefile) """ % (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, # Check if the nsd.conf is changing. If it isn't changing,
# return False to flag that no change was made. # return False to flag that no change was made.
with open("/etc/nsd/nsd.conf") as f: with open("/etc/nsd/nsd.conf") as f:
@ -688,14 +717,38 @@ def set_custom_dns_record(qname, rtype, value, env):
config[qname][rtype] = value config[qname][rtype] = value
# serialize & save # serialize & save
config_yaml = rtyaml.dump(config) write_custom_dns_config(config, env)
with open(os.path.join(env['STORAGE_ROOT'], 'dns/custom.yaml'), "w") as f:
f.write(config_yaml)
return True 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): def justtestingdotemail(domain, records):
# If the domain is a subdomain of justtesting.email, which we own, # If the domain is a subdomain of justtesting.email, which we own,
# automatically populate the zone where it is set up on dns4e.com. # automatically populate the zone where it is set up on dns4e.com.

View File

@ -1,16 +1,45 @@
<style> <style>
</style> </style>
<h2>Custom DNS (Advanced)</h2> <h2>Custom DNS</h2>
<p>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&rsquo;s DNS API.</p> <p class="text-warning">This is an advanced configuration page.</p>
<h4>The HTTP POST request</h4> <p>It is possible to set custom DNS records on domains hosted here.</p>
<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>
<form class="form-horizontal" role="form" onsubmit="do_set_secondary_dns(); return false;">
<div class="form-group">
<label for="secondarydnsHostname" class="col-sm-1 control-label">Hostname</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="secondarydnsHostname" placeholder="ns1.cloudprovider.com">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-1 col-sm-11">
<button type="submit" class="btn btn-primary">Update</button>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-1 col-sm-11">
<p class="small">Clear the box to use the box itself as secondary DNS, which is the default/normal setup.</p>
</div>
</div>
</form>
<h3>Custom DNS API</h3>
<p>Use your box&rsquo;s DNS API to set custom DNS records on domains hosted here. For instance, you can create your own dynamic DNS service.</p>
<p>Send a POST request like this:</p> <p>Send a POST request like this:</p>
<pre>curl -d "" --user {email}:{password} https://{{hostname}}/admin/dns/set/<b>qname</b>[/<b>rtype</b>[/<b>value</b>]]</pre> <pre>curl -d "" --user {email}:{password} https://{{hostname}}/admin/dns/set/<b>qname</b>[/<b>rtype</b>[/<b>value</b>]]</pre>
<h4>HTTP POST parameters</h4>
<table class="table"> <table class="table">
<thead><th>Parameter</th> <th>Value</th></thead> <thead><th>Parameter</th> <th>Value</th></thead>
<tr><td>email</td> <td>The email address of any administrative user here.</td></tr> <tr><td>email</td> <td>The email address of any administrative user here.</td></tr>
@ -41,5 +70,28 @@ curl -d "value=something%20here" --user me@mydomain.com:###### https://{{hostnam
<script> <script>
function show_custom_dns() { function show_custom_dns() {
api(
"/dns/secondary-nameserver",
"GET",
{ },
function(data) {
$('#secondarydnsHostname').val(data.hostname ? data.hostname : '');
});
}
function do_set_secondary_dns() {
api(
"/dns/secondary-nameserver",
"POST",
{
hostname: $('#secondarydnsHostname').val()
},
function(data) {
if (data == "") return; // nothing updated
show_modal_error("Secondary DNS", $("<pre/>").text(data));
},
function(err) {
show_modal_error("Secondary DNS", $("<pre/>").text(err));
});
} }
</script> </script>

View File

@ -25,9 +25,11 @@
} }
</style> </style>
<h2>External DNS (Advanced)</h2> <h2>External DNS</h2>
<p>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.</p> <p class="text-warning">This is an advanced configuration page.</p>
<p>Although your box is configured to serve its own DNS, it is possible to host your DNS elsewhere &mdash; such as in the DNS control panel provided by your domain name registrar or virtual cloud provider &mdash; by copying the DNS zone information shown in the table below into your external DNS server&rsquo;s control panel.</p>
<p>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.</p> <p>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.</p>