Over the last year, I’ve been slowly moving some of my private Puppet code to use LDAP as a data source. Through this process, a bit of strategy, some tool and some schema has emerged I’ll share here.
It’s worth noting that Puppet has had an LDAP Node Classifier for quite some time, probably longer than I’ve been using Puppet. Though looking over the documentation, you wouldn’t know it, but the most useful bit of this whole work is that fact that Puppet supports the configuration variables necessary to make a connection to an LDAP server, and those variables are available for reference.
Enter, ldapquery(). I initially wrote the function so I could retrieve arbitrary data from LDAP to make use of in manifest. In combination with the new language features, it turns out to yield some pretty powerful expressions. More on that later.
Before this, I’ve been using Hiera for all classification. It’s pretty simple
to use the hiera_include() function to drag in a bunch of classes from your
hiera data, but I’m getting tired of creating a new file for a node that only
contains a single instruction for which class to include on a given node.
So my task was thus: given a selection of nodes, replace the Hiera classification with LDAP as the data source. It turned out to be really easy.
This assumes the following schema has been loaded. To be honest, I’m not exactly sure why I wrote my own schema for this. Likely the stock Puppet schema with the registered OIDs would work just fine (I may switch to that later).
dn: cn=puppet,cn=schema,cn=config
cn: puppet
objectClass: olcSchemaConfig
olcAttributeTypes: ( 1.3.6.1.4.1.34380.900 NAME ( 'certName' )
DESC 'The certname of the node to match'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcAttributeTypes: ( 1.3.6.1.4.1.34380.901 NAME ( 'puppetClass' )
DESC 'Classes to be included on the node'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
olcObjectClasses: ( 1.3.6.1.4.1.34380.990 NAME 'puppetNode'
DESC 'Puppet classification information'
AUXILIARY
SUP top
MUST ( cn $ certName $ puppetClass )
)
Its dead simple. There are nodes, and they can have a certName and a
(list) of puppetClass entries.
So the manifest now looks like this.
$results = ldapquery("(&(objectClass=puppetNode)(certName=${::certname}))", ['puppetClass'])
if $results {
$ldap_classes = $results[0]['puppetclass']
$ldap_classes.each |$c| {
class { $c: tag => 'ldap_class' }
motd::register { $c:}
}
}
This code searches LDAP for entries who’s object classes include puppetNode
and match the current $certname, and returns the puppetClass attribute for
each matched entry.
Its been working great for my small use case, though I don’t really see a
reason why this wouldn’t scale. Also, I’ve not completely replaced my hiera
classification, so some nodes get classified by both hiera and LDAP. This
might sound messy, but its really quite simple to draw a clean line. Plus,
using the class declaration like above avoids class include collisions. The
motd::register ensures that the node lists the classes it has been assigned
from LDAP as well to make things clear.
I’m planning to write up some tooling to create the LDAP entries as well, but for now this is making my life easier. If you have LDAP, and the thought of classifying with LDAP sounds good, I hope this helps. Send me improvements when you find them.