0x4a42.net

Hello World.

BIND with dynamic zone updates and OpenDNSSEC

Usually I prefer the more lightweight Knot or NSD daemons for running authoritative nameservers, but you can do pretty cool things with BIND as it has much more features.

In this post I will cover how to allow dynamic updates to a DNS zone in BIND, then sign that zone using OpenDNSSEC, transfer it back to BIND where it gets served to clients and secondary nameservers asking for zone transfers. I will not cover how to set up OpenDNSSEC, you need to have that already running.

In case you are asking why I am not just using the built-in DNSSEC signing features of BIND: that should work too, but I already have OpenDNSSEC up and running and I had some spare time. Apart from that, does BIND support fully automatic key rollover in the meantime? OpenDNSSEC does.

So let’s get started. To achieve our goal, we will use a BIND feature called views, which is somewhat like running multiple nameservers within the same process. You may do all kinds of evil stuff with that like serving a different version of a zone to internal clients. We will do something very similar here and create an unsigned view that will be used for the dynamic DNS updates and the zone transfers to OpenDNSSEC while everyone else sees a view with the signed zone that gets transfered from OpenDNSSEC.

In order to make incoming and outgoing zone transfers with OpenDNSSEC, it has to listen on a port. To do so, add the following to the <Signer> section in conf.xml:

<Listener>
    <Interface>
        <Address>::1</Address>
        <Port>51</Port>
    </Interface>
</Listener>

The following addns.xml enables incoming zone transfers from ::1 using the key opendnssec-in and outgoing transfers to ::1 using the key opendnssec-out:

<?xml version="1.0" encoding="UTF-8"?>

<Adapter>
     <DNS>
        <TSIG>
            <Name>opendnssec-in</Name>
            <Algorithm>hmac-sha256</Algorithm>
            <Secret>/CHANGE/ME/1111[...]1111=</Secret>
        </TSIG>
        <TSIG>
            <Name>opendnssec-out</Name>
            <Algorithm>hmac-sha256</Algorithm>
            <Secret>/CHANGE/ME/2222[...]2222=</Secret>
        </TSIG>

        <Inbound>
            <RequestTransfer>
                <Remote>
                    <Address>::1</Address>
                    <Key>opendnssec-in</Key>
                </Remote>
            </RequestTransfer>

            <AllowNotify>
                <Peer>
                    <Prefix>::1</Prefix>
                </Peer>
            </AllowNotify>
        </Inbound>

        <Outbound>
            <ProvideTransfer>
                <Peer>
                    <Prefix>::1</Prefix>
                    <Key>opendnssec-out</Key>
                </Peer>
            </ProvideTransfer>

            <Notify>
                <Remote>
                    <Address>::1</Address>
                </Remote>
            </Notify>
        </Outbound>
    </DNS>
</Adapter>

All the following code snippets are from the named.conf file. If you are running Debian, you want to remove the line include "/etc/bind/named.conf.default-zones"; from /etc/bind/named.conf as this introduces some zone declaration on the global scope which conflicts with using views.

First we declare all tree keys that will be used:

  • opendnssec-in will be used for transfers of the unsigned zone from BIND to OpenDNSSEC
  • opendnssec-out will be used for transfers of the signed zone from OpenDNSSEC to BIND
  • ddns will be used for dynamic DNS updates
key opendnssec-in {
    algorithm hmac-sha256;
    secret "/CHANGE/ME/1111[...]1111=";
};

key opendnssec-out {
    algorithm hmac-sha256;
    secret "/CHANGE/ME/2222[...]2222=";
};

key ddns {
    algorithm hmac-sha512;
    secret "/CHANGE/ME/3333[...]3333=";
};

Next we need an ACL that matches all the keys for operations on the unsigned zone:

acl unsigned {
    key opendnssec-in;
    key ddns;
};

Now comes the fun part: views, some love them, others hate them, but they just do what we need and they aren’t that hard to use actually. So let’s create an unsigned view:

view unsigned {
    // We want this view to apply to all connections using one
    // of the keys in the 'unsigned' ACL, so we just reference
    // that ACL here.
    match-clients { unsigned; };

    // Now follows a pretty normal zone definiton.
    zone dyn.example {
        type master;
        file "unsigned/db.dyn.example";
        // Allow transfers to OpenDNSSEC using its key.
        allow-transfer {
            key opendnssec-in;
        };
        // Notify OpenDNSSEC which listens on port 51.
        also-notify {
            ::1 port 51;
        };
        // Here you can define all your fancy update policies.
        // 'ddns' refers to the key defined earlier.
        update-policy {
            grant ddns subdomain dyn.example;
        };
    };
};

And finally we define a second view for everything else, where we declare the same zone as a slave zone transfered from OpenDNSSEC:

view default {
    // This view applies to everything else.
    match-clients { any; };

    zone dyn.example {
        type slave;
        file "signed/db.dyn.example";
        masters {
            ::1 port 51 key opendnssec-out;
        };
    };
};

Now just add anything else you would normally do, like outgoing zone transfers to secondaries and you’re good to go.