This is an automated email from the ASF dual-hosted git repository.
taklwu pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-2 by this push:
new 04ba0f9c10f HBASE-30257 Fix case-sensitive hostname check in region
server (#8413) (#8430)
04ba0f9c10f is described below
commit 04ba0f9c10fdf83d3a497d7cd6bf2f8f603425ef
Author: Tak Lon (Stephen) Wu <[email protected]>
AuthorDate: Mon Jul 6 20:45:37 2026 -0700
HBASE-30257 Fix case-sensitive hostname check in region server (#8413)
(#8430)
Signed-off-by: Andor Molnár <[email protected]>
---
.../hbase/io/crypto/tls/HBaseHostnameVerifier.java | 50 +++++-----------------
.../java/org/apache/hadoop/hbase/util/Strings.java | 48 +++++++++++++++++++++
.../org/apache/hadoop/hbase/util/TestStrings.java | 16 +++++++
.../hadoop/hbase/regionserver/HRegionServer.java | 6 ++-
4 files changed, 78 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 49f8d97e744..2f8aa6952ab 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
@@ -18,11 +18,13 @@
package org.apache.hadoop.hbase.util;
import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.hadoop.conf.Configuration;
@@ -30,6 +32,7 @@ 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.
@@ -87,6 +90,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
index da388fda9b2..c428e7a0a57 100644
--- 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
@@ -18,6 +18,7 @@
package org.apache.hadoop.hbase.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -57,6 +58,21 @@ public class TestStrings {
assertEquals("foo.bar.com",
Strings.domainNamePointerToHostName("foo.bar.com."));
}
+ @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));
+ }
+
@Test
public void testPadFront() {
assertEquals("ddfoo", Strings.padFront("foo", 'd', 5));
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)