Authentication and Authorization with Shibboleth and LDAP

Previously, I tried setting up a more efficient Shibboleth Attribute Authority - one where I could query for a specific attribute value for a specific attribute for a specific user (e.g. does jane@itlab.stanford.edu have an experimentId attribute with the value 2?). While you can add attribute values to the attribute elements in a SimpleAggregation AttributeResolver query, the IdP rejects the query.

Another Shib-based option would be to develop a plugin that can create attributes from server environment variables (like REMOTE_URI). One could then use the Template and Tranform Attribute Resolvers to create a new NameID attribute for the SimpleAggregation query (something like eppn:https://SERVER_NAME/REMOTE_URI). Then an Attribute Authority could be configured to split the NameID into multiple attributes and use those for a SQL query or LDAP lookup.

That seemed like a bunch of work, so instead I took a look at using Apache’s mod_authnz_ldap on the SP instead. In this scenario, when an unauthenticated user attempts to access a set of experiment results, they are first sent to an IdP (via the embedded discovery service) to authenticate, then mod_authnz_ldap queries a remote LDAP server for attributes (group membership seems to be the easiest model) and determines whether the user has access.

I found some useful information in a blog post about Kerberized SSO from Windows to Apache.

The LDAP server needs at least two trees - one for people and one for experiments. I created these trees:

dn: cn=people,dc=itlab,dc=stanford,dc=edu
ou: people
objectClass: organizationalRole

dn: cn=experiments,dc=itlab,dc=stanford,dc=edu
ou: experiments
objectClass: organizationalRole

I also created another tree for service accounts:

dn: cn=services,dc=itlab,dc=stanford,dc=edu
ou: services
objectClass: organizationalRole

I created a service account for the experiments SP in the services tree

dn: uid=sp.experiments,cn=services,dc=itlab,dc=stanford,dc=edu
objectClass: account
objectClass: simpleSecurityObject
uid: sp.experiments
description: IT Lab Experiments SP
host: experiments.itlab.stanford.edu
userPassword: secret

then created some people:

dn: uid=jane,cn=people,dc=itlab,dc=stanford,dc=edu
displayName: Jane Stanford
uid: jane
eduPersonPrincipalName: jane@itlab.stanford.edu

dn: uid=leland,cn=people,dc=itlab,dc=stanford,dc=edu
displayName: Leland Stanford
uid: leland
eduPersonPrincipalName: leland@itlab.stanford.edu

dn: uid=lelandjr,cn=people,dc=itlab,dc=stanford,dc=edu
displayName: Leland Stanford, Jr
uid: lelandjr
eduPersonPrincipalName: lelandjr@itlab.stanford.edu

Finally, I created some experiments:

dn: cn=https://experiments.itlab.stanford.edu/experiments/1,cn=experiments,dc=itlab,dc=stanford,dc=edu
uniqueMember: uid=jane,cn=people,dc=itlab,dc=stanford,dc=edu
uniqueMember: uid=leland,cn=people,dc=itlab,dc=stanford,dc=edu
uniqueMember: uid=lelandjr,cn=people,dc=itlab,dc=stanford,dc=edu

dn: cn=https://experiments.itlab.stanford.edu/experiments/2,cn=experiments,dc=itlab,dc=stanford,dc=edu
uniqueMember: uid=jane,cn=people,dc=itlab,dc=stanford,dc=edu
uniqueMember: uid=leland,cn=people,dc=itlab,dc=stanford,dc=edu
uniqueMember: uid=lelandjr,cn=people,dc=itlab,dc=stanford,dc=edu

dn: cn=https://experiments.itlab.stanford.edu/experiments/3,cn=experiments,dc=itlab,dc=stanford,dc=edu
uniqueMember: uid=leland,cn=people,dc=itlab,dc=stanford,dc=edu

dn: cn=https://experiments.itlab.stanford.edu/experiments/4,cn=experiments,dc=itlab,dc=stanford,dc=edu
uniqueMember: uid=jane,cn=people,dc=itlab,dc=stanford,dc=edu

dn: cn=https://experiments.itlab.stanford.edu/experiments/5,cn=experiments,dc=itlab,dc=stanford,dc=edu
uniqueMember: uid=lelandjr,cn=people,dc=itlab,dc=stanford,dc=edu

dn: cn=https://experiments.itlab.stanford.edu/experiments/6,cn=experiments,dc=itlab,dc=stanford,dc=edu
uniqueMember: uid=jane,cn=people,dc=itlab,dc=stanford,dc=edu

That’s the LDAP part done… on to the Apache server…

Enable the authnz_ldap module. I also disabled caching for testing by adding:

LDAPCacheEntries 0

If you do that, it must be at the top level of Apache configuration - not inside a VirtualHost entry.

Then I created a Location for my experiments:

<Location /experiments/>

    AuthType shibboleth
    ShibRequestSetting requireSession 1
    ShibRequireAll on
    AuthzShibAuthoritative off
    AuthzLDAPAuthoritative off

    AuthLDAPURL ldap://aa.itlab.stanford.edu/cn=people,dc=itlab,dc=stanford,dc=edu?eduPersonPrincipalName
    AuthLDAPGroupAttribute uniqueMember
    AuthLDAPGroupAttributeIsDN on
    AuthLDAPBindDN 'uid=sp.experiments,cn=services,dc=itlab,dc=stanford,dc=edu'
    AuthLDAPBindPassword 'secret'

    Satisfy all
</Location>

A simple .htaccess file is needed in each experiment directory: for example, in /var/www/experiments/2/ I have:

require ldap-group cn=https://experiments.itlab.stanford.edu/experiments/2,cn=experiments,dc=itlab,dc=stanford,dc=edu

Yes, that’s all.

Now, when a user accesses a directory (after they have been authenticated by Shibboleth), the authnz_ldap module queries the LDAP server for a matching object using the query specified by AuthLDAPURL (it defaults to searching on the uid attribute, hence the eduPersonPrincipalName override for federated users). authnz_ldap then compares the DN (since AuthLDAPGroupAttributeIsDN is enabled) for the user object against the uniqueMember attribute (specified by AuthLDAPGroupAttribute) it then does a compare on the uniqueMember attribute (AuthLDAPGroupAttribute) of the group (specified by the require ldap-group setting in the .htaccess file). The user is authorized if the compare succeeds, and is denied access if it does not.