You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by di...@apache.org on 2026/03/23 14:02:43 UTC

(polaris) branch main updated: Add MinIO coverage for AWS-shaped KMS vended credentials (#4034)

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 fd7adbcafc Add MinIO coverage for AWS-shaped KMS vended credentials (#4034)
fd7adbcafc is described below

commit fd7adbcafc17f62cf1e1ac0d5d38546b5ad5c888
Author: Francisco Castanheira <32...@users.noreply.github.com>
AuthorDate: Mon Mar 23 14:02:39 2026 +0000

    Add MinIO coverage for AWS-shaped KMS vended credentials (#4034)
    
    * Add MinIO KMS edge case integration tests
    
    * Refactor MinIO KMS edge case tests
---
 .../service/it/RestCatalogMinIOSpecialIT.java      | 176 +++++++++++++++++++--
 1 file changed, 161 insertions(+), 15 deletions(-)

diff --git a/runtime/service/src/intTest/java/org/apache/polaris/service/it/RestCatalogMinIOSpecialIT.java b/runtime/service/src/intTest/java/org/apache/polaris/service/it/RestCatalogMinIOSpecialIT.java
index 3cc2ac9648..7fe3df75ea 100644
--- a/runtime/service/src/intTest/java/org/apache/polaris/service/it/RestCatalogMinIOSpecialIT.java
+++ b/runtime/service/src/intTest/java/org/apache/polaris/service/it/RestCatalogMinIOSpecialIT.java
@@ -103,6 +103,8 @@ public class RestCatalogMinIOSpecialIT {
   private static final String BUCKET_URI_PREFIX = "/minio-test";
   private static final String MINIO_ACCESS_KEY = "test-ak-123";
   private static final String MINIO_SECRET_KEY = "test-sk-123";
+  private static final String TEST_REGION = "us-west-2";
+  private static final String TEST_ROLE_ARN = "arn:aws:iam::000000000000:role/polaris-access-role";
   private static String adminToken;
 
   public static class Profile implements QuarkusTestProfile {
@@ -172,7 +174,15 @@ public class RestCatalogMinIOSpecialIT {
       Optional<AccessDelegationMode> delegationMode,
       boolean stsEnabled) {
     return createCatalog(
-        endpoint, stsEndpoint, pathStyleAccess, Optional.empty(), delegationMode, stsEnabled);
+        endpoint,
+        stsEndpoint,
+        pathStyleAccess,
+        Optional.empty(),
+        delegationMode,
+        stsEnabled,
+        Optional.empty(),
+        Optional.empty(),
+        Optional.empty());
   }
 
   private RESTCatalog createCatalog(
@@ -182,6 +192,28 @@ public class RestCatalogMinIOSpecialIT {
       Optional<String> endpointInternal,
       Optional<AccessDelegationMode> delegationMode,
       boolean stsEnabled) {
+    return createCatalog(
+        endpoint,
+        stsEndpoint,
+        pathStyleAccess,
+        endpointInternal,
+        delegationMode,
+        stsEnabled,
+        Optional.empty(),
+        Optional.empty(),
+        Optional.empty());
+  }
+
+  private RESTCatalog createCatalog(
+      Optional<String> endpoint,
+      Optional<String> stsEndpoint,
+      boolean pathStyleAccess,
+      Optional<String> endpointInternal,
+      Optional<AccessDelegationMode> delegationMode,
+      boolean stsEnabled,
+      Optional<String> region,
+      Optional<String> roleArn,
+      Optional<Boolean> kmsUnavailable) {
     AwsStorageConfigInfo.Builder storageConfig =
         AwsStorageConfigInfo.builder()
             .setStorageType(StorageConfigInfo.StorageTypeEnum.S3)
@@ -192,6 +224,9 @@ public class RestCatalogMinIOSpecialIT {
     endpoint.ifPresent(storageConfig::setEndpoint);
     stsEndpoint.ifPresent(storageConfig::setStsEndpoint);
     endpointInternal.ifPresent(storageConfig::setEndpointInternal);
+    region.ifPresent(storageConfig::setRegion);
+    roleArn.ifPresent(storageConfig::setRoleArn);
+    kmsUnavailable.ifPresent(storageConfig::setKmsUnavailable);
 
     CatalogProperties.Builder catalogProps =
         CatalogProperties.builder(storageBase.toASCIIString() + "/" + catalogName);
@@ -265,6 +300,107 @@ public class RestCatalogMinIOSpecialIT {
     assertThat(response.credentials()).hasSize(1);
   }
 
+  @ParameterizedTest
+  @CsvSource({
+    "false, false",
+    "true,  false",
+    "false, true",
+  })
+  public void testCreateTableVendedCredentialsWithPartialAwsShapePasses(
+      boolean includeRegion, boolean includeRoleArn) throws IOException {
+    try (RESTCatalog restCatalog =
+        createCatalog(
+            Optional.of(endpoint),
+            Optional.of(endpoint),
+            true,
+            Optional.empty(),
+            Optional.of(VENDED_CREDENTIALS),
+            true,
+            includeRegion ? Optional.of(TEST_REGION) : Optional.empty(),
+            includeRoleArn ? Optional.of(TEST_ROLE_ARN) : Optional.empty(),
+            Optional.of(false))) {
+      TableIdentifier id = createTableAndVerifyMetadata(restCatalog);
+      try {
+        assertLoadTableWithVendedCredentialsSucceeds(id);
+      } finally {
+        catalogApi.dropTable(catalogName, id);
+      }
+    }
+  }
+
+  @Test
+  public void testCreateTableVendedCredentialsWithFullAwsShapeAndKmsEnabledFails()
+      throws IOException {
+    try (RESTCatalog restCatalog =
+        createCatalog(
+            Optional.of(endpoint),
+            Optional.of(endpoint),
+            true,
+            Optional.empty(),
+            Optional.of(VENDED_CREDENTIALS),
+            true,
+            Optional.of(TEST_REGION),
+            Optional.of(TEST_ROLE_ARN),
+            Optional.of(false))) {
+      TableIdentifier id = createTableAndVerifyMetadata(restCatalog);
+      try {
+        assertLoadTableWithVendedCredentialsFailsWithKmsError(id);
+      } finally {
+        catalogApi.dropTable(catalogName, id);
+      }
+    }
+  }
+
+  @Test
+  public void testCreateTableVendedCredentialsWithAwsShapeAndKmsUnavailablePasses()
+      throws IOException {
+    try (RESTCatalog restCatalog =
+        createCatalog(
+            Optional.of(endpoint),
+            Optional.of(endpoint),
+            true,
+            Optional.empty(),
+            Optional.of(VENDED_CREDENTIALS),
+            true,
+            Optional.of(TEST_REGION),
+            Optional.of(TEST_ROLE_ARN),
+            Optional.of(true))) {
+      TableIdentifier id = createTableAndVerifyMetadata(restCatalog);
+      try {
+        assertLoadTableWithVendedCredentialsSucceeds(id);
+      } finally {
+        catalogApi.dropTable(catalogName, id);
+      }
+    }
+  }
+
+  private void assertLoadTableWithVendedCredentialsFailsWithKmsError(TableIdentifier id) {
+    assertThatThrownBy(
+            () ->
+                catalogApi.loadTable(
+                    catalogName,
+                    id,
+                    "ALL",
+                    Map.of("X-Iceberg-Access-Delegation", VENDED_CREDENTIALS.protocolValue())))
+        .hasMessageContaining("Failed to get subscoped credentials")
+        .hasMessageContaining("invalid resource")
+        .hasMessageContaining("arn:aws:kms:us-west-2:000000000000:key/*");
+  }
+
+  private void assertLoadTableWithVendedCredentialsSucceeds(TableIdentifier id) {
+    LoadTableResponse response =
+        catalogApi.loadTable(
+            catalogName,
+            id,
+            "ALL",
+            Map.of("X-Iceberg-Access-Delegation", VENDED_CREDENTIALS.protocolValue()));
+    assertThat(response.config())
+        .containsEntry(
+            REFRESH_CREDENTIALS_ENDPOINT,
+            "v1/" + catalogName + "/namespaces/test-ns/tables/t1/credentials");
+    assertThat(response.credentials()).hasSize(1);
+  }
+
   private LoadTableResponse doTestCreateTable(
       boolean pathStyle, Optional<AccessDelegationMode> dm, boolean stsEnabled) throws IOException {
     try (RESTCatalog restCatalog =
@@ -360,11 +496,33 @@ public class RestCatalogMinIOSpecialIT {
 
   public LoadTableResponse doTestCreateTable(
       RESTCatalog restCatalog, Optional<AccessDelegationMode> dm) {
+    TableIdentifier id = createTableAndVerifyMetadata(restCatalog);
+    assertThat(restCatalog.tableExists(id)).isTrue();
+
+    try {
+      LoadTableResponse loadTableResponse =
+          catalogApi.loadTable(
+              catalogName,
+              id,
+              "ALL",
+              dm.map(v -> Map.of("X-Iceberg-Access-Delegation", v.protocolValue()))
+                  .orElse(Map.of()));
+
+      assertThat(loadTableResponse.config()).containsKey(ENDPOINT);
+      return loadTableResponse;
+    } finally {
+      if (restCatalog.tableExists(id)) {
+        restCatalog.dropTable(id);
+        assertThat(restCatalog.tableExists(id)).isFalse();
+      }
+    }
+  }
+
+  private TableIdentifier createTableAndVerifyMetadata(RESTCatalog restCatalog) {
     catalogApi.createNamespace(catalogName, "test-ns");
     TableIdentifier id = TableIdentifier.of("test-ns", "t1");
     Table table = restCatalog.createTable(id, SCHEMA);
     assertThat(table).isNotNull();
-    assertThat(restCatalog.tableExists(id)).isTrue();
 
     TableOperations ops = ((HasTableOperations) table).operations();
     URI location = URI.create(ops.current().metadataFileLocation());
@@ -378,19 +536,7 @@ public class RestCatalogMinIOSpecialIT {
                     .build())
             .response();
     assertThat(response.contentLength()).isGreaterThan(0);
-
-    LoadTableResponse loadTableResponse =
-        catalogApi.loadTable(
-            catalogName,
-            id,
-            "ALL",
-            dm.map(v -> Map.of("X-Iceberg-Access-Delegation", v.protocolValue())).orElse(Map.of()));
-
-    assertThat(loadTableResponse.config()).containsKey(ENDPOINT);
-
-    restCatalog.dropTable(id);
-    assertThat(restCatalog.tableExists(id)).isFalse();
-    return loadTableResponse;
+    return id;
   }
 
   @ParameterizedTest