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

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


The following commit(s) were added to refs/heads/master by this push:
     new 752e96c2fb3 HDDS-15776. Make S3 Gateway VirtualHostStyleFilter 
IPv6-safe (#10809)
752e96c2fb3 is described below

commit 752e96c2fb3960a1fa5e242517d3c777df1f9a90
Author: KUAN-HAO HUANG <[email protected]>
AuthorDate: Wed Aug 12 08:04:29 2026 +0800

    HDDS-15776. Make S3 Gateway VirtualHostStyleFilter IPv6-safe (#10809)
---
 .../hadoop/ozone/s3/VirtualHostStyleFilter.java    | 37 ++++++++++++--
 .../ozone/s3/TestVirtualHostStyleFilter.java       | 58 ++++++++++++++++++++++
 2 files changed, 90 insertions(+), 5 deletions(-)

diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java
index 083c2a19048..539c960e366 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/VirtualHostStyleFilter.java
@@ -20,6 +20,7 @@
 import static 
org.apache.hadoop.ozone.s3.S3GatewayConfigKeys.OZONE_S3G_DOMAIN_NAME;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.net.HostAndPort;
 import java.io.IOException;
 import java.net.URI;
 import java.util.Arrays;
@@ -61,6 +62,9 @@ public class VirtualHostStyleFilter implements 
ContainerRequestFilter {
   public void filter(ContainerRequestContext requestContext) throws
       IOException {
     domains = conf.getTrimmedStrings(OZONE_S3G_DOMAIN_NAME);
+    for (int i = 0; i < domains.length; i++) {
+      domains[i] = normalizeDomain(domains[i]);
+    }
 
     if (domains.length == 0) {
       // domains is not configured, might be it is path style.
@@ -143,12 +147,35 @@ private String getDomainName(String host) {
     return match;
   }
 
-  private String checkHostWithoutPort(String host) {
-    int portIndex = host.lastIndexOf(':');
-    if (portIndex >= 0) {
-      return host.substring(0, portIndex);
-    } else {
+  /**
+   * Strips a trailing port from the Host header, if present. Uses 
bracket-aware
+   * parsing so IPv6 literals are handled correctly: {@code [::1]:9878} and
+   * {@code [::1]} both yield {@code ::1}, and a bare {@code 2001:db8::1} 
(whose
+   * colons are part of the address, not a port) is returned unchanged. A plain
+   * {@code lastIndexOf(':')} would mistake an address segment for the port.
+   */
+  @VisibleForTesting
+  String checkHostWithoutPort(String host) {
+    try {
+      return HostAndPort.fromString(host).getHost();
+    } catch (IllegalArgumentException e) {
+      // Malformed Host header (e.g. unbalanced brackets): fall back to the raw
+      // value so it is rejected with a clear "no matching domain" error rather
+      // than failing with an internal error.
       return host;
     }
   }
+
+  /**
+   * Strips surrounding brackets from a configured IPv6 domain so it matches 
the
+   * unbracketed host produced by {@link #checkHostWithoutPort(String)}, 
letting
+   * {@code ozone.s3g.domain.name} be configured in either form. For example
+   * {@code [::1]} becomes {@code ::1}; other values are returned unchanged.
+   */
+  private static String normalizeDomain(String domain) {
+    if (domain.length() > 1 && domain.startsWith("[") && domain.endsWith("]")) 
{
+      return domain.substring(1, domain.length() - 1);
+    }
+    return domain;
+  }
 }
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java
index 6120c349857..5c0edb6f084 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestVirtualHostStyleFilter.java
@@ -179,4 +179,62 @@ public void testVirtualHostStyleWithInvalidInputs(String 
hostAddress,
         () -> virtualHostStyleFilter.filter(requestContext));
     assertThat(exception).hasMessageContaining(expectErrorMessage);
   }
+
+  @ParameterizedTest
+  @CsvSource(value = {
+      // hostname / IPv4, with and without a port
+      "s3g.example.com,s3g.example.com",
+      "s3g.example.com:9878,s3g.example.com",
+      "bucket.s3g.example.com:9878,bucket.s3g.example.com",
+      "192.168.1.10,192.168.1.10",
+      "192.168.1.10:9878,192.168.1.10",
+      // bracketed IPv6 literal, with and without a port
+      "[::1],::1",
+      "[::1]:9878,::1",
+      "[2001:db8::1],2001:db8::1",
+      "[2001:db8::1]:9878,2001:db8::1",
+      // bare IPv6 literal has no port to strip; must be returned unchanged
+      "::1,::1",
+      "2001:db8::1,2001:db8::1",
+      // malformed host (unbalanced bracket) falls back to the raw value
+      "[::1,[::1",
+  })
+  public void testCheckHostWithoutPort(String host, String expected) {
+    assertThat(new VirtualHostStyleFilter().checkHostWithoutPort(host))
+        .isEqualTo(expected);
+  }
+
+  @Test
+  public void testVirtualHostStyleWithoutPort() throws Exception {
+    VirtualHostStyleFilter virtualHostStyleFilter = new 
VirtualHostStyleFilter();
+    virtualHostStyleFilter.setConfiguration(conf);
+
+    // Host header without a port still resolves the bucket.
+    ContainerRequestContext requestContext =
+        createRequestContext("mybucket.localhost", "/myfile");
+    virtualHostStyleFilter.filter(requestContext);
+    URI expected = new URI("http://"; + s3HttpAddr + "/mybucket/myfile");
+    verify(requestContext).setRequestUri(new URI("http://"; + s3HttpAddr), 
expected);
+  }
+
+  /**
+   * {@code ozone.s3g.domain.name} configured as an IPv6 literal must match an
+   * IPv6 Host header whether it is configured with or without brackets. Before
+   * normalization a bracketed config ({@code [::1]}) failed to match
+   * {@code Host: [::1]:9878}, which {@code getHost()} reduces to {@code ::1}.
+   */
+  @ParameterizedTest
+  @CsvSource(value = {"[::1]", "::1"})
+  public void testPathStyleWithIPv6Domain(String configuredDomain)
+      throws Exception {
+    conf.set(S3GatewayConfigKeys.OZONE_S3G_DOMAIN_NAME, configuredDomain);
+    VirtualHostStyleFilter virtualHostStyleFilter = new 
VirtualHostStyleFilter();
+    virtualHostStyleFilter.setConfiguration(conf);
+
+    // Path-style request whose Host matches the IPv6 domain is left unchanged.
+    ContainerRequestContext requestContext = 
mock(ContainerRequestContext.class);
+    
when(requestContext.getHeaderString(HttpHeaders.HOST)).thenReturn("[::1]:9878");
+    virtualHostStyleFilter.filter(requestContext);
+    verify(requestContext, never()).setRequestUri(any(URI.class), 
any(URI.class));
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to