Skip to content

[Proposal] Remove LDAP profile ‐ Support integation via setting system property

Lorenzo Natali edited this page Oct 16, 2023 · 1 revision

Overview

The goal of this improvement is to allow to configure MapStore (project or product) directy to integrate ldap/ldap-direct/keycloak without requiring a build profile.

Proposed By

  • Lorenzo Natali

Assigned to Release

TBD

State

  • TBD
  • Under Discussion
  • In Progress
  • Completed
  • Rejected
  • Deferred

Motivation

The maintainance of a build profile

Tecnical Background

Actually the build profile is necessary because spring-security-context.xml have to be configured differently depending on the desired configuration.

A build profile is:

  • hard to maintain
  • need to be duplicated in mapstore and projects
  • need a rebuild of MapStore if we want to use mapstore with ldap, ldap-direct or keycloak-direct

This only to supply some changes to one single file.

The differences between the files are:

LDAP integration

  • Add this configuration to security:authentication-manager
<security:authentication-manager>
        <security:authentication-provider ref='geoStoreUserServiceAuthenticationProvider' />
+        <security:authentication-provider ref='geostoreLdapProvider' />
    </security:authentication-manager>
  • Add this configuration at the end of the spring-security-context.
 <!-- LDAP Provider configuration -->
	<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
		<constructor-arg value="ldap://${ldap.host}:${ldap.port}/${ldap.root}" />
        <property name="userDn" value="${ldap.userDn}"/>
        <property name="password" value="${ldap.password}"/>
	</bean>

    <bean id="ldapInitializer" class="it.geosolutions.geostore.init.LDAPInit" lazy-init="false">
       <constructor-arg ref="geostoreLdapProvider" />
    </bean>

	<bean id="geostoreLdapProvider"
		class="it.geosolutions.geostore.services.rest.security.UserLdapAuthenticationProvider">
		<constructor-arg>
			<bean
				class="org.springframework.security.ldap.authentication.BindAuthenticator">
				<constructor-arg ref="contextSource" />
				<property name="userSearch">
					<bean id="userSearch"
						class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
						<constructor-arg index="0" value="${ldap.userBase}" />
						<constructor-arg index="1" value="${ldap.userFilter}" />
						<constructor-arg index="2" ref="contextSource" />
					</bean>
				</property>
			</bean>
		</constructor-arg>
		<constructor-arg>
			<bean
				class="it.geosolutions.geostore.services.rest.security.GeoStoreLdapAuthoritiesPopulator">
				<constructor-arg ref="contextSource" />
				<!-- groupSearchBase -->
				<constructor-arg value="${ldap.groupBase}" />
				<!-- roleSearchBase -->
				<constructor-arg value="${ldap.roleBase}" />
				<property name="groupSearchFilter" value="${ldap.groupFilter}" />
				<property name="roleSearchFilter" value="${ldap.roleFilter}" />

				<!-- nested groups support -->
				<property name="enableHierarchicalGroups" value="${ldap.hierachicalGroups}" />
				<property name="groupInGroupSearchFilter" value="${ldap.nestedGroupFilter}" />
				<property name="maxLevelGroupsSearch" value="${ldap.nestedGroupLevels}" />
				<!-- the GeoStore convention is:
				  * Groups starting with 'ROLE_' will be threated as Auth Roles
				  * Groups starting withOUT 'ROLE_' will be threated as Groups
				 -->
				<property name="rolePrefix" value="ROLE_" />
				<property name="searchSubtree" value="${ldap.searchSubtree}" />
				<property name="convertToUpperCase" value="${ldap.convertToUpperCase}" />
			</bean>
		</constructor-arg>
	</bean>

LDAP direct integration

Add this set of alias/beans to the configuration done for ldap.

    <!-- enable direct connection mode
    <bean id="ldapUserDAO" class="it.geosolutions.geostore.core.dao.ldap.impl.UserDAOImpl">
        <constructor-arg ref="contextSource"/>
        <property name="searchBase" value="${ldap.userBase}"/>
        <property name="memberPattern" value="${ldap.memberPattern}"/>
        <property name="attributesMapper">
            <map>
                <entry key="mail" value="email"/>
                <entry key="givenName" value="fullname"/>
                <entry key="description" value="description"/>
            </map>
        </property>
    </bean>
    <bean id="ldapUserGroupDAO" class="it.geosolutions.geostore.core.dao.ldap.impl.UserGroupDAOImpl">
        <constructor-arg ref="contextSource"/>
        <property name="searchBase" value="${ldap.groupBase}"/>
        <property name="addEveryOneGroup" value="true"/>
    </bean>


    <alias name="ldapUserGroupDAO" alias="userGroupDAO"/>
    <alias name="externalSecurityDAO" alias="SecurityDAO"/>
    <alias name="ldapUserDAO" alias="userDAO"/>
    -->

### keycloak-direct-integration

Need simply to add this set of alias and beans at the end of spring security context.

```xml

    <bean id="keycloakUserDAO" class="it.geosolutions.geostore.services.rest.security.keycloak.KeycloakUserDAO">
    <constructor-arg ref="keycloakRESTClient"/>
    </bean>
    <alias name="keycloakUserDAO" alias="userDAO"/>
    <bean id="keycloakUserGroupDAO" class="it.geosolutions.geostore.services.rest.security.keycloak.KeycloakUserGroupDAO">
        <constructor-arg ref="keycloakRESTClient"/>
        <property name="addEveryOneGroup" value="true"/>
    </bean>
    <alias name="keycloakUserGroupDAO" alias="userGroupDAO" />
    <alias name="externalSecurityDAO" alias="securityDAO"/>

Proposal

With these changes we want to allow to activate the integrations using a system property security.integration=<name> to activate keycloak-direct, ldap and ldap-direct. All the previous installation will remain backward compatity.

note: it must be a system property or a java property (-Dsecurity.integration=ldap) and it can not be set in the data directory, for the limits of this solution. See Limitations section to a possible suggested solutions for this.

All the integration consists in :

  1. Only for LDAP configurations, to add geostoreLdapProvider to security:authentication-manager.
  2. adding something to the spring security context.

The main idea is to use a property security.integration=ldap|ldap-direct|keycloak-direct|default and use this value to :

  • activate the security:authentication-manager ldap entry (by wrapping the ldap tool and the default and overriding the method supports)
  • Include some files in the spring context based on the property security.integration

1. security:authentication-manager entry for geostoreLdapProvider

To solve this issue we can create a wrapper for geostoreLdapProvider that simply activate only if geostoreLdapProvider is present ( using the method supports see here. The wrapper also provides backward compatibility for one who uses the old approach of custom security context.

2. Add content to spring-security

  • add an import entry to the end of the file like this, in project and product.
<import resource="classpath*:security-integtration-${security.integration:default}.xml"/>
  • Prepare several files in geostore (so they will be available for mapstore projects and product):
  • security-integtration-default.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:cxf="http://cxf.apache.org/core"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xmlns:jaxrs="http://cxf.apache.org/jaxrs" xsi:schemaLocation="
            http://www.springframework.org/schema/beans     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://cxf.apache.org/jaxws                     http://cxf.apache.org/schemas/jaxws.xsd
            http://cxf.apache.org/jaxrs                     http://cxf.apache.org/schemas/jaxrs.xsd
            http://cxf.apache.org/core                      http://cxf.apache.org/schemas/core.xsd
            http://www.springframework.org/schema/security  http://www.springframework.org/schema/security/spring-security.xsd
            http://www.springframework.org/schema/context   http://www.springframework.org/schema/context/spring-context-3.0.xsd" default-autowire="byName">


    <!-- default security integration file, empty -->
</beans>

-security-integtration-keycloak-direct.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:security="http://www.springframework.org/schema/security"
    xmlns:cxf="http://cxf.apache.org/core"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xmlns:jaxrs="http://cxf.apache.org/jaxrs" xsi:schemaLocation="
            http://www.springframework.org/schema/beans     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://cxf.apache.org/jaxws                     http://cxf.apache.org/schemas/jaxws.xsd
            http://cxf.apache.org/jaxrs                     http://cxf.apache.org/schemas/jaxrs.xsd
            http://cxf.apache.org/core                      http://cxf.apache.org/schemas/core.xsd
            http://www.springframework.org/schema/security  http://www.springframework.org/schema/security/spring-security.xsd
            http://www.springframework.org/schema/context   http://www.springframework.org/schema/context/spring-context-3.0.xsd" default-autowire="byName">

    <bean id="keycloakUserDAO" class="it.geosolutions.geostore.services.rest.security.keycloak.KeycloakUserDAO">
    <constructor-arg ref="keycloakRESTClient"/>
    </bean>
    <alias name="keycloakUserDAO" alias="userDAO"/>
    <bean id="keycloakUserGroupDAO" class="it.geosolutions.geostore.services.rest.security.keycloak.KeycloakUserGroupDAO">
        <constructor-arg ref="keycloakRESTClient"/>
        <property name="addEveryOneGroup" value="true"/>
    </bean>
    <alias name="keycloakUserGroupDAO" alias="userGroupDAO" />
    <alias name="externalSecurityDAO" alias="securityDAO"/>

</beans>

-security-integtration-ldap.xml:

    <!-- LDAP Provider configuration -->
	<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
		<constructor-arg value="ldap://${ldap.host}:${ldap.port}/${ldap.root}" />
        <property name="userDn" value="${ldap.userDn}"/>
        <property name="password" value="${ldap.password}"/>
	</bean>

    <bean id="ldapInitializer" class="it.geosolutions.geostore.init.LDAPInit" lazy-init="false">
       <constructor-arg ref="geostoreLdapProvider" />
    </bean>

	<bean id="geostoreLdapProvider"
		class="it.geosolutions.geostore.services.rest.security.UserLdapAuthenticationProvider">
		<constructor-arg>
			<bean
				class="org.springframework.security.ldap.authentication.BindAuthenticator">
				<constructor-arg ref="contextSource" />
				<property name="userSearch">
					<bean id="userSearch"
						class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
						<constructor-arg index="0" value="${ldap.userBase}" />
						<constructor-arg index="1" value="${ldap.userFilter}" />
						<constructor-arg index="2" ref="contextSource" />
					</bean>
				</property>
			</bean>
		</constructor-arg>
		<constructor-arg>
			<bean
				class="it.geosolutions.geostore.services.rest.security.GeoStoreLdapAuthoritiesPopulator">
				<constructor-arg ref="contextSource" />
				<!-- groupSearchBase -->
				<constructor-arg value="${ldap.groupBase}" />
				<!-- roleSearchBase -->
				<constructor-arg value="${ldap.roleBase}" />
				<property name="groupSearchFilter" value="${ldap.groupFilter}" />
				<property name="roleSearchFilter" value="${ldap.roleFilter}" />

				<!-- nested groups support -->
				<property name="enableHierarchicalGroups" value="${ldap.hierachicalGroups}" />
				<property name="groupInGroupSearchFilter" value="${ldap.nestedGroupFilter}" />
				<property name="maxLevelGroupsSearch" value="${ldap.nestedGroupLevels}" />
				<!-- the GeoStore convention is:
				  * Groups starting with 'ROLE_' will be threated as Auth Roles
				  * Groups starting withOUT 'ROLE_' will be threated as Groups
				 -->
				<property name="rolePrefix" value="ROLE_" />
				<property name="searchSubtree" value="${ldap.searchSubtree}" />
				<property name="convertToUpperCase" value="${ldap.convertToUpperCase}" />
			</bean>
		</constructor-arg>
	</bean>

-security-integtration-ldap-direct.xml: (it imports the default one and adds it's beans.

<import resource="classpath*:security-integtration-ldap.xml"/>
<bean id="ldapUserDAO" class="it.geosolutions.geostore.core.dao.ldap.impl.UserDAOImpl">
        <constructor-arg ref="contextSource"/>
        <property name="searchBase" value="${ldap.userBase}"/>
        <property name="memberPattern" value="${ldap.memberPattern}"/>
        <property name="attributesMapper">
            <map>
                <entry key="mail" value="email"/>
                <entry key="givenName" value="fullname"/>
                <entry key="description" value="description"/>
            </map>
        </property>
    </bean>
    <bean id="ldapUserGroupDAO" class="it.geosolutions.geostore.core.dao.ldap.impl.UserGroupDAOImpl">
        <constructor-arg ref="contextSource"/>
        <property name="searchBase" value="${ldap.groupBase}"/>
        <property name="addEveryOneGroup" value="true"/>
    </bean>


    <alias name="ldapUserGroupDAO" alias="userGroupDAO"/>
    <alias name="externalSecurityDAO" alias="SecurityDAO"/>
    <alias name="ldapUserDAO" alias="userDAO"/>

Pros and Cons

Cons:

  • security.integration must be a system property or a java property (-Dsecurity.integration=ldap) and it can not be set in the data directory, for the limits of this solution. We can try to avoid this problem with a solution like this using and env.properties in classpath or datadir.
  • After applying this change, the java instance to run need the system property to be set (if no solution found).

Pros:

  • Easier configuration then building a different profile
  • Remove build profile for ldap. It can be activated via system property.
  • Automatically supported by any project (applying migration guidelines on old projects)
  • Backward compatibility for old projects (customized spring security and old classes are not touched)
  • Probably it works also with docker instances (if you can pass the java env properties to the docker instance)

Suggested Tasks

The changes are very simple

  • Apply the changes
  • test if it works