I’m writing this up because it took me quite some time to get my head around how to do this, and I found answers around the internet varying from “not possible” through to “try this” (which didn’t work) and “switch off this security feature you really like having” (no)
I found a way to make it happen, but it’s not easy. I’ll walk you through the problem, and how each way I attempted to solve it failed.
All the names below are hypotheticals, and for the sake of argument we’re trying to make “foo.subdomain.local” resolve via the additional server.
Problem:
Suppose you have two DNS servers. One which we’ll call “NS1” and one which we’ll call “NS-NEW”.
- NS1 is a recursive server running bind, which all your clients point at to get their DNS information. It’s listening on port 53 as standard.
- NS-NEW is an authoritative server which is listening on a non-standard port (8600) and for these purposes it’s a black box, we can’t change its behaviour.
You want your clients to be able to resolve the names that NS-NEW is authoritative for, but you don’t want to have to reconfigure the clients. So NS1 needs to know to pass those queries on to NS-NEW to get an answer.
Attempt 1 – “slave zone”
My first thought was to configure NS1 to slave the zone from NS-NEW.
zone "subdomain.local" { type slave; file "/var/named/slave/priv.zone"; masters { $IP_OF_NS-NEW port 8600; }; };
This didn’t work for me because NS-NEW isn’t capable of doing zone transfers. Pity, as that would have been really neat and easy to manage!
Attempt 2 – “forward zone”
Then I tried forwarding queries from NS1 to NS-NEW, by using binds “forward zone” features.
zone "subdomain.local" { type forward; forward only; forwarders { $IP_OF_NS-NEW port 8600; }; };
This didn’t work because NS1 is configured to check for valid DNSSEC signatures. The root zone says that all its children are signed, and bind takes that to mean that all the grandchildren of the root should be signed as well.
The software running on NS-NEW isn’t capable of signing its zone information.
It doesn’t appear to be possible to selectively turn off DNSSEC checking on a per-zone basis, and I didn’t want to turn that off for our whole infrastructure as DNSSEC is generally a Good Thing.
Attempt 3 – “delegation”
I did think I could probably work around it by making NS1 authoritative for the “local.” top level domain, then using NS records in the zonefile for “local.” to directly delegate the zone to NS-NEW.
Something like this:
$TTL 86400 ; default TTL for this zone $ORIGIN local. @ IN SOA NS1.my.domain. hostmaster.my.domain. ( 2016031766 ; serial number 28800 ; refresh 7200 ; retry 604800 ; expire 3600 ; minimum ) IN NS NS1.my.domain. ; delegated zones subdomain IN NS NS-NEW.my.domain.
Unfortunately that doesn’t work either, as it’s not possible to specify a port number in an NS record, and NS-NEW isn’t listening on a standard port.
Attempt 3 – “a little of option 2 and a little of option 3”
Hold on to your hats, this gets a little self referential.
I made NS1 authoritative for “local.”
zone "local" { type master; file "/var/named/data/zone.local"; };
I configured NS records in the “local.” zone file, which point back at NS1
$TTL 86400 ; default TTL for this zone $ORIGIN local. @ IN SOA NS1.my.domain. hostmaster.my.domain. ( 2016031766 ; serial number 28800 ; refresh 7200 ; retry 604800 ; expire 3600 ; minimum ) IN NS NS1.my.domain. ; delegated zones subdomain IN NS NS1.my.domain.
I then configured a “subdomain.local.” forward zone on NS1 which forwards queries on to NS-NEW
zone "subdomain.local" { type forward; forward only; forwarders { $IP_OF_NS-NEW port 8600; }; };
To understand why this works, you need to understand how the recursion process for a query like “foo.subdomain.local.” happens.
When the query comes in NS1 does this:
– do I already know the answer from a previously cached query? Let’s assume no for now.
– do I know which DNS server is responsible for “subdomain.local.” from a previously cached query? Lets assume no for now.
– do I know which DNS server is responsible for “local.” – ooh! Yes! That’s me!
– now I can look in the zone file for “local.” and look to see how I resolve “subdomain.local.” – there’s an NS record which says I should ask NS1 in an authoritative way.
– now I ask NS1 for an answer to “foo.subdomain.local.”
– NS1 can then forward my query off to NS-NEW and fetch an answer.
Because we haven’t had to go all the way up to the root to get our answer, we avoid encountering the DNSSEC issue for this zone.
Did you really do it like *that*?
Yes and no.
The above is a simplified version of what I actually had to do, as our production equivalent of NS1 isn’t a single server – and I had to take account of our zone file management process, and all of that adds complexity which I don’t need to go into.
There are also a few extra hoops to jump through to make sure that the “local.” domain can only be accessed by clients on our network, and to make sure that our authoritative infrastructure doesn’t “leak” the “local.” zone to the outside world.
What would you have liked to have done?
If NS-NEW was able to listen on a standard port, I’d have used a straight delegation to do it.
If NS-NEW was able to sign it’s zone data with DNSSEC, I’d have used a simple forward zone to do it.
NS-NEW isn’t *quite* the black box I treated it as in this article, but the restriction about not being able to make it listen on port 53 is a real one.
The software running on NS-NEW does have a feature request in it’s issue tracker for DNSSEC, which I’ll watch with interest – as that would allow me to tidy up our config and might actually enable some other cool stuff further down the line…