Automating Secondary DNS servers



When running several name-servers, it can be difficult to configure which domains end up on them. There's multiple ways - copying config files, getting a config snippet from a web site regularly, or having a deployment script. All of which will break at some point in time and leave you with a semi-functional name server.

Wouldn't it be great if we could use DNS to configure DNS?

This is a great use for TXT records - or misuse - depending on how pure you want to be ;)

How good would it be to be able to have a TXT record that contains what zone files a secondary DNS should have in its config file by referencing a DNS entry?

So here's a script to do just that.

#!/usr/bin/perl
# vim: set ts=4:
use strict;
use warnings;
use Net::DNS;

my $outputfile = "/etc/named/secondary_domains.conf";
my $output = "";

my $header = '
masters dns-masters {
    1.2.3.4;
};
';

my $entry_template = '
zone "ZONE" IN {
    type        slave;
    file        "/var/named/slaves/FILE";
    masters     { dns-masters; };
};
';

my $resolver = Net::DNS::Resolver->new;
$resolver->nameservers("8.8.8.8");
my $reply = $resolver->query("secondary_domains.example.com", "TXT");

if ($reply) {
    $output = $header;
    foreach my $rr ($reply->answer) {
        foreach my $txt ( $rr->txtdata ) {
            my $entry = $entry_template;
            $entry =~ s/ZONE/$txt/g;

            ## Have a sane filename...
            $txt =~ s@/@_@g;
            $entry =~ s/FILE/$txt/g;
            $output = $output . $entry;
        }
    }

    ## Write file to disk.
    open(FH, '>', $outputfile) or die $!;
    print FH $output;
    close(FH);

    ## Find which systemd unit we use...
    my $service = "named-chroot.service";
    if ( -f "/etc/systemd/system/multi-user.target.wants/named.service" ) {
        $service = "named.service";
    }
    system("systemctl reload $service");
} else {
    warn "query failed: ", $resolver->errorstring, "\n";
}

Then in your /etc/named.conf config file, include the generated /etc/named/secondary_domains.conf with the following at the bottom of the file.

include "/etc/named/secondary_domains.conf";

Get cron or a systemd timer to run the perl script once an hour or so, and you'll be quickly adding / removing entire zones from your secondary DNS servers with ease.

On a second part, because this will query the 8.8.8.8 name-server for the TXT record, as long as one DNS server can respond with the correct entry, your secondary will be able to generate a new configuration file.

On your primary (normally a hidden master), you will add a TXT record to the zone file as follows:

secondary_domains.example.com.   1800 IN  TXT "domain1.com" "domain2.com" "domain3.com"

You can adjust your TTL, paths and other items to reflect your implementations. This is also simple enough that it will allow you to run a secondary DNS server on the free-tier cloud platforms like GCP.

What are the limitations of this approach? Well, once you get over 64Kb worth of domain names, you'll have to either split the TXT records and implement something like a "include:record_b.example.com" and loop over that as well for another 64Kb worth of text, or loop over a counter like secondary-1.example.com, secondary-2.example.com until you get an NXDOMAIN reply.

You could also change the domain name to be something that only exists on the hidden master server - and not on the wider internet and query the master directly to get the list of domains. This has the advantage that the list of domain names included can't become public.

There's probably more variations that can be done to further fine-tune this approach, but this is a good, functional start.

Comments


Comments powered by Disqus