From 2206b2fec20c688cb7b1e847d77b30ae0d3a9e3c Mon Sep 17 00:00:00 2001 From: Honah J Date: Fri, 26 Sep 2025 09:51:43 -0500 Subject: [PATCH 1/3] add additional config to disallow overriding sub-RBAC setting at catalog level --- .../core/config/FeatureConfiguration.java | 10 ++ .../service/admin/PolarisServiceImpl.java | 20 +++ .../service/admin/ManagementServiceTest.java | 140 +++++++++++++++++- 3 files changed, 168 insertions(+), 2 deletions(-) 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 764b00028c..9b9cedac9c 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 @@ -386,4 +386,14 @@ public static void enforceFeatureEnabledOrThrow( + "Defaults to enabled, but service providers may want to disable it.") .defaultValue(true) .buildFeatureConfiguration(); + + public static final FeatureConfiguration + ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS = + PolarisConfiguration.builder() + .key("ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS") + .description( + "If set to true (default), Polaris will allow configuring namespace/table-level RBAC for federated catalogs per catalog." + + "If set to false, Polaris will only allow configuring namespace/table-level RBAC for federated catalogs at realm level.") + .defaultValue(true) + .buildFeatureConfiguration(); } 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 54ff3e1cea..a471e9c1fc 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 @@ -25,6 +25,7 @@ import jakarta.ws.rs.core.SecurityContext; import java.util.List; import java.util.Locale; +import java.util.Map; import org.apache.iceberg.catalog.Namespace; import org.apache.iceberg.catalog.TableIdentifier; import org.apache.iceberg.rest.responses.ErrorResponse; @@ -124,6 +125,7 @@ public Response createCatalog( Catalog catalog = request.getCatalog(); validateStorageConfig(catalog.getStorageConfigInfo()); validateExternalCatalog(catalog); + validateCatalogProperties(catalog.getProperties()); Catalog newCatalog = CatalogEntity.of(adminService.createCatalog(request)).asCatalog(); LOGGER.info("Created new catalog {}", newCatalog); return Response.status(Response.Status.CREATED).entity(newCatalog).build(); @@ -176,6 +178,23 @@ private void validateExternalCatalog(Catalog catalog) { } } + private void validateCatalogProperties(Map catalogProperties) { + if (catalogProperties != null) { + if (!realmConfig.getConfig( + FeatureConfiguration.ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS) + && catalogProperties.containsKey( + FeatureConfiguration.ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS + .catalogConfig())) { + + throw new IllegalArgumentException( + String.format( + "Explicitly setting %s is not allowed.", + FeatureConfiguration.ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS + .catalogConfig())); + } + } + } + private void validateConnectionConfigInfo(ConnectionConfigInfo connectionConfigInfo) { String connectionType = connectionConfigInfo.getConnectionType().name(); @@ -227,6 +246,7 @@ public Response updateCatalog( if (updateRequest.getStorageConfigInfo() != null) { validateStorageConfig(updateRequest.getStorageConfigInfo()); } + validateCatalogProperties(updateRequest.getProperties()); return Response.ok(adminService.updateCatalog(catalogName, updateRequest).asCatalog()).build(); } 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 5ce6031783..277e7f7374 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 @@ -31,11 +31,16 @@ import java.util.function.Supplier; import org.apache.iceberg.exceptions.ValidationException; import org.apache.polaris.core.PolarisCallContext; +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.CatalogProperties; +import org.apache.polaris.core.admin.model.ConnectionConfigInfo; import org.apache.polaris.core.admin.model.CreateCatalogRequest; +import org.apache.polaris.core.admin.model.ExternalCatalog; import org.apache.polaris.core.admin.model.FileStorageConfigInfo; +import org.apache.polaris.core.admin.model.IcebergRestConnectionConfigInfo; +import org.apache.polaris.core.admin.model.OAuthClientCredentialsParameters; import org.apache.polaris.core.admin.model.PolarisCatalog; import org.apache.polaris.core.admin.model.StorageConfigInfo; import org.apache.polaris.core.admin.model.UpdateCatalogRequest; @@ -66,8 +71,16 @@ public class ManagementServiceTest { public void setup() { services = TestServices.builder() - .config(Map.of("SUPPORTED_CATALOG_STORAGE_TYPES", List.of("S3", "GCS", "AZURE"))) - .config(Map.of("ALLOW_SETTING_S3_ENDPOINTS", Boolean.FALSE)) + .config( + Map.of( + "SUPPORTED_CATALOG_STORAGE_TYPES", + List.of("S3", "GCS", "AZURE"), + "ALLOW_SETTING_S3_ENDPOINTS", + Boolean.FALSE, + "ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS", + Boolean.FALSE, + "ENABLE_CATALOG_FEDERATION", + Boolean.TRUE)) .build(); } @@ -226,6 +239,129 @@ public void testUpdateCatalogWithDisallowedStorageConfig() { .hasMessage("Explicitly setting S3 endpoints is not allowed."); } + @Test + public void testCreateCatalogWithDisallowedConfigs() { + AwsStorageConfigInfo awsConfigModel = + 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")) + .build(); + ConnectionConfigInfo connectionConfigInfo = + IcebergRestConnectionConfigInfo.builder( + ConnectionConfigInfo.ConnectionTypeEnum.ICEBERG_REST) + .setUri("https://myorg-my_account.snowflakecomputing.com/polaris/api/catalog") + .setRemoteCatalogName("my-remote-catalog") + .setAuthenticationParameters( + OAuthClientCredentialsParameters.builder( + AuthenticationParameters.AuthenticationTypeEnum.OAUTH) + .setClientId("my-client-id") + .setClientSecret("my-client-secret") + .setScopes(List.of("PRINCIPAL_ROLE:ALL")) + .build()) + .build(); + String catalogName = "mycatalog"; + CatalogProperties catalogProperties = + CatalogProperties.builder("s3://bucket/path/to/data") + .addProperty("polaris.config.enable-sub-catalog-rbac-for-federated-catalogs", "true") + .build(); + Catalog catalog = + ExternalCatalog.builder() + .setType(Catalog.TypeEnum.EXTERNAL) + .setName(catalogName) + .setProperties(catalogProperties) + .setStorageConfigInfo(awsConfigModel) + .setConnectionConfigInfo(connectionConfigInfo) + .build(); + Supplier createCatalog = + () -> + services + .catalogsApi() + .createCatalog( + new CreateCatalogRequest(catalog), + services.realmContext(), + services.securityContext()); + assertThatThrownBy(createCatalog::get) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "Explicitly setting polaris.config.enable-sub-catalog-rbac-for-federated-catalogs is not allowed."); + } + + @Test + public void testUpdateCatalogWithDisallowedConfigs() { + AwsStorageConfigInfo awsConfigModel = + 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")) + .build(); + ConnectionConfigInfo connectionConfigInfo = + IcebergRestConnectionConfigInfo.builder( + ConnectionConfigInfo.ConnectionTypeEnum.ICEBERG_REST) + .setUri("https://myorg-my_account.snowflakecomputing.com/polaris/api/catalog") + .setRemoteCatalogName("my-remote-catalog") + .setAuthenticationParameters( + OAuthClientCredentialsParameters.builder( + AuthenticationParameters.AuthenticationTypeEnum.OAUTH) + .setClientId("my-client-id") + .setClientSecret("my-client-secret") + .setScopes(List.of("PRINCIPAL_ROLE:ALL")) + .build()) + .build(); + String catalogName = "mycatalog"; + CatalogProperties catalogProperties = + CatalogProperties.builder("s3://bucket/path/to/data").build(); + Catalog catalog = + ExternalCatalog.builder() + .setType(Catalog.TypeEnum.EXTERNAL) + .setName(catalogName) + .setProperties(catalogProperties) + .setStorageConfigInfo(awsConfigModel) + .setConnectionConfigInfo(connectionConfigInfo) + .build(); + try (Response response = + services + .catalogsApi() + .createCatalog( + new CreateCatalogRequest(catalog), + services.realmContext(), + services.securityContext())) { + assertThat(response).returns(Response.Status.CREATED.getStatusCode(), Response::getStatus); + } + Catalog fetchedCatalog; + try (Response response = + services + .catalogsApi() + .getCatalog(catalogName, services.realmContext(), services.securityContext())) { + assertThat(response).returns(Response.Status.OK.getStatusCode(), Response::getStatus); + fetchedCatalog = (Catalog) response.getEntity(); + + assertThat(fetchedCatalog.getName()).isEqualTo(catalogName); + assertThat(fetchedCatalog.getProperties().toMap()) + .isEqualTo(Map.of("default-base-location", "s3://bucket/path/to/data")); + assertThat(fetchedCatalog.getEntityVersion()).isGreaterThan(0); + } + + UpdateCatalogRequest update = + UpdateCatalogRequest.builder() + .setProperties( + Map.of("polaris.config.enable-sub-catalog-rbac-for-federated-catalogs", "true")) + .build(); + assertThatThrownBy( + () -> + services + .catalogsApi() + .updateCatalog( + catalogName, update, services.realmContext(), services.securityContext())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "Explicitly setting polaris.config.enable-sub-catalog-rbac-for-federated-catalogs is not allowed."); + } + private PolarisAdminService setupPolarisAdminService( PolarisMetaStoreManager metaStoreManager, PolarisCallContext callContext) { return new PolarisAdminService( From 9fa9b56a2027d4c4b5b31213e42e46860b937147 Mon Sep 17 00:00:00 2001 From: Honah J Date: Fri, 26 Sep 2025 15:08:40 -0500 Subject: [PATCH 2/3] Update changelog and update flag description --- CHANGELOG.md | 3 +++ .../org/apache/polaris/core/config/FeatureConfiguration.java | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eba8e85551..88f239094a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,9 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti ### New Features - Added a Management API endpoint to reset principal credentials, controlled by the `ENABLE_CREDENTIAL_RESET` (default: true) feature flag. +- The `ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS` was added to support sub-catalog (initially namespace and table) RBAC for federated catalogs. + The setting can be configured on a per-catalog basis by setting the catalog property: `polaris.config.enable-sub-catalog-rbac-for-federated-catalogs`. + The realm-level feature flag `ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS` (default: true) controls whether this functionality can be enabled or modified at the catalog level. ### Changes 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 9b9cedac9c..8af3e18210 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 @@ -392,8 +392,9 @@ public static void enforceFeatureEnabledOrThrow( PolarisConfiguration.builder() .key("ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS") .description( - "If set to true (default), Polaris will allow configuring namespace/table-level RBAC for federated catalogs per catalog." - + "If set to false, Polaris will only allow configuring namespace/table-level RBAC for federated catalogs at realm level.") + "If set to true (default), Polaris will allow setting or changing " + + "catalog property polaris.config.enable-sub-catalog-rbac-for-federated-catalogs." + + "If set to false, Polaris will disallow setting or changing the above catalog property") .defaultValue(true) .buildFeatureConfiguration(); } From f0822a2ed77494a1ace8d3048084c1609cbe897c Mon Sep 17 00:00:00 2001 From: Honah J Date: Mon, 29 Sep 2025 14:20:24 -0500 Subject: [PATCH 3/3] Make the error message more useful --- .../apache/polaris/service/admin/PolarisServiceImpl.java | 6 +++--- .../apache/polaris/service/admin/ManagementServiceTest.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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 a471e9c1fc..e02d9d4bb7 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 @@ -188,9 +188,9 @@ private void validateCatalogProperties(Map catalogProperties) { throw new IllegalArgumentException( String.format( - "Explicitly setting %s is not allowed.", - FeatureConfiguration.ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS - .catalogConfig())); + "Explicitly setting %s is not allowed because %s is set to false.", + FeatureConfiguration.ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS.catalogConfig(), + FeatureConfiguration.ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS.key())); } } } 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 277e7f7374..c5fa44b9eb 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 @@ -286,7 +286,7 @@ public void testCreateCatalogWithDisallowedConfigs() { assertThatThrownBy(createCatalog::get) .isInstanceOf(IllegalArgumentException.class) .hasMessage( - "Explicitly setting polaris.config.enable-sub-catalog-rbac-for-federated-catalogs is not allowed."); + "Explicitly setting polaris.config.enable-sub-catalog-rbac-for-federated-catalogs is not allowed because ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS is set to false."); } @Test @@ -359,7 +359,7 @@ public void testUpdateCatalogWithDisallowedConfigs() { catalogName, update, services.realmContext(), services.securityContext())) .isInstanceOf(IllegalArgumentException.class) .hasMessage( - "Explicitly setting polaris.config.enable-sub-catalog-rbac-for-federated-catalogs is not allowed."); + "Explicitly setting polaris.config.enable-sub-catalog-rbac-for-federated-catalogs is not allowed because ALLOW_SETTING_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS is set to false."); } private PolarisAdminService setupPolarisAdminService(