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.