====== Dynamic DNS in LDAP ======
Actually using [[http://nimh.org/code/ldapdns/|ldapdns]]((By the way, this project seems dead and its [[http://ldapdns.sourceforge.net/|version 3]], too. But it's still in [[http://packages.qa.debian.org/l/ldapdns.html|Debian Lenny and will be in Squeeze]])), I want to have [[wp>DDNS|dynamic DNS (ddns)]] stored in LDAP tree.
Basically I wrote an perl script that monitor ''dhcpd.lease''((Yes, I'm using ISC DHCPD)) for change and update my DIT((The script is not released as I didn't take the time to do so. I'll release the script soon)). An entry looks like that :
dn: dc=iro023,dc=machines,dc=irovision,dc=ch,ou=dns,o=iro
objectClass: dNSDomain
objectClass: ieee802Device
dc: iro023
macAddress: 00:11:85:11:ec:14
seeAlso: uid=iro023$,ou=machines,o=iro
aRecord: 172.16.30.135
As you see there's a ''seeAlso'' attribute pointing to somewhere else in the DIT. This is a MS Windows machine join in our Samba domain. As you can see, below, the entry contains its mac address too.
for
dn: uid=iro023$,ou=machines,o=iro
objectClass: top
objectClass: account
objectClass: posixAccount
objectClass: sambaSamAccount
objectClass: ieee802Device
cn: iro023$
uid: iro023$
uidNumber: 10075
gidNumber: 515
homeDirectory: /dev/null
loginShell: /bin/false
description: Computer
gecos: Computer
sambaSID: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
displayName: IRO023$
sambaAcctFlags: [W ]
macAddress: 00:11:85:11:ec:14
sambaNTPassword: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
sambaPwdLastSet: XXXXXXXXXX
What we do is to record the mac address of the machine, when registering into the Samba domain, and use this information to give the DNS name, so it stay in sync.
Machines that are not part of the Samba domain and are not part of anything (if any), we just use the provided ''client-hostname''.
So that's what does the script. It sync all that, take care of registering reverse DNS (and keeping ''seeAlso'' pointing where it should) :
dn: dc=135,dc=30,dc=16,dc=172,dc=in-addr,dc=arpa,ou=dns,o=iro
dc: 135
cNAMERecord: iro023.machines.irovision.ch
seeAlso: dc=iro023,dc=machines,dc=irovision,dc=ch,ou=dns,o=iro
objectClass: dNSDomain
Unfortunately I'm having troubles with that. I don't know when it ends. I put some complex code to manage when machine should be deleted but it doesn't work at all.
To solve that problem, I need to put the end time of the lease into the entry, and by the way the time to live and the start time would be useful, but there's no provision for that provided by actual schema. A discussion about a DHCP schema is available on [[http://www.ietf.org/mail-archive/web/dhcwg/current/msg02709.html|IETF mailing list for some working draft]].
===== Work to be done =====
* Modify ldapdns to take entryttl into account
* Bit more of testing
* Cleanup all that debug stuff
* Write an auxiliary schema that provide useful attribute for DDNS.
* Modify the script to use it
* Release the script (GPL of course)
* Describe the setup
===== Schema =====
Schema I have found for DHCP are the following :
* http://tools.ietf.org/html/draft-ietf-dhc-schema-02
* http://tools.ietf.org/id/draft-gu-dhcp-ldap-schema-00.txt
==== Draft IETF DHC schema 02 ====
Just looking at what interest me :
NAME dhcpClient
DESCRIPTION This represents client-specific DHCP assignments.
TYPE Structural
DERIVED FROM dhcpRule
POSSIBLE SUPERIORS ( OrganizationalUnit dhcpRule )
MUST CONTAIN ( cn dhcpClientIdentifier )
MAY CONTAIN ( dhcpClassMember dhcpReservedAddress )
and
NAME dhcpAddress
DESCRIPTION This class represents an IP Address, which may or
may not have been leased.
TYPE Structural
DERIVED FROM Top
POSSIBLE SUPERIORS ( )
MUST CONTAIN ( cn dhcpAddressState )
MAY CONTAIN ( dhcpExpirationTime dhcpStartTimeOfState
dhcpLastTransactionTime dhcpBootpFlag
dhcpDomainName dhcpDnsStatus
dhcpRequestedHostName dhcpAssignedHostName
dhcpReservedForClient dhcpAssignedToClient
dhcpRelayAgentInfo dhcpOptionSetting
dhcpParameterSetting dhcpFieldSetting )
The idea is the following :
* A lease in LDAP might or might not be assigned to any client.
* When a client has an address assigned, an entry ''dhcpClient'' is created.
* The lease is updated and the DN of the entry ''dhcpClient'' is set in attribute ''dhcpAssignedToClient''.
==== Draft Gu DHCP LDAP schema 00 ====
Here is what interest me :
NAME 'DHCPLease'
DESCRIPTION 'This class specifies individual lease
information.'
TYPE Structural
DERIVED FROM Top
POSSIBLE SUPERIORS ( DHCPSubnet )
MUST CONTAIN ( IPAddress $ LeaseType )
MAY CONTAIN ( UniqueID $ ClientName $ LeaseExpiration $
LeaseState $ Description )
==== How I see it ====
I think there's already things that can carry client information in LDAP. In [[http://www.ietf.org/rfc/rfc2307.txt|RFC 2307]], we already find interesting class like :
* ieee802Device
* ipHost
Another class that would be interesting is ''DNSDomain'' defined in [[http://tools.ietf.org/html/rfc1274|RFC 1274]]. [[http://tools.ietf.org/html/rfc4524|RFC 4524]] (it obsoletes [[http://tools.ietf.org/html/rfc1274|RFC 1274]]) doesn't bring this class forward as this class is used as experimental in [[http://tools.ietf.org/html/rfc1279|RFC 1279]]. I'm not sure what all that means ...
But all in all, I think I don't want to redefine everything. We know we have IP host, we have [[http://en.wikipedia.org/wiki/IEEE_802.2|IEEE 802.2]] devices, we have domain and domain related object (in [[http://tools.ietf.org/html/rfc4524|RFC 4524]]). LDAP server maintains a ''createTimestamp'' and ''modifyTimestamp'', defined in [[http://www.ietf.org/rfc/rfc4512.txt|RFC 4512]].
So all we need, in the end, is TTL. This would be sufficient. This attribute has been proposed in [[http://tools.ietf.org/html/draft-ietf-asid-ldap-cache-01|draft IETF Asid LDAP CACHE 01]]
==== entryTTL ====
I came across [[http://tools.ietf.org/html/rfc2589|RFC 2589]]. This RFC define attribute ''entryTTL''. This is what I need. No need for a brand new schema, just use what is already available.
===== Implementation =====
The idea is to use ldapdns as follow
[ INTERNET ]
+-------------+ ^ ^
| LDAP master |<---------+ | |
+-------------+<-------+ | | |
| | +-------------+ +-DNS-----------+ |
| +-LDAP-| ldapdns 1 |<-----------+ | |
| | +-------------+<---------+ | | |
+-------------+ | | | | +-------+--------+ |
| LDAP slave |<-------(-+ | +------DNS-| pdns_recusor 1 |<---+ |
+-------------+<-------+ | | +----------------+ | |
| +---(-+ +-DNS---------(---+
| +-------------+ | | | +-DNS-[CLIENTS]
+-LDAP-| ldapdns 2 |<-------+ | +-------+--------+
+-------------+<-----------+--------DNS-| pdns_recusor 2 |<-----DNS-[CLIENTS]
+----------------+
==== Basic LDAP/DNS stuff ====
DNS entries in LDAP are used by ldapdns. It is configured with ''cosine'' schema (in ''ldapdns.conf''):
# the schema of LDAP database and queries (rfc1279, msdns, cosine, ldapdns)
SCHEMA=cosine
So we have a tree like that :
ou=dns,o=iro
|
+ dc=ch
|
+ dc=irovision
|
+ dc=www
All entries below ''ou=dns,o=irovision'' are ''dnsDomain'' class and also ''dynamicObject''. The ''dynamicObject'' class might not be available on your LDAP server if it doesn't implement RFC 2589. The ''entryTtl'' value is given a value corresponding to the time to live given to the lease by the DHCP server.
When registering a computer in the directory, the script looks if this computer is already registered in a Windows domain. If it is, the name set during registration is used instead of the name given by the computer to the DHCP server.
To find the machine account the MAC address is used. So each Windows machine account has ''ieee802device'' class. The address is actually set manually but I successfully wrote a script to do that auto-magically (sadly this script is lost as it was written at my previous job, but I'll write it again).
What we have here is the following :
ou=machines,o=iro
|
+ uid=iro001
+ uid=iro002
+ ...
If nothing is found in there, the name provided by the DHCP server is used.
=== Patch openLDAP ===
OpenLDAP has a nasty bug, so you can patch : [[http://www.openldap.org/its/index.cgi/Incoming?id=6490]]. The bug in itself shouldn't happen in normal case, but the script can crash the server. If there's a bug (in the script) which send a refresh request to openLDAP with an empty DN the server segfault. corrected in CVS. See bug [[http://www.openldap.org/its/index.cgi?selectid=6490|6490]].
==== DHCP ====
Nothing to do, really.
==== ldapdns ====
In order to use ''entryTtl'' as "Time To Live" in DNS answer, I made a first patch against the official ldapdns-2.06 source code. So when you request a DNS entry, the TTL is set according to the time to live set in the DIT. It allows to have caching DNS server (I use PowerDNS recursor in front of my ldapdns servers to do caching and recursion).
Here is an example of request made to ldapdns server and the same request made to the DIT (edited to remove comments and useless information). The TTL match the one found in the DIT.
$ dig @vs121.servers.irovision.ch iro025.machines.irovision.ch
;; QUESTION SECTION:
;iro025.machines.irovision.ch. IN A
;; ANSWER SECTION:
iro025.machines.irovision.ch. 5003 IN A 172.16.30.112
$ ldapsearch -h vs121.servers.irovision.ch -x -b dc=iro025,dc=machines,dc=irovision,dc=ch,ou=dns,o=iro "entryTtl"
# iro025, machines, irovision, ch, dns, iro
dn: dc=iro025,dc=machines,dc=irovision,dc=ch,ou=dns,o=iro
entryTtl: 5003
It also works with reverse DNS request.
=== The patch ===
The patch is quite experimental by now. It actually running on one production server at my work place to test it in real environment. I will also port the patch to Debian version of ldapdns as it's the only place I know bug fixes are still done on this software, so more to come.
[[http://www.tchetch.net/code/ldapdns/ldapdns-entryttl.patch|ldapdns-entryttl patch]]
==== Perl ====
You need to have the ''refresh'' extended operation available. I wrote it and you'll find it on the mailing list [[http://markmail.org/thread/tay3nyjeixor6xyu]] (well there are links pointing back to here to get the code, but you'd better follow the list thread to see if there's any change).
There's also a patch, but it's not needed actually. I hope this code will get into the main distribution of perl-ldap extension.\\
[[http://search.cpan.org/~gbarr/perl-ldap-0.4001/|It's available in latest release of perl-ldap]]
There's also a bunch of extension from CPAN I use (and one from nowhere, I think). Here is the ''use'' section :
use strict;
use Config::File qw(read_config_file);
use Net::LDAP;
use Net::LDAP::Constant qw(LDAP_SUCCESS);
use Net::LDAP::Extension::Refresh;
use Getopt::Std;
use Data::Dumper;
use Net::DHCP::ParseLeaseFile;
use Date::Parse;
use Linux::Inotify2;
use File::Copy;
use POSIX;
use File::Temp qw(tmpnam);
use Sys::Syslog;
The ''Net::DCHP::ParseLeaseFile'' comes from nowhere I think. I couldn't find this again (but I found it 2008 when I first wrote the script). The ''Data::Dumper'' may not be necessary, I'll check and update this.
The configuration file works like that :
# User and and group to use
USER=nobody
GROUP=nogroup
# Server configuration (support only simple)
AUTH = simple
# Can be ldap, ldaps and ldapi, default to ldap
SCHEME = ldap
# Default to 2
VERSION = 3
# Default to 389 for ldap and 686 for ldaps
PORT = 389
## Default to localhost
#SERVER =
## If not set, anonymous bind
#BIND_DN = xxxx
#BIND_PW = xxxx
## If not set, won't search for existing machines
#MACHINES_SUFFIX =
## If not set, die
#DNS_SUFFIX =
#RDNS_SUFFIX =
# Default to ieee802device
MACHINES_OBJECTCLASS = ieee802device
# Default to dnsdomain
DNS_OBJECTCLASS = dnsdomain
MACHINES_NAME_ATTR = cn
DNS_NAME_ATTR = dc
#DNS_DOMAIN_NAME =
# TLS Configuration (no support for now)
STARTTLS = no
TLS_VERIFY = none
#TLS_CAFILE =
#TLS_CERT =
#TLS_KEYCERT =
#TLS_KEYPASS =
#TLS_CAPATH =
#TLS_CIPHERS =
LEASE_FILE = /var/lib/dhcp3/dhcpd.leases
DDS_LEASE_EXPIRE_DIFF = 15
The TLS part is not supported as there's no code to handle this. The only authentication available is ''simple'', so user and password.
The user that the script use must have the ''manage'' right on ''DNS_SUFFIX'' and ''RDNS_SUFFIX''.
The script will drop its privileges to ''USER'' and ''GROUP'' after the configuration file is read.
The ''DDS_LEASE_EXPIRE_DIFF'' is the time, in second, of how big the difference between the LDAP ''entryTtl'' and the DHCP TTL can be. This avoid to be always refreshing all entries at every run of the script (and you don't need atomic precision for that).
The script is available at [[http://www.tchetch.net/code/perl/ldap_ddns.pl.txt]], it is not stable at all (but it's running for some days now on production servers without problem) so use it at you own risk.
=== Features list ===
* Keeps ''dhcpd.lease'' synchronized with you directory
* Write forward DNS and reverse DNS entry. Thoses entries are linked with attribute ''seeAlso'' (the reverse DNS entry point to the forward DNS entry, not the other way around).
* Can use Windows machine account name as DNS name
* Forward DNS entry point back to Windows machine account with ''seeAlso'' attribute
* Create entries in your directory that will automatically disappear