This is an automated email from the ASF dual-hosted git repository.
taklwu pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-2.6 by this push:
new cf8c75ff3d4 HBASE-30257 Fix case-sensitive hostname check in region
server (#8413) (#8431)
cf8c75ff3d4 is described below
commit cf8c75ff3d4ed63188615c5d1bc744ac2dd37623
Author: Tak Lon (Stephen) Wu <[email protected]>
AuthorDate: Mon Jul 6 20:46:17 2026 -0700
HBASE-30257 Fix case-sensitive hostname check in region server (#8413)
(#8431)
---
.../hbase/io/crypto/tls/HBaseHostnameVerifier.java | 50 +++++-----------------
.../java/org/apache/hadoop/hbase/util/Strings.java | 48 +++++++++++++++++++++
.../org/apache/hadoop/hbase/util/TestStrings.java | 48 +++++++++++++++++++++
.../hadoop/hbase/regionserver/HRegionServer.java | 6 ++-
4 files changed, 110 insertions(+), 42 deletions(-)
diff --git
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/HBaseHostnameVerifier.java
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/HBaseHostnameVerifier.java
index a703f5ff630..60ec927cfcf 100644
---
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/HBaseHostnameVerifier.java
+++
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/HBaseHostnameVerifier.java
@@ -17,7 +17,6 @@
*/
package org.apache.hadoop.hbase.io.crypto.tls;
-import java.net.InetAddress;
import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
@@ -28,7 +27,6 @@ import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Objects;
-import java.util.Optional;
import javax.naming.InvalidNameException;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
@@ -40,12 +38,11 @@ import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.security.auth.x500.X500Principal;
+import org.apache.hadoop.hbase.util.Strings;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.apache.hbase.thirdparty.com.google.common.net.InetAddresses;
-
/**
* When enabled in {@link X509Util}, handles verifying that the hostname of a
peer matches the
* certificate it presents.
@@ -112,9 +109,8 @@ final class HBaseHostnameVerifier implements
HostnameVerifier {
void verify(final String host, final X509Certificate cert) throws
SSLException {
final List<SubjectName> subjectAlts = getSubjectAltNames(cert);
if (subjectAlts != null && !subjectAlts.isEmpty()) {
- Optional<InetAddress> inetAddress = parseIpAddress(host);
- if (inetAddress.isPresent()) {
- matchIPAddress(host, inetAddress.get(), subjectAlts);
+ if (Strings.isInetAddress(host)) {
+ matchIPAddress(host, subjectAlts);
} else {
matchDNSName(host, subjectAlts);
}
@@ -131,14 +127,14 @@ final class HBaseHostnameVerifier implements
HostnameVerifier {
}
}
- private static void matchIPAddress(final String host, final InetAddress
inetAddress,
- final List<SubjectName> subjectAlts) throws SSLException {
+ private static void matchIPAddress(final String host, final
List<SubjectName> subjectAlts)
+ throws SSLException {
for (final SubjectName subjectAlt : subjectAlts) {
- if (subjectAlt.getType() == SubjectName.IP) {
- Optional<InetAddress> parsed = parseIpAddress(subjectAlt.getValue());
- if (parsed.filter(altAddr -> altAddr.equals(inetAddress)).isPresent())
{
- return;
- }
+ if (
+ subjectAlt.getType() == SubjectName.IP
+ && Strings.hostnamesEqual(host, subjectAlt.getValue())
+ ) {
+ return;
}
}
throw new SSLPeerUnverifiedException("Certificate for <" + host + ">
doesn't match any "
@@ -230,32 +226,6 @@ final class HBaseHostnameVerifier implements
HostnameVerifier {
}
}
- private static Optional<InetAddress> parseIpAddress(String host) {
- host = host.trim();
- // Uri strings only work for ipv6 and are wrapped with brackets
- // Unfortunately InetAddresses can't handle a mixed input, so we
- // check here and choose which parse method to use.
- if (host.startsWith("[") && host.endsWith("]")) {
- return parseIpAddressUriString(host);
- } else {
- return parseIpAddressString(host);
- }
- }
-
- private static Optional<InetAddress> parseIpAddressUriString(String host) {
- if (InetAddresses.isUriInetAddress(host)) {
- return Optional.of(InetAddresses.forUriString(host));
- }
- return Optional.empty();
- }
-
- private static Optional<InetAddress> parseIpAddressString(String host) {
- if (InetAddresses.isInetAddress(host)) {
- return Optional.of(InetAddresses.forString(host));
- }
- return Optional.empty();
- }
-
@SuppressWarnings("MixedMutabilityReturnType")
private static List<SubjectName> getSubjectAltNames(final X509Certificate
cert) {
try {
diff --git
a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/Strings.java
b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/Strings.java
index c1d860317b8..d35232e8448 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/Strings.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/Strings.java
@@ -17,11 +17,14 @@
*/
package org.apache.hadoop.hbase.util;
+import java.net.InetAddress;
+import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
import org.apache.hbase.thirdparty.com.google.common.base.Splitter;
+import org.apache.hbase.thirdparty.com.google.common.net.InetAddresses;
/**
* Utility for Strings.
@@ -79,6 +82,51 @@ public final class Strings {
return dnPtr.endsWith(".") ? dnPtr.substring(0, dnPtr.length() - 1) :
dnPtr;
}
+ /**
+ * Returns whether the given string is an IP address, including bracketed
IPv6 URI form.
+ * @param host hostname or IP
+ * @return {@code true} if {@code host} is an IP address
+ * @throws NullPointerException if {@code host} is {@code null}
+ */
+ public static boolean isInetAddress(String host) {
+ Objects.requireNonNull(host, "Hostname or IP cannot be null");
+ host = host.trim();
+ if (host.startsWith("[") && host.endsWith("]")) {
+ return InetAddresses.isUriInetAddress(host);
+ }
+ return InetAddresses.isInetAddress(host);
+ }
+
+ private static InetAddress parseInetAddress(String host) {
+ host = host.trim();
+ if (host.startsWith("[") && host.endsWith("]")) {
+ return InetAddresses.forUriString(host);
+ }
+ return InetAddresses.forString(host);
+ }
+
+ /**
+ * Compare two host identifiers for equality. DNS hostnames are compared
case-insensitively
+ * because DNS labels are case-insensitive. IP address literals are compared
by numeric address.
+ * @param left first hostname or IP
+ * @param right second hostname or IP
+ * @return {@code true} if both refer to the same host identifier
+ * @throws NullPointerException if either argument is {@code null}
+ */
+ public static boolean hostnamesEqual(String left, String right) {
+ Objects.requireNonNull(left, "Hostname or IP cannot be null");
+ Objects.requireNonNull(right, "Hostname or IP cannot be null");
+ boolean leftIsIp = isInetAddress(left);
+ boolean rightIsIp = isInetAddress(right);
+ if (leftIsIp != rightIsIp) {
+ return false;
+ }
+ if (leftIsIp) {
+ return parseInetAddress(left).equals(parseInetAddress(right));
+ }
+ return left.equalsIgnoreCase(right);
+ }
+
/**
* Push the input string to the right by appending a character before it,
usually a space.
* @param input the string to pad
diff --git
a/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestStrings.java
b/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestStrings.java
new file mode 100644
index 00000000000..be5d8aa97b9
--- /dev/null
+++ b/hbase-common/src/test/java/org/apache/hadoop/hbase/util/TestStrings.java
@@ -0,0 +1,48 @@
+/*
+ * 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.hadoop.hbase.util;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hadoop.hbase.testclassification.MiscTests;
+import org.apache.hadoop.hbase.testclassification.SmallTests;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+@Tag(MiscTests.TAG)
+@Tag(SmallTests.TAG)
+public class TestStrings {
+
+ @Test
+ public void testHostnamesEqual() {
+ assertTrue(Strings.hostnamesEqual("HOST.example.com", "host.example.com"));
+ assertTrue(Strings.hostnamesEqual("rs1", "RS1"));
+ assertFalse(Strings.hostnamesEqual("host-a.example.com",
"host-b.example.com"));
+ assertTrue(Strings.hostnamesEqual("10.0.0.1", "10.0.0.1"));
+ assertFalse(Strings.hostnamesEqual("10.0.0.1", "10.0.0.2"));
+ assertFalse(Strings.hostnamesEqual("HOST.example.com", "10.0.0.1"));
+ assertTrue(Strings.hostnamesEqual("::1", "0:0:0:0:0:0:0:1"));
+
assertTrue(Strings.hostnamesEqual("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334"));
+ assertThrows(NullPointerException.class, () ->
Strings.hostnamesEqual(null, "host"));
+ assertThrows(NullPointerException.class, () ->
Strings.hostnamesEqual("host", null));
+ }
+
+}
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
index c2491cd02e4..d59b1b222fd 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
@@ -199,6 +199,7 @@ import org.apache.hadoop.hbase.util.RetryCounter;
import org.apache.hadoop.hbase.util.RetryCounterFactory;
import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
import org.apache.hadoop.hbase.util.Sleeper;
+import org.apache.hadoop.hbase.util.Strings;
import org.apache.hadoop.hbase.util.Threads;
import org.apache.hadoop.hbase.util.VersionInfo;
import org.apache.hadoop.hbase.wal.AbstractFSWALProvider;
@@ -1718,8 +1719,9 @@ public class HRegionServer extends Thread
expectedHostName =
rpcServices.getSocketAddress().getAddress().getHostAddress();
}
boolean isHostnameConsist =
StringUtils.isBlank(useThisHostnameInstead)
- ? hostnameFromMasterPOV.equals(expectedHostName)
- : hostnameFromMasterPOV.equals(useThisHostnameInstead);
+ ? Strings.hostnamesEqual(hostnameFromMasterPOV, expectedHostName)
+ : Strings.hostnamesEqual(hostnameFromMasterPOV,
useThisHostnameInstead);
+
if (!isHostnameConsist) {
String msg = "Master passed us a different hostname to use; was="
+ (StringUtils.isBlank(useThisHostnameInstead)