22 Dec 2011

Securing the ActiveMQ web console using LDAP based authentication

In my previous blog post I described how to configure LDAP based authentication and authorization in ActiveMQ while also allowing anonymous access to certain destinations in the broker. That previous post as well as this post is based on the LDAP tutorial in the ActiveMQ Security Guide provided by Fusesource.com.

In this article I want to expand on the previous post and show how to secure the ActiveMQ web console so that web users will be authenticated against user information stored in LDAP. All the configuration from the previous post can extended in order to secure the web console but I will provide full configuration here as well so that there is no need to copy and paste config snippets from the previous article.
I recommend the LDAP tutorial as a prerequisite to this article as it populates the LDAP server with the right security information (users, passwords, groups and roles), which is needed if you want to follow the steps of this blog post.

The ActiveMQ web console internally uses Jetty as the web container. So securing the web console basically means configuring the Jetty HTTP server to authenticate any web clients. The way how Jetty gets secured is completely independent from the way how ActiveMQ gets secured. However both support JAAS so the actual configuration principles are the same. Jetty also supports LDAP based authentication through its own JAAS LoginModule class org.eclipse.jetty.plus.jaas.spi.LdapLoginModule.



Prerequisites before taking off

The LDAP tutorial in the ActiveMQ Security Guide (version 5.5) makes some configuration that will not work quite well with the JAAS LDAP login module used by the ActiveMQ web console (i.e. Jetty). Two configuration changes are required in your LDAP user and group data to successfully authenticate any web users via Jetty's LdapLoginModule. Here are the required changes to your LDAP data:

1) Store passwords in plain text in LDAP:
The LDAP tutorial instructs you to store any user passwords as SHA hashes. See steps 18 and 19 of the Add User Entries part of the tutorial (and Figure 6.7). The LdapLoginModule from Jetty reads that password from LDAP as it is (i.e. SHA hash) and tries to match it against the password supplied by the web user, which is in plain text. So the SHA hashed password stored in LDAP is string compared against the plain text password, which will obviously fail.
Rather than storing the password as SHA hash in LDAP, store it in plain text.


This needs to be done for all users.
Storing the password in plain text will not change the ability of the ActiveMQ LDAPLoginModule class to authenticate users. This LoginModule takes a different approach. Rather than retrieving the password and comparing it against the password given by the user, it tries to bind the user (an operation on the LDAP server) whereby the password gets verified by the LDAP server and not by the LoginModule class.
Perhaps there is a way to configure the Jetty LdapLoginModule to also work with SHA hashed password, however I did not find it (also tried DIGEST based authentication but that did not help).



2) User to group mapping in LDAP must use users full dn + basedn name:

Steps 30-31 and figure 6.8 of the Add User Entries part of the LDAP tutorial explain how to make users member of a particular group. It uses the member attribute of the groupOfNames LDAP class and the mapping to a user is done via the users uid attribute, like in this example mapping that is taken from the LDAP tutorial:
  member=uid=jdoe

This won't work with the Jetty LdapLoginModule implementation, as it queries for a users group using the user's full name (i.e. uid + base dn), e.g.

  member=uid=jdoe,ou=User,ou=ActiveMQ,ou=system

and as a result won't find any user groups if the group members are only specified using the uid (i.e. jdoe).
To overcome this difference in the LDAP login module implementations, it is necessary to always provide the dn + basedn of the user that you want to add to a group.
E.g. rather than setting the members of a group like this ((see Figure 6.8 of the LDAP tutorial)

use this

This change of users to groups wiring however requires an update on the ActiveMQ login module configuration in login.config from
  roleSearchMatching="(member=uid={1})"

to
  roleSearchMatching="(member=uid={1},ou=User,ou=ActiveMQ,ou=system)"

That way both, the LDAP LoginModule of ActiveMQ and Jetty will be able to retrieve the groups (roles) a user is in.  
With these two changes to the LDAP data we are ready to go and configure the web console for LDAP based authentication.



Steps to securing the web console

All of the Jetty configuration in ActiveMQ is stored in $ACTIVEMQ_HOME/conf/jetty.xml. Its Spring config already instantiates a security handler but authentication is turned off by default via the authenticate=false property.  It needs to be enable in this bean:



<bean id="securityConstraint" class="org.eclipse.jetty.http.security.Constraint">
  <property name="name" value="BASIC" />
  <property name="roles" value="admins" />
  <property name="authenticate" value="true" />
</bean>


Changing the "authenticate" property to "true" is required but isn't enough. Any authenticated user needs to have the role specified by the "roles" attribute in order to get authorized. The administrator role name in LDAP is called "admins" but the Jetty securityConstraint bean uses "admin" out of the box. It is necessary to change the value of the "roles" property to "admins" as in the above example. A fully configured jetty.xml is provided at the end of this article.

Out of the box Jetty is configured to use a HashLoginService class, which simply reads the user credentials from a text file ${activemq.base}/conf/jetty-realm.properties. In order to authenticate against an LDAP server we need to configure a JAAS LoginService class:



<bean id="securityLoginService" class="org.eclipse.jetty.plus.jaas.JAASLoginService">
  <property name="name" value="ActiveMQLDAPRealm" />
  <property name="LoginModuleName" value="jetty-ldap"/>
  <property name="CallbackHandlerClass" value="org.eclipse.jetty.plus.jaas.callback.DefaultCallbackHandler" />
  <property name="roleClassNames" value="org.eclipse.jetty.plus.jaas.JAASRole" />
</bean>


This JAASLoginService is configured for a LoginModuleName called "jetty-ldap". This name refers to the JAAS login module configuration in login.config. In the previous article I already provided a login.config to be used by ActiveMQ. You can simply add the following configuration to that same login.config:


/**
   This LoginModule configuration is only used by Jetty when
   authentication of the web console is enabled.
*/
jetty-ldap {
  org.eclipse.jetty.plus.jaas.spi.LdapLoginModule required
    debug="true"
    contextFactory="com.sun.jndi.ldap.LdapCtxFactory"
    hostname="localhost"
    port="10389"
    bindDn="uid=admin,ou=system"
    bindPassword="secret"
    authenticationMethod="simple"
    forceBindingLogin="false"
    userBaseDn="ou=User,ou=ActiveMQ,ou=system"
    userRdnAttribute="uid"
    userIdAttribute="uid"
    userPasswordAttribute="userPassword"
    userObjectClass="inetOrgPerson"
    roleBaseDn="ou=Group,ou=ActiveMQ,ou=system"
    roleNameAttribute="cn"
    roleMemberAttribute="member"
    roleObjectClass="groupOfNames"
    authenticated="true";
};

Finally Jetty needs to be told where to find this login.config file via a Java property to the JVM.
The easiest way is to add this propery to the bin/activemq script, e.g. 
 
ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS_MEMORY -Dorg.apache.activemq.UseDedicatedTaskRunner=true   -Djava.util.logging.config.file=logging.properties -Djava.security.auth.login.config=login.config"

That's about it. Assuming the data in LDAP is setup according to the prerequisites listed above, the ActiveMQ web console should now be secured. Trying to access http://localhost:8161 should now request a username and password from you which is then authenticated against the LDAP server.

Note we did not need to change the broker configuration (with the exception of pre-requisite #2) as the authentication done in the web console (based on Jetty) is completely independent of the authentication done by ActiveMQ.


Links to all files used in this post:
jetty.xml
login.config
activemq-ldap-with-anon-access.xml

The relevant documentation that helped me on this subject:
Jetty JAAS Tutorial
Jetty Realms Tutorial
Jetty 7 JavaDoc

Let me know if this post was helpful.
This configuration has been tested with ActiveMQ 5.5.1 and 5.6.0.

11 comments:

Andy said...

I was able to get the ActiveMQ v5.5.1 web console to work with SHA hashed passwords with two changes. First was to change the setting in login.config from forceBindingLogin="false" to true. Second was to add a second entry into the LDAP group jetty has been configured to check for membership in the securityConstraint in jetty.xml ex. property name="roles" value="admins". Watching the openldap log I was able to see that with the forced bind setting it was attempting to lookup "cn=Doe,ou=User,ou=ActiveMQ,ou=system" not the expected uid=jdoe,ou=ActiveMQ,ou=system. With the second member entry in the admins group starting with "cn=Doe" in place I could change the encryption on the user password to SHA and it worked.

anitakrueger said...

Hi Andy,
What is the second change you are making exactly? Is it in the jetty.xml adding another role entry?
I am stuck at this point exactly, because of the hashed passwords.

Thanks a lot for this great tutorial!
- Anita

Torsten Mielke said...

Hello Anita,

The first change to the LDAP tutorial from FuseSource is to store passwords in plain text and not SHA hashed.
The second change to the existing LDAP configuration was to use full dn name and basedn name in the users to group mapping. The FuseSource tutorial instructs to only store the basedn part of the username, which does not work with the Jetty LDAPLoginModule.

Also, I added links to all configuration files that are needed to setup LDAP based authentication in the web console at the end of my article.

anitakrueger said...

Hi Torsten,
Sorry if I wasn't clear in my question. In your first comment you said you were able to get the web console access to work with SHA hashed passwords and you had to make two changes to do so. The first one was setting forceBindingLogin to true and "to add a second entry into the LDAP group jetty has been configured to check for membership in the securityConstraint in jetty.xml". That second piece is what is not clear to me. You had to add your user to another group in OpenLDAP? The one that is configured in the security constraint?

Thanks a lot!
- Anita

Torsten Mielke said...

Hello Anita,

No, I was not able to get this working using SHA hashed passwords. I had to disable SHA as outlined in the article.

anitakrueger said...

Hi Torsten,
No worries. Unfortunately I haven't been able to get this working with SHA hashed passwords until now either :(
I will post it, if I get it working.

Thanks
- Anita

Anonymous said...

Hi,
I am trying to authenticate 5.7.0 using ldap. I tried each and every steps that you have written but it is not working.Can you please try in 5.7.0 and provide me the steps.
Also i get
javax.security.auth.login.LoginException: Error obtaining callback information.
message.
Thanks
Amy

Francesco said...

Thank you for the great post. If you mind using the super-powerful ldaptive ldap library, have a look at: Securing the ActiveMQ 5.8.0 web console using LDAP based authentication with Ldaptive.

Java Prasanna said...
This comment has been removed by the author.
Java Prasanna said...

Thank you for the post! It was helpful to me as well.

Here is my successful configuration.

1. Remove the property mentioned above.



2. Change the attribute value forceBindingLogin="true" in ldap login configuration.

The additional above 2 changes made the trick.

Thanks,

Prasanna Veeramani

Anonymous said...

I am getting the following error, can someone please point as to what problem the configuration may be?

javax.security.auth.login.LoginException: Error obtaining callback information.