/freeipa-plugin-dhcp

A plugins for FreeIPA 4+ to manage DHCP configurations

Primary LanguagePythonGNU General Public License v3.0GPL-3.0

IPA-dhcp

⚠️ Don’t use, this project it's still in development
⚠️ This project is not (yet) backward compatible with the original version

This is a rudimentary plugin that adds DHCP functionality to FreeIPA.

This plugin can be used in one of two ways. If you want, you can install ISC DHCP on your FreeIPA server itself and let your FreeIPA server act as a DHCP server; this approximately mirrors the way FreeIPA can run ISC BIND and act as a DNS server. Or if you prefer you can run ISC DHCP on another, separate server and point it at your FreeIPA server via an anonymous LDAP binding. Both methods are really exactly the same; the only difference is where you install and run the ISC DHCP software.

This is a forked version of the original (unfinished) plugin, completing a lot of features and most notably adding DHCPv6 support.

⚠️ For DHCPv6 to work, you need ICP DHCPd version 4.3.3 or newer.

Pictures

An important caveat

This plugin was built to purpose. It's not a totally general-purpose solution for DHCP integration into FreeIPA. Some major features of ISC DHCP are currently not supported at all by this plugin, including shared networks, classes and DHCP failover. It's not that those features can't be supported; it's just that I don't personally need them right now, so I haven't added them. So it might be better to think of this plugin as a sort of proof of concept, or maybe a reference implementation of the DHCP schema, rather than a piece of finished software for general use.

That being said, you, Constant Reader, are welcome to this software. If you can use it as is, great. If not but it's useful to you as a springboard toward your own solution, also great. Either way, welcome and good luck.

How it works in a nutshell

The DHCP LDAP schema is, let's say, not necessarily the easiest thing in the world to work with, and its implementation in ISC DHCP is incomplete. So I've chosen to take a fairly minimalist approach to this plugin.

This plugin supports a single, global configuration rooted at cn=dhcp,$SUFFIX. That's the dhcpService object. It has a handful of global attributes, and it also has links to the dhcpServer objects that identify the DHCP server hosts that get their configuration from this server.

Beneath cn=dhcp there are dhcpSubnet objects, one for each — surprise — subnet the DHCP server services. If the server receives a DHCP request from a device on a particular subnet, it will respond to that request if and only if there's a dhcpSubnet object in the tree that corresponds to that subnet. If there's no dhcpSubnet object, the server will ignore all requests from that subnet.

Each dhcpSubnet may have one or more dhcpPool objects under it. A dhcpPool has a range attribute that sets the first and last IP address in the pool; the DHCP server will draw from the IPs in that range when handing out dynamic leases.

And then there's the labor-saving part, the part which I personally find most useful but which I can imagine most people will find least appealing: Any "host" in the IPA database which has both a MAC address and an A record gets a dhcpHost object generated for it automatically and dynamically. This is why I wrote this plugin in the first place. In my environment, I want every device in the system to be able to get its IP address via DHCP (as long as it's plugged into the correct switch port). Most people probably don't want that, or at least not exactly that. But that's what I want, so that's what I wrote.

It would, of course, be possible to change the plugin such that dhcpHost objects aren't dynamic but rather are generated statically, giving the user the option of creating a dhcpHost from an existing host record, then making the dhcpHost records editable in the UI in much the same way that DNS records already are. That would be, as the saying goes, a "simple matter of programming."

How to use it

Once you've configured the plugin how you like, either via the IPA command line (start with ipa help dhcp and go from there) or via the web GUI, you need to configure an ISC DHCP server to talk to it. Install the server software by doing a

yum install -y dhcp

to get what you need (assuming you're using Red Hat Enterprise Linux or CentOS; details will differ if you're on Fedora).

Once this is done, add a dedicated DHCPd account to FreeIPA and grant it some privileges for reading the DHCP configuration:

  1. create a dhcpdaccount.ldif file:
dn: uid=dhcp,cn=sysaccounts,cn=etc,dc=example,dc=com
cn: uid=dhcp,cn=sysaccounts,cn=etc,dc=example,dc=com
memberprincipal: cn=dhcp server,cn=roles,cn=accounts,dc=example,dc=com
memberprincipal: cn=DHCP Server,cn=privileges,cn=pbac,dc=example,dc=com
memberprincipal: cn=System: Read Permissions,cn=permissions,cn=pbac,dc=example,dc=com
memberprincipal: cn=System: Read Privileges,cn=permissions,cn=pbac,dc=example,dc=com
memberprincipal: cn=System: Read DHCP Enteries,cn=permissions,cn=pbac,dc=example,dc=com
objectClass: account
objectClass: simpleSecurityObject
objectClass: top
objectClass: groupOfPrincipals
uid: dhcp

Modify your base DN accordingly and fill in a reasonable $DHCPD_PASSWORD.

⚠️ beware that whitespaces at the end of lines in LDIF files will produced errors/unexpected results!

This example assumes your base DN is dc=example,dc=com. Depending on how your server is configured, this could for example also be dc=ipa,dc=example,dc=com. If in doubt, use an LDAP client and investigate your DIT.

Hint: JXplorer, a graphical FOSS LDAP editor, has proven to be very helpful in that regards (https://jxplorer.org)

  1. add the user to LDAP DIT:
ldapmodify -D "cn=Directory Manager" -W -p 389 -h $IPASERVER_IP -a -f dhcpdaccount.ldif
  1. set a decent password for the newly created system account:
ldappasswd -x -S -W -D "cn=Directory Manager" uid=dhcp,cn=sysaccounts,cn=etc,dc=example,dc=com

Once the server is installed and the service account has been created, edit the file /etc/dhcp/dhcpd.conf and make it look like this:

ldap-server "ipa.example.com";
ldap-port 636;
ldpa-ssl on;
ldap-base-dn "cn=dhcp,dc=example,dc=com";
ldap-method static;
ldap-username uid=dhcp,cn=sysaccounts,cn=etc,dc=example,dc=com
ldap-password "$DHCPD_PASSWORD";
ldap-debug-file "/var/log/dhcp-ldap-startup.log";

Obviously the hostname ipa.example.com should instead be the hostname of your FreeIPA server, and the base DN suffix dc=example,dc=com should instead be your own base DN suffix. But other than that, make your config file look like this; this sets up an anonymous bind to the FreeIPA LDAP server.

To test out the configuration, as root on your DHCP server, run the following command:

dhcpd -d

You should get something like this (with your own MAC and IP address, obviously):

Internet Systems Consortium DHCP Server 4.2.5
Copyright 2004-2013 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/
Wrote 0 deleted host decls to leases file.
Wrote 0 new dynamic host decls to leases file.
Wrote 0 leases to leases file.
Listening on LPF/eth0/00:50:56:1e:01:1f/10.30.1.0/24
Sending on   LPF/eth0/00:50:56:1e:01:1f/10.30.1.0/24
Sending on   Socket/fallback/fallback-net

If you get any error messages, check your DHCP configuration in FreeIPA and try again. Assuming DHCPd started up correctly, hit control-C to stop it and then check the contents of /var/log/dhcp-ldap-startup.log. Modulo some awkward line breaks, this file should look like a dhcpd.conf file, something like this:

authoritative;
default-lease-time 43200;
max-lease-time 86400;
one-lease-per-client on;
option domain-name-servers ns.la.charlietango.com;
option domain-name "la.charlietango.com";
option domain-search "la.charlietango.com", "charlietango.com";

host mac1.la.charlietango.com {
    hardware ethernet 01:02:03:04:05:06;
    fixed-address mac1.la.charlietango.com;
    option host-name "mac1.la.charlietango.com";
}

subnet 10.30.1.0 netmask 255.255.255.0 {
    option broadcast-address 10.30.1.255;
    option subnet-mask 255.255.255.0;
    option routers 10.30.1.254;
    pool {
        range 10.30.1.201 10.30.1.249;
        allow known-clients;
        allow unknown-clients;
        max-lease-time 86400;
        default-lease-time 43200;
    }
}

I've fixed the line breaks and indentation to make it more readable, but other than that, that's what DHCPd generated for me based on the configuration I created in my FreeIPA server. Note that I have one host in FreeIPA that has a MAC address (the obviously bogus 01:02:03:04:05:06) and an IP address; this was automatically turned into a global DHCP host record. Below that, you see my one DHCP subnet containing its single address pool.

Now, to get this config generated I set my DHCP server's ldap-method to static. This is great for testing but not for production. See, when ldap-method is set to static the DHCP server queries the LDAP server once at start-up, downloads the whole tree, converts that tree into a running configuration and then never talks to the LDAP server again until DHCPd is restarted. This stinks because you have to restart DHCPd every time you add a host to the realm! It's better by far to set ldap-method to dynamic. This tells DHCPd to read its static configuration once at startup, but whenever a request comes in for a lease to query the LDAP server anew. Changes to the LDAP configuration, then, can propagate to the DHCP server(s) without having to restart them.

So for production, make your dhcpd.conf look like this:

ldap-server "ipa.example.com";
ldap-port 636;
ldpa-ssl on;
ldap-base-dn "cn=dhcp,dc=example,dc=com";
ldap-method dynamic;
ldap-username uid=dhcp,cn=sysaccounts,cn=etc,dc=example,dc=com
ldap-password "$DHCPD_PASSWORD";
ldap-debug-file "/var/log/dhcp-ldap-startup.log";

The ldap-debug-file line remains optional. If you leave it in and start up DHCPd, this time you'll get a config that looks like this:

authoritative;
default-lease-time 43200;
max-lease-time 86400;
one-lease-per-client on;
option domain-name-servers ns.la.charlietango.com;
option domain-name "la.charlietango.com";
option domain-search "la.charlietango.com", "charlietango.com";

subnet 10.30.1.0 netmask 255.255.255.0 {
    option broadcast-address 10.30.1.255;
    option subnet-mask 255.255.255.0;
    option routers 10.30.1.254;
    pool {
        range 10.30.1.201 10.30.1.249;
        allow known-clients;
        allow unknown-clients;
        max-lease-time 86400;
        default-lease-time 43200;
    }
}

Notice that this looks just the same as before, only the host entry isn't there. DHCPd will query the LDAP server for dhcpHost objects every time a DHCP request comes in … which, if you have a big, busy network with a lot of DHCP requests, can put some load on your LDAP server. But this can be addressed with replication, so it's rarely a real issue.

Areas for improvement

There are some pretty obvious low-hanging fruit that I haven't bothered to pluck.

Shared networks

ISC DHCP includes this concept called a "shared network," which is a topology in which multiple disjoint IP networks exist on the same physical network — where "physical network" here includes logical networks like VLANs. It's a pretty rarefied idea, really. It boils down to the idea that on a single broadcast domain you might have devices in the 172.16.1.0/24 network and also devices in the 172.16.2.0/24 network … again, on a single broadcast domain. Not on separate segments connected by a router, not on separate sets of ports belonging to different VLANs, but all on the same switch for some reason. I'm sure there are people out there who use this kind of topology and probably for good reason, but in all my years I've never actually seen it deployed, so I've not bothered to add support for it into this plugin.

Groups and classes

Groups and classes are labor-saving devices in the DHCP config file, basically. They're data structures that let you assign different parameters to hosts automatically rather than having to create a large, complex config file with a lot of copying and pasting. You can put a set of hosts under a group in order to give them some common options, or you can set up a class so hosts sharing common characteristics get the right options automatically.

I'm not using groups or classes, so I haven't added any support for them. It's really that simple.

Failover

If I'm not mistaken, there's no special LDAP support for DHCP failover in ISC DHCP 4.2.5, meaning it would all have to be configured using dhcpStatements and such. I haven't taken the time to do this, though I probably will eventually.

More info