Tom's FreeBSD blog

July 15, 2010

mod_auth_kerb + AD and LDAP authorization

Filed under: Active Directory,Apache — tmclaugh @ 12:34 pm

It’s not enough to authenticate a user.  You need to also check their authorization to see if that person should be allowed access.  With Apache at work I use mod_auth_kerb for authentication and it works well.  Both IE and Firefox will send the user’s domain credentials via GSSAPI once configured correctly.*  The next step is to setup mod_authnz_ldap so we can check user account information.  Probably the most common authorization check people might use is a group membership check.  You probably have content which you only want your administrators to have access to.  The combination of mod_auth_kerb and mod_authnz_ldap does not work directly out of the box.  Additionally, Active Directory throws another wrench into the problem.

Setup for mod_auth_kerb is simple enough.  With the system joined to AD already you can easily use Samba to create your HTTP SPN using the following command:

# net ads keytab add HTTP

Additionally you should use ktutil to extract only the keys for the HTTP/machine.example.com principle to a separate keytab readable by the apache process.  Once that is done the following in your Apache configuration will have kerberos authentication working:

<Location /private>
  AuthType Kerberos
  AuthName "EXAMPLE Domain Login"
  KrbMethodNegotiate On
  KrbMethodK5Passwd On
  KrbAuthRealms EXAMPLE.COM
  Krb5KeyTab /etc/httpd/conf/keytab
  require valid-user
</Location>

This location isn’t really private since every authenticated user has access to this content.  I want to restrict this content to our Domain Admins group in AD.  This is where mod_authnz_ldap comes in.  Once the user is authenticated I want to check their group membership.  Now the config block has been expanded with an authorization check to check the user’s group membership.

<Location /private>
  AuthType Kerberos
  AuthName "EXAMPLE Domain Login"
  KrbMethodNegotiate On
  KrbMethodK5Passwd On
  KrbAuthRealms EXAMPLE.COM
  Krb5KeyTab /etc/httpd/conf/keytab

  AuthLDAPURL "ldap://dc1.example.com dc2.example.com/dc=example,dc=com?sAMAccountName"
  AuthLDAPBindDN cn=nss_ldap,ou=services,dc=example,dc=com
  AuthLDAPBindPassword ********
  Require ldap-group cn=Domain Admins,ou=Groups,dc=example,dc=com
</Location>

What I’ve done above is in AuthLDAPURL first given two domain controllers to search for user information in case one is down.  (Remember the quotes if you specify multiple DCs.)  I’ve then specified that mod_authzn_ldap should perform searches from the domain root.  And finally, I want it to search for the entity with the sAMAccountName equal to the username provided by kerberos.  With AD you need to use sAMAccountName and not uid since uid is only available if you’ve extended the AD schema for POSIX info and entered it on the account.  sAMAccountName is your guaranteed unique username.  The AuthLDAPBindDN and AuthLDAPBindPassword lines are the DN and password of a user in AD with read only access to certain parts of the directory tree to get user information.  In my case it’s the same user I use for nss_ldap.  Finally I specify the DN of the group that the user is required to be a member of.  This will still not work though and you’ll see the following in your error log:

[Thu Jul 15 09:33:49 2010] [debug] src/mod_auth_kerb.c(1432): [client 172.30.20.2] kerb_authenticate_user entered with user (NULL) and auth_type Kerberos
[Thu Jul 15 09:33:49 2010] [debug] src/mod_auth_kerb.c(1432): [client 172.30.20.2] kerb_authenticate_user entered with user (NULL) and auth_type Kerberos
[Thu Jul 15 09:33:49 2010] [debug] src/mod_auth_kerb.c(1147): [client 172.30.20.2] Acquiring creds for HTTP@corptech.example.com
[Thu Jul 15 09:33:49 2010] [debug] src/mod_auth_kerb.c(1266): [client 172.30.20.2] Verifying client data using KRB5 GSS-API
[Thu Jul 15 09:33:49 2010] [debug] src/mod_auth_kerb.c(1282): [client 172.30.20.2] Verification returned code 0
[Thu Jul 15 09:33:49 2010] [debug] src/mod_auth_kerb.c(1300): [client 172.30.20.2] GSS-API token of length 163 bytes will be sent back
[Thu Jul 15 09:33:49 2010] [debug] src/mod_auth_kerb.c(1348): [client 172.30.20.2] set cached name TMCLAUGHLIN@EXAMPLE.COM for connection
[Thu Jul 15 09:33:49 2010] [debug] mod_authnz_ldap.c(683): [client 172.30.20.2] ldap authorize: Creating LDAP req structure
[Thu Jul 15 09:33:52 2010] [debug] mod_authnz_ldap.c(695): [client 172.30.20.2] auth_ldap authorise: User DN not found, ldap_search_ext_s() for user failed

This is because the kerberos supplied principle is in the form of <username>@EXAMPLE.COM while their sAMAccountName is simply <username>.  There is no attribute created by default in AD with the user’s full kerberos principle name.  There are two ways of mangling the kerberos principle to work in an LDAP search.  One is a patch to mod_auth_kerb which adds the “KrbStripDomain” directive to remove the user’s realm and pass only the username to mod_authnz_ldap.  The other is mod_map_name which lives in the mod_auth_kerb CVS here:

http://modauthkerb.cvs.sourceforge.net/viewvc/modauthkerb/mod_map_user/

I don’t like the idea of patching distro packages so I chose the latter option.  It’s not available as a tar ball from the project’s site so you need to retrieve it from CVS, autoconf it, build, and install it.  (I’ve emailed the author asking if he would create an official release.  Waiting to hear back.)  With that module now installed and loaded a simple line to mangle the user’s kerberos principle into the user’s sAMAccountName is added.

<Location /private>
  AuthType Kerberos
  AuthName "EXAMPLE Domain Login"
  KrbMethodNegotiate On
  KrbMethodK5Passwd On
  KrbAuthRealms EXAMPLE.COM
  Krb5KeyTab /etc/httpd/conf/keytab
  # Strip the kerberos realm from the principle.
  MapUsernameRule (.*)@(.*) "$1"
  AuthLDAPURL "ldap://dc1.example.com dc2.example.com/dc=example,dc=com?sAMAccountName"
  AuthLDAPBindDN cn=nss_ldap,ou=services,dc=example,dc=com
  AuthLDAPBindPassword ********
  Require ldap-group cn=Domain Admins,ou=Groups,dc=example,dc=com
</Location>

The mod_map_user documentation gives other creative examples of how to use it which can be combined with the AuthLDAPURL but I found this to be the simplest and fit my needs.  You could probably tune MapUsernameRule and AuthLDAPURL to place less load on your AD controller if you wanted/needed to.

AD however appears to throw a wrench into mod_authnz_ldap while trying to search for an entity with a sAMAccountName value of the transformed username.  The error log indicates the kerberos realm was stripped but mod_authnz_ldap still had problems finding a match.

[Thu Jul 15 11:48:49 2010] [info] [client 172.30.19.45] Applying pattern '^(.*)@(.*)$' to user 'TMCLAUGHLIN@EXAMPLE.COM', mech:'Any'
[Thu Jul 15 11:48:49 2010] [info] [client 172.30.19.45] Pattern matched
[Thu Jul 15 11:48:49 2010] [notice] [client 172.30.19.45] User name 'TMCLAUGHLIN@EXAMPLE.COM' rewritten to 'TMCLAUGHLIN'
[Thu Jul 15 11:48:49 2010] [debug] mod_authnz_ldap.c(683): [client 172.30.19.45] ldap authorize: Creating LDAP req structure
[Thu Jul 15 11:48:52 2010] [debug] mod_authnz_ldap.c(695): [client 172.30.19.45] auth_ldap authorise: User DN not found, ldap_search_ext_s() for user failed

After scratching my head for a bit I resorted to a packet trace between the web server and DC.  What I found was a search for “(&(objectClass=*)(sAMAccountName=TMCLAUGHLIN))” which yielded a result.  But, since the result was performed at the root of the directory the DC also returned referrals which mod_authnz_ldap attempted to search and fail in doing so.  I’d assume this should work but I had to workaround it.  The solution was to make the search path where all our users are.  This could be a problem depending on your tree layout however.

<Location /private>
  AuthType Kerberos
  AuthName "EXAMPLE Domain Login"
  KrbMethodNegotiate On
  KrbMethodK5Passwd On
  KrbAuthRealms EXAMPLE.COM
  Krb5KeyTab /etc/httpd/conf/keytab
  # Strip the kerberos realm from the principle.
  MapUsernameRule (.*)@(.*) "$1"

  AuthLDAPURL "ldap://dc1.example.com dc2.example.com/cn=users,dc=example,dc=com?sAMAccountName"
  AuthLDAPBindDN cn=nss_ldap,ou=services,dc=example,dc=com
  AuthLDAPBindPassword ********
  Require ldap-group cn=Domain Admins,ou=Groups,dc=example,dc=com
</Location>

With all this in place I’m now able to successfully authenticate access to content and authorize them based on the AD group membership.  One import note though.  If it’s really that important to restrict access to content then SSLRequireSSL should be added.  I simply left it out for debugging and setup purposes.

* For Firefox in about:config you can add “*.example.com” to “network.negotiate-auth.trusted-uris”.  For IE the default settings for the Intranet Zone is to send credentials automatically.  However, if you use an FQDN it assumes the host is part of the Internet Zone and you need to add “http://*.example.com” to the list of sites in the Intranet Zone.

4 Comments »

  1. Thanks for the great. I set my own system up like you did, but I dont get it to work.

    The apache log file says:

    [Fri Jul 23 16:35:48 2010] [info] [client 10.49.14.116] Applying pattern ‘^(.*)@(.*)$’ to user ‘schul2@FOO.COM’, mech:’Any’
    [Fri Jul 23 16:35:48 2010] [info] [client 10.49.14.116] Pattern matched
    [Fri Jul 23 16:35:48 2010] [notice] [client 10.49.14.116] User name ‘schul2@FOO.COM’ rewritten to ‘schul2′
    [Fri Jul 23 16:35:48 2010] [debug] mod_authnz_ldap.c(582): [client 10.49.14.116] ldap authorize: Creating LDAP req structure
    [Fri Jul 23 16:35:48 2010] [debug] mod_authnz_ldap.c(594): [client 10.49.14.116] auth_ldap authorise: User DN not found, LDAP: ldap_simple_bind_s() failed

    I guess the entry “User DN not found, LDAP: ldap_simple_bind_s() failed” is the cause of the issue.

    Heres the directive for the directory which should be secure:

    # Kerberos Auth
    AuthType Kerberos
    KrbAuthRealms FOO.COM
    KrbServiceName HTTP
    Krb5Keytab /etc/apache2/dokuwiki.HTTP.keytab
    KrbMethodNegotiate on
    KrbMethodK5Passwd on
    MapUsernameRule (.*)@(.*) “$1″
    AuthLDAPURL ldap://arsv0048.foo.com/cn=Users,dc=foo,dc=com?sAMAccountName
    AuthLDAPBindDN cn=wiki-sso-auth,ou=Administration,dc=foo,dc=com
    AuthLDAPBindPassword wiki-sso-auth
    Require ldap-group cn=g_edv,ou=Gruppen,ou=Users,dc=foo,dc=com

    Do you have any suggestions where the error could be or some hints for further investigations?

    Thanks in advance.

    (The Kerberos Authentification is working. If I comment out the LDAP-Authorization the browser is doing a sso.)

    Comment by ms — July 23, 2010 @ 10:49 am

  2. The problem is with your AuthLDAPBindDN/AuthLDAPBindPassword. The user/pass combo is failing to bind to the directory.

    Try this:

    $ ldapsearch -H ldap://arsv0048.foo.com -x -D cn=wiki-sso-auth,ou=Administration,dc=foo,dc=com -W sAMAccountName=wiki-sso-auth

    That should prompt you for a password. Enter your pass and see if it works.

    By the way, I found out about the userPrincipalName attribute in AD. If you use that instead of sAMAccountName in AuthLDAPURL you won’t need the MapUsernameRule. The caveat is not all users may have it populated. If the domain was an NT4 -> to AD conversion then users created in the NT4 period will not have this attribute. If that’s not the situation then you’re good. I’m going to update this post with a new one after I get some other questions answered and write a powershell script to sanitize my users at work.

    Comment by tmclaugh — July 23, 2010 @ 12:21 pm

  3. Thanks for the quick reply.

    I tried your ldapsearch and i get the error, that the credentials are wrong (as you said already).

    The password must be correct (i reset it in AD console). Does the same error occurs, if the ldap-path (e.g. cn-name) is wrong?

    Comment by ms — July 24, 2010 @ 4:50 am

  4. Got it. You have to care about the cn-value in the ldap search path. I set every account property to “wiki-sso-auth” (e.g. display name) in AD “Users and Groups” and now the ldapsearch accepts the password.

    Comment by ms — July 24, 2010 @ 4:55 am

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress