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
__queries__ = ['ExampleQuery']
conn_default = LdapConnection(
binddn="uid=example,ou=People,dc=example,dc=com",
bindpw = "foobat")
class ExampleQuery(LdapQuery):
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 the two required classes for dealing with connections and queries.
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["ExampleQuery"]["admin_name"]}
Connection retry¶
If the LDAP server is down during a request, the LDAP plugin tries to reconnect after a short delay. By default, it waits 3 seconds during the retries and tries to reconnect up to three times.
If you wish, you could customize these values in your bcfg2.conf
:
[ldap]
retries = 3
retry_delay = 3.0
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 toNone
).
-
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"]
(defaults to the class name).
-
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, **kwargs)¶ 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, **kwargs)¶ 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)
-
LdapQuery.
get_result
(self, metadata, **kwargs)¶ This executes the query. First it will call
prepare_query()
for you, then it will try to execute the query with the specified connection and last it will callprocess_result()
and return that return value.
If you use a LdapQuery class by yourself, you could pass additional keyword arguments to
get_result()
. It will call prepare_query()
and process_result()
for you and
also supply this additional arguments to this methods.
Here is an example:
__queries__ = ['WebPackageQuery']
class WebSitesQuery(LdapQuery):
filter = "(objectClass=webHostingSite)"
attrs = ["dc"]
connection = conn_default
def prepare_query(self, metadata, base_dn):
self.base = base_dn
def process_result(self, metadata, **kwargs):
[...] # build sites dict from returned dc attributes
return sites
class WebPackagesQuery(LdapQuery):
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 the WebPackagesQuery
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 LdapQuery to only
search below that DN.
You do not need to add all LdapQueries to the __queries__
list. Only add those to
that list, that should be called automatically and whose results should be added to the
client metadata.
Known Issues¶
- At this point there is no support for SSL/TLS.
- This module could not know, if a value changed on the LDAP server. So it could not expire the client metadata cache sanely. If you are using aggressive caching mode, this plugin will expire the metadata cache for a single client at the start of a client run. If you are using LDAP data from another client in a template, you will probably get the cached values from the last client run of that other client.