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

dimas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new 92ead05f2 Add feature flag to disallow custom S3 endpoints (#2442)
92ead05f2 is described below

commit 92ead05f2bcf1c3cd4769965a0de989de0a5d961
Author: Dmitri Bourlatchkov <dmitri.bourlatch...@gmail.com>
AuthorDate: Tue Aug 26 10:06:41 2025 -0400

    Add feature flag to disallow custom S3 endpoints (#2442)
    
    * Add new realm-level flag: `ALLOW_SETTING_S3_ENDPOINTS` (default: true)
    
    * Enforce in `PolarisServiceImpl.validateStorageConfig()`
    
    Fixes #2436
---
 CHANGELOG.md                                       |  2 +
 .../polaris/core/config/FeatureConfiguration.java  | 10 ++++
 .../polaris/service/admin/PolarisServiceImpl.java  | 11 ++++
 .../service/admin/ManagementServiceTest.java       | 64 ++++++++++++++++++++++
 4 files changed, 87 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f90392d8..a5d8cd4b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -51,6 +51,8 @@ request adding CHANGELOG notes for breaking (!) changes and 
possibly other secti
 ### New Features
 
 - Added Catalog configuration for S3 and STS endpoints. This also allows using 
non-AWS S3 implementations.
+  The realm-level feature flag `ALLOW_SETTING_S3_ENDPOINTS` (default: true) 
may be used to disable this
+  functionality.
 
 - The `IMPLICIT` authentication type enables users to create federated 
catalogs without explicitly
 providing authentication parameters to Polaris. When the authentication type 
is set to `IMPLICIT`,
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
index e01e065a1..9eba940a9 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
@@ -79,6 +79,16 @@ public class FeatureConfiguration<T> extends 
PolarisConfiguration<T> {
           .defaultValue(false)
           .buildFeatureConfiguration();
 
+  public static final FeatureConfiguration<Boolean> ALLOW_SETTING_S3_ENDPOINTS 
=
+      PolarisConfiguration.<Boolean>builder()
+          .key("ALLOW_SETTING_S3_ENDPOINTS")
+          .description(
+              "If set to true (default), Polaris will permit S3 storage 
configurations to have custom endpoints.\n"
+                  + "If set to false, Polaris will not accept catalog create 
and update requests that contain \n"
+                  + "S3 endpoint properties.")
+          .defaultValue(true)
+          .buildFeatureConfiguration();
+
   @SuppressWarnings("deprecation")
   public static final FeatureConfiguration<Boolean> 
ALLOW_TABLE_LOCATION_OVERLAP =
       PolarisConfiguration.<Boolean>builder()
diff --git 
a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java
 
b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java
index fb079cf64..c455e9c99 100644
--- 
a/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java
+++ 
b/runtime/service/src/main/java/org/apache/polaris/service/admin/PolarisServiceImpl.java
@@ -31,6 +31,7 @@ import org.apache.iceberg.exceptions.NotAuthorizedException;
 import org.apache.iceberg.rest.responses.ErrorResponse;
 import org.apache.polaris.core.admin.model.AddGrantRequest;
 import org.apache.polaris.core.admin.model.AuthenticationParameters;
+import org.apache.polaris.core.admin.model.AwsStorageConfigInfo;
 import org.apache.polaris.core.admin.model.Catalog;
 import org.apache.polaris.core.admin.model.CatalogGrant;
 import org.apache.polaris.core.admin.model.CatalogRole;
@@ -180,6 +181,16 @@ public class PolarisServiceImpl
       throw new IllegalArgumentException(
           "Unsupported storage type: " + storageConfigInfo.getStorageType());
     }
+
+    if 
(!realmConfig.getConfig(FeatureConfiguration.ALLOW_SETTING_S3_ENDPOINTS)) {
+      if (storageConfigInfo instanceof AwsStorageConfigInfo s3Config) {
+        if (s3Config.getEndpoint() != null
+            || s3Config.getStsEndpoint() != null
+            || s3Config.getEndpointInternal() != null) {
+          throw new IllegalArgumentException("Explicitly setting S3 endpoints 
is not allowed.");
+        }
+      }
+    }
   }
 
   private void validateExternalCatalog(Catalog catalog) {
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java
index 14e086478..088a8ae1e 100644
--- 
a/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/admin/ManagementServiceTest.java
@@ -28,6 +28,7 @@ import java.time.Instant;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Supplier;
 import org.apache.iceberg.exceptions.ValidationException;
 import org.apache.polaris.core.PolarisCallContext;
 import org.apache.polaris.core.admin.model.AwsStorageConfigInfo;
@@ -68,6 +69,7 @@ public class ManagementServiceTest {
     services =
         TestServices.builder()
             .config(Map.of("SUPPORTED_CATALOG_STORAGE_TYPES", List.of("S3", 
"GCS", "AZURE")))
+            .config(Map.of("ALLOW_SETTING_S3_ENDPOINTS", Boolean.FALSE))
             .build();
   }
 
@@ -97,6 +99,51 @@ public class ManagementServiceTest {
         .hasMessage("Unsupported storage type: FILE");
   }
 
+  @Test
+  public void testCreateCatalogWithDisallowedS3Endpoints() {
+    AwsStorageConfigInfo.Builder storageConfig =
+        AwsStorageConfigInfo.builder()
+            .setRoleArn("arn:aws:iam::123456789012:role/my-role")
+            .setExternalId("externalId")
+            .setUserArn("userArn")
+            .setStorageType(StorageConfigInfo.StorageTypeEnum.S3)
+            .setAllowedLocations(List.of("s3://my-old-bucket/path/to/data"));
+    String catalogName = "test-catalog";
+    Supplier<Catalog> catalog =
+        () ->
+            PolarisCatalog.builder()
+                .setType(Catalog.TypeEnum.INTERNAL)
+                .setName(catalogName)
+                .setProperties(new 
CatalogProperties("s3://bucket/path/to/data"))
+                .setStorageConfigInfo(storageConfig.build())
+                .build();
+    Supplier<Response> createCatalog =
+        () ->
+            services
+                .catalogsApi()
+                .createCatalog(
+                    new CreateCatalogRequest(catalog.get()),
+                    services.realmContext(),
+                    services.securityContext());
+
+    storageConfig.setEndpoint("http://example.com";);
+    assertThatThrownBy(createCatalog::get)
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Explicitly setting S3 endpoints is not allowed.");
+
+    storageConfig.setEndpoint(null);
+    storageConfig.setStsEndpoint("http://example.com";);
+    assertThatThrownBy(createCatalog::get)
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Explicitly setting S3 endpoints is not allowed.");
+
+    storageConfig.setStsEndpoint(null);
+    storageConfig.setEndpointInternal("http://example.com";);
+    assertThatThrownBy(createCatalog::get)
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Explicitly setting S3 endpoints is not allowed.");
+  }
+
   @Test
   public void testUpdateCatalogWithDisallowedStorageConfig() {
     AwsStorageConfigInfo awsConfigModel =
@@ -162,6 +209,23 @@ public class ManagementServiceTest {
                         services.securityContext()))
         .isInstanceOf(IllegalArgumentException.class)
         .hasMessage("Unsupported storage type: FILE");
+
+    UpdateCatalogRequest update2 =
+        new UpdateCatalogRequest(
+            fetchedCatalog.getEntityVersion(),
+            Map.of(),
+            AwsStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.S3)
+                .setRoleArn("arn:aws:iam::123456789012:role/my-role")
+                .setEndpoint("http://example.com";)
+                .build());
+    assertThatThrownBy(
+            () ->
+                services
+                    .catalogsApi()
+                    .updateCatalog(
+                        catalogName, update2, services.realmContext(), 
services.securityContext()))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Explicitly setting S3 endpoints is not allowed.");
   }
 
   private PolarisMetaStoreManager setupMetaStoreManager() {

Reply via email to