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() {