How to use FreeIPA 4.X as a back-end authentication server to OpenNMS


#1

The OpenNMS service by default needs the definition of users and privileges to be performed on its local-database. Even though this is a pretty straight-forward approach it is a bit cumbersome when there is a need to incorporate a large number of users and enforce a password management policy.

One way someone can alleviate all this effort is by integrating the OpenNMS authentication process to a FreeIPA IDM solution.

The installation and deployment of the FreeIPA can be easily performed through the project documentation guides found on https://www.freeipa.org/page/Quick_Start_Guide .

The only requirements for the FreeIPA is the creation of the user-groups as to directly assign them to the respective OpenNMS user privileges and a user assigned to OpenNMS with bind privileges.

As a minimum the following mapping can be performed:
Simple user access to OpenNMS --> opennms_users user-group on FreeIPA
Admin user access to OpenNMS --> opennms_admin user-group on FreeIPA

Population of each FreeIPA user-group can be performed through any desired manner.

The FreeIPA bind user can be created through the following process:

  1. SSH to the FreeIPA server
  2. Gain root privileges
  3. Get a kerberos ticket as FreeIPA admin user:
kinit admin
  1. Enter the admin password
  2. Login to the FreeIPA LDAP back-end as “Directory Manager”
ldapmodify -x -D 'cn=Directory Manager' -W
  1. Authenticate as to gain access
  2. Create the OpenNMS bind user.
dn: uid={OPENNMS_BIND_USER},cn=sysaccounts,cn=etc,dc=seceng,dc=pccwglobal,dc=com
changetype: add
objectclass: account
objectclass: simplesecurityobject
uid: {OPENNMS_BIND_USER}
userPassword: {OPENNMS_BIND_USER_PASSWORD}
passwordExpirationTime: 20380119031407Z
nsIdleTimeout: 0
<blank line>
^D

From the OpenNMS side the following changes need to be performed:

  1. Notify the authentication manager to use an external authentication provider.
    This is performed by editing the file {OPENNMS}/jetty-webapps/opennms/WEB-INF/applicationContext-spring-security.xml file line 213.
    INITIAL abstract
  <!-- use our custom authentication provider; to use RADIUS instead, change this to "radiusAuthenticationProvider" and uncomment below -->
  <authentication-manager alias="authenticationManager">
    <!-- If a user is pre-authenticated, make sure their user details are populated correctly. -->
    <authentication-provider ref="preauthAuthProvider" />
    <!-- Use our custom authentication provider -->
    <authentication-provider ref="hybridAuthenticationProvider" />
    <!-- To enable external (e.g. LDAP, RADIUS) authentication, uncomment the following.
         You must also rename and customize exactly ONE of the example files in the
         spring-security.d subdirectory. -->
    <!-- <authentication-provider ref="externalAuthenticationProvider" /> -->
  </authentication-manager>

EDITED abstract

  <!-- use our custom authentication provider; to use RADIUS instead, change this to "radiusAuthenticationProvider" and uncomment below -->
  <authentication-manager alias="authenticationManager">
    <!-- If a user is pre-authenticated, make sure their user details are populated correctly. -->
    <authentication-provider ref="preauthAuthProvider" />
    <!-- Use our custom authentication provider -->
    <authentication-provider ref="hybridAuthenticationProvider" />
    <!-- To enable external (e.g. LDAP, RADIUS) authentication, uncomment the following.
         You must also rename and customize exactly ONE of the example files in the
         spring-security.d subdirectory. -->
    <authentication-provider ref="externalAuthenticationProvider" /> 
  </authentication-manager>

Once this file has been edited change directory to {OPENNMS}/jetty-webapps/opennms/WEB-INF/spring-security.d/
Within this folder the only files that must have an XML ending must be:
a. header-preauth.xml
b. attribute-preauth.xml

Now is the time to create the FreeIPA LDAP XML file.
Create a new file and name it something that you can remember e.g. ldap_freeipa.xml
The only requirement is that it has the XML ending.

Populate the XML file with the following content:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
              http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd"> 
  
  <beans:bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
    <beans:constructor-arg ref="contextSource"/>
    <beans:property name="ignorePartialResultException" value="true"/>
  </beans:bean>
  <beans:bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
    <beans:property name="urls">
      <beans:list>
        <!-- List one or more of your enterprise's LDAP servers here -->
        <beans:value>ldap://{FREEIPA1_FULLY_QUALIFIED_DOMAIN_NAME}:389/</beans:value>
        <beans:value>ldap://{FREEIPA2_FULLY_QUALIFIED_DOMAIN_NAME}:389/</beans:value>
      </beans:list>
    </beans:property>
    <!-- An optional base DN. Every user and group below is relative to this. -->
    <beans:property name="base" value="dc=example,dc=com" />
    <beans:property name="authenticationSource" ref="authenticationSource" />
  </beans:bean>
  <beans:bean id="authenticationSource" class="org.springframework.ldap.authentication.DefaultValuesAuthenticationSourceDecorator">
    <beans:property name="target" ref="springSecurityAuthenticationSource"/>
    <!-- Specify the DN of an unprivileged user for initial binding to the directory -->
    <beans:property name="defaultUser" value="uid={OPENNMS_BIND_USER},cn=sysaccounts,cn=etc,dc=example,dc=com"/>
    <!-- Specify the unprivileged bind user's password here -->
    <beans:property name="defaultPassword" value="{OPENNMS_BIND_USER_PASSWORD}"/>
  </beans:bean>

  <beans:bean id="springSecurityAuthenticationSource" class="org.springframework.security.ldap.authentication.SpringSecurityAuthenticationSource">
  </beans:bean>

  <beans:bean id="externalAuthenticationProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
    <beans:constructor-arg ref="ldapAuthenticator"/>
    <beans:constructor-arg ref="userGroupLdapAuthoritiesPopulator"/>
  </beans:bean>

  <beans:bean id="ldapAuthenticator" class="org.springframework.security.ldap.authentication.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="cn=users,cn=accounts" />

    <!-- More complex filters are possible depending on the layout of your directory -->
    <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"/>
    <!-- Common LDAP container for the user and admin groups listed below -->
    <beans:constructor-arg value="cn=groups,cn=accounts" />
    <beans:property name="searchSubtree" value="true" />
    <beans:property name="convertToUpperCase" value="true" />
    <beans:property name="groupRoleAttribute" value="cn" />
    <beans:property name="groupSearchFilter" value="member={0}" />
    <beans:property name="groupToRoleMap">
      <beans:map>
        <!-- If the is an empty string, the roles are applied to all users -->
        <!--
        <beans:entry>
          <beans:key><beans:value></beans:value></beans:key>
          <beans:list>
            <beans:value>ROLE_USER</beans:value>
          </beans:list>
        </beans:entry>
        -->
        <beans:entry>
          <!-- Name of the LDAP group for normal (non-admin) OpenNMS users -->
          <beans:key><beans:value>opennms_users</beans:value></beans:key>
          <beans:list>
            <beans:value>ROLE_USER</beans:value>
          </beans:list>
        </beans:entry>
        <beans:entry>
          <!-- Name of the LDAP group for OpenNMS administrators -->
          <beans:key><beans:value>opennms_admins</beans:value></beans:key>
          <beans:list>
            <beans:value>ROLE_USER</beans:value>
            <beans:value>ROLE_ADMIN</beans:value>
          </beans:list>
        </beans:entry>
      </beans:map>
    </beans:property>
  </beans:bean>

</beans:beans>

Simply change
"dc=example,dc=com" to your FQDN and the ldap://{FREEIPA1_FULLY_QUALIFIED_DOMAIN_NAME}:389 to the respective FQDN of your FreeIPA enviroment.
uid={OPENNMS_BIND_USER},cn=sysaccounts,cn=etc,dc=example,dc=com to the FreeIPA bind username.
value={OPENNMS_BIND_USER_PASSWORD} to the FreeIPA bind password.

Any customization regarding the mapping of the OpenNMS privileges and the FreeIPA user-groups can be manipulated by changing the

<beans:key><beans:value>opennms_users</beans:value></beans:key>
          <beans:list>
            <beans:value>ROLE_USER</beans:value>
          </beans:list>

to the desired state.

Finally all errors can be viewed by checking the OpenNMS web.log
tail -n 100 {OPENNMS}/logs/web.log or
more /{OPENNMS}/logs/web.log | grep ldap

In most cases some typos can be immediatelly identified from this log.


Problem With FreeIPA LDAP Authentication