Bind with DNSSEC

This is not a complete guide to setting up bind with dnssec - such a guide actually already exists, and it is pretty comprehensive. If you need more info about nsec3 signing, check out the rndc man page, specifically the part about signing. You really want nsec3 and not nsec, because nsec makes it easy to "walk" the zone, which is equivalent to a zone transfer.

Recently I stumbled upon a problem with my webmail - it was suddenly impossible to send mail. The webmail server is not hosted on the same machine as the mail server, thus the url to the remote server was not something like "localhost" but an actual FQDN. I discovered that there was not even any trace of the webmail client connecting to the mail server in the mail server's logs, which was baffling. I double checked the dns entry and made sure it was resolvable.

However, in the logs of the webmail server, lots of name resolution errors showed up. These sadly lacked the most important part (which name resolution had been attempted) but there were not many possibilities (this is more a note to any programmers - make your error messages descriptive!).

I found out that the webmail server was actually configured to use Google's DNS servers, while my workstation used my ISP's DNS server. Testing the Google server I found that it would not resolve the address of the smtp server, which was smtp.example.com. Googling the problem I arrived at the very useful tool https://dns.google.com/. This pinpointed to an expired DNSSEC signature of the smtp.example.com domain and suggested to further evaluate the problem using another helpful tool, http://dnsviz.net/. Indeed, it turned out that the signature for that particular domain had expired almost 24 hours ago.

Checking my primary dns server's log, I stumbled upon messages of the type

named[99999]: dumping master file: tmp-fywoAON4pO: open: permission denied

I remembered having seen them for a while, and ("since everything was working", and besides, i was "really busy with other, more important stuff") ignored them. However, it seems that the DNS server was trying to "DNSSEC maintain" my zones, i.e. trying to re-sign them. This is - as far as I could deduce from the documentation - done by creating a temporary file with all the appropriate entries. Once this is done, the file is moved to the appropriate place. This is the usual way programmers avoid dirty files, and actually pretty nice.

However, my DNS server runs in paranoia mode, i.e. it has selinux enabled and uses very strict permissions, as well as chrooted bind. My zone configuration files orignally looked like this:

zone "example.com" IN {
        type master;
        file "example.com.zone";
        allow-update { "none"; };
        allow-transfer { 1.1.1.1; 2.2.2.2; };
        notify yes;

        key-directory "/etc/pki/dnssec-keys/example.com.zone";
        auto-dnssec maintain;
        inline-signing yes;
};

This turned out to be part of the problem, specifically the line

        file "example.com.zone";

Whether or not bind runs in a chroot does not really matter for this problem, but it can make it more confusing. The path to the zone file is relative to bind's working directory. This is given in bind's configuration file (usually /etc/named.conf or similar) in the options block, the directive "directory". On respectable linux-distributions, this working directory is not writable for the named daemon for security reasons.

If you want to resolve the issue properly, do not follow advice that you can frequently find online:

  • Do not change permissions of bind's working directory to make it writeable.
  • Do not change ownership of bind's working directory to make it writeable.
  • Do not turn off selinux.

All of these are dirty workarounds which hurt your system's security. Besides that, they are not even good, permanent solutions:

  • All respectable linux-distributions will fix permission problems when updates are performed. I.e. changing permissions or ownership will only last until the next update.
  • On some systems, permission problems might even get corrected on-the-fly after minutes or seconds by special services (e.g. puppet).

The appropriate solution, in my eyes, is to handle zone files as "dynamic", even if you do not use dynamic updates. Dynamic zones are meant to be stored in a directory which exists specifically for this purpose, on my system it is found in the bind working directory and named "dynamic". This directory has the proper permissions and selinux-context to allow bind to write files. Primary zones that use DNSSEC should thus have a configuration file which looks like this:

zone "example.com" IN {
        type master;
        file "dynamic/example.com.zone";
        allow-update { "none"; };
        allow-transfer { 1.1.1.1; 2.2.2.2; };
        notify yes;

        key-directory "/etc/pki/dnssec-keys/example.com.zone";
        auto-dnssec maintain;
        inline-signing yes;
};

Of course, if you allow dynamic updates, you have to adapt that part. Secondly, slave zones should not reside in bind's working directory either, since it is not writeable. Putting slave zones there will prevent them from being transferred and written. For slave zones, there is a dedicated directory in the bind working directory, again with proper permissions and selinux-context. On my system it is called "slaves".

It is also not a solution for this problem to enable the named_write_master_zones selinx boolean. This boolean enables bind to write to zone files in its working directory - but while this might "fix" the problem of slave zone transfers, it will not help with DNSSEC-enabled master zones, particularly if they are set to auto-maintain. This configuration requires the named process to periodically generate a certain set of files for each domain (the .jnl, .signed, .jbk...) and apparently the selinux boolean does not allow creation of these files. Furthermore - and here we arrive at the beginning again - the named process creates temporary files during the signing process, which it also tries to write to the directory where the zone file is stored. Again, the selinux boolean apparently only allows write access to the actual zone files and not to auxiliary files.

Takeaway

Several things worth memorizing:

  • Use the "dynamic" directory for auto-maintained DNSSEC-enabled master-zones. This might not be an incredibly appropriate name for the directory given the usage (the zones are not actually dynamic in the original sense, but bind will add additional entries - the signatures, so they kind-of are).
  • Use the "slaves" directory for slave zones.
  • The named_write_master_zones selinx boolean does not fix this problem.
  • Changing permissions or ownership is dangerous, and not a permanent fix.

On a side note, I am not sure since when Google has the policy to not cache DNS records whose DNSSEC-signatures are expired. It is, however, a step in the right direction by Google. Most DNS servers do not care about DNSSEC at all. Google chose to be conservative when it comes to DNSSEC-enabled zones, treating expired DNSSEC-signatures strictly. This is a strong statement in a world where security is only "top priority" in press releases, not in practice for most companies big and small.

© 2010-2021 Stefan Birgmeier
sbirgmeier@21er.org