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);