l2dd - LDAP to DNS Daemon

l2dd is a little tool I wrote because I didn't find any useful software to export a dns config stored in ldap into DNS. It's written in perl, and you need 4 modules for it (from cpan, apt, rpm, whatever you like) to work:

1
2
3
4
Net::LDAP
Data::Dumper
Getopt::Std
POSIX

Furthermore it was written for bind and openldap, not tested amongst other software. I guess it would work with other software with minor or no modifications.

How does it work?

l2dd connects to your LDAP server (via TLS!) and reads a specific directory tree. This tree contains your DNS hierachy for the zones your DNS server should serve. Wherever it finds a SOA record it will start creating a zonefile, every entry under a SOA record is considered a record within the zone-file. With examples explanations work best.

Let's say you have a zone in the .com TLD, so the first entry you need to have in your LDAP Directory looks like this:

1
2
3
4
5
dn: dc=com,ou=dnsConfig,dc=wogri,dc=at
objectClass: dNSDomain
objectClass: domain
objectClass: top
dc: com

This is just the '.com' part. As I guess you're not responsible for the .com domain you will make another entry below .com that looks like this:

1
2
3
4
5
6
dn: dc=wogri,dc=com,ou=dnsConfig,dc=wogri,dc=at
objectClass: dNSDomain
objectClass: domain
objectClass: dNSExtensionRecords
objectClass: top
dc: wogri

Allright, as you would like to be the master for the wogri.com domain, all you need to do is add the SOA attribute, and the NS attributes to the wogri-object:

(this is the complete object now):

1
2
3
4
5
6
7
8
9
10
11
12
dn: dc=wogri,dc=com,ou=dnsConfig,dc=wogri,dc=at
objectClass: dNSDomain
objectClass: domain
objectClass: dNSExtensionRecords
objectClass: top
aRecord: 85.233.97.251
dc: wogri
mXRecord:: 0 mail.wogri.at.
nSRecord: ns1.kt-net.at.
nSRecord: dns.wogri.at.
nSRecord: dns.wana.at.
sOARecord: dns.wogri.at. spam.wogri.at. 2007031501 600 100 600 100

Allright, with this litle record l2dd would already create a zone-file with the contents of the wogri.com domain. If we would like to add a record that belongs to a sub-domain, we would simply create such a record below the wogri-record:

1
2
3
4
5
6
7
dn: dc=www,dc=wogri,dc=com,ou=dnsConfig,dc=wogri,dc=at
objectClass: dNSDomain
objectClass: domain
objectClass: dNSExtensionRecords
objectClass: top
cNAMERecord: wogri.com.
dc: www

This creates a record in the zonefile called www.wogri.com. which is a CNAME for wogri.com.

And that's the whole principle. You can create subdomains of subdomains by just creating another object below the subdomain and so on.

On thing that is'nt implemented is another SOA record below a SOA record. I didn't find out why because I don't use such delegations and I didn't have time to thinkt about it.

So let's say we would like to add an SRV-Record for our VOIP-Service, we would create the following objects:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dn: dc=_udp,dc=wogri,dc=com,ou=dnsConfig,dc=wogri,dc=at
objectClass: dNSDomain
objectClass: domain
objectClass: dNSExtensionRecords
objectClass: top
dc: _udp

dn: dc=_sip,dc=_udp,dc=wogri,dc=com,ou=dnsConfig,dc=wogri,dc=at
objectClass: dNSDomain
objectClass: domain
objectClass: dNSExtensionRecords
objectClass: top
dc: _sip
SRVRecord: 0 1 5060        voip.wogri.at.

Got me?

Obviously you need ldap-schemes (l2dd has been written for openldap, so this instruction is for openldap). One is included in the openldap-distribution, it's called dNSDomain. Unfortunately it doesn't include a whole lot record types, so I extended the schema with my own, called dNSExtensionRecords. You can use it or modify it as you wish:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
dn: cn={5}dnsextension
objectClass: olcSchemaConfig
cn: {5}dnsextension
olcAttributeTypes: {0}( 1.3.6.1.4.1.23741.10.1 NAME 'TXTRecord' \
DESC 'A DNS Text Record ' EQUALITY caseIgnoreIA5Match SYNTAX \
1.3.6.1.4.1.1466.115.121.1.26 ) 
olcAttributeTypes: {1}( 1.3.6.1.4.1.23741.10.10 NAME 'SRVRecord' \
DESC 'A DNS SRV Record ' EQUALITY caseIgnoreIA5Match SYNTAX \
1.3.6.1.4.1.1466.115.121.1.26 ) 
olcAttributeTypes: {2}( 1.3.6.1.4.1.23741.10.20 NAME 'PTRRecord' \
DESC 'A DNS PTR Record ' EQUALITY caseIgnoreIA5Match SYNTAX \
1.3.6.1.4.1.1466.115.121.1.26 ) 
olcAttributeTypes: {3}( 1.3.6.1.4.1.23741.10.30 NAME 'AAAARecord' \
DESC 'A DNS quad A Record ' EQUALITY caseIgnoreMatch SYNTAX \
1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: {4}( 1.3.6.1.4.1.23741.10.40 NAME 'SPFRecord' \
DESC 'A DNS SPF Record ' EQUALITY caseIgnoreIA5Match SYNTAX \
1.3.6.1.4.1.1466.115.121.1.26 ) 
olcAttributeTypes: {5}( 1.3.6.1.4.1.23741.10.50 NAME 'RAWRecord' \
DESC 'A DNS RAW Record ' EQUALITY caseIgnoreIA5Match SYNTAX \
1.3.6.1.4.1.1466.115.121.1.26 ) 
olcObjectClasses: {6}( 1.3.6.1.4.1.23741.100.2 NAME 'dNSExtensionRecords' \
DESC 'Additional DNS Records' SUP top AUXILIARY MAY \
( TXTRecord $ SRVRecord $ PTRRecord $ AAAARecord $ SPFRecord $ RAWRecord ) )

How to use l2dd?

The usage is quite simple if you're experienced with LDAP.

Download the script below, and see the little help text:

1
2
3
4
5
6
7
8
9
10
11
12
13
./l2dd.pl -h 
usage: ./l2dd.pl [options]
where options can be:
-p      LDAP-Password
-H      LDAP-Host (URI-Support, e.g. ldaps://your.ldap.host:1234)
-u      LDAP-Username (e.g. cn=yourUser,dc=your,dc=domain)
-b      LDAP-Search-Base (e.g. ou=DNSConfig,dc=your,dc=domain)
-P  Filesystem-Path where to write the finished config.
-h  this help

All these options can be set in ./l2dd.pl (head section), so you
don't need to type them in your command-line (which is
not a good idea to do, other people would see the password)

I think this is quite self-explanatory, isn't it? One important thing to know is the -P switch - this defines the path where the zonefiles will be created to.

It works like this: l2dd gets called from cron and fetches all information from ldap, creates temporary zonefiles and compares those zonefiles with the original, running zonefiles. If there is no difference nothing will happen. If there is a difference, l2dd will adjust the serial of the zonefile and call rndc to reload just that zone. If the zone would contain errors bind wouldn't reload the file (and still use the old zone-file) and you will be notified from cron, because l2dd catches the tail of the logfile and sends it to you after the reload.

Hint: l2dd 'adjusts' the serial by creating a timestamp, where the last 2 digits of the timestamp is the hour (in 24h-format). e.g. 2007082123. It is very important that you also use this format in your ldap-soa-entries, otherwhise l2dd won't work! If you are changing stuff in LDAP and the serial number matters to you I recommend either rewriting l2dd to create better serials or call l2dd every hour from cron, so the zonefile will always use an up to date serial.

Allright, so my setup looks like this:

1
2
3
4
5
6
7
8
9
10
crontab: 
*/5     *       *       *       *       /usr/local/sbin/l2dd.pl

l2dd.pl:
my $passwd="mypasswd";
my $host="my.ldap.server.wogri.at";
my $user="cn=l2dd-user,dc=wogri,dc=com";
my $base="ou=dnsConfig,dc=wogri,dc=com";
my $path="/var/named/etc/masters/";
my $sudo="sudo"; # blank for no sudo.

Of course you still need to configure bind "by hand", but you could even automate that with some little scripting…

1
2
3
4
5
6
7
zone "wogri.com" {
  notify yes;
  type   master;
  allow-transfer {
  };
  file   "/etc/bind/masters/wogri.com.db";
};

Download

Get l2dd for linux and openbsd. There are 2 slightly different versions because I used 'sed' in there, and the command-line switches differ.

Letzte Änderung: 20.09.2013