We’ve discussed dynamic DNS updates a few times here, and I’ve given examples of securing updates with TSIG and with SIG(0). Both methods require the updating client has access to a file containing the key; only with that key can a client submit updates to a DNS server. (Unless the server authorizes updates by IP address, which I don’t recommend doing.)

In environments which use Kerberos, administrators already possess a key as part of their Kerberos principal. Can they use that to perform DNS updates against a BIND name server? They can, using GSS-TSIG.

GSS-TSIG (Generic Security Service Algorithm for Secret Key Transaction Authentication for DNS) is defined in RFC 3645. It’s an extension to TSIG, which provides a lightweight protocol for authenticating and protecting the integrity of messages between, say, DNS client and server. GSS-TSIG specifies an alogorithm based on GSS-API which provides authentication based on Kerberos. We’ll use GSS-TSIG to authenticate dynamic DNS updates.

The client

What I want to be able to do is to perform a dynamic DNS update authenticated by my Kerberos principal. I’m assuming you have Kerberos client libraries and tools, as well as the the BIND tools (nsupdate) installed on your client machine, and that you’re running BIND server (compiled with --enable-gssapi) with the required Kerberos libraries installed on the server.

So, what should be possible is a sequence of the following commands:

$ kinit f2
Password for f2@MENS.DE
$ nsupdate -g
> server 192.168.1.145
> update add hello.mens.de. 120 TXT "Hello from Kerberos"
> send

Option -g to nsupdate specifies that GSS-TSIG is to be used. (Alternatively, I can use the gsstsig subcommand to nsupdate, but I find the option easier.) Did it work? Yup: My nsupdate spoke to named and I see a Kerberos ticket from that server in my credentials cache:

$ klist
Kerberos 5 ticket cache: 'API:Initial default ccache'
Default principal: f2@MENS.DE

Valid Starting     Expires            Service Principal
06/29/12 16:13:08  06/30/12 02:13:05  krbtgt/MENS.DE@MENS.DE
        renew until 06/29/12 16:13:08

06/29/12 16:13:47  06/30/12 02:13:05  DNS/jmbp.ww.mens.de@MENS.DE
        renew until 06/29/12 16:13:08

Let’s have a look at how to enable named to allow GSS-TSIG-signed updates.

Configure BIND

BIND requires access to a Kerberos keytab, so I create a Kerberos service principal called DNS/jmbp.ww.mens.de@MENS.DE, and extract the principal’s key into a keytab called DNS.keytab. I then configure the keytab name in named.conf:

options {
   ...
   tkey-gssapi-keytab "/var/named/DNS.keytab";
   ...
};

I also specify which principals may update what in a zone’s update-policy stanza:

zone "mens.de" IN {
  type master;
  file "master/mens.de";

  update-policy {
    grant f2@MENS.DE zonesub ANY;
    grant MENS.DE krb5-self * A;
    grant "local:/tmp/auth.sock" external * ANY;
  };
};

I’ve used three distinct forms of grant statement, which we’ll look at in turn.

Individual principals

The first grant statement (grant f2@MENS.DE zonesub ANY) permits the specified principal to update any domain within the zone. I can specify as many individual principals as I wish, of course, and each can be authorized to perform different types of updates. (Consult the BIND ARM for more information.)

Map host principals to domain

The second grant statement (grant MENS.DE krb5-self * A;) allows any principle in the MENS.DE realm to update its own A record. This rule takes a Kerberos machine principal (host/machine@REALM) and converts it to machine.realm. The realm to be matched is in the identity field (MENS.DE). In other words, any host in my Kerberized environment can, using its own keytab, update its record in the DNS. An example:

# kinit -k -t /etc/krb5.keytab -p host/c2.mens.de
# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: host/c2.mens.de@MENS.DE

Valid starting     Expires            Service principal
06/29/12 16:46:52  06/30/12 16:46:52  krbtgt/MENS.DE@MENS.DE
        renew until 06/29/12 16:46:52

# nsupdate -g
> zone mens.de.
> update add c2.mens.de. 10 A 10.0.12.1
> send
> quit
# 

and BIND’s log shows

client 192.168.1.10#34206/key host/c2.mens.de\@MENS.DE:
        view internal:
        updating zone 'mens.de/IN': adding an RR at 'c2.mens.de' A

I’ve used the system’s keytab here, but I can of course use any other keytab.

External authentication

If you wish to defer the decision on whether or not named should grant the update request, you can do so using the external method, shown in the third grant statement (grant "local:/tmp/auth.sock" external * ANY;). This uses a UNIX socket to communicate with an external process which decides whether the update is allowed or not, and returns this information to named. I can use this to enable updates by particular user or service principals without having to reconfigure named, say, at particular times of the day, or requests with a particular source IP address.

BIND and external GSSAPI authenticator

The following program is basically a copy of bin/tests/system/tsiggss/authsock.pl from the BIND source tree: it creates the UNIX domain socket, starts listening for requests from named and checks the principal name of the requestor (signer) to decide whether or not to grant update-permission.

#!/usr/bin/perl

use strict;
use IO::Socket::UNIX;

my $path = '/tmp/auth.sock';
my $typeallowed = "A";

unlink($path);
my $server = IO::Socket::UNIX->new(Local => $path, Type => SOCK_STREAM, Listen => 8) or
    die "unable to create socket $path";
chmod 0777, $path;

while (my $client = $server->accept()) {
    $client->recv(my $buf, 8, 0);
    my ($version, $req_len) = unpack('N N', $buf);

    if ($version != 1 || $req_len < 17) {
        printf("Badly formatted request\n");
        $client->send(pack('N', 2));
        next;
    }

    $client->recv(my $buf, $req_len - 8, 0);

    my ($signer,
        $name,
        $addr,
        $type,
        $key,
        $key_data) = unpack('Z* Z* Z* Z* Z* N/a', $buf);

    if ($req_len != length($buf)+8) {
        printf("Length mismatch %u %u\n", $req_len, length($buf)+8);
        $client->send(pack('N', 2));
        next;
    }

    printf("Update by %s for name=%s, type=%s at address %s\n",
        $signer, $name, $type, $addr);

    # Allow myself to update anything
    my $result = ($signer eq 'jpm\@MENS.DE') ? 1 : 0;

    my $reply = pack('N', $result);
    $client->send($reply);
}

Requests from named are sent as datagrams over the UNIX domain socket; our authorization program replies with a four-byte value in network-byte order, specifying whether the update is permitted (1) or not (0).

Here are a few examples printed by my authenticator:

Update by host/c2.mens.de\@MENS.DE for name=whatever.mens.de, type=A at address 192.168.1.10
Update by jpm\@MENS.DE for name=whatever.mens.de, type=A at address 192.168.1.10
Update by jpm\@MENS.DE for name=book.mens.de, type=TXT at address 192.168.1.10

Fin

The flexibility provided by BIND permits just about any combination of authorization of principals updating DNS records that I can think of, and all others should be covered by the external grant-method.

Enjoy!

DNS, RFC2136, and Kerberos :: 29 Jun 2012 :: e-mail