You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2026/09/30 09:32:34 UTC

(knox) branch master updated: KNOX-3497: Resolve credential-store aliases in LDAP proxy backend password properties (#1436)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 7dfa32738 KNOX-3497: Resolve credential-store aliases in LDAP proxy backend password properties (#1436)
7dfa32738 is described below

commit 7dfa327380a71ab16e33f25222bf20efd52e8de5
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Wed Sep 30 11:32:29 2026 +0200

    KNOX-3497: Resolve credential-store aliases in LDAP proxy backend password properties (#1436)
---
 .github/workflows/build/gateway-site.xml           |  2 +-
 .github/workflows/build/gateway.sh                 |  6 ++
 .../workflows/tests/test_knox_ldap_proxy_search.py |  7 +++
 .../services/ldap/KnoxLDAPServerManager.java       | 30 ++++++++++
 .../knox/gateway/services/ldap/LdapMessages.java   |  9 +++
 .../services/ldap/KnoxLDAPServerManagerTest.java   | 70 ++++++++++++++++++++++
 knox-site/docs/service_ldap_server.md              | 17 +++++-
 7 files changed, 139 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/build/gateway-site.xml b/.github/workflows/build/gateway-site.xml
index be1c264b9..fe433851b 100644
--- a/.github/workflows/build/gateway-site.xml
+++ b/.github/workflows/build/gateway-site.xml
@@ -209,7 +209,7 @@ limitations under the License.
     </property>
     <property>
         <name>gateway.ldap.interceptor.demoldap.systemPassword</name>
-        <value>guest-password</value>
+        <value>S{ALIAS=gateway_ldap_demoldap_system_password}</value>
     </property>
     <property>
         <name>gateway.ldap.interceptor.demoldap.userSearchBase</name>
diff --git a/.github/workflows/build/gateway.sh b/.github/workflows/build/gateway.sh
index c67b467d1..0666bee29 100755
--- a/.github/workflows/build/gateway.sh
+++ b/.github/workflows/build/gateway.sh
@@ -51,6 +51,12 @@ LDAP_BIND_PASSWORD=bind-password
 LDAP_BIND_PASSWORD_ALIAS=gateway_ldap_bind_password
 /knox-runtime/bin/knoxcli.sh create-alias "$LDAP_BIND_PASSWORD_ALIAS" --value "$LDAP_BIND_PASSWORD"
 
+# 5) Store the demo LDAP backend's system (bind) password under an alias so the LdapProxyBackend
+# resolves it from the credential store instead of reading a literal from gateway-site.xml (KNOX-3497).
+LDAP_BACKEND_SYSTEM_PASSWORD=guest-password
+LDAP_BACKEND_SYSTEM_PASSWORD_ALIAS=gateway_ldap_demoldap_system_password
+/knox-runtime/bin/knoxcli.sh create-alias "$LDAP_BACKEND_SYSTEM_PASSWORD_ALIAS" --value "$LDAP_BACKEND_SYSTEM_PASSWORD"
+
 # Start Knox. Endpoint (hostname) identification is disabled for the embedded LDAPS
 # connection - the dev cert is self-signed - but trust is still enforced via cacerts.
 java -Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true \
diff --git a/.github/workflows/tests/test_knox_ldap_proxy_search.py b/.github/workflows/tests/test_knox_ldap_proxy_search.py
index beaca91b3..a1caefe06 100644
--- a/.github/workflows/tests/test_knox_ldap_proxy_search.py
+++ b/.github/workflows/tests/test_knox_ldap_proxy_search.py
@@ -24,6 +24,13 @@ with gateway.ldap.ssl.enabled=true, and it in turn proxies to the demo LDAP
 backend over LDAPS as well (gateway.ldap.interceptor.demoldap.url=ldaps://...).
 The gateway presents a self-signed dev certificate in CI, so certificate
 validation is disabled on the client side.
+
+The demo backend's system (bind) password is supplied to the proxy via a
+credential-store alias (gateway.ldap.interceptor.demoldap.systemPassword=
+S{ALIAS=gateway_ldap_demoldap_system_password}, seeded in gateway.sh). The proxy
+must resolve that alias to bind to the demo LDAP; if resolution regressed the
+literal alias string would be used as the password and every search below would
+fail on a bind error. This suite therefore also covers KNOX-3497.
 """
 
 from __future__ import annotations
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 615678cf4..0556b6cd0 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
@@ -54,6 +54,7 @@ import org.apache.knox.gateway.services.ldap.control.RolesLookupBypassControlFac
 import org.apache.knox.gateway.services.ldap.interceptor.InterceptorFactory;
 import org.apache.knox.gateway.services.ldap.interceptor.LDAPRolesLookupInterceptor;
 import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.AliasServiceException;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -72,6 +73,7 @@ import java.util.stream.IntStream;
 public class KnoxLDAPServerManager {
     private static final LdapMessages LOG = MessagesFactory.get(LdapMessages.class);
     private static final String LDAP_BIND_PASSWORD_ALIAS = "gateway_ldap_bind_password";
+    private static final List<String> BACKEND_PASSWORD_KEYS = List.of("bindPassword", "systemPassword", "trustStorePassword");
     private final AliasService aliasService;
     private final GatewayServices gatewayServices;
 
@@ -194,11 +196,39 @@ public class KnoxLDAPServerManager {
                 }
             }
 
+            resolveBackendPasswordAliases(interceptorName, interceptorConfig);
+
             interceptors.add(InterceptorFactory.createInterceptor(config, gatewayServices, interceptorName, interceptorConfig));
         }
         this.interceptors = interceptors;
     }
 
+    /**
+     * Resolves gateway credential-store alias references (e.g. {@code S{ALIAS=my-ad-password}}) held in the
+     * backend password properties, replacing each with the secret fetched from the gateway credential store.
+     * Literal (non-alias) values are left untouched for backward compatibility; an unresolvable alias is
+     * left as-is and logged at ERROR, so the backend bind fails rather than silently using a different credential.
+     */
+    private void resolveBackendPasswordAliases(String interceptorName, Map<String, String> interceptorConfig) {
+        for (String key : BACKEND_PASSWORD_KEYS) {
+            final String value = interceptorConfig.get(key);
+            if (StringUtils.isNotBlank(value) && aliasService.isAlias(value)) {
+                final String alias = aliasService.extractAlias(value);
+                try {
+                    final char[] password = aliasService.getPasswordFromAliasForGateway(alias);
+                    if (password == null) {
+                        LOG.ldapBackendPasswordAliasNotFound(alias, key, interceptorName);
+                    } else {
+                        interceptorConfig.put(key, new String(password));
+                        LOG.ldapBackendPasswordAliasResolved(key, interceptorName);
+                    }
+                } catch (AliasServiceException e) {
+                    LOG.ldapBackendPasswordAliasResolutionFailed(alias, key, interceptorName, e);
+                }
+            }
+        }
+    }
+
     /**
      * Start 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 ee2c26905..16e558d22 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
@@ -220,4 +220,13 @@ public interface LdapMessages {
 
     @Message(level = MessageLevel.ERROR, text = "Role lookup request to {0} failed with HTTP {1}, body: {2}")
     void restRolesLookupHttpError(String endpoint, int statusCode, String responseBody);
+
+    @Message(level = MessageLevel.DEBUG, text = "Resolved gateway credential store alias for LDAP backend property {0} (interceptor {1})")
+    void ldapBackendPasswordAliasResolved(String property, String interceptorName);
+
+    @Message(level = MessageLevel.ERROR, text = "No value found in the gateway credential store for alias {0} referenced by LDAP backend property {1} (interceptor {2}); the backend bind will fail until the alias is defined")
+    void ldapBackendPasswordAliasNotFound(String alias, String property, String interceptorName);
+
+    @Message(level = MessageLevel.ERROR, text = "Failed to resolve gateway credential store alias {0} for LDAP backend property {1} (interceptor {2}): {3}")
+    void ldapBackendPasswordAliasResolutionFailed(String alias, String property, String interceptorName, @StackTrace(level = MessageLevel.DEBUG) Exception e);
 }
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
index 8d39ab10c..c67610a0a 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
@@ -356,6 +356,63 @@ public class KnoxLDAPServerManagerTest {
                 new Dn(serverManager.directoryService.getSchemaManager(), interceptor.getBackend().getBaseDn()));
     }
 
+    @Test
+    public void testLdapBackendPasswordAliasIsResolved() throws Exception {
+        final String alias = "my-ad-password";
+        final String secret = "s3cr3t-ad-pass";
+        final String aliasRef = AliasService.ALIAS_PREFIX + alias + "}";
+
+        AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+        expect(aliasService.isAlias(aliasRef)).andReturn(true).anyTimes();
+        expect(aliasService.extractAlias(aliasRef)).andReturn(alias).anyTimes();
+        expect(aliasService.getPasswordFromAliasForGateway(alias)).andReturn(secret.toCharArray()).anyTimes();
+        replay(aliasService);
+        serverManager = new KnoxLDAPServerManager(aliasService);
+
+        Map<String, String> backendConfig = createLdapBackendInterceptorConfig();
+        backendConfig.put("systemPassword", aliasRef);
+
+        initializeWithLdapBackendConfig(backendConfig);
+
+        // createInterceptors mutates the interceptor config map in place before it reaches the backend.
+        assertEquals("Alias reference should be resolved to the credential-store secret",
+                secret, backendConfig.get("systemPassword"));
+    }
+
+    @Test
+    public void testLdapBackendLiteralPasswordIsUnchanged() throws Exception {
+        // Default alias service (setUp) resolves nothing; a literal password is not an alias.
+        Map<String, String> backendConfig = createLdapBackendInterceptorConfig();
+        assertEquals("admin-password", backendConfig.get("systemPassword"));
+
+        initializeWithLdapBackendConfig(backendConfig);
+
+        assertEquals("A literal password must be passed through untouched",
+                "admin-password", backendConfig.get("systemPassword"));
+    }
+
+    @Test
+    public void testLdapBackendUnresolvablePasswordAliasIsLeftUnchanged() throws Exception {
+        final String aliasRef = AliasService.ALIAS_PREFIX + "missing}";
+
+        AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+        expect(aliasService.isAlias(aliasRef)).andReturn(true).anyTimes();
+        expect(aliasService.extractAlias(aliasRef)).andReturn("missing").anyTimes();
+        expect(aliasService.getPasswordFromAliasForGateway("missing")).andReturn(null).anyTimes();
+        replay(aliasService);
+        serverManager = new KnoxLDAPServerManager(aliasService);
+
+        Map<String, String> backendConfig = createLdapBackendInterceptorConfig();
+        backendConfig.put("systemPassword", aliasRef);
+
+        initializeWithLdapBackendConfig(backendConfig);
+
+        // An unresolvable alias is left untouched (and logged at ERROR); the backend bind then fails
+        // naturally rather than the alias silently resolving to a different credential.
+        assertEquals("An unresolvable alias should be left as-is",
+                aliasRef, backendConfig.get("systemPassword"));
+    }
+
     @Test
     public void testStartWithMultipleBackends() throws Exception {
         GatewayConfig mockConfig = EasyMock.createNiceMock(GatewayConfig.class);
@@ -759,6 +816,19 @@ public class KnoxLDAPServerManagerTest {
         return config;
     }
 
+    /** Initialize the current server manager with a single "ldapbackend" interceptor using the given config. */
+    private void initializeWithLdapBackendConfig(Map<String, String> backendConfig) throws Exception {
+        GatewayConfig mockConfig = EasyMock.createNiceMock(GatewayConfig.class);
+        expect(mockConfig.getGatewayDataDir()).andReturn(tempWorkDir.getParent()).anyTimes();
+        expect(mockConfig.getLDAPPort()).andReturn(port).anyTimes();
+        expect(mockConfig.getLDAPBaseDN()).andReturn("dc=proxy,dc=com").anyTimes();
+        expect(mockConfig.getLDAPInterceptorNames()).andReturn(List.of("ldapbackend")).anyTimes();
+        expect(mockConfig.getLDAPInterceptorConfig("ldapbackend")).andReturn(backendConfig).anyTimes();
+        replay(mockConfig);
+
+        serverManager.initialize(mockConfig);
+    }
+
     private Map<String, String> createLdapBackendInterceptorConfig() {
         Map<String, String> config = new HashMap<>();
         config.put("interceptorType", "backend");
diff --git a/knox-site/docs/service_ldap_server.md b/knox-site/docs/service_ldap_server.md
index 3193852ba..9deb53275 100644
--- a/knox-site/docs/service_ldap_server.md
+++ b/knox-site/docs/service_ldap_server.md
@@ -199,7 +199,7 @@ The proxy backend delegates lookups to a remote LDAP or Active Directory server.
 | `gateway.ldap.interceptor.<name>.baseDn` | N/A | **Required**. The base DN of the LDAP proxy server. |
 | `gateway.ldap.interceptor.<name>.remoteBaseDn` | N/A | **Required**. The base DN of the remote LDAP server. |
 | `gateway.ldap.interceptor.<name>.systemUsername` | N/A | Bind DN for the remote server (alias: `bindDn`). |
-| `gateway.ldap.interceptor.<name>.systemPassword` | N/A | Password for the bind DN (alias: `bindPassword`). |
+| `gateway.ldap.interceptor.<name>.systemPassword` | N/A | Password for the bind DN (alias: `bindPassword`). May be a literal value or a credential store alias reference `S{ALIAS=...}` (see note below). |
 | `gateway.ldap.interceptor.<name>.userSearchBase` | `ou=people,{remoteBaseDn}` | Base DN for user searches on the remote server. |
 | `gateway.ldap.interceptor.<name>.groupSearchBase` | `ou=groups,{remoteBaseDn}` | Base DN for group searches on the remote server. |
 | `gateway.ldap.interceptor.<name>.userIdentifierAttribute` | `uid` | Attribute used for user lookup (e.g., `sAMAccountName` for AD). |
@@ -213,6 +213,21 @@ The proxy backend delegates lookups to a remote LDAP or Active Directory server.
 
 NOTE: If this value is undefined and the interceptor was created by the KnoxLDAPServerManager, the KnoxLDAPServerManager will set the `gateway.ldap.interceptor.<name>.maxResultSetSize` value to be 1 greater than the proxy's `gateway.ldap.max.size.limit` configuration. This will ensure that the proxy returns a "Size limit exceeded" result if the backend has more results than the proxy's limit.
 
+##### Credential store aliases for backend passwords
+
+The proxy backend's password properties (`systemPassword`/`bindPassword` and `trustStorePassword`) accept a credential store alias reference of the form `S{ALIAS=my-alias}` in addition to a literal value ([KNOX-3497](https://issues.apache.org/jira/browse/KNOX-3497)). When a value is an alias reference, Knox resolves it from the gateway credential store at startup so the secret does not need to live in cleartext in `gateway-site.xml`. Literal values are used as-is. If an alias cannot be r [...]
+
+```bash
+knoxcli.sh create-alias my_backend_bind_password --value <password>
+```
+
+```xml
+<property>
+    <name>gateway.ldap.interceptor.adexample.systemPassword</name>
+    <value>S{ALIAS=my_backend_bind_password}</value>
+</property>
+```
+
 ## Active Directory (AD) Integration
 
 The Knox LDAP Service includes several optimizations for working with Active Directory (improved in [KNOX-3277](https://issues.apache.org/jira/browse/KNOX-3277)):