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.

Reply via email to