-
Notifications
You must be signed in to change notification settings - Fork 405
[Proposal] Remove LDAP profile ‐ Support integation via setting system property
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.
- Lorenzo Natali
TBD
- TBD
- Under Discussion
- In Progress
- Completed
- Rejected
- Deferred
The maintainance of a build profile
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:
- 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>
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"/>
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 :
- Only for LDAP configurations, to add
geostoreLdapProvider
tosecurity:authentication-manager
. - 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 methodsupports
) - Include some files in the spring context based on the property
security.integration
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.
- 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"/>
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 andenv.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)
The changes are very simple
- Apply the changes
- test if it works