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

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


The following commit(s) were added to refs/heads/master by this push:
     new c631e8314 RANGER-5689: Standardize TLS hostname verification across 
Ranger HTTP clients using a uniform verifier (#1076)
c631e8314 is described below

commit c631e8314b70b01cd5bb5bb2bf4ba17985da585a
Author: Vyom Mani Tiwari <[email protected]>
AuthorDate: Wed Jul 15 17:55:04 2026 +0530

    RANGER-5689: Standardize TLS hostname verification across Ranger HTTP 
clients using a uniform verifier (#1076)
---
 .../plugin/util/RangerDefaultHostnameVerifier.java |  45 ++++++
 .../ranger/plugin/util/RangerRESTClient.java       |   7 +-
 .../apache/ranger/plugin/util/RangerSslHelper.java |   2 +-
 .../util/TestRangerDefaultHostnameVerifier.java    | 151 +++++++++++++++++++++
 .../src/test/resources/generate-test-keystores.sh  |  50 +++++++
 .../admin/client/RangerAdminJersey2RESTClient.java |   3 +-
 .../process/RangerUgSyncRESTClient.java            |   9 +-
 7 files changed, 252 insertions(+), 15 deletions(-)

diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerDefaultHostnameVerifier.java
 
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerDefaultHostnameVerifier.java
new file mode 100644
index 000000000..e8c74b360
--- /dev/null
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerDefaultHostnameVerifier.java
@@ -0,0 +1,45 @@
+/*
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.ranger.plugin.util;
+
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSession;
+
+public class RangerDefaultHostnameVerifier implements HostnameVerifier {
+    private static final Logger LOG = 
LoggerFactory.getLogger(RangerDefaultHostnameVerifier.class);
+    // Thread-safe, stateless, and standard Apache implementation
+    private static final HostnameVerifier DELEGATE = new 
DefaultHostnameVerifier();
+
+    @Override
+    public boolean verify(String hostname, SSLSession session) {
+        if (hostname == null || session == null) {
+            return false;
+        }
+        boolean verified = DELEGATE.verify(hostname, session);
+        if (!verified && LOG.isWarnEnabled()) {
+            LOG.warn("Hostname verification failed for [{}]: certificate 
identity does not match requested host", hostname);
+        }
+        return verified;
+    }
+}
diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
 
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
index a43e6573f..d4d49523b 100644
--- 
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerRESTClient.java
@@ -36,7 +36,6 @@
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.KeyManagerFactory;
 import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSession;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
 import javax.ws.rs.ProcessingException;
@@ -616,11 +615,7 @@ private Client buildClient() {
                 KeyManager[]     kmList     = getKeyManagers();
                 TrustManager[]   tmList     = getTrustManagers();
                 SSLContext       sslContext = getSSLContext(kmList, tmList);
-                HostnameVerifier hv = new HostnameVerifier() {
-                    public boolean verify(String urlHostName, SSLSession 
session) {
-                        return session.getPeerHost().equals(urlHostName);
-                    }
-                };
+                HostnameVerifier hv = new RangerDefaultHostnameVerifier();
 
                 // Use RangerJersey2ClientBuilder instead of unsafe 
ClientBuilder.newBuilder() to prevent MOXy usage
                 clientBuilder = RangerJersey2ClientBuilder.newBuilder()
diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerSslHelper.java
 
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerSslHelper.java
index 0d7415a4f..2fcbbca68 100644
--- 
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerSslHelper.java
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerSslHelper.java
@@ -60,7 +60,7 @@ public class RangerSslHelper {
     static final String           RANGER_SSL_KEYMANAGER_ALGO_TYPE              
     = KeyManagerFactory.getDefaultAlgorithm();
     static final String           RANGER_SSL_TRUSTMANAGER_ALGO_TYPE            
     = TrustManagerFactory.getDefaultAlgorithm();
     static final String           RANGER_SSL_CONTEXT_ALGO_TYPE                 
     = "TLS";
-    static final HostnameVerifier _Hv                                          
     = (urlHostName, session) -> session.getPeerHost().equals(urlHostName);
+    static final HostnameVerifier _Hv                                          
     = new RangerDefaultHostnameVerifier();
 
     final   String mSslConfigFileName;
     private String mKeyStoreURL;
diff --git 
a/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerDefaultHostnameVerifier.java
 
b/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerDefaultHostnameVerifier.java
new file mode 100644
index 000000000..002d679a9
--- /dev/null
+++ 
b/agents-common/src/test/java/org/apache/ranger/plugin/util/TestRangerDefaultHostnameVerifier.java
@@ -0,0 +1,151 @@
+/*
+ * 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
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.ranger.plugin.util;
+
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import java.io.FileInputStream;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TestRangerDefaultHostnameVerifier {
+    private static final RangerDefaultHostnameVerifier VERIFIER = new 
RangerDefaultHostnameVerifier();
+    private static Path keystoreDir;
+    private static HttpsServer mismatchedHostServer;
+    private static HttpsServer matchedHostServer;
+    private static HttpsServer wildcardHostServer;
+
+    @BeforeAll
+    static void generateKeystoresAndStartServers() throws Exception {
+        keystoreDir = 
Files.createTempDirectory("ranger-hostname-verifier-test");
+
+        String scriptPath = TestRangerDefaultHostnameVerifier.class
+                .getClassLoader()
+                .getResource("generate-test-keystores.sh")
+                .getPath();
+
+        ProcessBuilder pb = new ProcessBuilder("bash", scriptPath, 
keystoreDir.toString());
+        pb.redirectErrorStream(true);
+        Process process = pb.start();
+
+        String output = new String(process.getInputStream().readAllBytes());
+        int exitCode = process.waitFor();
+        if (exitCode != 0) {
+            throw new IllegalStateException("generate-test-keystores.sh failed 
(exit=" + exitCode + "):\n" + output);
+        }
+
+        mismatchedHostServer = startServer("attacker-cert.jks");
+        matchedHostServer = startServer("localhost-cert.jks");
+        wildcardHostServer = startServer("wildcard-cert.jks");
+    }
+
+    @AfterAll
+    static void stopServersAndCleanup() throws Exception {
+        mismatchedHostServer.stop(0);
+        matchedHostServer.stop(0);
+        wildcardHostServer.stop(0);
+        try (var files = Files.walk(keystoreDir)) {
+            files.sorted(java.util.Comparator.reverseOrder()).forEach(p -> 
p.toFile().delete());
+        }
+    }
+
+    @Test
+    void rejectsCertificateIssuedForDifferentHostname() throws Exception {
+        assertFalse(VERIFIER.verify("localhost", 
handshake(mismatchedHostServer)));
+    }
+
+    @Test
+    void acceptsCertificateMatchingHostname() throws Exception {
+        assertTrue(VERIFIER.verify("localhost", handshake(matchedHostServer)));
+    }
+
+    @Test
+    void acceptsWildcardSanForSingleLabel() throws Exception {
+        assertTrue(VERIFIER.verify("foo.example.test", 
handshake(wildcardHostServer)));
+    }
+
+    @Test
+    void rejectsWildcardSanForMultiLabel() throws Exception {
+        assertFalse(VERIFIER.verify("a.b.example.test", 
handshake(wildcardHostServer)));
+    }
+
+    @Test
+    void rejectsNullHostnameOrSession() {
+        assertFalse(VERIFIER.verify(null, null));
+    }
+
+    private static HttpsServer startServer(String keystoreFileName) throws 
Exception {
+        char[] pw = "changeit".toCharArray();
+        KeyStore ks = KeyStore.getInstance("JKS");
+        try (FileInputStream in = new 
FileInputStream(keystoreDir.resolve(keystoreFileName).toFile())) {
+            ks.load(in, pw);
+        }
+
+        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+        kmf.init(ks, pw);
+
+        SSLContext ctx = SSLContext.getInstance("TLS");
+        ctx.init(kmf.getKeyManagers(), null, null);
+
+        HttpsServer server = HttpsServer.create(new InetSocketAddress(0), 0);
+        server.setHttpsConfigurator(new HttpsConfigurator(ctx));
+        server.createContext("/", exchange -> {
+            exchange.sendResponseHeaders(200, 0);
+            exchange.close();
+        });
+        server.start();
+        return server;
+    }
+
+    private static SSLSession handshake(HttpsServer server) throws Exception {
+        TrustManager trustAllCerts = new X509TrustManager() {
+            public X509Certificate[] getAcceptedIssuers() {
+                return new X509Certificate[0];
+            }
+
+            public void checkClientTrusted(X509Certificate[] certs, String 
authType) {
+            }
+
+            public void checkServerTrusted(X509Certificate[] certs, String 
authType) {
+            }
+        };
+        SSLContext ctx = SSLContext.getInstance("TLS");
+        ctx.init(null, new TrustManager[] {trustAllCerts}, null);
+        try (SSLSocket socket = (SSLSocket) 
ctx.getSocketFactory().createSocket("localhost", 
server.getAddress().getPort())) {
+            socket.startHandshake();
+            return socket.getSession();
+        }
+    }
+}
diff --git a/agents-common/src/test/resources/generate-test-keystores.sh 
b/agents-common/src/test/resources/generate-test-keystores.sh
new file mode 100755
index 000000000..197b06fb7
--- /dev/null
+++ b/agents-common/src/test/resources/generate-test-keystores.sh
@@ -0,0 +1,50 @@
+#!/usr/bin/env bash
+#
+# 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
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+
+# Generates throwaway test keystores for TestRangerDefaultHostnameVerifier.
+set -euo pipefail
+
+OUT_DIR="${1:?Usage: generate-test-keystores.sh <output-dir>}"
+PASS="changeit"
+
+mkdir -p "$OUT_DIR"
+cd "$OUT_DIR"
+
+rm -f ca.jks ca.pem *.jks *.csr *.pem 2>/dev/null || true
+
+# --- CA ---
+keytool -genkeypair -alias fakeCA -keyalg RSA -keysize 2048 -validity 30 
-dname "CN=Ranger Test CA" -ext bc:c=ca:true -keystore ca.jks -storepass 
"$PASS" -keypass "$PASS" -noprompt
+keytool -exportcert -alias fakeCA -keystore ca.jks -storepass "$PASS" -rfc 
-file ca.pem
+
+# --- helper: issue a leaf cert with given CN/SAN into its own keystore ---
+issue_cert() {
+  local alias="$1" keystore="$2" dname="$3" san="$4"
+  keytool -genkeypair -alias "$alias" -keyalg RSA -keysize 2048 -validity 30 
-dname "$dname" -keystore "$keystore" -storepass "$PASS" -keypass "$PASS" 
-noprompt
+  keytool -certreq -alias "$alias" -keystore "$keystore" -storepass "$PASS" 
-file "$alias.csr"
+  keytool -gencert -alias fakeCA -keystore ca.jks -storepass "$PASS" -infile 
"$alias.csr" -outfile "$alias.pem" -ext "$san" -validity 30
+  keytool -importcert -alias fakeCA -keystore "$keystore" -storepass "$PASS" 
-file ca.pem -noprompt
+  keytool -importcert -alias "$alias" -keystore "$keystore" -storepass "$PASS" 
-file "$alias.pem" -noprompt
+}
+
+issue_cert serverkey attacker-cert.jks  "CN=attacker.internal"    
"san=dns:attacker.internal"
+issue_cert serverkey localhost-cert.jks "CN=localhost"            
"san=dns:localhost"
+issue_cert serverkey wildcard-cert.jks  "CN=*.example.test"       
"san=dns:*.example.test"
+
+echo "Generated attacker-cert.jks, localhost-cert.jks, wildcard-cert.jks in 
$OUT_DIR"
diff --git 
a/knox-agent/src/main/java/org/apache/ranger/admin/client/RangerAdminJersey2RESTClient.java
 
b/knox-agent/src/main/java/org/apache/ranger/admin/client/RangerAdminJersey2RESTClient.java
index 991e9b255..bc57cda73 100644
--- 
a/knox-agent/src/main/java/org/apache/ranger/admin/client/RangerAdminJersey2RESTClient.java
+++ 
b/knox-agent/src/main/java/org/apache/ranger/admin/client/RangerAdminJersey2RESTClient.java
@@ -32,6 +32,7 @@
 import org.apache.ranger.authorization.utils.StringUtil;
 import org.apache.ranger.plugin.util.GrantRevokeRequest;
 import org.apache.ranger.plugin.util.RangerCommonConstants;
+import org.apache.ranger.plugin.util.RangerDefaultHostnameVerifier;
 import org.apache.ranger.plugin.util.RangerJersey2ClientBuilder;
 import org.apache.ranger.plugin.util.RangerPluginCapability;
 import org.apache.ranger.plugin.util.RangerRESTUtils;
@@ -413,7 +414,7 @@ Client buildClient() {
             }
 
             if (hv == null) {
-                hv = (urlHostName, session) -> 
session.getPeerHost().equals(urlHostName);
+                hv = new RangerDefaultHostnameVerifier();
             }
 
             // Use RangerJersey2ClientBuilder instead of unsafe ClientBuilder 
patterns to prevent MOXy usage
diff --git 
a/ugsync/src/main/java/org/apache/ranger/unixusersync/process/RangerUgSyncRESTClient.java
 
b/ugsync/src/main/java/org/apache/ranger/unixusersync/process/RangerUgSyncRESTClient.java
index 53a4261d4..597a87ccc 100644
--- 
a/ugsync/src/main/java/org/apache/ranger/unixusersync/process/RangerUgSyncRESTClient.java
+++ 
b/ugsync/src/main/java/org/apache/ranger/unixusersync/process/RangerUgSyncRESTClient.java
@@ -21,6 +21,7 @@
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.security.SecureClientLogin;
+import org.apache.ranger.plugin.util.RangerDefaultHostnameVerifier;
 import org.apache.ranger.plugin.util.RangerJersey2ClientBuilder;
 import org.apache.ranger.plugin.util.RangerRESTClient;
 import org.apache.ranger.unixusersync.config.UserGroupSyncConfig;
@@ -30,7 +31,6 @@
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSession;
 import javax.net.ssl.TrustManager;
 import javax.ws.rs.client.Client;
 import javax.ws.rs.core.HttpHeaders;
@@ -56,12 +56,7 @@ public RangerUgSyncRESTClient(String policyMgrBaseUrls, 
String ugKeyStoreFile, S
             TrustManager[] tmList     = getTrustManagers(ugTrustStoreFile, 
ugTrustStoreFilepwd);
             SSLContext     sslContext = getSSLContext(kmList, tmList);
 
-            HostnameVerifier hv = new HostnameVerifier() {
-                @Override
-                public boolean verify(String urlHostName, SSLSession session) {
-                    return session.getPeerHost().equals(urlHostName);
-                }
-            };
+            HostnameVerifier hv = new RangerDefaultHostnameVerifier();
 
             ClientConfig config = new ClientConfig();
             RangerJersey2ClientBuilder.applyAntiMoxyConfiguration(config);

Reply via email to