You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@directory.apache.org by Eugen Stan <eu...@netdava.com> on 2022/01/02 22:30:37 UTC

implement an LDAP view for authentication - expose any identity data source as LDAP

Hi,

I would like to know if this is doable with Apache DS or a ldap library.

I would like to build an application that can offer basic set of 
functionality to perform LDAP authentication (and maybe password reset) 
and delegate this to an existing auth service (Keycloak, a user and 
password database, a simple file, Google Auth, whatever ).

The use case is that some applications work with LDAP auth for unified 
authentication and don't provide Oauth2 / OpenID connect support.

I would like to deploy keycloak or another IDM server to manage users 
and offer those applications an ldaps endpoint for which they authenticate.



To my knowledge I would need some sort of **SIMPLE** embedded ldap 
server that I can map the auth structure to my existing data stored in a 
DB or a rest service.

User will configure legacy app to sue my ldap Auth server.
The auth server will receive auth requests and read data from my real 
auth service (Keycloak, plain user + pass file, etc ).


This is kind of the reverse of what people are doing (putting OpenID 
Connect on top of LDAP servers).

The use case is pretty small and I think I could get away with a simple 
ldap protocol parsing library.

I would like to avoid any unnecessary complexity: ldap schemas, etc.


Would this be possible?
What should I try ?


Thanks,
-- 
Eugen Stan

+40770 941 271  / https://www.netdava.com

Re: implement an LDAP view for authentication - expose any identity data source as LDAP

Posted by Emmanuel Lécharny <el...@gmail.com>.
Hi Eugen !

You are courageous an persistent ;-)

So some insights about how the server deals with schemas.

When you create the DefaultDirectoryServiceFactory() instance, it gets 
'inited' at some point, and the initSchema() method is called:

in DefaultDirectoryServiceFactory:
     public void init( String name ) throws Exception
     {
         if ( ( directoryService != null ) && directoryService.isStarted() )
         {
             return;
         }

         build( name );
...
	
     private void build( String name ) throws Exception
     {
         directoryService.setInstanceId( name );
         buildInstanceDirectory( name );

         // Init the service now
         initSchema();
...	

and finally:

     private void initSchema() throws Exception
     {
         File workingDirectory = 
directoryService.getInstanceLayout().getPartitionsDirectory();

         // Extract the schema on disk (a brand new one) and load the 
registries
         File schemaRepository = new File( workingDirectory, "schema" );
         SchemaLdifExtractor extractor = new DefaultSchemaLdifExtractor( 
workingDirectory );

         try
         {
             extractor.extractOrCopy();
...


The key here is that we are going to fetch all files that match this 
pattern in the file system (at least in the files that are resources):
	".*schema[/\Q\\E]ou=schema.*\.ldif".

Either you have a system property that tells the server where to fetch 
those files (schema.resource.location) or it defaults to the files that 
are associated with the ResourceMap class loader (cl.getResources( 
"META-INF/apacheds-schema.index" );)

FYI, this index is created while building the Apache LDAP API package, 
using this maven plugin:

       <plugin>
         <artifactId>maven-antrun-plugin</artifactId>
         <executions>
           <execution>
             <phase>generate-resources</phase>
             <configuration>
               <target>
                 <!-- Various properties -->
                 <property name="schema.index" 
value="target/generated-resources/apacheds/META-INF/apacheds-schema.index" 
/>
                 <property name="schema.location" 
value="src${file.separator}main${file.separator}resources${file.separator}" 
/>

                 <!-- Listing all LDIF files under schema location -->
                 <path id="schema.files.path">
                   <fileset dir="${schema.location}">
                     <include name="**/*.ldif" />
                     <exclude name="schema-all.ldif" />
                   </fileset>
                 </path>
                 <property name="schema.files" refid="schema.files.path" />

                 <!-- Creating the schema index file -->
                 <echo message="${schema.files}" file="${schema.index}" />
                 <replace file="${schema.index}">
                   <!-- Replace the path separator (':' on Unix, ';' on 
Windows) by a new line -->
                   <replacefilter token="${path.separator}" 
value="${line.separator}" />
                   <!-- Remove the full path of the schema location to 
get relative paths for files -->
                   <replacefilter 
token="${basedir}${file.separator}${schema.location}" value="" />
                   <!-- Replace the file separator ('/' on Unix, '\' on 
Windows) by a '/' - Useful on Windows -->
                   <replacefilter token="${file.separator}" value="/" />
                 </replace>

                 <attachartifact file="${schema.index}" 
classifier="schema" type="index" />
               </target>
             </configuration>
             <goals>
               <goal>run</goal>
             </goals>
           </execution>
         </executions>
       </plugin>


(for the sake of completeness)

In any case, if you want to avoid processing this long list of files 
(which is kind of costly), all you have to do is to set the
"schema.resource.location" system property that points to an empty 
directory.

I'm not sure though that you won't need some part of the schemas for the 
server to work properly, as we need to parse the DNs and the attributes 
type within.

IMO, you should just get the minimal schema files (core, system), copy 
them in the location you chose, to minimize the cost of loading the 
schema at startup.

Side remark: up to a point, it would make sense to have a pre-loaded 
schema based on the files, to avoid the parsing that is quite costly. 
But that is an extension we have to include...

Hope it helps !


On 03/05/2022 02:27, Eugen Stan wrote:
> Hi,
> 
> After a few hours of hacking at this I managed to get something working.
> Thank you Emmanuel for your pointers.
> 
> I would like to improve startup time and avoid loading all (any of) the 
> schemas.
> 
> I am using DefaultDirectoryServiceFactory and LdapServer .
> I replace the AuthenticatorInterceptor with my own.
> 
> I am able to run a simple ldap query and I log the username and passowrd:
> 
> ldapsearch -x -b "ou=system" -H ldap://localhost:10389 -D 
> "uid=admin,ou=system" -w secret
> 
> Using DefaultDirectoryServiceFactory loads all the schemas and does some 
> disk IO + starts slow.
> 
> Can I avoid loading the schemas and doing this much IO ?
> I don't plan to use the schemas at all.
> 
> I gave it a shot but did not get far with that since 
> DefaultDirectoryService requires locks the disk and has some schema 
> initialization hardcoded inside - at specific paths.
> 
> I tink this logic could be made to be all in memory or to use a single 
> file but the code is too complex for me to figure out right now.
> 
> 
> Thanks,
> Eugen
> 
> 
> For who is interested, the code is bellow (clojure) :
> 
> 
> ----
> (ns ieugen.ldap-auth-provider.core
>    (:require [babashka.fs :as fs]
>              [taoensso.timbre :as log])
>    (:import (org.apache.directory.api.ldap.model.constants 
> AuthenticationLevel)
>             (org.apache.directory.server.core.api LdapPrincipal)
>             (org.apache.directory.server.core.api.interceptor.context 
> BindOperationContext)
>             (org.apache.directory.server.core.authn Authenticator)
>             (org.apache.directory.server.core.authn 
> AuthenticationInterceptor)
>             (org.apache.directory.server.core.factory 
> DefaultDirectoryServiceFactory)
>             (org.apache.directory.server.ldap LdapServer)
>             (org.apache.directory.server.protocol.shared.transport 
> Transport
> 
> TcpTransport)))
> 
> 
> (defn ->transport
>    "Create a Transport for ^LdapServer."
>    (^Transport [& {:keys [address port nbThreads backlog]
>                    :or {address "localhost"
>                         port 389
>                         nbThreads 3
>                         backlog 50}}]
>     (into-array Transport [(TcpTransport. address port nbThreads 
> backlog)])))
> 
> (defn authenticate
>    (^LdapPrincipal [schema-manager _this ^BindOperationContext 
> bind-context]
>     (let [credentials (.getCredentials bind-context)
>           principal (.getPrincipal bind-context)
>           dn (.getDn bind-context)
>           dn-name (.getName dn)
>           auth-rdn (.getRdn dn)
>           username (.getValue auth-rdn)
>           auth-lvl (.getAuthenticationLevel bind-context)]
>       (log/info "Create principal with"
>                 (String. credentials "UTF-8")
>                 "principal" principal
>                 "->" dn-name
>                 "type -> val:" (.getType auth-rdn) "->" username
>                 " | " (.getRdns dn))
>       (LdapPrincipal. schema-manager dn auth-lvl credentials))))
> 
> (defn delegating-authenticator
>    ([schema-manager]
>     (reify Authenticator
>       (getAuthenticatorType [_this]
>         (log/info "getAuthenticatorType")
>         AuthenticationLevel/SIMPLE)
>       (init [_this directory-service]
>         (log/info "init" directory-service))
>       (destroy [_this]
>         (log/info "destroy"))
>       (invalidateCache [_this bind-dn]
>         (log/info "invalidateCache" bind-dn))
>       (authenticate [_this bind-context]
>         (log/info "authenticate" bind-context)
>         (authenticate schema-manager _this bind-context))
>       (checkPwdPolicy [_this user-entry]
>         (log/info "checkPwdPolicy" user-entry))
>       (isValid [_this bind-dn]
>         (log/info "isValid" bind-dn)
>         true)
>       (getBaseDn [_this]
>         (log/info "getBaseDn"))
>       (setBaseDn [_this base-dn]
>         (log/info "setBaseDn" base-dn)))))
> 
> (defn authentication-interceptor
>    []
>    (proxy [AuthenticationInterceptor]
>           []
>      (loadPwdPolicyStateAttributeTypes []
>        (log/info "loadPwdPolicyStateAttributeTypes"))))
> 
> (defn index-of-class [clazz coll]
>    (count (take-while #(not (instance? clazz %)) coll)))
> 
> (comment
>    (let [_ (do
>              (System/setProperty "workingDirectory" "tmp")
>              (fs/create-dirs "tmp"))
>          ds-factory  (DefaultDirectoryServiceFactory.)
>          ds (doto (.getDirectoryService ds-factory)
>               (.setShutdownHookEnabled true)
>               (.setAllowAnonymousAccess true))
>          schema-manager (.getSchemaManager ds)
>          interceptors (.getInterceptors ds)
>          my-delegate-auth (delegating-authenticator schema-manager)
>          my-auth-interceptor (doto (authentication-interceptor)
>                                (.setAuthenticators (java.util.HashSet. 
> [my-delegate-auth])))
>          auth-interceptor-idx (index-of-class AuthenticationInterceptor 
> interceptors)
>          _ (.set interceptors auth-interceptor-idx my-auth-interceptor)
>          _ (.setInterceptors ds interceptors)
>          ldap-server (doto (LdapServer.)
>                        (.addTransports (->transport :port 10389))
>                        (.setDirectoryService ds))
> 
>          _ (do
>              (def ldaps ldap-server)
>              (-> ds .getChangeLog (.setEnabled true))
>              _ (.init ds-factory "ldap-auth-provider"))]
>      (log/info "Starting server")
>      (.start ldap-server)
> 
>      (log/info "Server started"))
> 
>    (.stop ldaps))
> ----
> 
> 

-- 
*Emmanuel Lécharny - CTO* 205 Promenade des Anglais – 06200 NICE
T. +33 (0)4 89 97 36 50
P. +33 (0)6 08 33 32 61
emmanuel.lecharny@busit.com https://www.busit.com/

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@directory.apache.org
For additional commands, e-mail: users-help@directory.apache.org


Re: implement an LDAP view for authentication - expose any identity data source as LDAP

Posted by Eugen Stan <eu...@netdava.com>.
Hi,

After a few hours of hacking at this I managed to get something working.
Thank you Emmanuel for your pointers.

I would like to improve startup time and avoid loading all (any of) the 
schemas.

I am using DefaultDirectoryServiceFactory and LdapServer .
I replace the AuthenticatorInterceptor with my own.

I am able to run a simple ldap query and I log the username and passowrd:

ldapsearch -x -b "ou=system" -H ldap://localhost:10389 -D 
"uid=admin,ou=system" -w secret

Using DefaultDirectoryServiceFactory loads all the schemas and does some 
disk IO + starts slow.

Can I avoid loading the schemas and doing this much IO ?
I don't plan to use the schemas at all.

I gave it a shot but did not get far with that since 
DefaultDirectoryService requires locks the disk and has some schema 
initialization hardcoded inside - at specific paths.

I tink this logic could be made to be all in memory or to use a single 
file but the code is too complex for me to figure out right now.


Thanks,
Eugen


For who is interested, the code is bellow (clojure) :


----
(ns ieugen.ldap-auth-provider.core
   (:require [babashka.fs :as fs]
             [taoensso.timbre :as log])
   (:import (org.apache.directory.api.ldap.model.constants 
AuthenticationLevel)
            (org.apache.directory.server.core.api LdapPrincipal)
            (org.apache.directory.server.core.api.interceptor.context 
BindOperationContext)
            (org.apache.directory.server.core.authn Authenticator)
            (org.apache.directory.server.core.authn 
AuthenticationInterceptor)
            (org.apache.directory.server.core.factory 
DefaultDirectoryServiceFactory)
            (org.apache.directory.server.ldap LdapServer)
            (org.apache.directory.server.protocol.shared.transport Transport
 
TcpTransport)))


(defn ->transport
   "Create a Transport for ^LdapServer."
   (^Transport [& {:keys [address port nbThreads backlog]
                   :or {address "localhost"
                        port 389
                        nbThreads 3
                        backlog 50}}]
    (into-array Transport [(TcpTransport. address port nbThreads 
backlog)])))

(defn authenticate
   (^LdapPrincipal [schema-manager _this ^BindOperationContext bind-context]
    (let [credentials (.getCredentials bind-context)
          principal (.getPrincipal bind-context)
          dn (.getDn bind-context)
          dn-name (.getName dn)
          auth-rdn (.getRdn dn)
          username (.getValue auth-rdn)
          auth-lvl (.getAuthenticationLevel bind-context)]
      (log/info "Create principal with"
                (String. credentials "UTF-8")
                "principal" principal
                "->" dn-name
                "type -> val:" (.getType auth-rdn) "->" username
                " | " (.getRdns dn))
      (LdapPrincipal. schema-manager dn auth-lvl credentials))))

(defn delegating-authenticator
   ([schema-manager]
    (reify Authenticator
      (getAuthenticatorType [_this]
        (log/info "getAuthenticatorType")
        AuthenticationLevel/SIMPLE)
      (init [_this directory-service]
        (log/info "init" directory-service))
      (destroy [_this]
        (log/info "destroy"))
      (invalidateCache [_this bind-dn]
        (log/info "invalidateCache" bind-dn))
      (authenticate [_this bind-context]
        (log/info "authenticate" bind-context)
        (authenticate schema-manager _this bind-context))
      (checkPwdPolicy [_this user-entry]
        (log/info "checkPwdPolicy" user-entry))
      (isValid [_this bind-dn]
        (log/info "isValid" bind-dn)
        true)
      (getBaseDn [_this]
        (log/info "getBaseDn"))
      (setBaseDn [_this base-dn]
        (log/info "setBaseDn" base-dn)))))

(defn authentication-interceptor
   []
   (proxy [AuthenticationInterceptor]
          []
     (loadPwdPolicyStateAttributeTypes []
       (log/info "loadPwdPolicyStateAttributeTypes"))))

(defn index-of-class [clazz coll]
   (count (take-while #(not (instance? clazz %)) coll)))

(comment
   (let [_ (do
             (System/setProperty "workingDirectory" "tmp")
             (fs/create-dirs "tmp"))
         ds-factory  (DefaultDirectoryServiceFactory.)
         ds (doto (.getDirectoryService ds-factory)
              (.setShutdownHookEnabled true)
              (.setAllowAnonymousAccess true))
         schema-manager (.getSchemaManager ds)
         interceptors (.getInterceptors ds)
         my-delegate-auth (delegating-authenticator schema-manager)
         my-auth-interceptor (doto (authentication-interceptor)
                               (.setAuthenticators (java.util.HashSet. 
[my-delegate-auth])))
         auth-interceptor-idx (index-of-class AuthenticationInterceptor 
interceptors)
         _ (.set interceptors auth-interceptor-idx my-auth-interceptor)
         _ (.setInterceptors ds interceptors)
         ldap-server (doto (LdapServer.)
                       (.addTransports (->transport :port 10389))
                       (.setDirectoryService ds))

         _ (do
             (def ldaps ldap-server)
             (-> ds .getChangeLog (.setEnabled true))
             _ (.init ds-factory "ldap-auth-provider"))]
     (log/info "Starting server")
     (.start ldap-server)

     (log/info "Server started"))

   (.stop ldaps))
----


-- 
Eugen Stan

+40770 941 271  / https://www.netdava.com

Re: implement an LDAP view for authentication - expose any identity data source as LDAP

Posted by Eugen Stan <eu...@netdava.com>.
Hello Emmanuel,

Thanks for the reply.

I started working on this based on the sample here 
https://github.com/apache/directory-samples/blob/def6f8ac997a1da81360e5b1fe716a27a97d1a5b/embedded-sample-trunk/src/main/java/org/apache/directory/seserver/EmbeddedADSVerTrunk.java#L154 
.

But I did not get very far since it's based on version 1.x .
In the mean time the API's have changed.

Any idea where I can find a simple working example to get me started 
with a server that I can implement the delegator in?

(This message was in draft for such a long time :( ) .

Thanks,
Eugen

On 06.01.2022 10:39, Emmanuel Lécharny wrote:
> Hi!
> 
> This is possible, all it needs is an implementation of a new Authenticator.
> 
> We already have a DelegatingAuthenticator class that delehgates 
> authenticatio to a remote LDAP server, so for your need, you have to 
> implement a similar class delagating authentication to a Oauth2 or 
> OpenID provider.
> 
> Here are the references to the interface and class :
> 
> https://nightlies.apache.org/directory/apacheds/2.0.0.AM26/apidocs/org/apache/directory/shared/kerberos/messages/Authenticator.html 
> 
> 
> and the DelegatingAuthenticator:
> 
> 
> https://nightlies.apache.org/directory/apacheds/2.0.0.AM26/apidocs/org/apache/directory/server/core/authn/DelegatingAuthenticator.html 
> 
> 
> (code : 
> https://nightlies.apache.org/directory/apacheds/2.0.0.AM26/apidocs/src-html/org/apache/directory/server/core/authn/DelegatingAuthenticator.html#line.45) 
> 
> 
> In order to have it to work, you will need to add some configurtaion 
> element, like what has been done for the DelegatingAuthenticator:
> 
> dn: 
> ads-authenticatorid=delegatingauthenticator,ou=authenticators,ads-interceptorId=authenticationInterceptor,ou=interceptors,ads-directoryServiceId=default,ou=config 
> 
> ads-authenticatorid: delegatingauthenticator
> objectclass: top
> objectclass: ads-base
> objectClass: ads-authenticator
> objectClass: ads-authenticatorImpl
> ads-authenticatorClass: 
> org.apache.directory.server.core.authn.DelegatingAuthenticator
> ads-baseDn:
> ads-enabled: FALSE
> 
> (this is in the config.ldif file).
> 
> Let me know if you need more direction...
> 
> On 02/01/2022 23:30, Eugen Stan wrote:
>> Hi,
>>
>> I would like to know if this is doable with Apache DS or a ldap library.
>>
>> I would like to build an application that can offer basic set of 
>> functionality to perform LDAP authentication (and maybe password 
>> reset) and delegate this to an existing auth service (Keycloak, a user 
>> and password database, a simple file, Google Auth, whatever ).
>>
>> The use case is that some applications work with LDAP auth for unified 
>> authentication and don't provide Oauth2 / OpenID connect support.
>>
>> I would like to deploy keycloak or another IDM server to manage users 
>> and offer those applications an ldaps endpoint for which they 
>> authenticate.
>>
>>
>>
>> To my knowledge I would need some sort of **SIMPLE** embedded ldap 
>> server that I can map the auth structure to my existing data stored in 
>> a DB or a rest service.
>>
>> User will configure legacy app to sue my ldap Auth server.
>> The auth server will receive auth requests and read data from my real 
>> auth service (Keycloak, plain user + pass file, etc ).
>>
>>
>> This is kind of the reverse of what people are doing (putting OpenID 
>> Connect on top of LDAP servers).
>>
>> The use case is pretty small and I think I could get away with a 
>> simple ldap protocol parsing library.
>>
>> I would like to avoid any unnecessary complexity: ldap schemas, etc.
>>
>>
>> Would this be possible?
>> What should I try ?
>>
>>
>> Thanks,
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: users-unsubscribe@directory.apache.org
>> For additional commands, e-mail: users-help@directory.apache.org
>>
> 


-- 
Eugen Stan

+40770 941 271  / https://www.netdava.com

Re: implement an LDAP view for authentication - expose any identity data source as LDAP

Posted by Emmanuel Lécharny <el...@gmail.com>.
Hi!

This is possible, all it needs is an implementation of a new Authenticator.

We already have a DelegatingAuthenticator class that delehgates 
authenticatio to a remote LDAP server, so for your need, you have to 
implement a similar class delagating authentication to a Oauth2 or 
OpenID provider.

Here are the references to the interface and class :

https://nightlies.apache.org/directory/apacheds/2.0.0.AM26/apidocs/org/apache/directory/shared/kerberos/messages/Authenticator.html

and the DelegatingAuthenticator:


https://nightlies.apache.org/directory/apacheds/2.0.0.AM26/apidocs/org/apache/directory/server/core/authn/DelegatingAuthenticator.html

(code : 
https://nightlies.apache.org/directory/apacheds/2.0.0.AM26/apidocs/src-html/org/apache/directory/server/core/authn/DelegatingAuthenticator.html#line.45)

In order to have it to work, you will need to add some configurtaion 
element, like what has been done for the DelegatingAuthenticator:

dn: 
ads-authenticatorid=delegatingauthenticator,ou=authenticators,ads-interceptorId=authenticationInterceptor,ou=interceptors,ads-directoryServiceId=default,ou=config
ads-authenticatorid: delegatingauthenticator
objectclass: top
objectclass: ads-base
objectClass: ads-authenticator
objectClass: ads-authenticatorImpl
ads-authenticatorClass: 
org.apache.directory.server.core.authn.DelegatingAuthenticator
ads-baseDn:
ads-enabled: FALSE

(this is in the config.ldif file).

Let me know if you need more direction...

On 02/01/2022 23:30, Eugen Stan wrote:
> Hi,
> 
> I would like to know if this is doable with Apache DS or a ldap library.
> 
> I would like to build an application that can offer basic set of 
> functionality to perform LDAP authentication (and maybe password reset) 
> and delegate this to an existing auth service (Keycloak, a user and 
> password database, a simple file, Google Auth, whatever ).
> 
> The use case is that some applications work with LDAP auth for unified 
> authentication and don't provide Oauth2 / OpenID connect support.
> 
> I would like to deploy keycloak or another IDM server to manage users 
> and offer those applications an ldaps endpoint for which they authenticate.
> 
> 
> 
> To my knowledge I would need some sort of **SIMPLE** embedded ldap 
> server that I can map the auth structure to my existing data stored in a 
> DB or a rest service.
> 
> User will configure legacy app to sue my ldap Auth server.
> The auth server will receive auth requests and read data from my real 
> auth service (Keycloak, plain user + pass file, etc ).
> 
> 
> This is kind of the reverse of what people are doing (putting OpenID 
> Connect on top of LDAP servers).
> 
> The use case is pretty small and I think I could get away with a simple 
> ldap protocol parsing library.
> 
> I would like to avoid any unnecessary complexity: ldap schemas, etc.
> 
> 
> Would this be possible?
> What should I try ?
> 
> 
> Thanks,
> 
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: users-unsubscribe@directory.apache.org
> For additional commands, e-mail: users-help@directory.apache.org
> 

-- 
*Emmanuel Lécharny - CTO* 205 Promenade des Anglais – 06200 NICE
T. +33 (0)4 89 97 36 50
P. +33 (0)6 08 33 32 61
emmanuel.lecharny@busit.com https://www.busit.com/

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@directory.apache.org
For additional commands, e-mail: users-help@directory.apache.org