4. Further Bind configuration

So we have a couple of name servers, doing a good job and allowing us to address our DMZ servers by name now. Their setup is rather simple, but can be good enough in many environments. Anyway, Bind can do much more and solve many of the potential problems you may have to face; let's see some of the most common ones.

4.1 Views and split namespace

Our name servers are configured to return the private addresses of our DMZ servers, i.e. the addresses on the 172.16.240.0/24 network. However, some of those servers (e.g. mail and web servers) must be accessed from the Internet too, using the public (NATted) IP address. Therefore, the name server should return different values depending on the origin of the query: private addresses if queried from the internal network, public address(es) if queried from the outside.

This is called a split namespace: the real namespace is only available to the internal systems, while hosts on the internet can only see its reduced and translated version (called shadow namespace). Bind achieves this through one of its greatest features: views. Let's see them in action with a brief example.

First we need to define the group of hosts that should access the servers by their private address. We do this by defining an acl, which is simply a statement that associates a name with a group of hosts.

/var/named/etc/named.conf
acl "internal" {
    127/8; 172.16.240/24; 172.16.0.0/24;
};

Next we add the views to named.conf(5) and specify different zone data files for each view.

/var/named/etc/named.conf
view "internal" {
    # This view applies to machines in the 'internal' acl
    match-clients   { "internal" };
    # Allow 'internal' machines to query for internet names
    recursion       yes;

    zone "kernel-panic.it" {
        type        master;
        file        "master/db.kernel-panic.it"
    };

    zone "240.16.172.in-addr.arpa" {
        type        master;
        file        "master/db.172.16.240"
    };

    zone "250.16.172.in-addr.arpa" {
        type        master;
        file        "master/db.172.16.250"
    };

    zone "3.2.1.in-addr.arpa" {
        type        master;
        file        "master/db.1.2.3"
    };

    # Loopback address
    zone "localhost" {
        type        master;
        file        "master/db.localhost";
    };

    zone "127.in-addr.arpa" {
        type        master;
        file        "master/db.127";
    };

    # Special zones
    zone "255.in-addr.arpa" {
        type        master;
        file        "master/db.255";
    };

    zone "0.in-addr.arpa" {
        type        master;
        file        "master/db.0";
    };

    # Root zone
    zone "." {
        type        hint;
        file        "master/db.cache"
    };
};

view "internet" {
    # This view applies to all the other machines
    match-clients   { any; };
    # Do not allow external machines to query for internet names
    recursion       no;

    zone "kernel-panic.it" {
        type        master;
        file        "master/db.kernel-panic.it.shadow"
    };

    zone "3.2.1.in-addr.arpa" {
        type        master;
        file        "master/db.1.2.3.shadow"
    };
};

The following are the shadow zone data files:

/var/named/master/db.kernel-panic.it.shadow
$TTL 1d

@ IN SOA dns.kernel-panic.it. danix.kernel-panic.it. (
    2007020601 ; serial
    3h         ; refresh after 3 hours
    1h         ; retry after 1 hour
    1w         ; expire after 1 week
    1h )       ; negative caching TTL of 1 hour

                    IN NS       dns.kernel-panic.it.
                    IN NS       dns.provider.com.

                    IN MX    0  mail.kernel-panic.it.
                    IN MX   10  mail.provider.com.

                    IN A        1.2.3.4

www                 IN CNAME    kernel-panic.it.
mail                IN CNAME    kernel-panic.it.
dns                 IN CNAME    kernel-panic.it.

*                   IN MX    0  mail.kernel-panic.it.
                    IN MX   10  mail.provider.com.
/var/named/master/db.1.2.3.shadow
$TTL 1d

@ IN SOA dns.kernel-panic.it. danix.kernel-panic.it. (
    2007020601 ; serial
    3h         ; refresh after 3 hours
    1h         ; retry after 1 hour
    1w         ; expire after 1 week
    1h )       ; negative caching TTL of 1 hour

                    IN NS       dns.kernel-panic.it.
                    IN NS       dns.provider.com.

4                   IN PTR      kernel-panic.it.

As you may have noticed, to increase DNS service availability, we have set up an additional name server hosted by our ISP, containing only the information about the shadow namespace.

4.2 Delegation

So far, we have taken into account only our DMZ servers: now is time for our LAN name servers to enter the scene. Let's see how they relate to the other hosts:

Probably, the simplest solution would be once again to take advantage of views and add the internal servers to the zone data files configured in the "internal" view (see above). A more interesting and scalable solution, however, is to create a new zone, for example "lan.kernel-panic.it", and delegate it to a couple of name servers (master and slave) that we will place in the LAN.

On the parent side ("kernel-panic.it" domain name servers), we simply need to add the appropriate NS records and the corresponding A records:

/var/named/master/db.kernel-panic.it
[...]
lan                 IN NS       dns1.lan.kernel-panic.it.
                    IN NS       dns2.lan.kernel-panic.it.

dns1.lan.kernel-panic.it.   IN A        172.16.0.161
dns2.lan.kernel-panic.it.   IN A        172.16.0.162
[...]

Delegated name servers will simply have to create the appropriate configuration and zone data files the usual way. You can find the complete files here.

4.3 Dynamic updates and notify

And what about our DHCP-enabled clients? Can Bind map names to dynamic IP addresses? Of course the answer is "yes"! Bind supports dynamic update (see [RFC2136]), which enables the DHCP server to automatically add/delete/modify resource records whenever changes occur. Configuration is very simple (assuming the DHCP server address is 172.16.0.163):

/var/named/etc/named.conf
zone "lan.kernel-panic.it" {
    type            master;
    file            "master/db.lan.kernel-panic.it";
    allow-update    { 172.16.0.163; };
    notify          yes;
};

The allow-update clause specifies the list of IP addresses allowed to update the zone (usually just the DHCP server). It may also accept an ACL name or a TSIG key (see below for further details). For example:

/var/named/etc/named.conf
key dhcp-dns1.lan.kernel-panic.it. {
    algorithm       hmac-md5;
    secret          "+io/5nabnVFgC4Tx+UAkgg==";
};

zone "lan.kernel-panic.it" {
    type            master;
    file            "master/db.lan.kernel-panic.it";
    allow-update    { key dhcp-dns1.lan.kernel-panic.it.; };
    notify          yes;
};

The notify clause tells Bind to send a NOTIFY announcement to all of the slave name servers for that zone to inform them that the zone data has changed. This allows Bind to minimize the delay in synchronization between master and slave name servers. Dynamic update and DNS NOTIFY work great together, beacuse Bind 9 automatically increments the zone's serial number after each update and this increment automatically triggers zone change notification.

Alternatively to allow-update, Bind 9 also supports the update-policy clause, which allows for a stricter control over which keys are allowed to update which records in a specific zone. For example:

/var/named/etc/named.conf
zone "lan.kernel-panic.it" {
    type            master;
    file            "master/db.lan.kernel-panic.it";
    update-policy   { grant dhcp-dns1.lan.kernel-panic.it. subdomain lan.kernel-panic.it. A; };
    notify          yes;
};

Please refer to the official documentation for a detailed explanation of the update-policy's syntax.

4.4 TSIG and security

So far, our only concern was having everything running smooth, without caring much about security; but we can't ignore that part of our name servers will be exposed to the Internet, so security is not an option.

The most basic security measures are implemented by default on OpenBSD: Bind runs as the unprivileged user "named" and chrooted inside /var/named. This will make it much harder for attackers to exploit any newly-discovered vulnerabilities.

Another important security measure is to configure Bind not to reveal its version number, just to make attackers' lives a little more complicated.

/var/named/etc/named.conf
options {
    version         "Go hack yourself!";
};

We have already seen how views and acls can help in dealing with NAT and firewalls, but they are also a great security feature, since they allow you to select which hosts should access which information. For example, using the recursion substatement within a view (or the allow-recursion clause in the options statement), you can specify which hosts are allowed to perform recursive queries against your name servers. This allows you to prevent some of the most common spoofing attacks (see [DNS&BIND]).

/var/named/etc/named.conf
acl "dmz" {
    127/8; 172.16.240/24;
};

view "dmz" {
    match-clients   { "dmz" };
    recursion       yes;
};

view "internet" {
    match-clients   { any; };
    recursion       no;
};

Needless to say, if your name server only answers queries from other name servers or for domains it is authoritative for (such as our LAN servers), you should completely turn off recursion.

/var/named/etc/named.conf
options {
    version         "Go hack yourself!";
    recursion       no;
};

Besides recursion, Bind also allows you to restrict queries and zone transfers using the allow-query and allow-transfer clauses respectively. These clauses apply to a specific zone, if used within a zone statement, or globally, if used within the options statement. E.g.:

/var/named/etc/named.conf
acl "dmz" { 127/8; 172.16.240/24; };

options {
    # Restrict zone transfers to our internal name servers
    allow-transfer  { 172.16.0.161; 172.16.0.162; };
};

zone "kernel-panic.it" {
    type            master;
    file            "master/db.kernel-panic.it"
    # Restrict queries to DMZ servers
    allow-query     { "dmz" };
};

Using acls and address match lists to restrict zone transfers is better than nothing, but using transaction signatures, or TSIG (see [RFC2845]), is considerably better. TSIG allows name servers to authenticate DNS messages, using shared secrets (TSIG keys) and a one-way hash function (HMAC-MD5).

TSIG configuration is very simple. The first step is to create the shared key(s): the easiest way is using the dnssec-keygen(8) program, which creates two files, both containing the key generated.

# dnssec-keygen -a HMAC-MD5 -b 128 -n HOST rndc-key
Krndc-key.+157+32572
# ls
Krndc-key.+157+32572.key             Krndc-key.+157+32572.private
# cat Krndc-key.+157+32572.key
rndc-key. IN KEY 512 3 157 p2L9cNndDtTTHn6GzGHOEg==
# cat Krndc-key.+157+32572.private
Private-key-format: v1.2
Algorithm: 157 (HMAC_MD5)
Key: p2L9cNndDtTTHn6GzGHOEg==

The next step is to configure both name servers with the shared key:

/var/named/etc/named.conf
key dns1-dns2.kernel-panic.it. {
    algorithm       hmac-md5;
    secret          "p2L9cNndDtTTHn6GzGHOEg==";
};

Though it may look like a domain name, the argument to the key statement (dns1-dns2.kernel-panic.it.) is actually the name of the key. As suggested by the RFC, it is made up of the names of the two hosts that use it. The RFC also recommends that you use different keys for each pair of hosts.

Now that the keys are in place, we can use the keys clause of the server statement to tell the slave name server to sign all zone transfer requests and queries sent to its master server:

/var/named/etc/named.conf
server 172.16.240.154
    keys            { dns1-dns2.kernel-panic.it.; };
};

Similarly, on the master name server, we can restrict zone tranfers only to those signed with a specific key:

/var/named/etc/named.conf
zone "kernel-panic.it" {
    type            master;
    file            "master/db.kernel-panic.it";
    allow-transfer  { key dns1-dns2.kernel-panic.it.; };
};

4.5 Logging

Bind allows for a very flexible and fine-grained configuration of logging options. Log messages are divided into a number of categories, according to the information they contain, and each category can have its log messages sent to one or more user-defined channels.

Channels allow you to specify the output destination (a file, syslogd(8) or stderr), the minimum severity level required to report an event (critical, error, warning and so on) and whether to include time, category or severity information in the log message.

The configuration of channels and categories is placed inside the logging directive; below is a sample configuration, with Bind logging to the local0 syslog facility, writing security events to an additional file (/var/named/log/security.log) and discarding messages about misconfigured remote servers (please refer to the documentation for further details on available categories and predefined channels):

/var/named/etc/named.conf
# Configure the logging options
logging {
    channel security_channel {
        # Send log messages to the specified file
        file            "log/security.log";
        # Log all messages
        severity        debug;
        # Log the date and time of the message
        print-time      yes;
        # Log the category of the message
        print-category  yes;
        # Log the severity level of the message
        print-severity  yes; 
    };

    channel default {
        # Send logs to the 'local0' syslog facility
        syslog          local0;
        # Log messages of severity 'info' or higher
        severity        info;
        print-category  yes;
        print-severity  yes;
    };

    # Logs about approval and denial of requests
    category security {
        security_channel;
        default;
    };

    # Ignore logs about misconfigured remote servers
    category lame-servers { null; };

    # Default logging options
    category default { default; };
};

Using the local0 facility allows Bind to log to a dedicated file, without cluttering generic log files. After adding the appropriate line to /etc/syslog.conf(5):

/etc/syslog.conf
[ ... ]
local0.*                                                /var/log/named.log

we need to create the log file and reload both syslogd(8) and named(8). We will also create the /var/named/log directory, where the security_channel log file will be written:

# touch /var/log/named.log
# install -m 700 -o named -g named -d /var/named/log
# pkill -HUP syslogd
# rndc -c /var/named/etc/rndc.conf reload
server reload successful
#