5 Dec 2011

ActiveMQ: LDAP based authentication and authorization

The FuseSource ActiveMQ Security Guide has two great chapters on how to configure ActiveMQ for authentication and authorization against an LDAP server. Chapter 5 even has a tutorial that contains step-by-step instructions on how to configure your LDAP server and ActiveMQ based on ApacheDS, an open-source LDAP server. If you need to secure your ActiveMQ broker, then I highly recommend this documentation.

The tutorial in the Security Guide shows the relevant configuration needed so that every JMS connection into the broker is authenticated and checked for authorization against the security information stored in an LDAP server.

Sometimes you may want to allow anonymous access to certain destinations on your broker that are not critical while securing access to your critical destinations.  In this post I like to outline a possible solution for such use-case. I will assume the reader is generally familiar with LDAP based authentication and authorization in ActiveMQ. If not, I suggest to first consult the ActiveMQ Security Guide.


When configuring ActiveMQ for JAAS based authentication against an LDAP server, the file login.config could read as follows (pretty much a copy-and-paste from the FuseSource Security Guide):






/** JAAS LoginModule that uses LDAP based authentication.

Every connection into broker must supply username and password

for authentication to succeed. Anonymous access not allowed.

*/

LDAPLogin {

  org.apache.activemq.jaas.LDAPLoginModule required

  debug=true

  initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory

  connectionURL="ldap://localhost:10389"

  connectionUsername="uid=admin,ou=system"

  connectionPassword=secret

  connectionProtocol=""

  authentication=simple

  userBase="ou=User,ou=ActiveMQ,ou=system"

  userSearchMatching="(uid={0})"

  userSearchSubtree=false

  roleBase="ou=Group,ou=ActiveMQ,ou=system"

  roleName=cn

  roleSearchMatching="(member=uid={1})"

  roleSearchSubtree=false;

};

The broker then needs to use the JAAS authentication plug-in referencing the LDAPLogin authentication realm:




<plugins>

  <jaasAuthenticationPlugin configuration="LDAPLogin" />

</plugins>



Using such configuration, every JMS connection will be authenticated using the LDAPLoginModule, no matter if it provides a username and password. Needless to say the authentication will fail if no username/password is supplied. Anonymous access won't be allowed in this configuration.

So in order to also allow anonymous access to the broker we can leverage the ActiveMQ GuestLoginModule.
The GuestLoginModule allows JMS connections that are not configured for username/password to still access a secured broker. It is typically used in conjunction with other JAAS login modules and basically successfully authenticates a JMS connection using a configurable username and group name. Here is a sample configuration of this login module:


org.apache.activemq.jaas.GuestLoginModule sufficient

    debug=true

    org.apache.activemq.jaas.guest.user="guest"

    org.apache.activemq.jaas.guest.group="guests";

Authentication will succeed even if no username/password was supplied by the JMS client. Clients will be authenticated as user "guest" and belong to the group "guests". As you see, these names are configurable.

In JAAS configuration, multiple login modules can be combined in one JAAS authentication realm. They will then be tried in order.

The basic idea is to configure authentication so that the broker first invokes the GuestLoginModule, before trying the LDAPLoginModule. Here is the configuration:



/** JAAS LoginModule configuration that combines LDAPLoginModule with

  GuestLoginModule. 

  LoginModules can be combined in JAAS.

  See http://fusesource.com/docs/broker/5.5/security/Auth-JAAS-GuestLoginModule.html

  for more information.

*/

LDAPLogin-with-Anon-Access {

  org.apache.activemq.jaas.GuestLoginModule sufficient

    debug=true

    credentialsInvalidate=true

    org.apache.activemq.jaas.guest.user="guest"

    org.apache.activemq.jaas.guest.group="guests";



  org.apache.activemq.jaas.LDAPLoginModule requisite

    debug=true

    initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory

    connectionURL="ldap://localhost:10389"

    connectionUsername="uid=admin,ou=system"

    connectionPassword=secret

    connectionProtocol=""

    authentication=simple

    userBase="ou=User,ou=ActiveMQ,ou=system"

    userSearchMatching="(uid={0})"

    userSearchSubtree=false

    roleBase="ou=Group,ou=ActiveMQ,ou=system"

    roleName=cn

    roleSearchMatching="(member=uid={1})"

    roleSearchSubtree=false;

};



<plugins>

  <jaasAuthenticationPlugin configuration="LDAPLogin-with-Anon-Access" />

</plugins>



The GuestLoginModule config above uses an additional property credentialsInvalidate that when set to "true" will only authenticate requests that do not have a username/password supplied. Each  connection without username gets authenticated as user "guest" and belongs
to the group "guest".
Any other JMS connections that contain username and password will be authenticated using the next login module; the LDAP LoginModule. So its important to set credentialsInvalidate=true as otherwise the GuestLoginModule authenticates all requests no matter whether or what username/password is supplied.


Authenticating anonymous connections is only half the story. Without further authorization anonymous users would be allowed all operations on the broker, which is definitely not what we want.
With additional authorization anonymous access to the broker can be restricted to only specific destinations with restricted rights on each destination.
The FuseSource Security Guide also explains how to configure the LDAP Authorization plug-in and how to create groups and destinations in the LDAP server. A step-by-step guide for adding all required entries is given in chapter 5.
Based on the configuration proposed in that documentation it is additionally also necessary to 
  • define all those destinations in LDAP that anonymous access will be allowed on and
  • grant the required permissions to the group "guests" to these destinations.

If for example anonymous access should be allowed to queue "example.A", then the destination "example.A"  needs to be defined in LDAP. Further "read", "write" and potentially "admin" privileges need to be given to the group "guests" on this destination.
This procedure needs to be applied to all destinations that should allow anonymous access.



Finally, every new connection into the broker triggers some advisory messages. Without giving "guests" access to these advisory topics, the connection will fail, typically with an error similar to this:



java.lang.SecurityException: User null is not authorized to create: topic://ActiveMQ.Advisory.Connection


It is necessary to also grant "admin", "write" and potentially "read" rights for the group "guests" to ActiveMQ.Advisory topics.



This will allow for anonymous access on certain destinations while enforcing username/password based authentication on all other destinations in the broker.



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




3 comments:

vatsal said...

Hi,
I tried exact configurations but i am getting this error.

| WARN | /admin | org.eclipse.jetty.server.AbstractHttpConnection | qtp1354255499-37
java.lang.SecurityException: login.config (No such file or directory)
at com.sun.security.auth.login.ConfigFile.(ConfigFile.java:110)
at

Torsten Mielke said...

You should place the attached login.config into the etc/ folder of your ESB. IIRC it will then be picked up automatically.

vatsal said...

Thanks.
It worked perfectly.