This is an automated email from the ASF dual-hosted git repository.
JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git
The following commit(s) were added to refs/heads/master by this push:
new d4d62558a7 [oss] Add fs.oss.cname.enabled so non-Aliyun endpoints sign
the bucket (#9238)
d4d62558a7 is described below
commit d4d62558a78d7adec3dea0665298e525ebdb5e8b
Author: ZIHAN DAI <[email protected]>
AuthorDate: Mon Aug 17 15:31:16 2026 +1000
[oss] Add fs.oss.cname.enabled so non-Aliyun endpoints sign the bucket
(#9238)
---
.../main/java/org/apache/paimon/oss/OSSFileIO.java | 31 +++++++++++
.../java/org/apache/paimon/oss/OSSFileIOTest.java | 60 ++++++++++++++++++++++
2 files changed, 91 insertions(+)
diff --git
a/paimon-filesystems/paimon-oss-impl/src/main/java/org/apache/paimon/oss/OSSFileIO.java
b/paimon-filesystems/paimon-oss-impl/src/main/java/org/apache/paimon/oss/OSSFileIO.java
index 1e25b8f695..5a3b48a5c5 100644
---
a/paimon-filesystems/paimon-oss-impl/src/main/java/org/apache/paimon/oss/OSSFileIO.java
+++
b/paimon-filesystems/paimon-oss-impl/src/main/java/org/apache/paimon/oss/OSSFileIO.java
@@ -83,6 +83,13 @@ public class OSSFileIO extends HadoopCompliantFileIO
implements HadoopOptionsPro
private static final String OSS_ACCESS_KEY_SECRET =
"fs.oss.accessKeySecret";
private static final String OSS_SECURITY_TOKEN = "fs.oss.securityToken";
private static final String OSS_SECOND_LEVEL_DOMAIN_ENABLED =
"fs.oss.sld.enabled";
+
+ /**
+ * Set to false for an OSS-compatible endpoint that is neither an official
Aliyun domain nor a
+ * CNAME custom domain. The SDK otherwise treats such a host as a CNAME
and drops the bucket
+ * from the host it signs, which the server rejects with
SignatureDoesNotMatch.
+ */
+ private static final String OSS_CNAME_ENABLED = "fs.oss.cname.enabled";
// Paimon OSS SSE keys, mapping 1:1 to the OSS headers; they take
precedence over hadoop's
// server-side-encryption-algorithm.
/** SSE method -> x-oss-server-side-encryption (AES256 / KMS / SM4). */
@@ -209,6 +216,10 @@ public class OSSFileIO extends HadoopCompliantFileIO
implements HadoopOptionsPro
enableSecondLevelDomain(fs);
}
+ if (!hadoopOptions.getBoolean(OSS_CNAME_ENABLED, true)) {
+ disableCname(fs);
+ }
+
SseConfig sse = configuredSse();
if (sse != null) {
enableSse(fs, sse);
@@ -294,6 +305,26 @@ public class OSSFileIO extends HadoopCompliantFileIO
implements HadoopOptionsPro
}
}
+ public void disableCname(AliyunOSSFileSystem fs) {
+ try {
+ setSupportCname(getOssClient(fs), false);
+ } catch (Exception e) {
+ LOG.error("Failed to disable CNAME support.", e);
+ throw new RuntimeException("Failed to disable CNAME support.", e);
+ }
+ }
+
+ /**
+ * Flip the SDK's CNAME heuristic; package-private so a test pins the
reflected name. The
+ * configuration reached here is the one {@code OSSRequestMessageBuilder}
reads while building
+ * every request, so this takes effect on an already-initialized client.
+ */
+ static void setSupportCname(OSSClient ossClient, boolean supportCname)
throws Exception {
+ ServiceClient serviceClient =
+ ReflectionUtils.getPrivateFieldValue(ossClient,
"serviceClient");
+ serviceClient.getClientConfiguration().setSupportCname(supportCname);
+ }
+
/** Reflectively extract the underlying {@link OSSClient} that
hadoop-aliyun keeps private. */
private static OSSClient getOssClient(AliyunOSSFileSystem fs) throws
Exception {
AliyunOSSFileSystemStore store = fs.getStore();
diff --git
a/paimon-filesystems/paimon-oss-impl/src/test/java/org/apache/paimon/oss/OSSFileIOTest.java
b/paimon-filesystems/paimon-oss-impl/src/test/java/org/apache/paimon/oss/OSSFileIOTest.java
index 3366dd7f01..0e68472e8a 100644
---
a/paimon-filesystems/paimon-oss-impl/src/test/java/org/apache/paimon/oss/OSSFileIOTest.java
+++
b/paimon-filesystems/paimon-oss-impl/src/test/java/org/apache/paimon/oss/OSSFileIOTest.java
@@ -33,6 +33,7 @@ import com.aliyun.oss.common.comm.RequestMessage;
import com.aliyun.oss.common.comm.ResponseMessage;
import com.aliyun.oss.common.comm.RetryStrategy;
import com.aliyun.oss.common.comm.ServiceClient;
+import com.aliyun.oss.internal.OSSUtils;
import com.aliyun.oss.model.AbortMultipartUploadRequest;
import com.aliyun.oss.model.CompleteMultipartUploadRequest;
import com.aliyun.oss.model.CopyObjectRequest;
@@ -543,6 +544,65 @@ public class OSSFileIOTest {
.containsEntry("x-oss-server-side-data-encryption", "SM4");
}
+ /**
+ * The SDK reads {@code isSupportCname()} while building every request, so
an endpoint outside
+ * its exclude list loses the bucket from the host that gets signed.
Disabling the heuristic
+ * must put the bucket back.
+ */
+ @Test
+ public void testDisableCnameRestoresTheBucketInTheSignedHost() throws
Exception {
+ String endpoint = "http://oss-cn-x.inter.env99.example.com";
+ OSSClient client = hadoopStyleClient(endpoint);
+ try {
+ assertThat(signedHost(client, endpoint, "my-bucket"))
+ .isEqualTo("oss-cn-x.inter.env99.example.com");
+
+ OSSFileIO.setSupportCname(client, false);
+
+ assertThat(signedHost(client, endpoint, "my-bucket"))
+ .isEqualTo("my-bucket.oss-cn-x.inter.env99.example.com");
+ } finally {
+ client.shutdown();
+ }
+ }
+
+ /** A public-cloud endpoint already signs the bucket, and the flag must
not disturb it. */
+ @Test
+ public void testDisableCnameLeavesPublicCloudEndpointsAlone() throws
Exception {
+ String endpoint = "http://oss-cn-hangzhou.aliyuncs.com";
+ OSSClient client = hadoopStyleClient(endpoint);
+ try {
+ assertThat(signedHost(client, endpoint, "my-bucket"))
+ .isEqualTo("my-bucket.oss-cn-hangzhou.aliyuncs.com");
+
+ OSSFileIO.setSupportCname(client, false);
+
+ assertThat(signedHost(client, endpoint, "my-bucket"))
+ .isEqualTo("my-bucket.oss-cn-hangzhou.aliyuncs.com");
+ } finally {
+ client.shutdown();
+ }
+ }
+
+ /**
+ * A client configured the way hadoop-aliyun configures it. This matters:
{@code
+ * AliyunOSSFileSystemStore} passes a bare {@link ClientConfiguration},
where {@code
+ * supportCname} defaults to true, while {@code OSSClientBuilder} would
hand over a {@code
+ * ClientBuilderConfiguration} that turns it off. Only the former reaches
this bug.
+ */
+ private static OSSClient hadoopStyleClient(String endpoint) {
+ return new OSSClient(
+ endpoint, new DefaultCredentialProvider("ak", "sk"), new
ClientConfiguration());
+ }
+
+ /** The host the signer sees, resolved exactly as OSSRequestMessageBuilder
resolves it. */
+ private static String signedHost(OSSClient client, String endpoint, String
bucket)
+ throws Exception {
+ return OSSUtils.determineFinalEndpoint(
+ new URI(endpoint), bucket,
client.getClientConfiguration())
+ .getHost();
+ }
+
@Test
public void testResolveSse() {
// Nothing set -> no SSE.