Using Linux with OpenLDAP for User, DHCP and DNS

Reading Time: 27 minutes

I’m using Microsoft Active Directory in my Lab for most of the tasks, like user authentication, DNS services, and DHCP. The windows VM is getting bigger and bigger so I decided to switch to Linux. The goal is, to have my user directory, my DNS zones and DHCP subnets managed in OpenLDAP. This post shows the steps to reach this goal and how to switch from Active Directory to OpenLDAP.

First of all. I’m not an OpenLDAP expert, so everything covered in this post is working for my lab. It is not tuned or optimized.

I use the latest Debian version for my Lab server. For other distributions, the steps might differ. But the concept is still the same.

Install OpenLDAP and Configure the Basics

I assume, that the base OS is installed. I use the latest version as of today:

root@devil:~# cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
NAME="Debian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

To install OpenLDAP I followed the Debian wiki here:

https://wiki.debian.org/LDAP/OpenLDAPSetup

So the first step is to install OpenLDAP on the system:

# apt install slapd ldap-utils ldapscripts

During the installation, the system asks you for the “Administrator Password”. This is the root password for LDAP. So keep it safe and secure.

To create an initial configuration for OpenLDAP use this command:

dpkg-reconfigure -plow slapd

Answer the first question with no:

Omit OpenLDAP server configuration?

Next, the system will grab your DNS name to build the base dn. If the DNS name is not correct, replace the string with the correct DNS name.

You will now be asked for the organization name. It might be the same as the DNS name, but without the .tld part.

You need to enter a new admin password.

Choose MDB as the backend.

As this is a new install, I let the setup remove the database. so the answer is yes, to this question:

Do you want the database to be removed when slapd is purged?

Answer the next question with yes again:

Move old database?

Afterward, the setup creates all the needed files and configurations and you are done. OpenLDAP is installed.

As adviced in the Debian Wiki, I also added the additional indexes to OpenLDAP. Just create a file “indexes.ldif” with the following content:

dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcDbIndex
olcDbIndex: cn pres,sub,eq
-
add: olcDbIndex
olcDbIndex: sn pres,sub,eq
-
add: olcDbIndex
olcDbIndex: uid pres,sub,eq
-
add: olcDbIndex
olcDbIndex: displayName pres,sub,eq
-
add: olcDbIndex
olcDbIndex: default sub
-
add: olcDbIndex
olcDbIndex: uidNumber eq
-
add: olcDbIndex
olcDbIndex: gidNumber eq
-
add: olcDbIndex
olcDbIndex: mail,givenName eq,subinitial
-
add: olcDbIndex
olcDbIndex: dc eq

Make sure, that the database type matches the one you choose during initial setup.

Afterward, you can apply the indexes to OpenLDAP with this command:

# ldapmodify -Y EXTERNAL -H ldapi:/// -f indexes.ldif 
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
modifying entry "olcDatabase={1}mdb,cn=config"

To check, if the new indexes are applied, use this command:

# ldapsearch -Y EXTERNAL -H ldapi:/// -b "cn=config"  -W olcDatabase={1}mdb olcDbIndex
Enter LDAP Password: 
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
# extended LDIF
#
# LDAPv3
# base <cn=config> with scope subtree
# filter: olcDatabase={1}mdb
# requesting: olcDbIndex 
#

# {1}mdb, config
dn: olcDatabase={1}mdb,cn=config
olcDbIndex: cn pres,sub,eq
olcDbIndex: sn pres,sub,eq
olcDbIndex: uid pres,sub,eq
olcDbIndex: displayName pres,sub,eq
olcDbIndex: default sub
olcDbIndex: uidNumber eq
olcDbIndex: gidNumber eq
olcDbIndex: mail,givenName eq,subinitial
olcDbIndex: dc eq

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

The system will ask for a password, but as you are on the localhost, you do not need a password.

You can now start to deploy users and groups.

There are plenty of documents on the web, who shows how to structure an LDAP directory. So I will not cover this here, but here are some good links:

My structure is as follows, but remember, this is for a lab environment (and for my home use):

OpenLDAP - My Strucuture
OpenLDAP – My Structure

On the second level, I have inserted another layer to separate my users and groups from the services like DHCP and DNS.

The first part was the easy one, lets start to integrate DHCP and DNS. It took me several weeks to figure this out.

OpenLDAP: Integrate Bind

My main goal is not performance but easy change. So I wanted to have one database for all and searched for a way to integrate Bind (my DNS server) into my OpenLDAP server. The following part shows how I did it. Bind9 can use OpenLDAP to store data for DNS zones. Other configurations like forwarders are still in the Bind config file.

First of all, you need to install the OpenLDAP extension for Bind:

# apt-get install bind9-dyndb-ldap

If you have Bind already installed, this only adds the extension. If you haven’t, this will also install Bind.

After that, you need to add the Bind schema to your OpenLDAP server. The package includes a schema file here:

/usr/share/doc/bind9-dyndb-ldap/schema.ldif.gz

But this schema file was not working for me, so I changed the schema file to this one:

# cat bind9.schema.ldif 
# This schema contains OIDs from Uninett and FreeIPA.
#
# Unninet: http://drift.uninett.no/nett/ip-nett/dnsattributes.schema
#          Base OID for DNS records is 1.3.6.1.4.1.2428.20.1,
#          see http://drift.uninett.no/nett/ip-nett/oids.html
#
# FreeIPA: http://freeipa.org/
#          Base OID for DNS records is 2.16.840.1.113730.3.8.5
#          Base OID for DNS objectClasses is 2.16.840.1.113730.3.8.6
#
# If you want to add some record types that are defined by IANA,
# please define it similar to what is done for the existing ones. The
# name should be {TYPE}Record, and OID should be
# 1.3.6.1.4.1.2428.20.1.value. For instance the RR type LOC has value
# 29, so attribute name should be LocRecord (casing shouldn't matter),
# and the OID is 1.3.6.1.4.1.2428.20.1.29. If you follow this, you
# know that it will be compatible with what others use, and one is
# guaranteed that the OIDs are unique.
# The IANA DNS record type values are available from
# .
#
# If you define new attributes, please report them to [email protected]
# to get them added of this schema.
#
# The basic record types like A, CNAME etc are defined in the cosine
# schema and not by UNINETT or FreeIPA.  This means that your LDAP server
# should use the old COSINE schema (RFC 1274) plus this one to get
# all the DNS attributes defined.
#
# Alternativelly you can use included excerpt from COSINE schema to get all
# the missing attributes.
#
#
# 389 DS requires following DN
#dn: cn=schema
#
# OpenLDAP 2.4 requires following DN + objectClass + different attribute names
# s/^olcAttributeTypes:/olcAttributeTypes:/
# s/^olcObjectClasses:/olcObjectClasses:/
dn: cn=dns,cn=schema,cn=config
objectClass: olcSchemaConfig
#
#
# COSINE schema
# comment out if your server has COSINE schema installed
#olcAttributeTypes: ( 0.9.2342.19200300.100.1.26 
# NAME 'aRecord' 
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
# EQUALITY caseIgnoreIA5Match )
#
#olcAttributeTypes: ( 0.9.2342.19200300.100.1.27 
# NAME 'mDRecord' 
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
# EQUALITY caseIgnoreIA5Match )
#
#olcAttributeTypes: ( 0.9.2342.19200300.100.1.28 
# NAME 'mXRecord' 
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
# EQUALITY caseIgnoreIA5Match )
#
#olcAttributeTypes: ( 0.9.2342.19200300.100.1.29 
# NAME 'nSRecord' 
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
# EQUALITY caseIgnoreIA5Match )
# CNAME record was originally defined as multi-value
# but we redefined it as single-value to conform with RFC 2136, section 1.1.5.
#olcAttributeTypes: ( 0.9.2342.19200300.100.1.31 
# NAME 'cNAMERecord' 
# SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
# EQUALITY caseIgnoreIA5Match 
# SINGLE-VALUE )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.0.0 
 NAME 'dNSTTL' 
 DESC 'An integer denoting time to live' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 
 EQUALITY integerMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.0.2 
 NAME 'dNSdefaultTTL' 
 DESC 'An integer denoting default time to live, RFC 2308' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 
 EQUALITY integerMatch 
 ORDERING integerOrderingMatch )
#
#
# UNINETT and FreeIPA attributes
# dnsClass attribute is in fact unsupported by bind-dyndb-ldap
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.0.1 
 NAME 'dNSClass' 
 DESC 'The class of a resource record' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.12 
 NAME 'pTRRecord' 
 DESC 'domain name pointer, RFC 1035' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.13 
 NAME 'hInfoRecord' 
 DESC 'host information, RFC 1035' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.14 
 NAME 'mInfoRecord' 
 DESC 'mailbox or mail list information, RFC 1035' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.16 
 NAME 'tXTRecord' 
 DESC 'text string, RFC 1035' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.18 
 NAME 'aFSDBRecord' 
 DESC 'for AFS Data Base location, RFC 1183' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.28 
 NAME 'aAAARecord' 
 DESC 'IPv6 address, RFC 1886' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.29 
 NAME 'LocRecord' 
 DESC 'Location, RFC 1876' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.30 
 NAME 'nXTRecord' 
 DESC 'non-existant, RFC 2535' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.33 
 NAME 'sRVRecord' 
 DESC 'service location, RFC 2782' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.35 
 NAME 'nAPTRRecord' 
 DESC 'Naming Authority Pointer, RFC 2915' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.36 
 NAME 'kXRecord' 
 DESC 'Key Exchange Delegation, RFC 2230' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.37 
 NAME 'certRecord' 
 DESC 'certificate, RFC 2538' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.38 
 NAME 'a6Record' 
 DESC 'A6 Record Type, RFC 2874' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.39 
 NAME 'dNameRecord' 
 DESC 'Non-Terminal DNS Name Redirection, RFC 6672' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.43 
 NAME 'dSRecord' 
 DESC 'Delegation Signer, RFC 3658' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.44 
 NAME 'sSHFPRecord' 
 DESC 'SSH Key Fingerprint, draft-ietf-secsh-dns-05.txt' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.51 
 NAME 'nSEC3PARAMRecord' 
 DESC 'RFC 5155' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch 
 SINGLE-VALUE ) 
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.52 NAME 'TLSARecord' 
 DESC 'DNS-Based Authentication of Named Entities - Transport Layer Security Protocol, RFC 6698' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.1.32769 
 NAME 'DLVRecord' 
 DESC 'RFC 4431: DNSSEC Lookaside Validation' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
# See https://fedorahosted.org/bind-dyndb-ldap/wiki/Design/UnknownRecord
olcAttributeTypes: ( 1.3.6.1.4.1.2428.20.4 
 NAME 'UnknownRecord' 
 DESC 'unknown DNS record, RFC 3597' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.0 
 NAME 'idnsName' 
 DESC 'DNS FQDN' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.1 
 NAME 'idnsAllowDynUpdate' 
 DESC 'permit dynamic updates on this zone' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 
 EQUALITY booleanMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.2 
 NAME 'idnsZoneActive' 
 DESC 'define if the zone is considered in use' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 
 EQUALITY booleanMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.3 
 NAME 'idnsSOAmName' 
 DESC 'SOA Name' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.4 
 NAME 'idnsSOArName' 
 DESC 'SOA root Name' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.5 
 NAME 'idnsSOAserial' 
 DESC 'SOA serial number' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 
 EQUALITY numericStringMatch 
 SINGLE-VALUE ) 
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.6 
 NAME 'idnsSOArefresh' 
 DESC 'SOA refresh value' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 
 EQUALITY numericStringMatch 
 SINGLE-VALUE ) 
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.7 
 NAME 'idnsSOAretry' 
 DESC 'SOA retry value' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 
 EQUALITY numericStringMatch 
 SINGLE-VALUE ) 
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.8 
 NAME 'idnsSOAexpire' 
 DESC 'SOA expire value' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 
 EQUALITY numericStringMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.9 
 NAME 'idnsSOAminimum' 
 DESC 'SOA minimum value' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.36 
 EQUALITY numericStringMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.10 
 NAME 'idnsUpdatePolicy' 
 DESC 'DNS dynamic updates policy' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.11 
 NAME 'idnsAllowQuery' 
 DESC 'BIND9 allow-query ACL element' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SINGLE-VALUE ) 
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.12 
 NAME 'idnsAllowTransfer' 
 DESC 'BIND9 allow-transfer ACL element' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.13 
 NAME 'idnsAllowSyncPTR' 
 DESC 'permit synchronization of PTR records' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 
 EQUALITY booleanMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.14 
 NAME 'idnsForwardPolicy' 
 DESC 'forward policy: only or first' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch 
 SINGLE-VALUE ) 
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.15 
 NAME 'idnsForwarders' 
 DESC 'list of forwarders' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match 
 SUBSTR caseIgnoreIA5SubstringsMatch )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.18 
 NAME 'idnsSecInlineSigning' 
 DESC 'DNSSEC in-line signing' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 
 EQUALITY booleanMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.31
 NAME 'idnsServerId'
 DESC 'DNS server identifier'
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
 EQUALITY caseIgnoreMatch 
 SINGLE-VALUE )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.30 
 NAME 'idnsSubstitutionVariable' 
 DESC 'User defined variable for DNS plugin' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match )
#
olcAttributeTypes: ( 2.16.840.1.113730.3.8.5.29 
 NAME 'idnsTemplateAttribute' 
 DESC 'Template attribute for dynamic attribute generation' 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 
 EQUALITY caseIgnoreIA5Match )
#
olcObjectClasses: ( 2.16.840.1.113730.3.8.6.0 
 NAME 'idnsRecord' 
 DESC 'dns Record, usually a host' 
 SUP top 
 STRUCTURAL 
 MUST idnsName 
 MAY ( cn $ idnsAllowDynUpdate $ DNSTTL $ DNSClass $ ARecord $ 
       AAAARecord $ A6Record $ NSRecord $ CNAMERecord $ PTRRecord $ 
       SRVRecord $ TXTRecord $ MXRecord $ MDRecord $ HINFORecord $ 
       MINFORecord $ AFSDBRecord $ LOCRecord $ 
       NXTRecord $ NAPTRRecord $ KXRecord $ CERTRecord $ DNAMERecord $ 
       DSRecord $ SSHFPRecord $ DLVRecord $ TLSARecord $ UnknownRecord
     ) )
#
olcObjectClasses: ( 2.16.840.1.113730.3.8.6.1 
 NAME 'idnsZone' 
 DESC 'Zone class' 
 SUP idnsRecord 
 STRUCTURAL 
 MUST ( idnsName $ idnsZoneActive $ idnsSOAmName $ idnsSOArName $ 
        idnsSOAserial $ idnsSOArefresh $ idnsSOAretry $ idnsSOAexpire $ 
        idnsSOAminimum 
      ) 
 MAY ( idnsUpdatePolicy $ idnsAllowQuery $ idnsAllowTransfer $ 
       idnsAllowSyncPTR $ idnsForwardPolicy $ idnsForwarders $ 
       idnsSecInlineSigning $ nSEC3PARAMRecord $ dNSdefaultTTL 
     ) )
#
olcObjectClasses: ( 2.16.840.1.113730.3.8.6.2 
 NAME 'idnsConfigObject' 
 DESC 'DNS global config options' 
 STRUCTURAL 
 MAY ( idnsForwardPolicy $ idnsForwarders $ idnsAllowSyncPTR ) )
#
olcObjectClasses: ( 2.16.840.1.113730.3.8.6.3 
 NAME 'idnsForwardZone' 
 DESC 'Forward Zone class' 
 SUP top 
 STRUCTURAL 
 MUST ( idnsName $ idnsZoneActive ) 
 MAY ( idnsForwarders $ idnsForwardPolicy ) )
#
olcObjectClasses: ( 2.16.840.1.113730.3.8.6.6 
 NAME 'idnsServerConfigObject' 
 DESC 'DNS server configuration' 
 SUP top 
 STRUCTURAL 
 MUST ( idnsServerId ) 
 MAY ( idnsSOAmName $ idnsForwarders $ idnsForwardPolicy $ 
       idnsSubstitutionVariable 
     ) )
#
olcObjectClasses: ( 2.16.840.1.113730.3.8.6.5 
 NAME 'idnsTemplateObject' 
 DESC 'Template object for dynamic DNS attribute generation' 
 SUP top 
 AUXILIARY 
 MUST ( idnsTemplateAttribute ) )

To add this schema to OpenLDAP I use the following command:

# ldapadd -Y EXTERNAL -H ldapi:/// -f bind9.schema.ldif 
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
adding new entry "cn=dns,cn=schema,cn=config"

To check, if the new schema is available just search for it:

# ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=schema,cn=config cn
dn: cn=schema,cn=config
cn: schema

dn: cn={0}core,cn=schema,cn=config
cn: {0}core

dn: cn={1}cosine,cn=schema,cn=config
cn: {1}cosine

dn: cn={2}nis,cn=schema,cn=config
cn: {2}nis

dn: cn={3}inetorgperson,cn=schema,cn=config
cn: {3}inetorgperson

dn: cn={4}samba,cn=schema,cn=config
cn: {4}samba

dn: cn={5}dns,cn=schema,cn=config
cn: {5}dns

The last entry shows, that is it now available.

You can now start to deploy your zones into LDAP. The following page describes the main options:

https://pagure.io/bind-dyndb-ldap

For my first zone, I use the example zone from the package here:

# vi /usr/share/doc/bind9-dyndb-ldap/example.ldif

I modified this to reflect my environment here:

# ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b ou=dns,o=services,dc=flomain,dc=local
dn: ou=dns,o=services,dc=flomain,dc=local
ou: dns
objectClass: organizationalUnit
objectClass: top

dn: idnsName=flomain.local,ou=dns,o=services,dc=flomain,dc=local
idnsSOAmName: devil.flomain.local.
idnsSOArName: devil.flomain.local.
idnsName: flomain.local
objectClass: idnsZone
objectClass: idnsRecord
objectClass: top
idnsSOAexpire: 604800
idnsSOAminimum: 86400
aRecord: 10.104.104.10
idnsSOArefresh: 10800
idnsSOAretry: 900
idnsZoneActive: TRUE
aAAARecord: fd04:57cd:0f5d:f9c6::10
nSRecord: flomain.local.
idnsSOAserial: 1534088762

dn: idnsName=devil,idnsName=flomain.local,ou=dns,o=services,dc=flomain,dc=loca
 l
idnsName: devil
objectClass: idnsRecord
objectClass: top
cNAMERecord: flomain.local.

A reverse zone might look like this:

dn: idnsName=100.100.10.in-addr.arpa.,ou=dns,o=services,dc=flomain,dc=local
idnsSOAmName: devil.flomain.local.
idnsSOArefresh: 10800
idnsSOArName: devil.flomain.local.
nSRecord: devil.flomain.local.
idnsSOAretry: 900
idnsZoneActive: TRUE
objectClass: idnsZone
objectClass: idnsRecord
objectClass: top
idnsSOAexpire: 604800
idnsSOAminimum: 86400
idnsSOAserial: 1534092448
idnsName: 100.100.10.in-addr.arpa.

dn: idnsName=haan-router,idnsName=flomain.local,ou=dns,o=services,dc=flomain,d
 c=local
aRecord: 10.100.100.1
aAAARecord: fdf0:4d22:9d3e:bcc2::1
idnsName: haan-router
objectClass: idnsRecord
objectClass: top

Now, tell bind to use OpenLDAP as the database. Simply add the following to your configuration:

dynamic-db "my_openldap_db" {
        library "ldap.so";
        arg "uri ldap://localhost";
        arg "base ou=dns,o=services,dc=flomain,dc=local";
        arg "auth_method simple";
        arg "bind_dn cn=admin,dc=flomain,dc=local";
        arg "password Ka02629153";
};

Before you start/restart Bind enable replication on the OpenLDAP server. I use those two LDIF files for this task:

# cat syncrepl.ldif 
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: syncprov
root@devil:~/ldap-scripts# ldapmodify -Y EXTERNAL -H ldapi:/// -f syncrepl.ldif 
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
modifying entry "cn=module{0},cn=config"

and the following to make the new module active:

# cat syncrepl_prov.ldif 
dn: olcOverlay=syncprov,olcDatabase={1}mdb,cn=config
changeType: add
objectClass: olcOverlayConfig
objectClass: olcSyncProvConfig
olcOverlay: syncprov
olcSpCheckpoint: 100 10
olcSpSessionLog: 100
root@devil:~/ldap-scripts# ldapmodify -Y EXTERNAL -H ldapi:/// -f syncrepl_prov.ldif 
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
adding new entry "olcOverlay=syncprov,olcDatabase={1}mdb,cn=config"

Now, you can use OpenLDAP to store your zones. The cool thing, you can change the entries, without restarting bind. Due to the replication module, bind is aware of any change during runtime.

OpenLDAP: Integrate DHCP Server

For DHCP, I use the ISC-DHCP-Server. This one can also work with OpenLDAP.

To use the OpenLDAP backend for DHCP you install the required package first:

# apt-get install isc-dhcp-server-ldap

Next, you need the schema for DHCP. The package comes with a schema file. But this one is not for the online config in OpenLDAP. You can either convert it manually or use the one I converted:

dn: cn=dhcp,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: dhcp
olcAttributeTypes: {0}( 2.16.840.1.113719.1.203.4.1 NAME 'dhcpPrimaryDN' DES
 C 'The DN of the dhcpServer which is the primary server for the configurati
 on.' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 S
 INGLE-VALUE )
olcAttributeTypes: {1}( 2.16.840.1.113719.1.203.4.2 NAME 'dhcpSecondaryDN' D
 ESC 'The DN of dhcpServer(s) which provide backup service for the configura
 tion.' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  )
olcAttributeTypes: {2}( 2.16.840.1.113719.1.203.4.3 NAME 'dhcpStatements' DE
 SC 'Flexible storage for specific data depending on what object this exists
  in. Like conditional statements, server parameters, etc. This allows the s
 tandard to evolve without needing to adjust the schema.' EQUALITY caseIgnor
 eIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {3}( 2.16.840.1.113719.1.203.4.4 NAME 'dhcpRange' DESC 'T
 he starting & ending IP Addresses in the range (inclusive), separated by a 
 hyphen; if the range only contains one address, then just the address can b
 e specified with no hyphen.  Each range is defined as a separate value.' EQ
 UALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {4}( 2.16.840.1.113719.1.203.4.5 NAME 'dhcpPermitList' DE
 SC 'This attribute contains the permit lists associated with a pool. Each p
 ermit list is defined as a separate value.' EQUALITY caseIgnoreIA5Match SYN
 TAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {5}( 2.16.840.1.113719.1.203.4.6 NAME 'dhcpNetMask' DESC 
 'The subnet mask length for the subnet.  The mask can be easily computed fr
 om this length.' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
  SINGLE-VALUE )
olcAttributeTypes: {6}( 2.16.840.1.113719.1.203.4.7 NAME 'dhcpOption' DESC '
 Encoded option values to be sent to clients.  Each value represents a singl
 e option and contains (OptionTag, Length, OptionValue) encoded in the forma
 t used by DHCP.' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.12
 1.1.26 )
olcAttributeTypes: {7}( 2.16.840.1.113719.1.203.4.8 NAME 'dhcpClassData' DES
 C 'Encoded text string or list of bytes expressed in hexadecimal, separated
  by colons.  Clients match subclasses based on matching the class data with
  the results of match or spawn with statements in the class name declaratio
 ns.' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGL
 E-VALUE )
olcAttributeTypes: {8}( 2.16.840.1.113719.1.203.4.9 NAME 'dhcpOptionsDN' DES
 C 'The distinguished name(s) of the dhcpOption objects containing the confi
 guration options provided by the server.' EQUALITY distinguishedNameMatch S
 YNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {9}( 2.16.840.1.113719.1.203.4.10 NAME 'dhcpHostDN' DESC 
 'the distinguished name(s) of the dhcpHost objects.' EQUALITY distinguished
 NameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {10}( 2.16.840.1.113719.1.203.4.11 NAME 'dhcpPoolDN' DESC
  'The distinguished name(s) of pools.' EQUALITY distinguishedNameMatch SYNT
 AX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {11}( 2.16.840.1.113719.1.203.4.12 NAME 'dhcpGroupDN' DES
 C 'The distinguished name(s)   of the groups.' EQUALITY distinguishedNameMa
 tch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {12}( 2.16.840.1.113719.1.203.4.13 NAME 'dhcpSubnetDN' DE
 SC 'The distinguished name(s) of the subnets.' EQUALITY distinguishedNameMa
 tch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {13}( 2.16.840.1.113719.1.203.4.14 NAME 'dhcpLeaseDN' DES
 C 'The distinguished name of a client address.' EQUALITY distinguishedNameM
 atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 SINGLE-VALUE )
olcAttributeTypes: {14}( 2.16.840.1.113719.1.203.4.15 NAME 'dhcpLeasesDN' DE
 SC 'The distinguished name(s) client addresses.' EQUALITY distinguishedName
 Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {15}( 2.16.840.1.113719.1.203.4.16 NAME 'dhcpClassesDN' D
 ESC 'The distinguished name(s) of a class(es) in a subclass.' EQUALITY dist
 inguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {16}( 2.16.840.1.113719.1.203.4.17 NAME 'dhcpSubclassesDN
 ' DESC 'The distinguished name(s) of subclass(es).' EQUALITY distinguishedN
 ameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {17}( 2.16.840.1.113719.1.203.4.18 NAME 'dhcpSharedNetwor
 kDN' DESC 'The distinguished name(s) of sharedNetworks.' EQUALITY distingui
 shedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {18}( 2.16.840.1.113719.1.203.4.19 NAME 'dhcpServiceDN' D
 ESC 'The DN of dhcpService object(s)which contain the configuration informa
 tion. Each dhcpServer object has this attribute identifying the DHCP config
 uration(s) that the server is associated with.' EQUALITY distinguishedNameM
 atch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {19}( 2.16.840.1.113719.1.203.4.20 NAME 'dhcpVersion' DES
 C 'The version attribute of this object.' EQUALITY caseIgnoreIA5Match SYNTA
 X 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {20}( 2.16.840.1.113719.1.203.4.21 NAME 'dhcpImplementati
 on' DESC 'Description of the DHCP Server implementation e.g. DHCP Servers v
 endor.' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SI
 NGLE-VALUE )
olcAttributeTypes: {21}( 2.16.840.1.113719.1.203.4.22 NAME 'dhcpAddressState
 ' DESC 'This stores information about the current binding-status of an addr
 ess.  For dynamic addresses managed by DHCP, the values should be restricte
 d to the following: "FREE", "ACTIVE", "EXPIRED", "RELEASED", "RESET", "ABAN
 DONED", "BACKUP".  For other addresses, it SHOULD be one of the following: 
 "UNKNOWN", "RESERVED" (an address that is managed by DHCP that is reserved 
 for a specific client), "RESERVED-ACTIVE" (same as reserved, but address is
  currently in use), "ASSIGNED" (assigned manually or by some other mechanis
 m), "UNASSIGNED", "NOTASSIGNABLE".' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.
 6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {22}( 2.16.840.1.113719.1.203.4.23 NAME 'dhcpExpirationTi
 me' DESC 'This is the time the current lease for an address expires.' EQUAL
 ITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE-VALUE 
 )
olcAttributeTypes: {23}( 2.16.840.1.113719.1.203.4.24 NAME 'dhcpStartTimeOfS
 tate' DESC 'This is the time of the last state change for a leased address.
 ' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.24 SINGLE
 -VALUE )
olcAttributeTypes: {24}( 2.16.840.1.113719.1.203.4.25 NAME 'dhcpLastTransact
 ionTime' DESC 'This is the last time a valid DHCP packet was received from 
 the client.' EQUALITY generalizedTimeMatch SYNTAX 1.3.6.1.4.1.1466.115.121.
 1.24 SINGLE-VALUE )
olcAttributeTypes: {25}( 2.16.840.1.113719.1.203.4.26 NAME 'dhcpBootpFlag' D
 ESC 'This indicates whether the address was assigned via BOOTP.' EQUALITY b
 ooleanMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.7 SINGLE-VALUE )
olcAttributeTypes: {26}( 2.16.840.1.113719.1.203.4.27 NAME 'dhcpDomainName' 
 DESC 'This is the name of the domain sent to the client by the server.  It 
 is essentially the same as the value for DHCP option 15 sent to the client,
  and represents only the domain - not the full FQDN.  To obtain the full FQ
 DN assigned to the client you must prepend the "dhcpAssignedHostName" to th
 is value with a ".".' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.1
 15.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {27}( 2.16.840.1.113719.1.203.4.28 NAME 'dhcpDnsStatus' D
 ESC 'This indicates the status of updating DNS resource records on behalf o
 f the client by the DHCP server for this address.  The value is a 16-bit bi
 tmask.' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-V
 ALUE )
olcAttributeTypes: {28}( 2.16.840.1.113719.1.203.4.29 NAME 'dhcpRequestedHos
 tName' DESC 'This is the hostname that was requested by the client.' EQUALI
 TY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {29}( 2.16.840.1.113719.1.203.4.30 NAME 'dhcpAssignedHost
 Name' DESC 'This is the actual hostname that was assigned to a client. It m
 ay not be the name that was requested by the client.  The fully qualified d
 omain name can be determined by appending the value of "dhcpDomainName" (wi
 th a dot separator) to this name.' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6
 .1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {30}( 2.16.840.1.113719.1.203.4.31 NAME 'dhcpReservedForC
 lient' DESC 'The distinguished name of a "dhcpClient" that an address is re
 served for.  This may not be the same as the "dhcpAssignedToClient" attribu
 te if the address is being reassigned but the current lease has not yet exp
 ired.' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
  SINGLE-VALUE )
olcAttributeTypes: {31}( 2.16.840.1.113719.1.203.4.32 NAME 'dhcpAssignedToCl
 ient' DESC 'This is the distinguished name of a "dhcpClient" that an addres
 s is currently assigned to.  This attribute is only present in the class wh
 en the address is leased.' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4
 .1.1466.115.121.1.12 SINGLE-VALUE )
olcAttributeTypes: {32}( 2.16.840.1.113719.1.203.4.33 NAME 'dhcpRelayAgentIn
 fo' DESC 'If the client request was received via a relay agent, this contai
 ns information about the relay agent that was available from the DHCP reque
 st.  This is a hex-encoded option value.' EQUALITY octetStringMatch SYNTAX 
 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE )
olcAttributeTypes: {33}( 2.16.840.1.113719.1.203.4.34 NAME 'dhcpHWAddress' D
 ESC 'The clients hardware address that requested this IP address.' EQUALITY
  caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {34}( 2.16.840.1.113719.1.203.4.35 NAME 'dhcpHashBucketAs
 signment' DESC 'HashBucketAssignment bit map for the DHCP Server, as define
 d in DHC Load Balancing Algorithm [RFC 3074].' EQUALITY octetStringMatch SY
 NTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE )
olcAttributeTypes: {35}( 2.16.840.1.113719.1.203.4.36 NAME 'dhcpDelayedServi
 ceParameter' DESC 'Delay in seconds corresponding to Delayed Service Parame
 ter configuration, as defined in  DHC Load Balancing Algorithm [RFC 3074]. 
 ' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {36}( 2.16.840.1.113719.1.203.4.37 NAME 'dhcpMaxClientLea
 dTime' DESC 'Maximum Client Lead Time configuration in seconds, as defined 
 in DHCP Failover Protocol [FAILOVR]' EQUALITY integerMatch SYNTAX 1.3.6.1.4
 .1.1466.115.121.1.27 SINGLE-VALUE )
olcAttributeTypes: {37}( 2.16.840.1.113719.1.203.4.38 NAME 'dhcpFailOverEndp
 ointState' DESC 'Server (Failover Endpoint) state, as defined in DHCP Failo
 ver Protocol [FAILOVR]' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466
 .115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {38}( 2.16.840.1.113719.1.203.4.39 NAME 'dhcpErrorLog' DE
 SC 'Generic error log attribute that allows logging error conditions within
  a dhcpService or a dhcpSubnet, like no IP addresses available for lease.' 
 EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VAL
 UE )
olcAttributeTypes: {39}( 2.16.840.1.113719.1.203.4.40 NAME 'dhcpLocatorDN' D
 ESC 'The DN of dhcpLocator object which contain the DNs of all DHCP configu
 ration objects. There will be a single dhcpLocator object in the tree with 
 links to all the DHCP objects in the tree' EQUALITY distinguishedNameMatch 
 SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {40}( 2.16.840.1.113719.1.203.4.41 NAME 'dhcpKeyAlgorithm
 ' DESC 'Algorithm to generate TSIG Key' EQUALITY caseIgnoreIA5Match SYNTAX 
 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {41}( 2.16.840.1.113719.1.203.4.42 NAME 'dhcpKeySecret' D
 ESC 'Secret to generate TSIG Key' EQUALITY octetStringMatch SYNTAX 1.3.6.1.
 4.1.1466.115.121.1.40 SINGLE-VALUE )
olcAttributeTypes: {42}( 2.16.840.1.113719.1.203.4.43 NAME 'dhcpDnsZoneServe
 r' DESC 'Master server of the DNS Zone' EQUALITY caseIgnoreIA5Match SYNTAX 
 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {43}( 2.16.840.1.113719.1.203.4.44 NAME 'dhcpKeyDN' DESC 
 'The DNs of TSIG Key to use in secure dynamic updates. In case of locator o
 bject, this will be list of TSIG keys.  In case of DHCP Service, Shared Net
 work, Subnet and DNS Zone, it will be a single key.' EQUALITY distinguished
 NameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {44}( 2.16.840.1.113719.1.203.4.45 NAME 'dhcpZoneDN' DESC
  'The DNs of DNS Zone. In case of locator object, this will be list of DNS 
 Zones in the tree. In case of DHCP Service, Shared Network and Subnet, it w
 ill be a single DNS Zone.' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1.4
 .1.1466.115.121.1.12 )
olcAttributeTypes: {45}( 2.16.840.1.113719.1.203.4.46 NAME 'dhcpFailOverPrim
 aryServer' DESC 'IP address or DNS name of the server playing primary role 
 in DHC Load Balancing and Fail over.' EQUALITY caseIgnoreIA5Match SYNTAX 1.
 3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {46}( 2.16.840.1.113719.1.203.4.47 NAME 'dhcpFailOverSeco
 ndaryServer' DESC 'IP address or DNS name of the server playing secondary r
 ole in DHC Load Balancing and Fail over.' EQUALITY caseIgnoreIA5Match SYNTA
 X 1.3.6.1.4.1.1466.115.121.1.26 )
olcAttributeTypes: {47}( 2.16.840.1.113719.1.203.4.48 NAME 'dhcpFailOverPrim
 aryPort' DESC 'Port on which primary server listens for connections from it
 s fail over peer (secondary server)' EQUALITY integerMatch SYNTAX 1.3.6.1.4
 .1.1466.115.121.1.27 )
olcAttributeTypes: {48}( 2.16.840.1.113719.1.203.4.49 NAME 'dhcpFailOverSeco
 ndaryPort' DESC 'Port on which secondary server listens for connections fro
 m its fail over peer (primary server)' EQUALITY integerMatch SYNTAX 1.3.6.1
 .4.1.1466.115.121.1.27 )
olcAttributeTypes: {49}( 2.16.840.1.113719.1.203.4.50 NAME 'dhcpFailOverResp
 onseDelay' DESC 'Maximum response time in seconds, before Server assumes th
 at connection to fail over peer has failed' EQUALITY integerMatch SYNTAX 1.
 3.6.1.4.1.1466.115.121.1.27 )
olcAttributeTypes: {50}( 2.16.840.1.113719.1.203.4.51 NAME 'dhcpFailOverUnac
 kedUpdates' DESC 'Number of BNDUPD messages that server can send before it 
 receives BNDACK from its fail over peer' EQUALITY integerMatch SYNTAX 1.3.6
 .1.4.1.1466.115.121.1.27 )
olcAttributeTypes: {51}( 2.16.840.1.113719.1.203.4.52 NAME 'dhcpFailOverSpli
 t' DESC 'Split between the primary and secondary servers for fail over purp
 ose' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
olcAttributeTypes: {52}( 2.16.840.1.113719.1.203.4.53 NAME 'dhcpFailOverLoad
 BalanceTime' DESC 'Cutoff time in seconds, after which load balance is disa
 bled' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 )
olcAttributeTypes: {53}( 2.16.840.1.113719.1.203.4.54 NAME 'dhcpFailOverPeer
 DN' DESC 'The DNs of Fail over peers. In case of locator object, this will 
 be list of fail over peers in the tree. In case of Subnet and pool, it will
  be a single Fail Over Peer' EQUALITY distinguishedNameMatch SYNTAX 1.3.6.1
 .4.1.1466.115.121.1.12 )
olcAttributeTypes: {54}( 2.16.840.1.113719.1.203.4.55 NAME 'dhcpServerDN' DE
 SC 'List of all  DHCP Servers in the tree. Used by dhcpLocatorObject' EQUAL
 ITY distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 )
olcAttributeTypes: {55}( 2.16.840.1.113719.1.203.4.56 NAME 'dhcpComments' DE
 SC 'Generic attribute that allows coments  within any DHCP object' EQUALITY
  caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE )
olcAttributeTypes: {56}( 2.16.840.1.113719.1.203.4.57 NAME 'dhcpClientId' DE
 SC 'client Identifier.' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466
 .115.121.1.26 )
olcAttributeTypes: {57}( 2.16.840.1.113719.1.203.4.58 NAME 'dhcpRange6' DESC
  'The starting & ending IP Addresses in the range (inclusive), separated by
  a hyphen; if the range only contains one address, then just the address ca
 n be specified with no hyphen.  Each range is defined as a separate value.'
  EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 )
olcObjectClasses: {0}( 2.16.840.1.113719.1.203.6.1 NAME 'dhcpService' DESC '
 Service object that represents the actual DHCP Service configuration. This 
 is a container object.' SUP top STRUCTURAL MUST cn MAY ( dhcpPrimaryDN $ dh
 cpSecondaryDN $ dhcpServerDN $ dhcpSharedNetworkDN $ dhcpSubnetDN $ dhcpGro
 upDN $ dhcpHostDN $ dhcpClassesDN $ dhcpOptionsDN $ dhcpZoneDN $ dhcpKeyDN 
 $ dhcpFailOverPeerDN $ dhcpStatements $ dhcpComments $ dhcpOption ) )
olcObjectClasses: {1}( 2.16.840.1.113719.1.203.6.2 NAME 'dhcpSharedNetwork' 
 DESC 'This stores configuration information for a shared network.' SUP top 
 STRUCTURAL MUST cn MAY ( dhcpSubnetDN $ dhcpPoolDN $ dhcpOptionsDN $ dhcpZo
 neDN $ dhcpStatements $ dhcpComments $ dhcpOption ) X-NDS_CONTAINMENT 'dhcp
 Service' )
olcObjectClasses: {2}( 2.16.840.1.113719.1.203.6.3 NAME 'dhcpSubnet' DESC 'T
 his class defines a subnet. This is a container object.' SUP top STRUCTURAL
  MUST ( cn $ dhcpNetMask ) MAY ( dhcpRange $ dhcpPoolDN $ dhcpGroupDN $ dhc
 pHostDN $ dhcpClassesDN $ dhcpLeasesDN $ dhcpOptionsDN $ dhcpZoneDN $ dhcpK
 eyDN $ dhcpFailOverPeerDN $ dhcpStatements $ dhcpComments $ dhcpOption ) X-
 NDS_CONTAINMENT ( 'dhcpService' 'dhcpSharedNetwork' ) )
olcObjectClasses: {3}( 2.16.840.1.113719.1.203.6.4 NAME 'dhcpPool' DESC 'Thi
 s stores configuration information about a pool.' SUP top STRUCTURAL MUST (
  cn $ dhcpRange ) MAY ( dhcpClassesDN $ dhcpPermitList $ dhcpLeasesDN $ dhc
 pOptionsDN $ dhcpZoneDN $ dhcpKeyDN $ dhcpStatements $ dhcpComments $ dhcpO
 ption ) X-NDS_CONTAINMENT ( 'dhcpSubnet' 'dhcpSharedNetwork' ) )
olcObjectClasses: {4}( 2.16.840.1.113719.1.203.6.5 NAME 'dhcpGroup' DESC 'Gr
 oup object that lists host DNs and parameters. This is a container object.'
  SUP top STRUCTURAL MUST cn MAY ( dhcpHostDN $ dhcpOptionsDN $ dhcpStatemen
 ts $ dhcpComments $ dhcpOption ) X-NDS_CONTAINMENT ( 'dhcpSubnet' 'dhcpServ
 ice' ) )
olcObjectClasses: {5}( 2.16.840.1.113719.1.203.6.6 NAME 'dhcpHost' DESC 'Thi
 s represents information about a particular client' SUP top STRUCTURAL MUST
  cn MAY ( dhcpLeaseDN $ dhcpHWAddress $ dhcpOptionsDN $ dhcpStatements $ dh
 cpComments $ dhcpOption $ dhcpClientId ) X-NDS_CONTAINMENT ( 'dhcpService' 
 'dhcpSubnet' 'dhcpGroup' ) )
olcObjectClasses: {6}( 2.16.840.1.113719.1.203.6.7 NAME 'dhcpClass' DESC 'Re
 presents information about a collection of related clients.' SUP top STRUCT
 URAL MUST cn MAY ( dhcpSubClassesDN $ dhcpOptionsDN $ dhcpStatements $ dhcp
 Comments $ dhcpOption ) X-NDS_CONTAINMENT ( 'dhcpService' 'dhcpSubnet' ) )
olcObjectClasses: {7}( 2.16.840.1.113719.1.203.6.8 NAME 'dhcpSubClass' DESC 
 'Represents information about a collection of related classes.' SUP top STR
 UCTURAL MUST cn MAY ( dhcpClassData $ dhcpOptionsDN $ dhcpStatements $ dhcp
 Comments $ dhcpOption ) X-NDS_CONTAINMENT 'dhcpClass' )
olcObjectClasses: {8}( 2.16.840.1.113719.1.203.6.9 NAME 'dhcpOptions' DESC '
 Represents information about a collection of options defined.' SUP top AUXI
 LIARY MUST cn MAY ( dhcpOption $ dhcpComments ) X-NDS_CONTAINMENT ( 'dhcpSe
 rvice' 'dhcpSharedNetwork' 'dhcpSubnet' 'dhcpPool' 'dhcpGroup' 'dhcpHost' '
 dhcpClass' ) )
olcObjectClasses: {9}( 2.16.840.1.113719.1.203.6.10 NAME 'dhcpLeases' DESC '
 This class represents an IP Address, which may or may not have been leased.
 ' SUP top STRUCTURAL MUST ( cn $ dhcpAddressState ) MAY ( dhcpExpirationTim
 e $ dhcpStartTimeOfState $ dhcpLastTransactionTime $ dhcpBootpFlag $ dhcpDo
 mainName $ dhcpDnsStatus $ dhcpRequestedHostName $ dhcpAssignedHostName $ d
 hcpReservedForClient $ dhcpAssignedToClient $ dhcpRelayAgentInfo $ dhcpHWAd
 dress ) X-NDS_CONTAINMENT ( 'dhcpService' 'dhcpSubnet' 'dhcpPool' ) )
olcObjectClasses: {10}( 2.16.840.1.113719.1.203.6.11 NAME 'dhcpLog' DESC 'Th
 is is the object that holds past information about the IP address. The cn i
 s the time/date stamp when the address was assigned or released, the addres
 s state at the time, if the address was assigned or released.' SUP top STRU
 CTURAL MUST cn MAY ( dhcpAddressState $ dhcpExpirationTime $ dhcpStartTimeO
 fState $ dhcpLastTransactionTime $ dhcpBootpFlag $ dhcpDomainName $ dhcpDns
 Status $ dhcpRequestedHostName $ dhcpAssignedHostName $ dhcpReservedForClie
 nt $ dhcpAssignedToClient $ dhcpRelayAgentInfo $ dhcpHWAddress $ dhcpErrorL
 og ) X-NDS_CONTAINMENT ( 'dhcpLeases' 'dhcpPool' 'dhcpSubnet' 'dhcpSharedNe
 twork' 'dhcpService' ) )
olcObjectClasses: {11}( 2.16.840.1.113719.1.203.6.12 NAME 'dhcpServer' DESC 
 'DHCP Server Object' SUP top STRUCTURAL MUST cn MAY ( dhcpServiceDN $ dhcpL
 ocatorDN $ dhcpVersion $ dhcpImplementation $ dhcpHashBucketAssignment $ dh
 cpDelayedServiceParameter $ dhcpMaxClientLeadTime $ dhcpFailOverEndpointSta
 te $ dhcpStatements $ dhcpComments $ dhcpOption ) X-NDS_CONTAINMENT ( 'orga
 nization' 'organizationalunit' 'domain' ) )
olcObjectClasses: {12}( 2.16.840.1.113719.1.203.6.13 NAME 'dhcpTSigKey' DESC
  'TSIG key for secure dynamic updates' SUP top STRUCTURAL MUST ( cn $ dhcpK
 eyAlgorithm $ dhcpKeySecret ) MAY dhcpComments X-NDS_CONTAINMENT ( 'dhcpSer
 vice' 'dhcpSharedNetwork' 'dhcpSubnet' ) )
olcObjectClasses: {13}( 2.16.840.1.113719.1.203.6.14 NAME 'dhcpDnsZone' DESC
  'DNS Zone for updating leases' SUP top STRUCTURAL MUST ( cn $ dhcpDnsZoneS
 erver ) MAY ( dhcpKeyDN $ dhcpComments ) X-NDS_CONTAINMENT ( 'dhcpService' 
 'dhcpSharedNetwork' 'dhcpSubnet' ) )
olcObjectClasses: {14}( 2.16.840.1.113719.1.203.6.15 NAME 'dhcpFailOverPeer'
  DESC 'This class defines the Fail over peer' SUP top STRUCTURAL MUST ( cn 
 $ dhcpFailOverPrimaryServer $ dhcpFailOverSecondaryServer $ dhcpFailoverPri
 maryPort $ dhcpFailOverSecondaryPort ) MAY ( dhcpFailOverResponseDelay $ dh
 cpFailOverUnackedUpdates $ dhcpMaxClientLeadTime $ dhcpFailOverSplit $ dhcp
 HashBucketAssignment $ dhcpFailOverLoadBalanceTime $ dhcpComments ) X-NDS_C
 ONTAINMENT ( 'dhcpService' 'dhcpSharedNetwork' 'dhcpSubnet' ) )
olcObjectClasses: {15}( 2.16.840.1.113719.1.203.6.16 NAME 'dhcpLocator' DESC
  'Locator object for DHCP configuration in the tree. There will be a single
  dhcpLocator object in the tree with links to all the DHCP objects in the t
 ree' SUP top STRUCTURAL MUST cn MAY ( dhcpServiceDN $ dhcpServerDN $ dhcpSh
 aredNetworkDN $ dhcpSubnetDN $ dhcpPoolDN $ dhcpGroupDN $ dhcpHostDN $ dhcp
 ClassesDN $ dhcpKeyDN $ dhcpZoneDN $ dhcpFailOverPeerDN $ dhcpOption $ dhcp
 Comments ) X-NDS_CONTAINMENT ( 'organization' 'organizationalunit' 'domain'
  ) )
olcObjectClasses: {16}( 2.16.840.1.113719.1.203.6.17 NAME 'dhcpSubnet6' DESC
  'This class defines an IPv6 subnet. This is a container object.' SUP top S
 TRUCTURAL MUST cn MAY ( dhcpRange6 $ dhcpPoolDN $ dhcpGroupDN $ dhcpHostDN 
 $ dhcpClassesDN $ dhcpLeasesDN $ dhcpOptionsDN $ dhcpZoneDN $ dhcpKeyDN $ d
 hcpFailOverPeerDN $ dhcpStatements $ dhcpComments $ dhcpOption $ dhcpPermit
 List ) X-NDS_CONTAINMENT ( 'dhcpService' 'dhcpSharedNetwork' ) )
olcObjectClasses: {17}( 2.16.840.1.113719.1.203.6.18 NAME 'dhcpPool6' DESC '
 This stores configuration information about an IPv6 pool.' SUP top STRUCTUR
 AL MUST ( cn $ dhcpRange6 ) MAY ( dhcpClassesDN $ dhcpPermitList $ dhcpLeas
 esDN $ dhcpOptionsDN $ dhcpZoneDN $ dhcpKeyDN $ dhcpStatements $ dhcpCommen
 ts $ dhcpOption ) X-NDS_CONTAINMENT ( 'dhcpSubnet6' 'dhcpSharedNetwork' ) )

The above one is converted from the schema, which comes with Debian. Import the schema to OpenLDAP:

# ldapadd -Y EXTERNAL -H ldapi:/// -f dhcp_schema.ldif 
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
adding new entry "cn=dhcp,cn=schema,cn=config"

To check, if the schema is there, use this command:

# ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=schema,cn=config cn
dn: cn=schema,cn=config
cn: schema

dn: cn={0}core,cn=schema,cn=config
cn: {0}core

dn: cn={1}cosine,cn=schema,cn=config
cn: {1}cosine

dn: cn={2}nis,cn=schema,cn=config
cn: {2}nis

dn: cn={3}inetorgperson,cn=schema,cn=config
cn: {3}inetorgperson

dn: cn={4}samba,cn=schema,cn=config
cn: {4}samba

dn: cn={5}dns,cn=schema,cn=config
cn: {5}dns

dn: cn={6}dhcp,cn=schema,cn=config
cn: {6}dhcp

The last entry is the new DHCP schema.

We can now start to populate the objects to OpenLDAP. My entry point is again below “o=services,dc=flomain,dc=local”. Here I create a new ou dhcp:

# ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b ou=dhcp,o=services,dc=flomain,dc=local
dn: ou=dhcp,o=services,dc=flomain,dc=local
ou: dhcp
objectClass: organizationalUnit
objectClass: top

All the DHCP related stuff will go into this branch. Let’s start with the DHCP server configuration:

# ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b ou=dhcp,o=services,dc=flomain,dc=local
dn: ou=dhcp,o=services,dc=flomain,dc=local
ou: dhcp
objectClass: organizationalUnit
objectClass: top

dn: cn=server,ou=dhcp,o=services,dc=flomain,dc=local
cn: server
objectClass: dhcpServer
objectClass: top

We need to modify this entry later. The cn for the “dhcpServer” object is the name of the server. If you have multiple, make them unique.

Next step, is to create the service:

# ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b ou=dhcp,o=services,dc=flomain,dc=local
dn: ou=dhcp,o=services,dc=flomain,dc=local
ou: dhcp
objectClass: organizationalUnit
objectClass: top

dn: cn=config,ou=dhcp,o=services,dc=flomain,dc=local
cn: config
objectClass: dhcpService
objectClass: top
objectClass: dhcpOptions
dhcpPrimaryDN: cn=server,ou=dhcp,o=services,dc=flomain,dc=local
dhcpStatements: default-lease-time 691200
dhcpStatements: max-lease-time 1382400
dhcpStatements: ddns-update-style none
dhcpStatements: authoritative
dhcpOption: domain-name "flomain.local"
dhcpOption: domain-name-servers 10.104.104.10

I think the “dhcpStatements” and “dhcpOption” values are well known to people, who have configured the DHCP server with a config file already.

Now, we need to bind the server to this services as well:

# ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=server,ou=dhcp,o=services,dc=flomain,dc=local
dn: cn=server,ou=dhcp,o=services,dc=flomain,dc=local
cn: server
objectClass: dhcpServer
objectClass: top
dhcpServiceDN: cn=config,ou=dhcp,o=services,dc=flomain,dc=local

The attribute “dhcpServiceDN” is new.

We can now start to populate the subnets. First, we start with the one for the local interface. This one is just there to let the server start without issues, but will not serve IP addresses:

# ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=config,ou=dhcp,o=services,dc=flomain,dc=local
dn: cn=config,ou=dhcp,o=services,dc=flomain,dc=local
cn: config
objectClass: dhcpService
objectClass: top
objectClass: dhcpOptions
dhcpPrimaryDN: cn=server,ou=dhcp,o=services,dc=flomain,dc=local
dhcpStatements: default-lease-time 691200
dhcpStatements: max-lease-time 1382400
dhcpStatements: ddns-update-style none
dhcpStatements: authoritative
dhcpOption: domain-name "flomain.local"
dhcpOption: domain-name-servers 10.104.104.10

dn: cn=10.104.104.0,cn=config,ou=dhcp,o=services,dc=flomain,dc=local
dhcpNetMask: 24
cn: 10.104.104.0
objectClass: dhcpSubnet
objectClass: top

I include the service object as well, as this, together with the subnets creates the whole config. I add 2 other subnets as well, which I need for my network to work properly. One for my users and one for my Aruba Campus AP’s. This one also includes the option 43 for those AP’s, to find the Aruba Controller. Below is my full config in OpenLDAP for DHCP:

# ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b cn=config,ou=dhcp,o=services,dc=flomain,dc=local
dn: cn=config,ou=dhcp,o=services,dc=flomain,dc=local
cn: config
objectClass: dhcpService
objectClass: top
objectClass: dhcpOptions
dhcpPrimaryDN: cn=server,ou=dhcp,o=services,dc=flomain,dc=local
dhcpStatements: default-lease-time 691200
dhcpStatements: max-lease-time 1382400
dhcpStatements: ddns-update-style none
dhcpStatements: authoritative
dhcpOption: domain-name "flomain.local"
dhcpOption: domain-name-servers 10.104.104.10
dhcpOption: master code 43 = ip-address

dn: cn=192.168.2.0,cn=config,ou=dhcp,o=services,dc=flomain,dc=local
dhcpOption: routers 192.168.2.1
dhcpOption: subnet-mask 255.255.255.0
dhcpOption: domain-search "flomain.local"
dhcpOption: domain-name "flomain.local"
dhcpOption: domain-name-servers 10.104.104.10
dhcpNetMask: 24
cn: 192.168.2.0
objectClass: dhcpSubnet
objectClass: top
dhcpRange: 192.168.2.2 192.168.2.254
dhcpComments: VLAN 1 range, will be removed in the future

dn: cn=10.104.104.0,cn=config,ou=dhcp,o=services,dc=flomain,dc=local
dhcpNetMask: 24
cn: 10.104.104.0
objectClass: dhcpSubnet
objectClass: top
dhcpComments: Local Subnet to let the DHCP start

dn: cn=10.106.106.0,cn=config,ou=dhcp,o=services,dc=flomain,dc=local
dhcpOption: subnet-mask 255.255.255.0
dhcpOption: domain-search "flomain.local"
dhcpOption: domain-name "flomain.local"
dhcpOption: domain-name-servers 10.104.104.10
dhcpOption: routers 10.106.106.1
dhcpComments: VLAN 106, CAP Management
dhcpNetMask: 24
cn: 10.106.106.0
objectClass: dhcpSubnet
objectClass: top
dhcpRange: 10.106.106.10 10.106.106.200

dn: cn=ArubaAP-Class,cn=config,ou=dhcp,o=services,dc=flomain,dc=local
dhcpStatements: match option vendor-class-identifier
dhcpComments: Aruba AP DHCP Class for Option 43
cn: ArubaAP-Class
objectClass: dhcpClass
objectClass: top

dn: cn=ArubaAP,cn=10.106.106.0,cn=config,ou=dhcp,o=services,dc=flomain,dc=loca
 l
dhcpOption: vendor-class-identifier "ArubaAP"
dhcpOption: master 10.100.100.50
cn: ArubaAP
objectClass: dhcpSubClass
objectClass: top
dhcpClassData: ArubaAP-Class

Below is my config for the dhcpd daemon:

#dhcpd.conf
#
# LDAP config

ldap-server                 "localhost";
ldap-port                   389;
# We do an anonymous bind
# ldap-username             "cn=directorymanagerloginname";
# ldap-password             "mypassword";
ldap-base-dn                "ou=dhcp,o=services,dc=flomain,dc=local";
ldap-method                 static;
ldap-debug-file             "/var/log/dhcp-ldap-startup.log";
ldap-dhcp-server-cn         "server";

Restart the daemon and check the LDAP debug file:

# cat /var/log/dhcp-ldap-startup.log 
default-lease-time 691200;
max-lease-time 1382400;
ddns-update-style none;
authoritative;
option domain-name "flomain.local";
option domain-name-servers 10.104.104.10;
option master code 43 = ip-address;
class "ArubaAP-Class" {
match option vendor-class-identifier;
}
subnet 192.168.2.0 netmask 255.255.255.0 {
range 192.168.2.2 192.168.2.254;
option routers 192.168.2.1;
option subnet-mask 255.255.255.0;
option domain-search "flomain.local";
option domain-name "flomain.local";
option domain-name-servers 10.104.104.10;
}
subnet 10.104.104.0 netmask 255.255.255.0 {
}
subnet 10.106.106.0 netmask 255.255.255.0 {
range 10.106.106.10 10.106.106.200;
option subnet-mask 255.255.255.0;
option domain-search "flomain.local";
option domain-name "flomain.local";
option domain-name-servers 10.104.104.10;
option routers 10.106.106.1;
subclass "ArubaAP-Class" "ArubaAP" {
option vendor-class-identifier "ArubaAP";
option master 10.100.100.50;
}
}

If this is the config you expect, you have done a great job.

If you change something in OpenLDAP, you need to restart the DHCP server, to read the new options.

OpenLDAP: Update Bind from DHCP

This part describes the configuration to update dynamic IP leases from the DHCP server to the DNS server.

First, you should create a key, which is used for the secure communication between the DHCP server and the DNS server. I use the “dnssec-keygen” tool. The “-a” defines the algorithm, “-b” the key size and “-n” the nametype. For dynamic dns updates, this value needs to be host, followed by the hostname:

# dnssec-keygen -a HMAC-SHA512 -b 512 -n HOST test.flomain.local.
Ktest.flomain.local.+165+51489

This creates two files in the current directory:

# ls -l Ktest.flomain.local.+165+51489.*
-rw-r--r-- 1 root root 127 Aug 20 20:35 Ktest.flomain.local.+165+51489.key
-rw------- 1 root root 232 Aug 20 20:35 Ktest.flomain.local.+165+51489.private

The secret key is in both of them:

# cat Ktest.flomain.local.+165+51489.private 
Private-key-format: v1.3
Algorithm: 165 (HMAC_SHA512)
Key: D19nAcL6JLhVoNldTvcZ7B3Ni77Yc4hkhG59kNi0chz8owyeYEFl53S62YAgDh5yeKJ0SAB+fZ9OtyRIkAmsGw==
Bits: AAA=
Created: 20180820183533
Publish: 20180820183533
Activate: 20180820183533
root@devil:~# cat Ktest.flomain.local.+165+51489.key 
test.flomain.local. IN KEY 512 3 165 D19nAcL6JLhVoNldTvcZ7B3Ni77Yc4hkhG59kNi0chz8owyeYEFl53S6 2YAgDh5yeKJ0SAB+fZ9OtyRIkAmsGw==

The line with “key” is the secret key. Copy this in this file:

cat /etc/bind/rndc.key 
key "test.flomain.local" {
        algorithm hmac-sha512;
        secret "D19nAcL6JLhVoNldTvcZ7B3Ni77Yc4hkhG59kNi0chz8owyeYEFl53S62YAgDh5yeKJ0SAB+fZ9OtyRIkAmsGw==";
};

The string in quotes is used to reference the secret. This should be the same value as after the “-n HOST” part form the command above.

Now, include the key in the bind configuration and configure the key usage for updates:

# cat /etc/bind/named.conf.local 
//
// Do any local configuration here
//

// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";

dynamic-db "my_openldap_db" {
        library "ldap.so";
        arg "uri ldap://localhost";
        arg "base ou=dns,o=services,dc=flomain,dc=local";
        arg "auth_method simple";
        arg "bind_dn cn=admin,dc=flomain,dc=local";
        arg "password Ka02629153";
};

include "/etc/bind/rndc.key";

The last line includes the key file. Now, add the following line to the “named.conf.options” file in the “options” section:

allow-update { key test.flomain.local; };

You need to do this globally, as the bind schema for OpenLDAP does not allow this kind of configuration in the zone. Normally, you would have this option in the zone section of your config.

Now, update the zone object in OpenLDAP to look like this:

# ldapsearch -LLLQY EXTERNAL -H ldapi:/// -b idnsName=flomain.local,ou=dns,o=services,dc=flomain,dc=local
dn: idnsName=flomain.local,ou=dns,o=services,dc=flomain,dc=local
idnsSOAmName: devil.flomain.local.
idnsSOArName: devil.flomain.local.
idnsName: flomain.local
objectClass: idnsZone
objectClass: idnsRecord
objectClass: top
idnsSOAexpire: 604800
idnsSOAminimum: 86400
aRecord: 10.104.104.10
idnsSOArefresh: 10800
idnsSOAretry: 900
idnsZoneActive: TRUE
aAAARecord: fd04:57cd:0f5d:f9c6::10
nSRecord: flomain.local.
idnsSOAserial: 1534151701
idnsAllowDynUpdate: TRUE
idnsUpdatePolicy: grant test.flomain.local zonesub ANY;
idnsAllowSyncPTR: TRUE

The important parts are the last 3 ones:

idnsAllowDynUpdate: TRUE
idnsUpdatePolicy: grant test.flomain.local zonesub ANY;
idnsAllowSyncPTR: TRUE

The first one allows dynamic updates for that zone, the second one specifies the policy and the last one enables the reverse lookup creation.

Do this for all zones with dynamic updates. Afterward, the DNS part is done.

Now let’s do the DHCP part.

Change the DHCP service object to look like this:

dn: cn=config,ou=dhcp,o=services,dc=flomain,dc=local
cn: config
objectClass: dhcpService
objectClass: top
objectClass: dhcpOptions
dhcpPrimaryDN: cn=server,ou=dhcp,o=services,dc=flomain,dc=local
dhcpStatements: default-lease-time 691200
dhcpStatements: max-lease-time 1382400
dhcpStatements: authoritative
dhcpStatements: ddns-update-style interim
dhcpStatements: update-static-leases on
dhcpOption: domain-name "flomain.local"
dhcpOption: domain-name-servers 10.104.104.10
dhcpOption: master code 43 = ip-address

The following two lines are important:

dhcpStatements: ddns-update-style interim
dhcpStatements: update-static-leases on

Next, you need to create a “dhcpTSigKey” object. This object holds the key, generated with “dnssec-keygen” tool:

dn: cn=test.flomain.local,cn=config,ou=dhcp,o=services,dc=flomain,dc=local
dhcpKeyAlgorithm: hmac-sha512
objectClass: dhcpTSigKey
objectClass: top
cn: test.flomain.local
dhcpKeySecret:D19nAcL6JLhVoNldTvcZ7B3Ni77Yc4hkhG59kNi0chz8owyeYEFl53S62YAgDh5yeKJ0SAB+fZ9OtyRIkAmsGw==

The last step is to create a DNS zone:

dn: cn=flomain.local.,cn=config,ou=dhcp,o=services,dc=flomain,dc=local
cn: flomain.local.
objectClass: dhcpDnsZone
objectClass: top
dhcpDnsZoneServer: 127.0.0.1
dhcpKeyDN: cn=test.flomain.local,cn=config,ou=dhcp,o=services,dc=flomain,dc=l
 ocal

This “dhcpDnsZone” object references the “dhcpTSigKey” object. And whenever an IP for this zone gets an update, this update is sent to the DNS server as well.

You can see  a successful update in the Bind logs:

0-Aug-2018 21:17:25.883 update: info: client 127.0.0.1#54490/key test.flomain.local: updating zone 'flomain.local/IN': adding an RR at 'Netatmo-Personal-Weather-Station.flomain.local' A 192.168.2.108
20-Aug-2018 21:17:25.885 update: info: client 127.0.0.1#54490/key test.flomain.local: updating zone 'flomain.local/IN': adding an RR at 'Netatmo-Personal-Weather-Station.flomain.local' TXT "0031f4153610e090a639d5c80be22fc76f"
20-Aug-2018 21:17:25.971 notify: info: zone flomain.local/IN: sending notifies (serial 1534792646)
20-Aug-2018 21:17:25.972 notify: info: zone 2.168.192.in-addr.arpa/IN: sending notifies (serial 1534792645)
20-Aug-2018 21:17:25.972 notify: info: client 10.104.104.10#46481: received notify for zone 'flomain.local'
20-Aug-2018 21:17:26.472 notify: info: client fd04:57cd:f5d:f9c6::10#58608: received notify for zone 'flomain.local'
20-Aug-2018 21:17:30.971 notify: info: zone flomain.local/IN: sending notifies (serial 1534792646)
20-Aug-2018 21:17:30.972 notify: info: client 10.104.104.10#59458: received notify for zone 'flomain.local'
20-Aug-2018 21:17:31.471 notify: info: client fd04:57cd:f5d:f9c6::10#50364: received notify for zone 'flomain.local'
20-Aug-2018 21:17:34.865 update: info: client 127.0.0.1#54490/key test.flomain.local: updating zone 'flomain.local/IN': deleting an RR at Netatmo-Personal-Weather-Station.flomain.local A
20-Aug-2018 21:17:34.906 notify: info: zone 2.168.192.in-addr.arpa/IN: sending notifies (serial 1534792654)
20-Aug-2018 21:17:34.907 update: info: client 127.0.0.1#54490/key test.flomain.local: updating zone 'flomain.local/IN': deleting an RR at Netatmo-Personal-Weather-Station.flomain.local TXT
20-Aug-2018 21:17:35.971 notify: info: zone flomain.local/IN: sending notifies (serial 1534792648)
20-Aug-2018 21:17:35.971 notify: info: client 10.104.104.10#58622: received notify for zone 'flomain.local'
20-Aug-2018 21:17:36.471 notify: info: client fd04:57cd:f5d:f9c6::10#57899: received notify for zone 'flomain.local'

This is an update for the Zone flomain.local. The client is my weather station, which comes online for a very short time, so you see the first entries, where the station is added to the DNS zone, and seconds later, after the station sends the DHCP release, the entry is removed again. Due to the pointer sync, the reverse zone is updated as well.

That’s it. The last important point for me is to enable monitoring with Munin.

OpenLDAP: Monitoring with Munin

To start monitoring with OpenLDAP and online config, you need to load the monitoring module first.

Check for the next free sequence number:

# ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=module{0},cn=config
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
# extended LDIF
#
# LDAPv3
# base <cn=module{0},cn=config> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# module{0}, config
dn: cn=module{0},cn=config
objectClass: olcModuleList
cn: module{0}
olcModulePath: /usr/lib/ldap
olcModuleLoad: {0}back_mdb
olcModuleLoad: {1}syncprov

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

In my case it is {2}, as {1} is already used by the “syncprov” module. So, create the following file:

# cat monitoring.ldif 
dn: cn=module{0},cn=config
changetype: modify
add: olcModuleLoad
olcModuleLoad: {2}back_monitor

Replace the {2} with your sequence number and execute the LDIF file:

# ldapmodify -Y EXTERNAL -H ldapi:/// -f monitoring.ldif

Afterward, create a new user for monitoring purposes. The first step is to create a password for that user:

s# slappasswd -s secret_password
{SSHA}GkbsH9EDpeydBvGqAvWEgwxkoPXZjW73

Using this password create the following file:

# cat monitoring-user.ldif 
dn: cn=monitor,dc=flomain,dc=local
objectClass: simpleSecurityObject
objectClass: organizationalRole
cn: monitor
description: LDAP monitor
userPassword:{SSHA}GkbsH9EDpeydBvGqAvWEgwxkoPXZjW73

Add the new user to LDAP:

# ldapadd -x -D cn=admin,dc=flomain,dc=local -w secre_password_from_admin -f monitoring-user.ldif 
adding new entry "cn=monitor,dc=flomain,dc=local"

Afterward, add the monitoring database by creating this file:

# cat monitor-database.ldif 
dn: olcDatabase={2}Monitor,cn=config
objectClass: olcDatabaseConfig
objectClass: olcMonitorConfig
olcDatabase: {2}Monitor
olcAccess: {0}to dn.subtree="cn=Monitor" by dn.base="cn=monitor,dc=flomain,dc=local" read by * none

And add the database to OpenLDAP:

# ldapadd -Y EXTERNAL -H ldapi:/// -f monitor-database.ldif 
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
adding new entry "olcDatabase={2}Monitor,cn=config"

I assume Munin is already up and running. to make sure you have all the needed libraries, run this command:

# apt-get install munin-node libnet-ldap-perl

and add the following lines to your munin config (/etc/munin/plugin-conf.d/munin-node):

[slapd_*]
env.server 127.0.0.1
env.binddn cn=monitor,dc=flomain,dc=local
env.bindpw secret_password

The last step is to change into the plugins directory and create the needed symlinks:

# cd /etc/munin/plugins/
root@devil:/etc/munin/plugins# ln -s /usr/share/munin/plugins/slapd_ slapd_statistics_bytes
root@devil:/etc/munin/plugins# ln -s /usr/share/munin/plugins/slapd_ slapd_statistics_pdu
root@devil:/etc/munin/plugins# ln -s /usr/share/munin/plugins/slapd_ slapd_statistics_referrals
root@devil:/etc/munin/plugins# ln -s /usr/share/munin/plugins/slapd_ slapd_operations_diff
root@devil:/etc/munin/plugins# ln -s /usr/share/munin/plugins/slapd_ slapd_statistics_entries
root@devil:/etc/munin/plugins# ln -s /usr/share/munin/plugins/slapd_ slapd_connections
root@devil:/etc/munin/plugins# ln -s /usr/share/munin/plugins/slapd_ slapd_waiters
root@devil:/etc/munin/plugins# ln -s /usr/share/munin/plugins/slapd_ slapd_operations

Now, restart munin and wait for the result:

# /etc/init.d/munin-node restart
OpenLDAP - Monitoring with Munin
OpenLDAP – Monitoring with Munin

As you can see above, the OpenLDAP server in my LAB is not heavily used 

How do you manage your services in your LAB, like user database, DNS and DHCP?

If you find this post interesting, leave me a comment and share it with your friends. If you don’t like the post, leave me a comment and share it with your enemy. But whatever you do, leave me a comment, now.

9 thoughts on “Using Linux with OpenLDAP for User, DHCP and DNS”

  1. Hi Florian,

    I am not having any luck, I m getting stuck at the zone creation. with the following error:
    SASL/EXTERNAL authentication started
    SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
    SASL SSF: 0
    adding new entry “cn=dns, dc=bunker, dc=telensa, dc=com”
    ldap_add: Invalid syntax (21)
    additional info: objectClass: value #0 invalid per syntax

    it seems to be down to line 3
    objectClass: nsContainer

    do you have any suggestion?

    The install is on debian 10, but I have test it on 9 as well

    Peder.

    Reply
  2. your solution is very interesting, I have been looking for several days to make my arubaAP work with my Dhcp – LDAP server. Is your confusion giving you full satisfaction?
    the dhcp-eval part is poorly documented to work with ldap …
    I am looking to write the ldif file for this configuration:
    subnet 192.168.1.0 netmask 255.255.255.0 {
    subnet-mask option 255.255.255.0;
    range dynamic-bootp 192.168.1.10 192.168.1.254;
    default-lease-time 21600;
    max-lease-time 43200;
    option domain-name-servers 192.168.1.1;
    # option routers x.x.x.x;

    class “UEFI” {
    match if substring (vendor-class-identifier option, 0, 20) = “PXEClient: Arch: 00006”;
    filename “ipxe.efi”;
    }

    class “Legacy” {
    match if substring (vendor-class-identifier option, 0, 20) = “PXEClient: Arch: 00000”;
    filename “undionly.kkpxe”;
    }

    }
    thank you for you help?

    Reply
    • wow, thats a long time ago 🙂

      I’m running now windows server again, not because I’m happy with the windows server, but most of the customers use windows server and I just need to know how it works.
      but I have found the following post here:

      There is a description of the Perl script, which will convert your dhcpd.conf to a ldif file. Maybe this is what you are looking for.

      BR
      Florian

      Reply
  3. Hi Florian,
    It is a pity I did not find your page until I was in the end phase. I have been trying to get bind-dyndb-ldap running for 3 days straight! The obstacles were schema.ldif, syncprov and updatepolicy. I did manage to get schema.ldif and syncprov going but got stuck with updatepolicy. The server would state “key ddns_update: updating zone ‘example.com/IN’: update failed: rejected by secure update (REFUSED) “.

    Thanks to your page, I now have a fully working setup. Besides the doc on pagure.io, I did not find any documentation for bind-dyndb-ldap which describes each idns* values. This made me make some wrong assumption (e.g. idnsSOAmName, I thought mName = Mail Name). The server did perform well for DNS but did not work for reverse lookup.

    Thank you for your detailed description. I have added your site to my bookmarks.

    Greetings from Switzerland

    Reply
    • Hi joanandk,

      thanks for the feedback.
      Yeah, this was a hard one for me as well. It took me several weeks to get everything working. There is nearly no documentation out there and you need to find out most of it the hard way. Good to see that I was not the only one 😉

      BR
      Florian

      Reply
  4. Hello Florián, I have a couple of questions. The first would be if in the part of integrating dhcp it is necessary to install only isc-dhcp-server-ldap or also isc-dhcp-server. And the second question would be that when you show the configuration of the dhcp daemon you are showing the file /etc/dhcp/dhcpd.conf, are the lines that appear in the photo the only ones that the file should contain? Thank you very much for the guide.

    Reply
    • Hi Jorge,

      Thanks for your comment.

      I only installed “isc-dhcp-server-ldap” and let the system keep track of additional packages to install to satisfy the requirements of the “isc-dhcp-server-ldap” package.

      The config file is complete. Nothing else is required. The real configuration will be created after you start the service.

      BR
      Florian

      Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.