summaryrefslogtreecommitdiffstats
path: root/doc/server/plugins/grouping/ldap.txt
blob: abbd5e0051c1d8fefe8d94ce5411a33fd728a469 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
.. -*- mode: rst -*-

.. _server-plugins-grouping-ldap:

====
Ldap
====

.. warning::
    This plugin is considered experimental.

Purpose
-------

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

Installation
------------

__ http://www.python-ldap.org/

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

Caching
+++++++

This module could not know, if a value changed on the LDAP server. So it does not cache
the results of the LDAP queries by default.

You could enable the cache of the results in your ``bcfg2.conf``:

    [ldap]
    cache = on

If you enable the caching, you have to expire it manually. This module provides a XML-RPC
method for this purpose: :func:`Ldap.expire_cache
<Bcfg2.Server.Plugins.Ldap.expire_cache>`.

Even without enabling caching, the results of the LDAP queries are cached, but are
discarded before each client run. If you access the Ldap results of different client, you
may get cached results of the last run of this client. If you do not want this behaviour,
you can disable the caching completely by setting it to ``off``.

Class reference
---------------

LdapConnection
++++++++++++++

.. class:: LdapConnection

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

.. attribute:: LdapConnection.binddn

   DN used to authenticate against LDAP (required).

.. attribute:: LdapConnection.bindpw

   Password for the previously mentioned **binddn** (required).

.. attribute:: LdapConnection.host

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

.. attribute:: LdapConnection.port

   Port where LDAP server is listening (defaults to 389). If you use
   port 636 this module will use ldaps to connect to the server.

.. attribute:: LdapConnection.uri

   LDAP URI of the LDAP server to connect to. This is prefered over
   :attr:`LdapConnection.host` and :attr:`LdapConnection.port`.

   .. note::

      If you are using ldaps you may have to specify additional options
      for enabling the certificate validation or setting the path for
      the trusted certificates with :attr:`LdapConnection.options`.

.. attribute:: LdapConnection.options

   Arbitrary options for the LDAP connection. You should specify it
   as a dict and use the ``OPT_*`` constants from ``python-ldap``.

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.

.. attribute:: 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``).

.. attribute:: LdapQuery.base

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

.. attribute:: LdapQuery.connection

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

.. attribute:: LdapQuery.filter

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

.. attribute:: 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).

.. attribute:: LdapQuery.scope

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

.. method:: 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)

.. method:: 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)

.. method:: 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)

.. method:: 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 call ``process_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.