This is an automated email from the ASF dual-hosted git repository.

smolnar82 pushed a commit to branch knox_idf
in repository https://gitbox.apache.org/repos/asf/knox.git

commit 7a73bf96835799bd750d688e6b848444246130ef
Author: Sandor Molnar <[email protected]>
AuthorDate: Thu May 28 15:14:58 2026 +0200

    KNOX-3328: Add Docker-based integration tests for Knox LDAP service (#1242)
---
 .../workflows/build/conf/topologies/knoxldap.xml   |  2 +-
 .github/workflows/build/gateway-site.xml           | 48 ++++++++++++++++++++++
 .../tests/test_knox_auth_service_and_LDAP.py       |  4 +-
 gateway-server/pom.xml                             | 11 ++---
 .../services/ldap/GroupLookupInterceptor.java      | 40 +++++++++++++++++-
 .../services/ldap/KnoxLDAPServerManager.java       | 27 +++++++++++-
 .../knox/gateway/services/ldap/LdapMessages.java   |  6 +++
 .../knox/gateway/services/ldap/LdapUtils.java      | 36 ++++++++++++++++
 .../gateway/services/ldap/backend/FileBackend.java | 16 ++++++++
 .../gateway/services/ldap/backend/LdapBackend.java | 10 +++++
 .../services/ldap/backend/LdapProxyBackend.java    | 21 ++++++++++
 pom.xml                                            |  1 +
 12 files changed, 210 insertions(+), 12 deletions(-)

diff --git a/.github/workflows/build/conf/topologies/knoxldap.xml 
b/.github/workflows/build/conf/topologies/knoxldap.xml
index 102797634..dc1b32237 100644
--- a/.github/workflows/build/conf/topologies/knoxldap.xml
+++ b/.github/workflows/build/conf/topologies/knoxldap.xml
@@ -35,7 +35,7 @@ limitations under the License.
                        </param>
                        <param>
                                <name>main.ldapRealm.contextFactory.url</name>
-                               <value>ldap://ldap:33389</value>
+                               <value>ldap://localhost:33390</value>  <!-- 
Local Knox LDAP service which is configured to proxy to the demo LDAP as a 
backend -->
                        </param>
                        <param>
                                
<name>main.ldapRealm.contextFactory.authenticationMechanism</name>
diff --git a/.github/workflows/build/gateway-site.xml 
b/.github/workflows/build/gateway-site.xml
index 5f333f063..74a13a4a0 100644
--- a/.github/workflows/build/gateway-site.xml
+++ b/.github/workflows/build/gateway-site.xml
@@ -160,4 +160,52 @@ limitations under the License.
         <value>max-age=300; includeSubDomains</value>
     </property>
 
+    <!-- KnoxLDAP Service Configuration -->
+    <property>
+        <name>gateway.ldap.enabled</name>
+        <value>true</value>
+    </property>
+    <property>
+        <name>gateway.ldap.port</name>
+        <value>33390</value>
+    </property>
+    <property>
+        <name>gateway.ldap.base.dn</name>
+        <value>dc=hadoop,dc=apache,dc=org</value>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.type</name>
+        <value>ldap</value>
+    </property>
+
+    <!-- LDAP Backend specific configuration (proxying to demo ldap) -->
+    <property>
+        <name>gateway.ldap.backend.ldap.url</name>
+        <value>ldap://ldap:33389</value>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.remoteBaseDn</name>
+        <value>dc=hadoop,dc=apache,dc=org</value>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.systemUsername</name>
+        <value>uid=guest,ou=people,dc=hadoop,dc=apache,dc=org</value>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.systemPassword</name>
+        <value>guest-password</value>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.userSearchBase</name>
+        <value>ou=people,dc=hadoop,dc=apache,dc=org</value>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.groupSearchBase</name>
+        <value>ou=groups,dc=hadoop,dc=apache,dc=org</value>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.groupMemberAttribute</name>
+        <value>member</value>
+    </property>
+
 </configuration>
diff --git a/.github/workflows/tests/test_knox_auth_service_and_LDAP.py 
b/.github/workflows/tests/test_knox_auth_service_and_LDAP.py
index 51a42d84e..43fb886ab 100644
--- a/.github/workflows/tests/test_knox_auth_service_and_LDAP.py
+++ b/.github/workflows/tests/test_knox_auth_service_and_LDAP.py
@@ -31,7 +31,7 @@ class TestKnoxAuthService(unittest.TestCase):
     def setUp(self):
         self.base_url = gateway_base_url()
         # The topology name is based on the filename knoxldap.xml
-        self.topology_url = self.base_url + 
"gateway/knoxldap/auth/api/v1/extauthz"
+        self.topology_url = self.base_url + "gateway/knoxldap/auth/api/v1/pre"
 
     def test_auth_service_guest(self):
         """
@@ -47,7 +47,7 @@ class TestKnoxAuthService(unittest.TestCase):
         self.assertEqual(response.status_code, 200)
         
         # Check for Actor ID header
-        # The config in knoxtoken.xml sets 'preauth.auth.header.actor.id.name' 
to 'x-knox-actor-username'
+        # The config in knoxldap.xml sets 'preauth.auth.header.actor.id.name' 
to 'x-knox-actor-username'
         actor_id_header = 'x-knox-actor-username'
         self.assertIn(actor_id_header, response.headers)
         self.assertEqual(response.headers[actor_id_header], 'guest')
diff --git a/gateway-server/pom.xml b/gateway-server/pom.xml
index ab7ae5bb1..41341bbfe 100644
--- a/gateway-server/pom.xml
+++ b/gateway-server/pom.xml
@@ -519,6 +519,12 @@
             <artifactId>api-ldap-client-api</artifactId>
             <scope>provided</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.mina</groupId>
+            <artifactId>mina-core</artifactId>
+        </dependency>
+
         <!-- ********** ********** ********** ********** ********** ********** 
-->
         <!-- ********** Test Dependencies                           ********** 
-->
         <!-- ********** ********** ********** ********** ********** ********** 
-->
@@ -612,10 +618,5 @@
             <scope>test</scope>
         </dependency>
 
-        <dependency>
-            <groupId>org.apache.mina</groupId>
-            <artifactId>mina-core</artifactId>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
 </project>
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/GroupLookupInterceptor.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/GroupLookupInterceptor.java
index 12a1cd428..1218c8c78 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/GroupLookupInterceptor.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/GroupLookupInterceptor.java
@@ -17,15 +17,19 @@
  */
 package org.apache.knox.gateway.services.ldap;
 
+import org.apache.directory.api.ldap.model.constants.AuthenticationLevel;
 import org.apache.directory.api.ldap.model.cursor.ListCursor;
 import org.apache.directory.api.ldap.model.entry.Entry;
 import org.apache.directory.api.ldap.model.exception.LdapException;
 import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.server.core.api.CoreSession;
 import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.LdapPrincipal;
 import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
 import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
 import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
 import 
org.apache.directory.server.core.api.interceptor.context.BindOperationContext;
+import 
org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
 import 
org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.services.ldap.backend.LdapBackend;
@@ -51,6 +55,22 @@ public class GroupLookupInterceptor extends BaseInterceptor {
         this.backend = backend;
     }
 
+    @Override
+    public Entry lookup(LookupOperationContext ctx) throws LdapException {
+        Entry entry = next(ctx);
+        if (entry == null) {
+            String username = LdapUtils.extractUsernameFromDn(ctx.getDn());
+            if (username != null) {
+                try {
+                    entry = backend.getUser(username, 
directoryService.getSchemaManager());
+                } catch (Exception e) {
+                    LOG.ldapServiceStopFailed(e);
+                }
+            }
+        }
+        return entry;
+    }
+
     @Override
     public EntryFilteringCursor search(SearchOperationContext ctx) throws 
LdapException {
         String filter = ctx.getFilter() != null ? ctx.getFilter().toString() : 
"";
@@ -120,9 +140,25 @@ public class GroupLookupInterceptor extends 
BaseInterceptor {
     }
 
     @Override
-    public void bind(BindOperationContext ctx) {
-        // Allow anonymous bind or simple bind
+    public void bind(BindOperationContext ctx) throws LdapException {
         LOG.ldapBind(ctx.getDn() != null ? ctx.getDn().toString() : 
"anonymous");
+
+        // Try backend first for non-system users
+        if (ctx.getDn() != null && 
!ctx.getDn().toString().endsWith("ou=system")) {
+            byte[] credentials = ctx.getCredentials();
+            if (credentials != null) {
+                String password = new String(credentials, 
java.nio.charset.StandardCharsets.UTF_8);
+                if (backend.authenticate(ctx.getDn(), password)) {
+                    // Create session for the authenticated user and set it in 
context
+                    // This is required by LdapServer to avoid 
NullPointerException
+                    LdapPrincipal principal = new 
LdapPrincipal(directoryService.getSchemaManager(), ctx.getDn(), 
AuthenticationLevel.SIMPLE);
+                    CoreSession session = 
directoryService.getSession(principal);
+                    ctx.setSession(session);
+                    return; // Successfully authenticated via backend, bypass 
local check
+                }
+            }
+        }
+
         try {
             next(ctx);
         } catch (Exception e) {
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
index f0a87dc5b..300e6f936 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
@@ -24,6 +24,7 @@ import 
org.apache.directory.api.ldap.model.schema.SchemaManager;
 import org.apache.directory.server.core.DefaultDirectoryService;
 import org.apache.directory.server.core.api.DirectoryService;
 import org.apache.directory.server.core.api.InstanceLayout;
+import org.apache.directory.server.core.api.interceptor.Interceptor;
 import org.apache.directory.server.core.api.schema.SchemaPartition;
 import 
org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
 import org.apache.directory.server.core.partition.ldif.LdifPartition;
@@ -34,6 +35,8 @@ import 
org.apache.knox.gateway.services.ldap.backend.BackendFactory;
 import org.apache.knox.gateway.services.ldap.backend.LdapBackend;
 
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -125,8 +128,7 @@ public class KnoxLDAPServerManager {
             directoryService.addPartition(remotePartition);
         }
 
-        // Add our interceptor for group lookups
-        directoryService.addLast(new GroupLookupInterceptor(directoryService, 
backend));
+        addGroupLookupInterceptor();
 
         // Allow anonymous access
         directoryService.setAllowAnonymousAccess(true);
@@ -147,6 +149,27 @@ public class KnoxLDAPServerManager {
         LOG.ldapServiceStarted(port);
     }
 
+    private void addGroupLookupInterceptor() {
+        // Add our interceptor for group lookups and bind proxying
+        // We need to insert it before AuthenticationInterceptor to intercept 
bind requests
+        final List<Interceptor> interceptors = new 
ArrayList<>(directoryService.getInterceptors());
+        int authIdx = -1;
+        for (int i = 0; i < interceptors.size(); i++) {
+            if 
(interceptors.get(i).getName().equalsIgnoreCase("authenticationInterceptor")) {
+                authIdx = i;
+                break;
+            }
+        }
+
+        final GroupLookupInterceptor interceptor = new 
GroupLookupInterceptor(directoryService, backend);
+        if (authIdx != -1) {
+            interceptors.add(authIdx, interceptor);
+        } else {
+            interceptors.add(interceptor);
+        }
+        directoryService.setInterceptors(interceptors);
+    }
+
     /**
      * Stop the LDAP server
      */
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
index a0b09c244..4845cda79 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
@@ -96,4 +96,10 @@ public interface LdapMessages {
     @Message(level = MessageLevel.ERROR,
             text = "Failed to copy attribute: {0}")
     void ldapAttributeCopyError(@StackTrace(level = MessageLevel.DEBUG) 
Exception e);
+
+    @Message(level = MessageLevel.DEBUG, text = "LDAP authentication succeeded 
for user: {0}")
+    void ldapAuthSucceeded(String user);
+
+    @Message(level = MessageLevel.WARN, text = "LDAP authentication failed for 
user: {0}")
+    void ldapAuthFailed(String user, @StackTrace(level = MessageLevel.INFO) 
Throwable cause);
 }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapUtils.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapUtils.java
new file mode 100644
index 000000000..117c778ea
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations 
under
+ * the License.
+ */
+package org.apache.knox.gateway.services.ldap;
+
+import org.apache.directory.api.ldap.model.name.Dn;
+
+public class LdapUtils {
+
+    public static String extractUsernameFromDn(Dn dn) {
+        if (dn == null || dn.isEmpty()) {
+            return null;
+        }
+
+        try {
+            return "uid".equalsIgnoreCase(dn.getRdn().getType())
+                    ? dn.getRdn().getValue()
+                    : null;
+        } catch (Exception ignored) {
+            return null;
+        }
+    }
+}
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
index b0cdcd860..9c3d96aed 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
@@ -20,9 +20,11 @@ package org.apache.knox.gateway.services.ldap.backend;
 import com.google.gson.Gson;
 import org.apache.directory.api.ldap.model.entry.DefaultEntry;
 import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.name.Dn;
 import org.apache.directory.api.ldap.model.schema.SchemaManager;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.services.ldap.LdapMessages;
+import org.apache.knox.gateway.services.ldap.LdapUtils;
 
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -45,6 +47,7 @@ public class FileBackend implements LdapBackend {
 
     static class UserData {
         String username;
+        String password;
         String cn;
         String sn;
         List<String> groups;
@@ -141,4 +144,17 @@ public class FileBackend implements LdapBackend {
 
         return results;
     }
+
+    @Override
+    public boolean authenticate(Dn userDn, String password) {
+        // Extract username from DN (e.g., uid=admin,  
ou=people,dc=hadoop,dc=apache,dc=org)
+        final String username = LdapUtils.extractUsernameFromDn(userDn);
+
+        if (username != null) {
+            UserData userData = users.get(username);
+            return userData != null && password != null && 
password.equals(userData.password);
+        }
+
+        return false;
+    }
 }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
index 6530c13b3..4c459ff26 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
@@ -18,6 +18,7 @@
 package org.apache.knox.gateway.services.ldap.backend;
 
 import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.name.Dn;
 import org.apache.directory.api.ldap.model.schema.SchemaManager;
 
 import java.util.List;
@@ -65,4 +66,13 @@ public interface LdapBackend {
      * @return List of matching entries
      */
     List<Entry> searchUsers(String filter, SchemaManager schemaManager) throws 
Exception;
+
+    /**
+     * Authenticate a user with password
+     *
+     * @param userDn   The user's Distinguished Name
+     * @param password The user's password
+     * @return true if authentication is successful, false otherwise
+     */
+    boolean authenticate(Dn userDn, String password);
 }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
index 38328b571..053a487bb 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
@@ -24,11 +24,13 @@ import 
org.apache.directory.api.ldap.model.entry.DefaultEntry;
 import org.apache.directory.api.ldap.model.entry.Entry;
 import org.apache.directory.api.ldap.model.exception.LdapException;
 import org.apache.directory.api.ldap.model.message.SearchScope;
+import org.apache.directory.api.ldap.model.name.Dn;
 import org.apache.directory.api.ldap.model.schema.SchemaManager;
 import org.apache.directory.ldap.client.api.DefaultLdapConnectionFactory;
 import org.apache.directory.ldap.client.api.LdapConnection;
 import org.apache.directory.ldap.client.api.LdapConnectionConfig;
 import org.apache.directory.ldap.client.api.LdapConnectionPool;
+import org.apache.directory.ldap.client.api.LdapNetworkConnection;
 import 
org.apache.directory.ldap.client.api.ValidatingPoolableLdapConnectionFactory;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.services.ldap.LdapMessages;
@@ -253,6 +255,25 @@ public class LdapProxyBackend implements LdapBackend {
         }
     }
 
+    @Override
+    public boolean authenticate(Dn userDn, String password) {
+        final String userDnText = userDn.toString(); //at this point we are 
sure it's not NULL
+        // Create a temporary connection for authentication (bind)
+        final LdapConnectionConfig authConfig = new LdapConnectionConfig();
+        authConfig.setLdapHost(host);
+        authConfig.setLdapPort(port);
+        authConfig.setName(userDnText);
+        authConfig.setCredentials(password);
+        try (LdapConnection connection =  new 
LdapNetworkConnection(authConfig)){
+            connection.bind();
+            LOG.ldapAuthSucceeded(userDnText);
+            return true;
+        } catch (Exception e) {
+            LOG.ldapAuthFailed(userDnText, e);
+            return false;
+        }
+    }
+
     @Override
     public Entry getUser(String username, SchemaManager schemaManager) throws 
Exception {
         LdapConnection connection = null;
diff --git a/pom.xml b/pom.xml
index 64d09811a..581228400 100644
--- a/pom.xml
+++ b/pom.xml
@@ -802,6 +802,7 @@
                                 
<ignoredNonTestScopedDependency>com.nimbusds:nimbus-jose-jwt</ignoredNonTestScopedDependency>
                                 
<ignoredNonTestScopedDependency>org.apache.httpcomponents:httpcore</ignoredNonTestScopedDependency>
                                 
<ignoredNonTestScopedDependency>org.apache.knox:gateway-server</ignoredNonTestScopedDependency>
+                                
<ignoredNonTestScopedDependency>org.apache.mina:mina-core</ignoredNonTestScopedDependency>
                             </ignoredNonTestScopedDependencies>
                             <ignoredUsedUndeclaredDependencies>
                                 
<ignoredUsedUndeclaredDependency>org.glassfish.hk2.external:jakarta.inject</ignoredUsedUndeclaredDependency>

Reply via email to