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]