How-Tos

How-To: Hardening an Authoritative BIND DNS Server

Leave a comment

If you run your own authoritative DNS with BIND, it’s easy to set it up once and forget it. Here’s the checklist I work through: audit first without changing anything, then harden one step at a time.

Ground rules

  • Back up the config and zone files before every step, and write the rollback down before you start.
  • After every change run named-checkconf and named-checkzone, then dig your SOA, MX and TXT against each of your servers and a public resolver. If you sign with DNSSEC, run delv too and make sure it still validates.
  • One change at a time.

Step 1: a read-only audit

Run these from a machine that is not in any of your allow lists, over IPv4 and IPv6:

dig @ns1.example.com example.net A +recurse     # should be REFUSED (no open resolver)
dig @ns1.example.com example.com AXFR           # should fail (no public zone transfers)
dig @ns1.example.com version.bind CH TXT        # should not reveal your version

Also read your allow-recursion and allow-transfer lists and look for addresses that don’t belong there anymore.

Step 2: the safe wins (named.conf.options)

options {
    recursion no;              // authoritative only
    version none;
    hostname none;
    server-id none;
    minimal-responses yes;
    rate-limit {               // Response Rate Limiting
        responses-per-second 15;
        window 5;
        slip 2;
        exempt-clients { 127.0.0.1; ::1; <your-other-nameserver>; };
    };
};

RRL keeps your server from being used to amplify attacks against somebody else.

Step 3: sign zone transfers with TSIG

IP-only transfer ACLs work, but TSIG is better. Generate a key on the primary and copy it to the secondary over SSH, never through git:

tsig-keygen -a hmac-sha256 xfr-key > /etc/bind/xfr.key   # then chmod 640, owned by the bind group

# primary
include "/etc/bind/xfr.key";
server <secondary-ip> { keys { xfr-key; }; };
allow-transfer { key xfr-key; };

# secondary
include "/etc/bind/xfr.key";
server <primary-ip> { keys { xfr-key; }; };
primaries { <primary-ip> key xfr-key; };   // inside the zone
allow-notify { key xfr-key; };

Do the secondary first, then the primary. TSIG is time-sensitive, so check NTP on both. Test with rndc retransfer and a serial bump.

Step 4: the small stuff that adds up

  • rndc: make sure there’s a controls block bound to 127.0.0.1 with a key, and move old hmac-md5 keys to hmac-sha256 with rndc-confgen -a -A hmac-sha256. Keep port 953 closed to the outside.
  • CAA records so only your certificate authority can issue for you: 0 issue "letsencrypt.org", 0 issuewild ";" and an iodef mailto.
  • SOA: the RNAME should be a real mailbox like hostmaster.example.com., retry should be a sensible fraction of refresh, and bump the serial on every edit. For dynamic zones, rndc freeze before editing and rndc thaw after.
  • Stale DS records: compare dig DS at the parent with your zone’s DNSKEY. A DS with no matching key is leftover from an old rollover. Confirm no rollover is in progress (rndc dnssec -status), then remove only the stale one at your registrar. Never remove the active DS, or validating resolvers will stop resolving your domain.
  • Firewall: 53 TCP and UDP open to everyone is correct for an authoritative server. Everything else, closed.

Step 5: keep it as code

Put the configs in an Ansible role with templates, validate with named-checkconf before anything is written, and always run ansible-playbook bind.yml --check --diff before the real run. Generate keys on the servers themselves and keep secrets out of the repo.

Have a blessed day! And Happy Hacking!

References & Further Reading

Subscribe (RSS) Follow How-Tos via RSS

Leave a note

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.