Monday, 24 June 2013

Securing the ActiveMQ 5.8.0 web console using LDAP based authentication with Ldaptive

This is a follow-up of the great post by Torsten Mielke, updated for the 5.8.0 activemq release with the great ldaptive ldap java library.
First of all, drop the ldaptive.jar in the ${activemq-home}/lib directory.
Next you need to edit two files:
  • login.config
  • jetty.xml
In jetty.xml, the key task is to substitute to the default securityLoginService based on a org.eclipse.jetty.security.HashLoginService a section like:
<bean id="defaultIdentityService" class="org.eclipse.jetty.security.DefaultIdentityService" />
<bean id="securityLDAPLoginService" class="org.eclipse.jetty.plus.jaas.JAASLoginService">
<property name="name" value="ActiveMQLdapRealm" />
<property name="LoginModuleName" value="jetty-ldap" />
<property name="identityService" ref="defaultIdentityService" />
<property name="roleClassNames" value="org.ldaptive.jaas.LdapRole" />
</bean>
<bean id="securityConstraint" class="org.eclipse.jetty.util.security.Constraint">
<property name="name" value="BASIC" />
<property name="roles" value="admins" />
<property name="authenticate" value="true" />
</bean>
[...]
<bean id="securityHandler" class="org.eclipse.jetty.security.ConstraintSecurityHandler">
<property name="realmName" value="ActiveMQLdapRealm" />
<property name="loginService" ref="securityLDAPLoginService" />
<property name="authenticator">
<bean class="org.eclipse.jetty.security.authentication.BasicAuthenticator" />
</property>
[...]
</bean>
where jetty-ldap has to match the label in login.config, and roleClassNames match the class role exported by ldaptive.
The property 'roles' should include the group names from ldap which define if a member is allowed to access web console. In this example, a user should be a member of the admins group.
Please note identityService has to be present even if it is default.
The login.config is:
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
activemq-local {
org.apache.activemq.jaas.PropertiesLoginModule required
debug=true
org.apache.activemq.jaas.properties.user="org/apache/activemq/security/users.properties"
org.apache.activemq.jaas.properties.group="org/apache/activemq/security/groups.properties";
};
jetty-ldap {
org.ldaptive.jaas.LdapLoginModule required
debug=true
storePass="true"
ldapUrl="ldap://ldap1.test.com:389 ldap://ldap2.test.com:389"
connectionStrategy="ACTIVE_PASSIVE"
baseDn="ou=people,dc=test,dc=com"
useStartTLS="true"
credentialConfig="{trustCertificates=file:/etc/ssl/certs/ca-chain.pem}"
userFilter="(uid={user})";
org.ldaptive.jaas.LdapRoleAuthorizationModule required
useFirstPass="true"
ldapUrl="ldap://ldap1.test.com:389 ldap://ldap2.test.com:389"
connectionStrategy="ACTIVE_PASSIVE"
bindDn="cn=jetty-jaas,ou=agents,dc=test,dc=com"
baseDn="ou=groups,dc=test,dc=com"
bindCredential="secret"
roleFilter="(memberUid={user})"
useStartTLS="true"
credentialConfig="{trustCertificates=file:/etc/ssl/certs/ca-chain.pem}"
roleAttribute="cn";
};
view raw login.config hosted with ❤ by GitHub
ldap1 and ldap2 are contacted via start_tls, with a ACTIVE_PASSIVE strategy, the groups are in the ou=groups,dc=test,dc=com and a authentication principal is required to browse them. "storepass=true" in the LdapLoginModule is required in order to allow to the filter 'memberUid={user}' to resolve the {user} placeholder.

The hardest part of this setup is the role mapping from group membership. These errors show up with the following error in web console following a successful login:
Problem accessing /. Reason:      !role
In this example groups/role mappings are in web.xml. They could be done in login.config as well with the following lines:
            roleFilter="(&(cn=admin_group)(memberUid={user}))"
     defaultRole="admin" 
If a user matches the filter (she's member of the admin_group group) is added to the admin role which in turn should match the roles property in securityConstraints.

The following lines in your logback.xml can help you a lot to spot errors:
<logger name="org.ldaptive" additivity="false">
<level value="DEBUG"/>
<appender-ref ref="R" />
<appender-ref ref="stdout" />
</logger>

Link to the complete jetty.xml.
Ldaptive jaas guide.