Spring Security and LDAP
Subscribe

From OpenNMS

Jump to: navigation, search

Contents

Abstract

Centralized authentication is a "big deal" in any enterprise environment. Integrating your OpenNMS implementation with your environment is a Good Thing. Before linking the two, there are some considerations. The main one is if you want to rely solely on your central authenticator for all OpenNMS auth events, or support a hybrid scheme of the built-in local (aka UserDAO) as well as an LDAP or similar authenticator. Keep in mind that OpenNMS, itself, has processes periodically authenticating (think RTC and Provisioning) and for this reason it is better to go the loosely-coupled route by continuing to support the UserDAO mechanism, as described in the latter implementation section below.

Another source of minutiae lies in the specific LDAP implementation you're working with. While AD and OpenLDAP (and Sun DS, etc.) implement the same protocol, their schemas are substantially different. The biggest difference (as highlighted by LDAP search filters referenced below) is the memberOf attribute, which is not supported by default LDAP v3 spec (and is not present in common OpenLDAP installs), but comes standard with AD. This has a significant implication in that you cannot filter a user search by supplementary group membership, and most installs tend not to be fully hierarchical (User's DN doesn't cite a home group). Fortunately, you can either create dedicated per-role LDAP groups or map existing groups to roles by selecting and configuring the appropriate AuthoritiesPopulator. A 'role' is how OpenNMS refers to an access level, btw.

Sample Configuration "LDAP only"

OpenNMS Version

OpenNMS version 1.7.5 and later

Spring Security Version

Spring Security 2.04

Spring Security

Version 1.7.5 of openNMS never included the spring security framework so you will need to download it from the following URL spring-security-2.0.4.zip.

Notes about provided configs

I developed these configurations for my company but have tried to sanitize them for public consumption there are fields of note that will be required to be changed in addition to the following you may enable TLS/SSL for your LDAP server if you install root ca's which is beyond the scope of this document.

You will need to configure the following values.

LDAP host and its port : ldap://foo.com:389 LDAP search base : ou=mydepartment,o=mycompany,dc=com

      • Note this account only needs search privileges and should be acl limited

User that will do binds to perform lookups : uid=myUser,ou=Users,ou=mydepartment,o=mycompany,dc=ca Password for the above user : myPassword

The group section for the bind is based off the ldif I have provided, customize as you see fit.


Configuring applicationContext-spring-security.xml

Spring security is configured in $OPENNMS_HOME/jetty-webapps/opennms/WEB-INF/applicationContext-spring-security.xml. You have to add the following definitions there.

  <beans:bean id="upperCaseMd5PasswordEncoder" class="org.opennms.web.springframework.security.UpperCaseMd5PasswordEncoder"/>

<!-- LDAP Start -->
  <beans:bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">

    <!-- Enter your LDAP server address, dc, o and ou  here -->
    <beans:constructor-arg value="ldap://foo.com:389/ou=mydepartment,o=mycompany,dc=com" />

    <!-- Enter User parameters for accessing your LDAP server here -->
    <beans:property name="userDn" value="uid=myUser,ou=Users,ou=mydepartment,o=mycompany,dc=ca"/>
    <beans:property name="password" value="myPassword"/>

  </beans:bean>
    <beans:bean id="ldapAuthProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
      <custom-authentication-provider />
        <beans:constructor-arg>
           <beans:bean class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
             <beans:constructor-arg ref="contextSource"/>
             <beans:property name="userSearch" ref="userSearch">
             </beans:property>
           </beans:bean>
        </beans:constructor-arg>
        <beans:constructor-arg>
           <beans:bean class="org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator">
             <beans:constructor-arg ref="contextSource"/>
             <beans:constructor-arg value="ou=ONMS,ou=Groups"/>
             <beans:property name="groupRoleAttribute" value="cn"/>
             <beans:property name="searchSubtree" value="false" />
             <beans:property name="convertToUpperCase" value="true" />
             <beans:property name="rolePrefix" value="ROLE_" />
             <beans:property name="groupSearchFilter" value="memberUid={1}" />
           </beans:bean>
        </beans:constructor-arg>
  </beans:bean>

  <beans:bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
    <beans:constructor-arg index="0" value="ou=Users" />
    <beans:constructor-arg index="1" value="(uid={0})" />
    <beans:constructor-arg index="2" ref="contextSource" />
    <beans:property name="searchSubtree" value="true" />
  </beans:bean>


<!-- LDAP End -->

  <!-- ====================== RADIUS AUTHENTICATION ===================== -->

Modify the magic-users.properties

###########################################################################
## U S E R S
###########################################################################

# A comma-separated list of user keys.  A user.{KEY}.username and
# user.{KEY}.password property must be set for each key in this property.
users=rtc

# The RTC View Control Manager daemon uses this user to authenticate itself
# while sending RTC data posts.
user.rtc.username=rtc
user.rtc.password=rtc

###########################################################################
## R O L E S
###########################################################################

# A comma-separated list of role keys.  A role.{KEY}.name and
# role.{KEY}.users property must be set for each key in this property.
#roles=rtc, admin, rouser, dashboard, provision
roles=rtc

# This role allows a user to make RTC data posts.
role.rtc.name=OpenNMS RTC Daemon
role.rtc.users=rtc
role.rtc.notInDefaultGroup=true

# This role allows users access to configuration and
# administrative web pages.
#role.admin.name=OpenNMS Administrator
#role.admin.users=admin

# This role disallows user write access
#role.rouser.name=OpenNMS Read-Only User
#role.rouser.users=

# This role allows access to the dashboard only
#role.dashboard.name=OpenNMS Dashboard User
#role.dashboard.users=
#role.dashboard.notInDefaultGroup=true

# This role was added for WIND
#role.provision.name=OpenNMS Provision User
#role.provision.users=

Sample LDIF For group structure

dn: ou=ONMS, ou=Groups,ou=mydepartment,o=mycompany,dc=com
ou: ONMS
description: Open NMS groups
objectClass: top
objectClass: organizationalUnit

dn: cn=ADMIN, ou=ONMS, ou=Groups,ou=mydepartment,o=mycompany,dc=com
gidNumber: 7010
userPassword:: e2NyeXB0fXg=
memberUid: myAdminUser
objectClass: posixGroup
objectClass: top
cn: ADMIN

dn: cn=ANONYMOUS, ou=ONMS, ou=Groups,ou=mydepartment,o=mycompany,dc=com
gidNumber: 7011
userPassword:: e2NyeXB0fXg=
memberUid: myAnonymousUser
objectClass: posixGroup
objectClass: top
cn: ANONYMOUS

dn: cn=USER, ou=ONMS, ou=Groups,ou=mydepartment,o=mycompany,dc=com
gidNumber: 7012
userPassword:: e2NyeXB0fXg=
memberUid: myRegUser
objectClass: posixGroup
objectClass: top
cn: USER

dn: cn=DASHBOARD, ou=ONMS, ou=Groups,ou=mydepartment,o=mycompany,dc=com
gidNumber: 7013
userPassword:: e2NyeXB0fXg=
memberUid: myDashboardUser
objectClass: posixGroup
objectClass: top
cn: DASHBOARD

dn: cn=PROVISION, ou=ONMS, ou=Groups,ou=mydepartment,o=mycompany,dc=com
gidNumber: 7014
userPassword:: e2NyeXB0fXg=
memberUid: myProvisionUser
objectClass: posixGroup
objectClass: top
cn: PROVISION

dn: cn=RTC, ou=ONMS, ou=Groups,ou=mydepartment,o=mycompany,dc=com
gidNumber: 7015
userPassword:: e2NyeXB0fXg=
memberUid: myRTCUser
objectClass: posixGroup
objectClass: top
cn: RTC

dn: cn=REMOTING, ou=ONMS, ou=Groups,ou=mydepartment,o=mycompany,dc=com
gidNumber: 7015
userPassword:: e2NyeXB0fXg=
memberUid: myRemotePollerUser
objectClass: posixGroup
objectClass: top
cn: REMOTING

Sample Configuration "Local Authentication and LDAP"

OpenNMS Version

OpenNMS version 1.7.8 and later

Spring Security Version

Spring Security 2.04 as integrated in OpenNMS 1.7.8

Design

  • OpenNMS Administration is done for some (internal or external) customer
  • OpenNMS Admin Responsibility should never be gained by the customer
  • LDAP administration is done by the customer (internal or external)
  • OpenNMS internal users shouldn't be authenticated by LDAP
    • to prevent OpenNMS outages when someone by error changes OpenNMS LDAP users
    • for having availability of OpenNMS even when LDAP is down/unreachable

Technics

  • OpenNMS-provided authentication is used for Admin and internal users
  • LDAP is provided from Active Directory (AD)
    • only LDAP users from a specific department (OU=...) are allowed
    • only users belonging to one of the groups g_operating or g_helpdesk are allowed
    • saMAccountName is used to identify the UserID within LDAP/AD

Solution

  • use DAO authentication (OpenNMS local authentication) for Admin Users
  • use DAO authentication for opennms internal users like RTC, MAP etc.
  • use LDAP authentication for all (internal or external) customers
  • assign ROLE_USER to all LDAP authenticated users

Values from AD

Values from LDAP/AD (some lines from LDIF of a sample-user):

dn: CN=SampleUserId,OU=SomeDept,OU=SomeOrgUnit,dc=SomeCompany,dc=SomeCountry
...
memberOf: CN=g_operating,OU=SomeDept,OU=SomeOrgUnit,dc=SomeCompany,dc=SomeCountry
...
sAMAccountName: SampleUserID
...

Spring Security Configuration

Spring security is configured in $OPENNMS_HOME/jetty-webapps/opennms/WEB-INF/applicationContext-spring-security.xml. You have to add the following definitions there. You don't have to alter other configuration files for this LDAP integration. Admin-Users are configured in OpenNMS as described in Docu-overview#Users.

Just add the following lines below the Dao Authentication in applicationContext-spring-security.xml and change the fields like IP-address / Root-DN / Userid / Password for the LDAP-server, UserSearch path and groups the users must belong to.

Take a look at the filters for the groups - they contain the complete path, obviously in this notation neither the root-DN nor the SearchBase part of the path is added to the filter! (Took me some time to figure this out...).


LDAP Authentication Configuration

Below is the configuration for doing the actual authentication. You will also need to include one of the two AuthoritiesPopulators.

  • Detailed AD-specific Config (userSearch with group membership checks, etc.)
  <!-- ======================= LDAP AUTHENTICATION ====================== -->

  <!-- you need the following line only if you commented out
       User Dao Authentication above where it is already defined
  -->
  <!--
  <beans:bean id="upperCaseMd5PasswordEncoder" class="org.opennms.web.springframework.security.UpperCaseMd5PasswordEncoder"/>
  -->
 
 
  <!-- this defines your ldap server address, userid, password etc. -->
 
  <beans:bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
    <beans:constructor-arg value="ldap://11.12.13.14:389/dc=SomeCompany,dc=SomeCountry" />
    <beans:property name="userDn" value="userIDforLDAP-Queries"/>
    <beans:property name="password" value="password"/>
  </beans:bean>
 
  <!-- this defines LDAP as an authentication provider -->
 
  <beans:bean id="ldapAuthProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
    <custom-authentication-provider />      <!-- this adds the ldap authentication method to the ProviderManager -->
    <beans:constructor-arg ref="ldapAuthenticator"/>
    <beans:constructor-arg ref="ldapAuthoritiesPopulator"/>
  </beans:bean>
 
  <!-- authenticate the user with ldapAuthenticator using userSearch -->
 
  <beans:bean id="ldapAuthenticator" class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
    <beans:constructor-arg ref="contextSource"/>
    <beans:property name="userSearch" ref="userSearch">
    </beans:property>
  </beans:bean>
 
    <!-- userSearch (alt.: userDnPatterns) -->
 
    <beans:bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
      <beans:constructor-arg index="0" value="OU=SomeDept,OU=SomeOrgUnit" />
 
      <!-- allow only users belonging to specific groups to get authenticated -->
      <beans:constructor-arg index="1" value="(&amp;(sAMAccountName={0})
                 (|(memberOf=cn=g_operating,OU=SomeDept,OU=SomeOrgUnit,dc=SomeCompany,dc=SomeCountry)
                 (memberOf=cn=g_helpdesk, OU=SomeDept,OU=SomeOrgUnit,dc=SomeCompany,dc=SomeCountry)))" />
      <beans:constructor-arg index="2" ref="contextSource" />
      <beans:property name="searchSubtree" value="true" />
    </beans:bean>
  • Basic OpenLDAP-friendly userSearch
  <beans:bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
    <beans:constructor-arg index="0" value="ou=People" />
    <beans:constructor-arg index="1" value="(uid={0})" />
    <beans:constructor-arg index="2" ref="contextSource" />
    <beans:property name="searchSubtree" value="true" />
  </beans:bean>

LDAP Authorization

There are two AuthoritiesPopulator's, these use information gathered from LDAP to provide the authorization within OpenNMS.

org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator

Use this one when you are going to create role-specific LDAP groups (per the group structure LDIF sample above) and assign users to them for their corresponding OpenNMS access.

 <beans:bean id="ldapAuthoritiesPopulator" class="org.springframework.security.ldap.populator.DefaultLdapAuthoritiesPopulator">
   <beans:constructor-arg ref="contextSource"/>

   <beans:constructor-arg value="OU=SomeDept,OU=SomeOrgUnit"/>

   <beans:property name="groupRoleAttribute" value=""/>
   <beans:property name="rolePrefix" value="" />

   <beans:property name="defaultRole" value="ROLE_USER" />

 </beans:bean>

org.opennms.web.springframework.security.UserGroupLdapAuthoritiesPopulator

Use this one when you want to assign OpenNMS roles to an existing LDAP group structure. The value in the bean key is the name (cn) of the LDAP group you want to map to one or more roles (the bean values list).

  • AD Variant
          <beans:bean id="UserGroupLdapAuthoritiesPopulator" class="org.opennms.web.springframework.security.UserGroupLdapAuthoritiesPopulator">
            <beans:constructor-arg ref="contextSource"/>
            <beans:constructor-arg value="OU=SomeDept,OU=SomeOrgUnit"/>
            <beans:property name="searchSubtree" value="true" />
            <beans:property name="groupRoleAttribute" value="cn" />
            <beans:property name="groupSearchFilter" value="member={0}" />
            <beans:property name="groupToRoleMap">
              <beans:map>
                <beans:entry>
                  <beans:key><beans:value>myusersgroup</beans:value></beans:key>
                  <beans:list>
                    <beans:value>ROLE_USER</beans:value>
                  </beans:list>
                </beans:entry>
                <beans:entry>
                  <beans:key><beans:value>myadminsgroup</beans:value></beans:key>
                  <beans:list>
                    <beans:value>ROLE_ADMIN</beans:value>
                    <beans:value>ROLE_USER</beans:value>
                  </beans:list>
                </beans:entry>
              </beans:map>
            </beans:property>
          </beans:bean>
  • OpenLDAP variant
         <beans:bean id="UserGroupLdapAuthoritiesPopulator" class="org.opennms.web.springframework.security.UserGroupLdapAuthoritiesPopulator"> 
           <beans:constructor-arg ref="contextSource"/> 
           <beans:constructor-arg value="ou=Group"/> 
           <beans:property name="searchSubtree" value="false" /> 
           <beans:property name="groupRoleAttribute" value="cn" /> 
           <beans:property name="groupSearchFilter" value="memberUid={1}" /> 
           <beans:property name="groupToRoleMap"> 
             <beans:map> 
               <beans:entry> 
                 <beans:key><beans:value>myusersgroup</beans:value></beans:key> 
                 <beans:list> 
                   <beans:value>ROLE_USER</beans:value> 
                 </beans:list> 
               </beans:entry> 
               <beans:entry> 
                 <beans:key><beans:value>myadminsgroup</beans:value></beans:key> 
                 <beans:list> 
                   <beans:value>ROLE_ADMIN</beans:value> 
                   <beans:value>ROLE_USER</beans:value> 
                 </beans:list> 
               </beans:entry> 
             </beans:map> 
           </beans:property> 
         </beans:bean>

Another Example

  <beans:bean id="upperCaseMd5PasswordEncoder" class="org.opennms.web.springframework.security.UpperCaseMd5PasswordEncoder"/>

<!-- LDAP Start -->
  <beans:bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">

    <!-- Enter your LDAP server address, dc, o and ou  here -->
    <beans:constructor-arg value="ldaps://ldap.example.com:636/dc=example,dc=com" />

    <!-- Enter User parameters for accessing your LDAP server here 
    <beans:property name="userDn" value="uid=myUser,ou=Users,ou=mydepartment,o=mycompany,dc=ca"/>
    <beans:property name="password" value="myPassword"/>
    -->

  </beans:bean>
  <beans:bean id="ldapAuthProvider" class="org.springframework.security.providers.ldap.LdapAuthenticationProvider">
    <custom-authentication-provider />      <!-- this adds the ldap authentication method to the ProviderManager -->
    <beans:constructor-arg ref="ldapAuthenticator"/>
    <beans:constructor-arg ref="UserGroupLdapAuthoritiesPopulator"/>
  </beans:bean>
 
  <!-- authenticate the user with ldapAuthenticator using userSearch -->
 
  <beans:bean id="ldapAuthenticator" class="org.springframework.security.providers.ldap.authenticator.BindAuthenticator">
    <beans:constructor-arg ref="contextSource"/>
    <beans:property name="userSearch" ref="userSearch">
    </beans:property>
  </beans:bean>

  <beans:bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
    <beans:constructor-arg index="0" value="ou=people" />
    <beans:constructor-arg index="1" value="(uid={0})" />
    <beans:constructor-arg index="2" ref="contextSource" />
    <beans:property name="searchSubtree" value="true" />
  </beans:bean>

         <beans:bean id="UserGroupLdapAuthoritiesPopulator" class="org.opennms.web.springframework.security.UserGroupLdapAuthoritiesPopulator"> 
           <beans:constructor-arg ref="contextSource"/> 
           <beans:constructor-arg value="ou=group"/> 
           <beans:property name="searchSubtree" value="true" /> 
           <beans:property name="groupRoleAttribute" value="cn" /> 
           <beans:property name="groupSearchFilter" value="memberUid={1}" /> 
           <beans:property name="groupToRoleMap"> 
             <beans:map> 
               <beans:entry> 
                 <beans:key><beans:value>opennms</beans:value></beans:key> 
                 <beans:list> 
                   <beans:value>ROLE_USER</beans:value> 
                 </beans:list> 
               </beans:entry> 
               <beans:entry> 
                 <beans:key><beans:value>opennms_admin</beans:value></beans:key> 
                 <beans:list> 
                   <beans:value>ROLE_ADMIN</beans:value> 
                   <beans:value>ROLE_USER</beans:value> 
                 </beans:list> 
               </beans:entry> 
             </beans:map> 
           </beans:property> 
         </beans:bean>


<!-- LDAP End -->

  <!-- ====================== RADIUS AUTHENTICATION ===================== -->

Troubleshooting

Enable DEBUG in log4j.properties for

log4j.category.org.springframework.security=DEBUG, MISC
log4j.category.OpenNMS.Spring=DEBUG, SPRING
log4j.category.org.springframework=DEBUG, SPRING

Don't forget to disable DEBUG after you finished your tests!

Search for LDAP server IP, userid's / groups or IP address of authenticating clients in spring.log and misc.log.

Look out for Granted Authorities: ROLE_USER. If there are other roles too there is a possibility to gain admin rights for your OpenNMS system by providing the roles in LDAP / AD.

You might try to add the following bean definitions to your applicationContext-springSecurity.xml file.

This bean provides authentication information:

<beans:bean id="authenticationLoggerListener" class="org.springframework.security.authentication.event.LoggerListener" />

This bean provides authorisation information (if you need it):

<beans:bean id="authorisationLoggerListener" class="org.springframework.security.access.event.LoggerListener" />

For authorisation bean, failures are logged at 'WARN' level, success events are logged at 'INFO' level. So if you need success authorisation events, change the logging level to 'INFO'.

List of Roles

ROLE DESCRIPTION
ROLE_USER OpenNMS User <== Must be included in with each group.
ROLE_ADMIN OpenNMS Administrator
ROLE_READONLY OpenNMS Read-Only User
ROLE_DASHBOARD OpenNMS Dashboard User
ROLE_RTC OpenNMS RTC Daemon
ROLE_PROVISION OpenNMS Provision User
ROLE_REMOTING OpenNMS Remote Poller User