Apache lookup module
This is an Apache2 module (which appears to work in Apache 2.0 and Apache 2.2) designed to perform authorization functions for an existing REMOTE_USER (as derived from Raven or otherwise) by querying lookup.
- Media:Ucam_lookupquery-0.90.tar.gz - Source tar ball
- Media:Ucam_lookupquery-0.90-1.src.rpm - Very basic source RPM
Please send questions about this module to lookup-support@ucs.cam.ac.uk.
Building the module
To build the module from source and copy it into the correct Apache directory, first unpack the distribution tar file and cd into the directory it creates:
tar zxf mod_ucam_lookupquery-<ver>.tar.gz cd mod_ucam_lookupquery-<ver>
then issue the following command as root:
apxs -c -i mod_ucam_lookupquery.c
apxs may not be on root's default path and may be renamed, e.g. to apxs2, on some platforms. apxs and related files are sometimes provided by a httpd-devel, apache-devel, or apache2-devel package which will need to be installed. You will also need the OpenLDAP development files which will probably be provided by an openldap2-devel package or similar.
Version 0.90 of the module isn't as portable as one might like. Depending on your platform details (Apache version, OpenLDAP version, C compiler, etc.) you may need to make some or all of the following changes:
- Add, around line 26:
#include "http_request.h" /* for ap_hook_auth_checker */ #include "apr_lib.h" /* for apr_isspace.h */
- At line 150, change
int error_code;
to
int error_code = 0;
- For some older versions of OpenLDAP, Change lines 187/8 from
else if (strcmp(scope, "one") == 0) intscope = LDAP_SCOPE_ONE; else if (strcmp(scope, "sub") == 0) intscope = LDAP_SCOPE_SUB;
to
else if (strcmp(scope, "one") == 0) intscope = LDAP_SCOPE_ONELEVEL; else if (strcmp(scope, "sub") == 0) intscope = LDAP_SCOPE_SUBTREE;
- Change line 575 from
regex_t *r = ap_pregcomp(p, w, REG_ICASE);
to
ap_regex_t *r = ap_pregcomp(p, w, AP_REG_ICASE);
- Change line 829 from
while (s > fakeuser && isspace(s[-1])) s--;
to
while (s > fakeuser && apr_isspace(s[-1])) s--;
Basic documentation
Load the module by including
LoadModule ucam_lookupquery_module modules/mod_ucam_lookupquery.so
in the Apache configuration file, replacing 'modules/mod_ucamlookupquery.so' if necessary with the path to the the file containing the module, relative to ServerRoot if it doesn't start '/'.
To use it, include something like
Require LookupInst CS
in a protection block. See below for other configuration options.
OPTIONS ------- LookupAuthoritative Value: on|off Default: on LookupHostPort Value: space-separated list Default: ldap.lookup.cam.ac.uk:389 (actually uses LDAP_PORT) (1) IPv6 literal addresses can be enclosed in []. (2) It tries each one till one answers, one way or another. LookupBase Value: string Default: "o=University of Cambridge,dc=cam,dc=ac,dc=uk" AUTHENTICATION -------------- LookupBindDN Value: string Default: unset LookupPwd Value: string Default: unset If both of these are set, a non-anonymous LDAP bind is done, first setting up an encrypted (TLS) session. If either one is not set, a non-encrypted, anonymous bind is done. REQUIREMENTS ------------ Apart from LookupQuery, all these come in pairs, LookupXxx and LookupXxxMatch. The former do exact matches on the data, the latter do regular expression matches. Multiple data values for a Require implement OR. Multiple Requires implement AND. It is not clear if this is the True Apache Way or not - I have failed to find a definitive statement - but it seems to me that whether multiple Requires do AND or OR should be controlled from a higher level (compare "satisfy any" for Require+Allow) because each run of the module just deals with a single Require and returns yes/no. There would have to be a much more elaborate memory scheme to implement OR at this level. Require LookupInst[Match] name1 [name2 ...] Runs: ou=people sub (uid=%u) inst Needs: (a) Exactly one entry (b) Exactly one attribute value, must match one of the names Example: Require LookupInst CS CL Require LookupAttr[Match] comma-separated-attrnames value1 [value2 ...] Runs: ou=people sub (uid=%u) comma-separated-attrnames Needs: (a) Exactly one entry (b) At least one attribute value must match something in the list Example: Require LookupAttr cn,displayName "Jon Warbrick" "Philip Hazel" Require LookupParentInst[Match] name1 [name2 ...] Runs: ou=people sub (uid=%u) inst Needs: (a) Exactly one entry (b) Exactly one attribute value, must match one of the names Else: ou=insts sub (instID=inst) parentInstID Needs: (a) Exactly one entry (b) Exactly one attribute value, must match one of the names Else: Loop up the tree till ROOT (which is checked). Example: Require LookupParentInst COLL Require LookupUserInGroup[Match] name1 [name2 ...] Runs: ou=groups sub (uid=%u) groupID Needs: (a) One or more entries (b) At least one of the groupIDs to match one of the names. Example: Require LookupUserInGroup 100001 100656 Require LookupQuery baseplus scope filter Runs: baseplus scope filter Needs: One or more entries (no attributes are looked up) Example: RequireLookupQuery ou=groups sub (&(uid=%u)(groupTitle=*Computing Service*)) The last rather artificial example checks that the user is a member of at least one group that has "Computing Service" in its title. To run a query on the base itself, the first argument can be given as an empty string in quotes. In the filter, literal characters * ( ) \ must be escaped as follows: * => \2A ( => \28 ) => \29 \ => \5C The sequence %u is replaced by the userid. To include a literal % before a lower case u, use \75.
Bugs and potential enhancements
- What about Apache 1.3?
- Is there any way to keep a cached connection to ldap.lookup.cam.ac.uk? If so, should it be done?
- 'Require LookupInst[Match]' expects exactly one attribute value, which won't be the case for someone affiliated to multiple institutions. It should require at least one attribute value, which must match one of the names. As a work around, you can use something like
RequireLookupQuery ou=people sub (&(uid=%u)(instID=CS))
- There is no scope for matching against the allUid when checking group membership from a privileged account. Probably needs to run
ou=groups sub (&(uid=%u)(allUid=%u)) groupID