switching from dnsZone to dlz-ldap

In our infrastructure, all our DNS data is stored in multiple LDAP servers, which synchronize via OpenLDAP-internal mechanisms. One of the DNS servers is an openSUSE 12.2 physical server, all others run on SLES11 virtual machines. All DNS servers are using the “bind” software by ISC, and on every host running the “named” process, there’s a local “slapd” instance providing the DNS data.

One of the major aspects of our setup is that any change in the LDAP-stored DNS data is instantaneously reflected in the answers handed out by the corresponding name server. Periodically generating new zone data files from the LDAP store and then reloading them from the name server was out of the question. For quite some time, that left us with only the “sdb:ldap” implementation, a BIND extension from the unsupported “contrib” area.

But is was not only that sdb:ldap had it shortcomings, even more it is nowadays hard to find a pre-compiled server package with that extension activated. We could compile our own server software (actually, we did that during the last years in order to incorporate local fixes), but it’s usually better to run software that has a broader user base and thus is more thoroughly tested. Not to mention incorporated upstream fixes to security problems.

dlz-ldap – the more current trend

While sdb:ldap is still available, the current notion is to use bind’s “dynamically loadable zone” extension (DLZ) and its LDAP back-end for this purpose. It’s newer, it’s multi-threaded, and it seems that support for it is still available. Unfortunately, the move from sdb:ldap to DLZ isn’t that smooth as one might have wished. I don’t know exactly why “Stichting NLnet”, the developer of DLZ, did have to create a complete new LDAP scheme… after all, the project page claims that

“flexibility was a goal from day one with DLZ. It’s more than a little annoying to find a cool piece of software that can’t be used without modification because it doesn’t support your preferred database or your database schema. So, unlike most DNS servers, DLZ supports a wide variety of databases: PostgreSQL, MySQL, Berkeley DB, ODBC (thus Firebird, DB2, Oracle, Sybase, SAPDB) and LDAP. It can also use the hierarchal nature of a standard file system as a database. Here is the best part though: DLZ does not impose a pre-defined database schema with most databases.”

Those “most databases” unfortunately do not include LDAP. Quite contrary, the code is built around a new LDAP schema that significantly differs from sdb:ldap – less concerning the used objectClasses, but more concerning the structure of records:

  • There currently seems to be no way to create a hierarchical setup of domains like within sdb:ldap. Your zones will all have to exist in parallel, under a common root node, unless you’d like to impose the potential delay of searching the directory by some attribute, rather than by distinguished name. But to be honest: The same limitation exists for sdb:ldap, but there associating records via a common attribute (“zoneName”) was part of the design.
  • Similar to the way records are stored in file-based zones, each DNS record has to have its own LDAP element, rather than storing all DNS records of a host in the attributes of that host’s LDAP element.

This is different to the way the information is stored within the sdb:ldap schema (often referenced as “DNSZone” schema), so migrating from sdb:ldap to dlz needs conversion of your LDAP data. When working on the subject, I’ve come across quite some searches on the net for such a converter, but found no working script.

Being into automation as I am, I couldn’t let it stay that way and created both an according script and brought it to use. The remainder of this document describes the steps we took:

Installing a proper server package

While the bind package distributed with OpenSUSE Linux 12.2 is already prepared to run DLZ-LDAP (as you can verify by calling “named -V” to see the compile-time options, where you’ll find “–with-dlz-ldap”), “bind” packaged with SLES11 (SP2)  does not. We’ve therefore decided to resort to the packages provided by “Flacco” via OpenSUSE’s build service (http://download.opensuse.org/repositories/home:/flacco:/sles/SLE_11_SP2), although this leads to a possible (and practical) delay with package updates: Flacco’s packages usually take a short time to incorporate updates provided by Novell. As we’re running these daemons only on internal servers, the implications are rather small and controllable.

Migrating the data

Since we couldn’t just keep our DNS data as is in our LDAP directory for both sdb:ldap and dlz-ldap, we created a migration path allowing parallel operation of both versions by keeping a redundant copy in separate LDAP trees.

We created a sample awk script that takes an LDIF dump of sdb:ldap and creates an according LDIF file, suitable for import to our LDAP server(s). (Please keep in mind that the version available here is intended as a sample, which doesn’t cover all record types/fields and may need changes to be suitable for your exact environment.)

Joining long LDIF lines

A minor “difficulty” with manipulating LDIF files results from the fact that long lines are broken up into multiple lines. Indicator for this is a leading blank in the follow-up line, which would need to be removed to be able to handle the expressed value as a single line.

A standard solution to this is a small “sed” script, which you can use i.e. as a filter expression on any command chain:

sed -e :a -e ‘$!N;s/\n //;ta’ -e ‘P;D’

LDIF dump of your sdb:ldap data

Starting point our conversion is a current dump of the LDAP-stored DNS data:

ldapsearch -h ldapserver.company.com -b “ou=dns,dc=company,dc=com” -x > sdbldap.ldif

Of course, you’ll have to substitute the host name of your LDAP server, the base DN of your DNS data and optionally provide proper credentials to get access to your data.

awk-based conversion

The above sample script will read your LDIF data (once you’ve rejoined the lines), detect the dnszone entries and create according LDAP objects suitable for direct import. You should provide the base DN of your DLZ LDAP tree,  else you’d have to edit the resulting LDIF file.

cat sdbldap.ldif |
sed -e :a -e ‘$!N;s/\n //;ta’ -e ‘P;D’ |
awk -v basedn=”ou=dns-dlz,dc=company,dc=com” -f dnszone2dlzldap.awk
> dlzldap.ldif

The above is to be entered as a single line.

In addition to the new DNS data, the output will give a sample block of the required configuration statements that you need to insert into your name server configuration:

# The following lines need to be added to the bind configuration to access the LDAP-based zones:
# dlz “dlz-ldap” {
#       database “ldap 2
#       v3 simple {cn=Manager,o=bind-dlz} {secret} {127.0.0.1}
#       ldap:///dlzZoneName=$zone$,ou=dns-dlz,dc=company,dc=com???objectclass=dlzZone
#       ldap:///dlzHostName=$record$,dlzZoneName=$zone$,ou=dns-dlz,dc=company,dc=com?dlzTTL,dlzType,dlzPreference,dlzData,dlzIPAddr?sub?(&(objectclass=dlzAbstractRecord)(!(dlzType=soa)))
#       ldap:///dlzHostName=@,dlzZoneName=$zone$,ou=dns-dlz,dc=company,dc=com?dlzTTL,dlzType,dlzData,dlzPrimaryNS,dlzAdminEmail,dlzSerial,dlzRefresh,dlzRetry,dlzExpire,dlzMinimum?sub?(&(objectclass=dlzAbstractRecord)(dlzType=soa))
#       ldap:///dlzZoneName=$zone$,ou=dns-dlz,dc=company,dc=com?dlzTTL,dlzType,dlzHostName,dlzPreference,dlzData,dlzIPAddr,dlzPrimaryNS,dlzAdminEmail,dlzSerial,dlzRefresh,dlzRetry,dlzExpire,dlzMinimum?sub?(&(objectclass=dlzAbstractRecord)(!(dlzType=soa)))
#       ldap:///dlzZoneName=$zone$,ou=dns-dlz,dc=company,dc=com??sub?(&(objectclass=dlzXFR)(dlzIPAddr=$client$))”;
# };

Each line is preceeded by a comment character so it won’t disturb an LDIF import – and to make you think about it when you insert it via cut&paste into your DNS configuration 😉

The last (commented) line of output is a short statistics information, i.e.

# statistics: read 1398 records, 1357 were DNS records.

You needn’t worry if not all of your exported records were DNS records – those giving structure (i.e. “dc=company,dc=com,ou=dns,dc=company,dc=com”) have no dnszone objectClass and therefor don’t count as DNS record, formally.

If any error is detected during the conversion, the awk script will insert according error messages (search for the string “error”).

Let me emphasize again that the published script is a sample only – only a limited number of records types are supported, not all “multi-value attributes” support more than one value, and the resulting LDAP tree is just one of multiple ways of handling this. But it should give you a good starting point to create a version suitable for your specific environment, and you’re encouraged to extend, optimize or just simply rewrite all or any part. The only thing I ask for is that you report back any substantial errors you find with the existing version by leaving a comment.

Preparing your LDAP servers

Before importing the data to your LDAP server(s), a little preparation is in order: The least thing to do is to make the required schema available (i.e. dlz.schema), but you’ll probably want to add some indices, too (i.e. “dlzType eq”). Depending on your LDAP server setup, you might have to remember do configure this for replicant servers, too – else the new DLZ entries won’t sync due to the missing classes, and any subsequent changes to *any* LDAP data either.

The last step is to import your generated and manually checked LDAP file to the LDAP server(s):

ldapadd -h ldapserver.company.com -D “cn=accountdn” -Wx -f dlzldap.ldif

Again you’ll have to substitute your server name and will most likely have to provide some sort of credentials to gain write access to your LDAP server.

The name server

In order to use that newly imported data, you’ll have to adjust the configuration of your name server. Of course you’ll have to take out the part referencing the sdb:ldap-based zones (which can be quite a lot of statements), and add in that single block of statements activating the dlz-ldap access. (This is something you might find confusing at first: you actually only have a single block of DLZ statements – there’s currently no way to mix and match multiple DLZ access paths, either via the same back-end or different back-ends. That also means that you cannot split up LDAP-based zones between different trees, unless you’re able to specify single LDAP queries that apply to all zones.)

A sample block is generated by the above conversion script, but will likely have to be modified concerning the address of the LDAP server(s) to be queried and the account used to access the LDAP server(s). I’ve seen no way to specify anonymous binds to the LDAP server, so if not done already, you’ll need to create some account you can use in the named configuration.

If you compare the generated config statements with the documentation from the dlz home page, you’ll notice that the “token” words are not enclosed by percent signs, but by dollar signs. The percent signs caused the URI validator of OpenLDAP’s library to fail, therefore the DLZ LDAP driver wouldn’t load those lines at all. The corresponding fix is from 2006 – I don’t understand why this hasn’t been reflected on the project page to date. At least that patch is already included in the provided RPMs.

Again you are free to change the LDAP queries, and if you have changed the structure of the generated LDAP tree, that’ll be likely required. I always recommend to understand what’s presented to you, so my advice is to go through the documentation pages and verify what’s generated – there even may be errors hiding in the LDIF file or the generated config statements 😉

After restarting the server, you should be all set.

Debugging

What to do if you cannot get this to work? You may see error messages in syslog (or where-ever your named is configured to report its messages). Or there’s nothing but silence.

I recommend starting the name server with some debug level, i.e. by calling

/usr/sbin/named -g -d1 -t /var/lib/named -u named

directly on the command line. That way, the output is directed to your terminal session and you may find it easier to start, debug and stop your server.

If you don’t see even a hint at all in named’s error messages, chances are high that you either have no trace of DLZ configuration in your config files – or your named binary is compiled without the appropriate switched (see “named -V” above).

Depending on your setup, you’ll have to watch out when changing the server configuration files: Typically, the name server is configured to run in it’s own “jail” with files beneath /var/lib/named (or similar) – the configuration files under /etc/named* are copied to the jail directories by the named startup scripts. But when starting your server manually via the above statement, none of that takes place… you’ll have to either copy yourself or remember to edit the config files in the jail directory, migrating the final changes back to the config files under /etc.

If the debug log tells you that the last (or first) LDAP statement is wrong, check for line breaks, extra blanks or percent signs instead of dollar signs.

And as always… if every thing else fails: Read the documentation.

 

 

This entry was posted in Linux, Uncategorized. Bookmark the permalink.

Leave a Reply