sampledoc

Ldap

Warning

This plugin is considered experimental and has known issues (see below).

Purpose

This plugin makes it possible to fetch data from an LDAP directory, process it and attach it to your metadata.

Installation

First, you need to install the python-ldap library. On debian-based systems this is accomplished by:

aptitude install python-ldap

To enable the plugin, add “Ldap” to the plugins line in your bcfg2.conf. Then add a new directory called “Ldap” to the root of your Bcfg2 repository and define your queries in a file called config.py using the information in the next section.

Configuration

As processing LDAP search results can get pretty complex, the configuration has to be written in Python.

Here is a minimal example to get you started:

from Bcfg2.Server.Plugins.Ldap import LdapConnection, LdapQuery, LdapSubQuery, register_query

conn_default = LdapConnection()
conn_default.binddn = "uid=example,ou=People,dc=example,dc=com"
conn_default.bindpw = "foobat"

@register_query
class ExampleQuery(LdapQuery):
    name = "example"
    base = "ou=People,dc=example,dc=com"
    scope = "one"
    attrs = ["cn", "uid"]
    connection = conn_default

    def prepare_query(self, metadata):
        self.filter = "(personalServer=" + metadata.hostname + ")"

    def process_result(self, metadata):
        if not self.result:
            admin_uid = None
            admin_name = "This server has no admin."
        return {
            "admin_uid" : self.result[0][1]["uid"],
            "admin_name" : self.result[0][1]["cn"]
        }

The first line provides three classes for dealing with connections and queries (details below) and a decorator function for registering your queries with the plugin.

In this example our LDAP directory has a number of user objects in it. Each of those may have a personal server they administer. Whenever metadata for this machine is being generated by the Bcfg2 server, the UID and name of the admin are retrieved from LDAP.

In your bundles and config templates, you can access this data via the metadata object:

${metadata.Ldap["example"]["admin_name"]}

Class reference

LdapConnection

class LdapConnection

This class represents an LDAP connection. Every query must be associated with exactly one connection.

LdapConnection.binddn

DN used to authenticate against LDAP (required).

LdapConnection.bindpw

Password for the previously mentioned binddn (required).

LdapConnection.host

Hostname of host running the LDAP server (defaults to “localhost”).

LdapConnection.port

Port where LDAP server is listening (defaults to 389).

You may pass any of these attributes as keyword arguments when creating the connection object.

LdapQuery

class LdapQuery

This class defines a single query that may adapt itself depending on the current metadata.

LdapQuery.attrs

Can be used to retrieve only a certain subset of attributes. May either be a list of strings (attribute names) or None, meaning all attributes (defaults to None).

LdapQuery.base

This is the search base. Only LDAP entries below this DN will be included in your search results (required).

LdapQuery.connection

Set this to an instance of the LdapConnection class (required).

LdapQuery.filter

LDAP search filter used to narrow down search results (defaults to (objectClass=*)).

LdapQuery.name

This will be used as the dictionary key that provides access to the query results from the metadata object (metadata.Ldap["NAMEGOESHERE"]) (required).

LdapQuery.scope

Set this to one of “base”, “one” or “sub” to specify LDAP search depth (defaults to “sub”).

LdapQuery.is_applicable(self, metadata)

You can override this method to indicate whether this query makes sense for a given set of metadata (e.g. you need a query only for a certain bundle or group).

(defaults to returning True)

LdapQuery.prepare_query(self, metadata)

Override this method to alter the query prior to execution. This is useful if your filter depends on the current metadata, e.g.:

self.filter = "(cn=" + metadata.hostname + ")"

(defaults to doing nothing)

LdapQuery.process_result(self, metadata)

You will probably override this method in every query to reformat the results from LDAP. The raw result is stored in self.result, you must return the altered data. Note that LDAP search results are presented in this structure:

(
    ("DN of first entry returned",
        {
            "firstAttribute" : 1,
            "secondAttribute" : 2,
        }
    ),
    ("DN of second entry returned",
        {
            "firstAttribute" : 1,
            "secondAttribute" : 2,
        }
    ),
)

Therefore, to return just the value of the firstAttribute of the second object returned, you’d write:

return self.result[1][1][0]

(defaults to returning self.result unaltered)

LdapSubQuery

class LdapSubQuery

Sometimes you need more than one query to obtain the data you need (e.g. use the first query to return all websites running on metadata.hostname and another query to find all customers that should have access to those sites).

LdapSubQueries are the same as LdapQueries, except for that the methods

  • get_result()
  • prepare_query()
  • process_result()

allow any additional keyword arguments that may contain additional data as needed. Note that get_result() will call prepare_query() and process_result() for you, so you shouldn’t ever need to invoke these yourself, just override them.

Here is another example that uses LdapSubQuery:

class WebSitesQuery(LdapSubQuery):
    name = "web_sites"
    filter = "(objectClass=webHostingSite)"
    attrs = ["dc"]
    connection = conn_default

    def prepare_query(self, metadata, base_dn):
        self.base = base_dn

    def process_result(self, metadata):
        [...] # build sites dict from returned dc attributes
        return sites

@register_query
class WebPackagesQuery(LdapQuery):
    name = "web_packages"
    base = "dc=example,dc=com"
    attrs = ["customerId"]
    connection = conn_default

    def prepare_query(self, metadata):
        self.filter = "(&(objectClass=webHostingPackage)(cn:dn:=" + metadata.hostname + "))"

    def process_result(self, metadata):
        customers = {}
        for customer in self.result:
            dn = customer[0]
            cid = customer[1]["customerId"][0]
            customers[cid]["sites"] = WebSitesQuery().get_result(metadata, base_dn = dn)
        return customers

This example assumes that we have a number of webhosting packages that contain various sites. We need a first query (“web_packages”) to get a list of the packages our customers have and another query for each of those to find out what sites are contained in each package. The magic happens in the second class where WebSitesQuery.get_result() is called with the additional base_dn parameter that allows our LdapSubQuery to only search below that DN.

Warning

Do NOT apply the register_query decorator to LdapSubQueries.

Known Issues

  • At this point there is no support for SSL/TLS.

Table Of Contents

Previous topic

GroupPatterns

Next topic

Metadata

This Page