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 7b04abe04 Push AccessConfig creation to PolarisStorageIntegration 
(#2171)
7b04abe04 is described below

commit 7b04abe044995c6d7f5762acc959c007bcae5a44
Author: Dmitri Bourlatchkov <dmitri.bourlatch...@gmail.com>
AuthorDate: Fri Jul 25 15:55:06 2025 -0400

    Push AccessConfig creation to PolarisStorageIntegration (#2171)
    
    This refactoring does not change Polaris behaviour.
    
    * Move storage-specific access properties processing logic from
      core code to storage integration implementations.
    
    * Add `isExpirationTimestamp` flag to `StorageAccessProperty` to
      allow them to be processed uniformly.
    
    * Prepare for supporting access config properties that may have
      different values in Polaris Servers and Clients. This enables
      future enhancements to support different S3 endpoint DNS names
      in servers and clients for #1530
---
 .../AtomicOperationMetaStoreManager.java           |   7 +-
 .../dao/entity/ScopedCredentialsResult.java        |  32 ++---
 .../TransactionalMetaStoreManagerImpl.java         |   7 +-
 .../apache/polaris/core/storage/AccessConfig.java  |  50 +++++++-
 .../core/storage/PolarisStorageIntegration.java    |   3 +-
 .../core/storage/StorageAccessProperty.java        |  33 +++--
 .../aws/AwsCredentialsStorageIntegration.java      |  34 +++---
 .../azure/AzureCredentialsStorageIntegration.java  |  48 ++++++--
 .../core/storage/cache/StorageCredentialCache.java |   3 +-
 .../storage/cache/StorageCredentialCacheEntry.java |  74 +----------
 .../gcp/GcpCredentialsStorageIntegration.java      |  12 +-
 .../polaris/core/storage/AccessConfigTest.java     |  93 ++++++++++++++
 .../storage/InMemoryStorageIntegrationTest.java    |   3 +-
 .../AzureCredentialsStorageIntegrationTest.java    |  55 +++++++++
 .../storage/cache/StorageCredentialCacheTest.java  | 135 +++------------------
 .../aws/AwsCredentialsStorageIntegrationTest.java  |  93 +++++++-------
 .../AzureCredentialStorageIntegrationTest.java     |  41 +++----
 .../gcp/GcpCredentialsStorageIntegrationTest.java  |  31 +++--
 .../catalog/AbstractIcebergCatalogTest.java        |  12 +-
 .../service/catalog/io/DefaultFileIOFactory.java   |   1 +
 .../PolarisStorageIntegrationProviderImpl.java     |   7 +-
 21 files changed, 413 insertions(+), 361 deletions(-)

diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java
index e4e77c155..35d0098b3 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/AtomicOperationMetaStoreManager.java
@@ -21,7 +21,6 @@ package org.apache.polaris.core.persistence;
 import jakarta.annotation.Nonnull;
 import jakarta.annotation.Nullable;
 import java.util.ArrayList;
-import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -71,9 +70,9 @@ import 
org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
 import org.apache.polaris.core.policy.PolicyEntity;
 import org.apache.polaris.core.policy.PolicyMappingUtil;
 import org.apache.polaris.core.policy.PolicyType;
+import org.apache.polaris.core.storage.AccessConfig;
 import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
 import org.apache.polaris.core.storage.PolarisStorageIntegration;
-import org.apache.polaris.core.storage.StorageAccessProperty;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -1614,14 +1613,14 @@ public class AtomicOperationMetaStoreManager extends 
BaseMetaStoreManager {
     PolarisStorageConfigurationInfo storageConfigurationInfo =
         BaseMetaStoreManager.extractStorageConfiguration(callCtx, 
reloadedEntity.getEntity());
     try {
-      EnumMap<StorageAccessProperty, String> creds =
+      AccessConfig accessConfig =
           storageIntegration.getSubscopedCreds(
               callCtx,
               storageConfigurationInfo,
               allowListOperation,
               allowedReadLocations,
               allowedWriteLocations);
-      return new ScopedCredentialsResult(creds);
+      return new ScopedCredentialsResult(accessConfig);
     } catch (Exception ex) {
       return new ScopedCredentialsResult(
           BaseResult.ReturnStatus.SUBSCOPE_CREDS_ERROR, ex.getMessage());
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/ScopedCredentialsResult.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/ScopedCredentialsResult.java
index 60e1cee01..76526a863 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/ScopedCredentialsResult.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/dao/entity/ScopedCredentialsResult.java
@@ -18,19 +18,15 @@
  */
 package org.apache.polaris.core.persistence.dao.entity;
 
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
 import jakarta.annotation.Nonnull;
 import jakarta.annotation.Nullable;
-import java.util.EnumMap;
-import java.util.Map;
-import org.apache.polaris.core.storage.StorageAccessProperty;
+import org.apache.polaris.core.storage.AccessConfig;
 
 /** Result of a getSubscopedCredsForEntity() call */
 public class ScopedCredentialsResult extends BaseResult {
 
   // null if not success. Else, set of name/value pairs for the credentials
-  private final EnumMap<StorageAccessProperty, String> credentials;
+  private final AccessConfig accessConfig;
 
   /**
    * Constructor for an error
@@ -41,32 +37,20 @@ public class ScopedCredentialsResult extends BaseResult {
   public ScopedCredentialsResult(
       @Nonnull ReturnStatus errorCode, @Nullable String extraInformation) {
     super(errorCode, extraInformation);
-    this.credentials = null;
+    this.accessConfig = null;
   }
 
   /**
    * Constructor for success
    *
-   * @param credentials credentials
+   * @param accessConfig credentials
    */
-  public ScopedCredentialsResult(@Nonnull EnumMap<StorageAccessProperty, 
String> credentials) {
+  public ScopedCredentialsResult(AccessConfig accessConfig) {
     super(ReturnStatus.SUCCESS);
-    this.credentials = credentials;
+    this.accessConfig = accessConfig;
   }
 
-  @JsonCreator
-  private ScopedCredentialsResult(
-      @JsonProperty("returnStatus") @Nonnull ReturnStatus returnStatus,
-      @JsonProperty("extraInformation") String extraInformation,
-      @JsonProperty("credentials") Map<String, String> credentials) {
-    super(returnStatus, extraInformation);
-    this.credentials = new EnumMap<>(StorageAccessProperty.class);
-    if (credentials != null) {
-      credentials.forEach((k, v) -> 
this.credentials.put(StorageAccessProperty.valueOf(k), v));
-    }
-  }
-
-  public EnumMap<StorageAccessProperty, String> getCredentials() {
-    return credentials;
+  public AccessConfig getAccessConfig() {
+    return accessConfig;
   }
 }
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalMetaStoreManagerImpl.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalMetaStoreManagerImpl.java
index 8c8e26eb8..53d57ceb2 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalMetaStoreManagerImpl.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/persistence/transactional/TransactionalMetaStoreManagerImpl.java
@@ -22,7 +22,6 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import jakarta.annotation.Nonnull;
 import jakarta.annotation.Nullable;
 import java.util.ArrayList;
-import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -76,9 +75,9 @@ import 
org.apache.polaris.core.policy.PolarisPolicyMappingRecord;
 import org.apache.polaris.core.policy.PolicyEntity;
 import org.apache.polaris.core.policy.PolicyMappingUtil;
 import org.apache.polaris.core.policy.PolicyType;
+import org.apache.polaris.core.storage.AccessConfig;
 import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
 import org.apache.polaris.core.storage.PolarisStorageIntegration;
-import org.apache.polaris.core.storage.StorageAccessProperty;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -2062,14 +2061,14 @@ public class TransactionalMetaStoreManagerImpl extends 
BaseMetaStoreManager {
     PolarisStorageConfigurationInfo storageConfigurationInfo =
         BaseMetaStoreManager.extractStorageConfiguration(callCtx, 
reloadedEntity.getEntity());
     try {
-      EnumMap<StorageAccessProperty, String> creds =
+      AccessConfig accessConfig =
           storageIntegration.getSubscopedCreds(
               callCtx,
               storageConfigurationInfo,
               allowListOperation,
               allowedReadLocations,
               allowedWriteLocations);
-      return new ScopedCredentialsResult(creds);
+      return new ScopedCredentialsResult(accessConfig);
     } catch (Exception ex) {
       return new ScopedCredentialsResult(
           BaseResult.ReturnStatus.SUBSCOPE_CREDS_ERROR, ex.getMessage());
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/AccessConfig.java 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/AccessConfig.java
index 61a754ecc..e15fd2e91 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/AccessConfig.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/AccessConfig.java
@@ -18,7 +18,10 @@
  */
 package org.apache.polaris.core.storage;
 
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import java.time.Instant;
 import java.util.Map;
+import java.util.Optional;
 import org.apache.polaris.immutables.PolarisImmutable;
 
 @PolarisImmutable
@@ -27,7 +30,52 @@ public interface AccessConfig {
 
   Map<String, String> extraProperties();
 
-  static ImmutableAccessConfig.Builder builder() {
+  /**
+   * Configuration properties that are relevant only to the Polaris Server, 
but not to clients.
+   * These properties override corresponding entries from {@link 
#extraProperties()}.
+   */
+  Map<String, String> internalProperties();
+
+  Optional<Instant> expiresAt();
+
+  default String get(StorageAccessProperty key) {
+    if (key.isCredential()) {
+      return credentials().get(key.getPropertyName());
+    } else {
+      String value = internalProperties().get(key.getPropertyName());
+      return value != null ? value : 
extraProperties().get(key.getPropertyName());
+    }
+  }
+
+  static AccessConfig.Builder builder() {
     return ImmutableAccessConfig.builder();
   }
+
+  interface Builder {
+    @CanIgnoreReturnValue
+    Builder putCredential(String key, String value);
+
+    @CanIgnoreReturnValue
+    Builder putExtraProperty(String key, String value);
+
+    @CanIgnoreReturnValue
+    Builder putInternalProperty(String key, String value);
+
+    @CanIgnoreReturnValue
+    Builder expiresAt(Instant expiresAt);
+
+    default Builder put(StorageAccessProperty key, String value) {
+      if (key.isExpirationTimestamp()) {
+        expiresAt(Instant.ofEpochMilli(Long.parseLong(value)));
+      }
+
+      if (key.isCredential()) {
+        return putCredential(key.getPropertyName(), value);
+      } else {
+        return putExtraProperty(key.getPropertyName(), value);
+      }
+    }
+
+    AccessConfig build();
+  }
 }
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisStorageIntegration.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisStorageIntegration.java
index 147ec5c66..c7f060a12 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisStorageIntegration.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/PolarisStorageIntegration.java
@@ -19,7 +19,6 @@
 package org.apache.polaris.core.storage;
 
 import jakarta.annotation.Nonnull;
-import java.util.EnumMap;
 import java.util.Map;
 import java.util.Set;
 import org.apache.polaris.core.context.CallContext;
@@ -53,7 +52,7 @@ public abstract class PolarisStorageIntegration<T extends 
PolarisStorageConfigur
    * @param allowedWriteLocations a set of allowed to write locations
    * @return An enum map including the scoped credentials
    */
-  public abstract EnumMap<StorageAccessProperty, String> getSubscopedCreds(
+  public abstract AccessConfig getSubscopedCreds(
       @Nonnull CallContext callContext,
       @Nonnull T storageConfig,
       boolean allowListOperation,
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/StorageAccessProperty.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/StorageAccessProperty.java
index bfd8c934d..33526d2e2 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/StorageAccessProperty.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/StorageAccessProperty.java
@@ -31,7 +31,9 @@ public enum StorageAccessProperty {
   AWS_SESSION_TOKEN_EXPIRES_AT_MS(
       String.class,
       "s3.session-token-expires-at-ms",
-      "the time the aws session token expires, in milliseconds"),
+      "the time the aws session token expires, in milliseconds",
+      true,
+      true),
   AWS_ENDPOINT(String.class, "s3.endpoint", "the S3 endpoint to use for 
requests", false),
   AWS_PATH_STYLE_ACCESS(
       Boolean.class, "s3.path-style-access", "whether to use S3 path style 
access", false),
@@ -42,23 +44,26 @@ public enum StorageAccessProperty {
   GCS_ACCESS_TOKEN_EXPIRES_AT(
       String.class,
       "gcs.oauth2.token-expires-at",
-      "the time the gcs access token expires, in milliseconds"),
+      "the time the gcs access token expires, in milliseconds",
+      true,
+      true),
 
   // Currently not using ACCESS TOKEN as the ResolvingFileIO is using 
ADLSFileIO for azure case and
   // it expects for SAS
   AZURE_ACCESS_TOKEN(String.class, "", "the azure scoped access token"),
   AZURE_SAS_TOKEN(String.class, "adls.sas-token.", "an azure shared access 
signature token"),
-  AZURE_ACCOUNT_HOST(
-      String.class,
-      "the azure storage account host",
-      "the azure account name + endpoint that will append to the 
ADLS_SAS_TOKEN_PREFIX"),
   EXPIRATION_TIME(
-      Long.class, "expiration-time", "the expiration time for the access 
token, in milliseconds");
+      Long.class,
+      "expiration-time",
+      "the expiration time for the access token, in milliseconds",
+      true,
+      true);
 
   private final Class valueType;
   private final String propertyName;
   private final String description;
   private final boolean isCredential;
+  private final boolean isExpirationTimestamp;
 
   /*
   s3.access-key-id`: id for for credentials that provide access to the data in 
S3
@@ -71,10 +76,20 @@ public enum StorageAccessProperty {
 
   StorageAccessProperty(
       Class valueType, String propertyName, String description, boolean 
isCredential) {
+    this(valueType, propertyName, description, isCredential, false);
+  }
+
+  StorageAccessProperty(
+      Class valueType,
+      String propertyName,
+      String description,
+      boolean isCredential,
+      boolean isExpirationTimestamp) {
     this.valueType = valueType;
     this.propertyName = propertyName;
     this.description = description;
     this.isCredential = isCredential;
+    this.isExpirationTimestamp = isExpirationTimestamp;
   }
 
   public String getPropertyName() {
@@ -84,4 +99,8 @@ public enum StorageAccessProperty {
   public boolean isCredential() {
     return isCredential;
   }
+
+  public boolean isExpirationTimestamp() {
+    return isExpirationTimestamp;
+  }
 }
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java
index 338f60c4e..f619a9a13 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsCredentialsStorageIntegration.java
@@ -22,13 +22,13 @@ import static 
org.apache.polaris.core.config.FeatureConfiguration.STORAGE_CREDEN
 
 import jakarta.annotation.Nonnull;
 import java.net.URI;
-import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.storage.AccessConfig;
 import org.apache.polaris.core.storage.InMemoryStorageIntegration;
 import org.apache.polaris.core.storage.StorageAccessProperty;
 import org.apache.polaris.core.storage.StorageUtil;
@@ -66,7 +66,7 @@ public class AwsCredentialsStorageIntegration
 
   /** {@inheritDoc} */
   @Override
-  public EnumMap<StorageAccessProperty, String> getSubscopedCreds(
+  public AccessConfig getSubscopedCreds(
       @Nonnull CallContext callContext,
       @Nonnull AwsStorageConfigurationInfo storageConfig,
       boolean allowListOperation,
@@ -90,50 +90,48 @@ public class AwsCredentialsStorageIntegration
     credentialsProvider.ifPresent(
         cp -> request.overrideConfiguration(b -> b.credentialsProvider(cp)));
 
+    String region = storageConfig.getRegion();
     @SuppressWarnings("resource")
     // Note: stsClientProvider returns "thin" clients that do not need closing
     StsClient stsClient =
-        stsClientProvider.stsClient(
-            StsDestination.of(storageConfig.getStsEndpointUri(), 
storageConfig.getRegion()));
+        
stsClientProvider.stsClient(StsDestination.of(storageConfig.getStsEndpointUri(),
 region));
 
     AssumeRoleResponse response = stsClient.assumeRole(request.build());
-    EnumMap<StorageAccessProperty, String> credentialMap =
-        new EnumMap<>(StorageAccessProperty.class);
-    credentialMap.put(StorageAccessProperty.AWS_KEY_ID, 
response.credentials().accessKeyId());
-    credentialMap.put(
+    AccessConfig.Builder accessConfig = AccessConfig.builder();
+    accessConfig.put(StorageAccessProperty.AWS_KEY_ID, 
response.credentials().accessKeyId());
+    accessConfig.put(
         StorageAccessProperty.AWS_SECRET_KEY, 
response.credentials().secretAccessKey());
-    credentialMap.put(StorageAccessProperty.AWS_TOKEN, 
response.credentials().sessionToken());
+    accessConfig.put(StorageAccessProperty.AWS_TOKEN, 
response.credentials().sessionToken());
     Optional.ofNullable(response.credentials().expiration())
         .ifPresent(
             i -> {
-              credentialMap.put(
+              accessConfig.put(
                   StorageAccessProperty.EXPIRATION_TIME, 
String.valueOf(i.toEpochMilli()));
-              credentialMap.put(
+              accessConfig.put(
                   StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS,
                   String.valueOf(i.toEpochMilli()));
             });
 
-    if (storageConfig.getRegion() != null) {
-      credentialMap.put(StorageAccessProperty.CLIENT_REGION, 
storageConfig.getRegion());
+    if (region != null) {
+      accessConfig.put(StorageAccessProperty.CLIENT_REGION, region);
     }
 
     URI endpointUri = storageConfig.getEndpointUri();
     if (endpointUri != null) {
-      credentialMap.put(StorageAccessProperty.AWS_ENDPOINT, 
endpointUri.toString());
+      accessConfig.put(StorageAccessProperty.AWS_ENDPOINT, 
endpointUri.toString());
     }
 
     if (Boolean.TRUE.equals(storageConfig.getPathStyleAccess())) {
-      credentialMap.put(StorageAccessProperty.AWS_PATH_STYLE_ACCESS, 
Boolean.TRUE.toString());
+      accessConfig.put(StorageAccessProperty.AWS_PATH_STYLE_ACCESS, 
Boolean.TRUE.toString());
     }
 
-    if (storageConfig.getAwsPartition().equals("aws-us-gov")
-        && credentialMap.get(StorageAccessProperty.CLIENT_REGION) == null) {
+    if (storageConfig.getAwsPartition().equals("aws-us-gov") && region == 
null) {
       throw new IllegalArgumentException(
           String.format(
               "AWS region must be set when using partition %s", 
storageConfig.getAwsPartition()));
     }
 
-    return credentialMap;
+    return accessConfig.build();
   }
 
   /**
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegration.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegration.java
index 232994a37..cc6ccfdb3 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegration.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegration.java
@@ -37,17 +37,18 @@ import 
com.azure.storage.file.datalake.DataLakeServiceClientBuilder;
 import com.azure.storage.file.datalake.models.DataLakeStorageException;
 import com.azure.storage.file.datalake.sas.DataLakeServiceSasSignatureValues;
 import com.azure.storage.file.datalake.sas.PathSasPermission;
+import com.google.common.annotations.VisibleForTesting;
 import jakarta.annotation.Nonnull;
 import java.time.Instant;
 import java.time.OffsetDateTime;
 import java.time.Period;
 import java.time.ZoneOffset;
 import java.time.temporal.ChronoUnit;
-import java.util.EnumMap;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
 import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.storage.AccessConfig;
 import org.apache.polaris.core.storage.InMemoryStorageIntegration;
 import org.apache.polaris.core.storage.StorageAccessProperty;
 import org.slf4j.Logger;
@@ -71,14 +72,12 @@ public class AzureCredentialsStorageIntegration
   }
 
   @Override
-  public EnumMap<StorageAccessProperty, String> getSubscopedCreds(
+  public AccessConfig getSubscopedCreds(
       @Nonnull CallContext callContext,
       @Nonnull AzureStorageConfigurationInfo storageConfig,
       boolean allowListOperation,
       @Nonnull Set<String> allowedReadLocations,
       @Nonnull Set<String> allowedWriteLocations) {
-    EnumMap<StorageAccessProperty, String> credentialMap =
-        new EnumMap<>(StorageAccessProperty.class);
     String loc =
         !allowedWriteLocations.isEmpty()
             ? allowedWriteLocations.stream().findAny().orElse(null)
@@ -171,12 +170,41 @@ public class AzureCredentialsStorageIntegration
       throw new RuntimeException(
           String.format("Endpoint %s not supported", location.getEndpoint()));
     }
-    credentialMap.put(StorageAccessProperty.AZURE_SAS_TOKEN, sasToken);
-    credentialMap.put(StorageAccessProperty.AZURE_ACCOUNT_HOST, 
storageDnsName);
-    credentialMap.put(
-        StorageAccessProperty.EXPIRATION_TIME,
-        String.valueOf(sanitizedEndTime.toInstant().toEpochMilli()));
-    return credentialMap;
+
+    return toAccessConfig(sasToken, storageDnsName, 
sanitizedEndTime.toInstant());
+  }
+
+  @VisibleForTesting
+  static AccessConfig toAccessConfig(String sasToken, String storageDnsName, 
Instant expiresAt) {
+    AccessConfig.Builder accessConfig = AccessConfig.builder();
+    handleAzureCredential(accessConfig, sasToken, storageDnsName);
+    accessConfig.put(
+        StorageAccessProperty.EXPIRATION_TIME, 
String.valueOf(expiresAt.toEpochMilli()));
+    return accessConfig.build();
+  }
+
+  private static void handleAzureCredential(
+      AccessConfig.Builder config, String sasToken, String host) {
+    
config.putCredential(StorageAccessProperty.AZURE_SAS_TOKEN.getPropertyName() + 
host, sasToken);
+
+    // Iceberg 1.7.x may expect the credential key to _not_ be suffixed with 
endpoint
+    if (host.endsWith(AzureLocation.ADLS_ENDPOINT)) {
+      int suffixIndex = host.lastIndexOf(AzureLocation.ADLS_ENDPOINT) - 1;
+      if (suffixIndex > 0) {
+        String withSuffixStripped = host.substring(0, suffixIndex);
+        config.putCredential(
+            StorageAccessProperty.AZURE_SAS_TOKEN.getPropertyName() + 
withSuffixStripped, sasToken);
+      }
+    }
+
+    if (host.endsWith(AzureLocation.BLOB_ENDPOINT)) {
+      int suffixIndex = host.lastIndexOf(AzureLocation.BLOB_ENDPOINT) - 1;
+      if (suffixIndex > 0) {
+        String withSuffixStripped = host.substring(0, suffixIndex);
+        config.putCredential(
+            StorageAccessProperty.AZURE_SAS_TOKEN.getPropertyName() + 
withSuffixStripped, sasToken);
+      }
+    }
   }
 
   private String getBlobUserDelegationSas(
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java
index 49dc858d3..d8d88edc6 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java
@@ -133,7 +133,8 @@ public class StorageCredentialCache {
                   k.allowedWriteLocations());
           if (scopedCredentialsResult.isSuccess()) {
             long maxCacheDurationMs = 
maxCacheDurationMs(callCtx.getRealmConfig());
-            return new StorageCredentialCacheEntry(scopedCredentialsResult, 
maxCacheDurationMs);
+            return new StorageCredentialCacheEntry(
+                scopedCredentialsResult.getAccessConfig(), maxCacheDurationMs);
           }
           LOGGER
               .atDebug()
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java
index cef5907f2..7f5789ecb 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheEntry.java
@@ -18,27 +18,19 @@
  */
 package org.apache.polaris.core.storage.cache;
 
-import java.util.EnumMap;
-import java.util.function.BiConsumer;
-import org.apache.polaris.core.persistence.dao.entity.ScopedCredentialsResult;
+import java.time.Instant;
 import org.apache.polaris.core.storage.AccessConfig;
-import org.apache.polaris.core.storage.ImmutableAccessConfig;
-import org.apache.polaris.core.storage.StorageAccessProperty;
-import org.apache.polaris.core.storage.azure.AzureLocation;
 
 /** A storage credential cached entry. */
 public class StorageCredentialCacheEntry {
   /** The scoped creds map that is fetched from a creds vending service */
-  public final EnumMap<StorageAccessProperty, String> credsMap;
+  public final AccessConfig accessConfig;
 
-  private final ScopedCredentialsResult scopedCredentialsResult;
   private final long maxCacheDurationMs;
 
-  public StorageCredentialCacheEntry(
-      ScopedCredentialsResult scopedCredentialsResult, long 
maxCacheDurationMs) {
-    this.scopedCredentialsResult = scopedCredentialsResult;
+  public StorageCredentialCacheEntry(AccessConfig accessConfig, long 
maxCacheDurationMs) {
+    this.accessConfig = accessConfig;
     this.maxCacheDurationMs = maxCacheDurationMs;
-    this.credsMap = scopedCredentialsResult.getCredentials();
   }
 
   public long getMaxCacheDurationMs() {
@@ -47,45 +39,7 @@ public class StorageCredentialCacheEntry {
 
   /** Get the expiration time in millisecond for the cached entry */
   public long getExpirationTime() {
-    if 
(credsMap.containsKey(StorageAccessProperty.GCS_ACCESS_TOKEN_EXPIRES_AT)) {
-      return 
Long.parseLong(credsMap.get(StorageAccessProperty.GCS_ACCESS_TOKEN_EXPIRES_AT));
-    }
-    if 
(credsMap.containsKey(StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS)) {
-      return 
Long.parseLong(credsMap.get(StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS));
-    }
-    if (credsMap.containsKey(StorageAccessProperty.EXPIRATION_TIME)) {
-      return 
Long.parseLong(credsMap.get(StorageAccessProperty.EXPIRATION_TIME));
-    }
-    return Long.MAX_VALUE;
-  }
-
-  /**
-   * Azure needs special handling, the credential key is dynamically generated 
based on the storage
-   * account endpoint
-   */
-  private void handleAzureCredential(
-      BiConsumer<String, String> results, StorageAccessProperty 
credentialProperty, String value) {
-    if (credentialProperty.equals(StorageAccessProperty.AZURE_SAS_TOKEN)) {
-      String host = credsMap.get(StorageAccessProperty.AZURE_ACCOUNT_HOST);
-      results.accept(credentialProperty.getPropertyName() + host, value);
-
-      // Iceberg 1.7.x may expect the credential key to _not_ be suffixed with 
endpoint
-      if (host.endsWith(AzureLocation.ADLS_ENDPOINT)) {
-        int suffixIndex = host.lastIndexOf(AzureLocation.ADLS_ENDPOINT) - 1;
-        if (suffixIndex > 0) {
-          String withSuffixStripped = host.substring(0, suffixIndex);
-          results.accept(credentialProperty.getPropertyName() + 
withSuffixStripped, value);
-        }
-      }
-
-      if (host.endsWith(AzureLocation.BLOB_ENDPOINT)) {
-        int suffixIndex = host.lastIndexOf(AzureLocation.BLOB_ENDPOINT) - 1;
-        if (suffixIndex > 0) {
-          String withSuffixStripped = host.substring(0, suffixIndex);
-          results.accept(credentialProperty.getPropertyName() + 
withSuffixStripped, value);
-        }
-      }
-    }
+    return 
accessConfig.expiresAt().map(Instant::toEpochMilli).orElse(Long.MAX_VALUE);
   }
 
   /**
@@ -94,22 +48,6 @@ public class StorageCredentialCacheEntry {
    * @return a map of string representing the subscoped creds info.
    */
   AccessConfig toAccessConfig() {
-    ImmutableAccessConfig.Builder config = AccessConfig.builder();
-    if (!credsMap.isEmpty()) {
-      credsMap.forEach(
-          (key, value) -> {
-            if (!key.isCredential()) {
-              config.putExtraProperty(key.getPropertyName(), value);
-              return;
-            }
-
-            if (key.equals(StorageAccessProperty.AZURE_SAS_TOKEN)) {
-              handleAzureCredential(config::putCredential, key, value);
-            } else if (!key.equals(StorageAccessProperty.AZURE_ACCOUNT_HOST)) {
-              config.putCredential(key.getPropertyName(), value);
-            }
-          });
-    }
-    return config.build();
+    return accessConfig;
   }
 }
diff --git 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/gcp/GcpCredentialsStorageIntegration.java
 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/gcp/GcpCredentialsStorageIntegration.java
index 2fc1438fe..1dff89ca1 100644
--- 
a/polaris-core/src/main/java/org/apache/polaris/core/storage/gcp/GcpCredentialsStorageIntegration.java
+++ 
b/polaris-core/src/main/java/org/apache/polaris/core/storage/gcp/GcpCredentialsStorageIntegration.java
@@ -30,7 +30,6 @@ import jakarta.annotation.Nonnull;
 import java.io.IOException;
 import java.net.URI;
 import java.util.ArrayList;
-import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -39,6 +38,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Stream;
 import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.storage.AccessConfig;
 import org.apache.polaris.core.storage.InMemoryStorageIntegration;
 import org.apache.polaris.core.storage.PolarisStorageIntegration;
 import org.apache.polaris.core.storage.StorageAccessProperty;
@@ -69,7 +69,7 @@ public class GcpCredentialsStorageIntegration
   }
 
   @Override
-  public EnumMap<StorageAccessProperty, String> getSubscopedCreds(
+  public AccessConfig getSubscopedCreds(
       @Nonnull CallContext callContext,
       @Nonnull GcpStorageConfigurationInfo storageConfig,
       boolean allowListOperation,
@@ -106,12 +106,12 @@ public class GcpCredentialsStorageIntegration
 
     // If expires_in missing, use source credential's expire time, which 
require another api call to
     // get.
-    EnumMap<StorageAccessProperty, String> propertyMap = new 
EnumMap<>(StorageAccessProperty.class);
-    propertyMap.put(StorageAccessProperty.GCS_ACCESS_TOKEN, 
token.getTokenValue());
-    propertyMap.put(
+    AccessConfig.Builder accessConfig = AccessConfig.builder();
+    accessConfig.put(StorageAccessProperty.GCS_ACCESS_TOKEN, 
token.getTokenValue());
+    accessConfig.put(
         StorageAccessProperty.GCS_ACCESS_TOKEN_EXPIRES_AT,
         String.valueOf(token.getExpirationTime().getTime()));
-    return propertyMap;
+    return accessConfig.build();
   }
 
   private String convertToString(CredentialAccessBoundary accessBoundary) {
diff --git 
a/polaris-core/src/test/java/org/apache/polaris/core/storage/AccessConfigTest.java
 
b/polaris-core/src/test/java/org/apache/polaris/core/storage/AccessConfigTest.java
new file mode 100644
index 000000000..57e1f1465
--- /dev/null
+++ 
b/polaris-core/src/test/java/org/apache/polaris/core/storage/AccessConfigTest.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.polaris.core.storage;
+
+import static 
org.apache.polaris.core.storage.StorageAccessProperty.AWS_ENDPOINT;
+import static 
org.apache.polaris.core.storage.StorageAccessProperty.AWS_SECRET_KEY;
+import static 
org.apache.polaris.core.storage.StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS;
+import static 
org.apache.polaris.core.storage.StorageAccessProperty.EXPIRATION_TIME;
+import static 
org.apache.polaris.core.storage.StorageAccessProperty.GCS_ACCESS_TOKEN_EXPIRES_AT;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.Instant;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+public class AccessConfigTest {
+
+  @Test
+  public void testPutGet() {
+    AccessConfig.Builder b = AccessConfig.builder();
+    b.put(AWS_ENDPOINT, "ep1");
+    b.put(AWS_SECRET_KEY, "sk2");
+    AccessConfig c = b.build();
+    
assertThat(c.credentials()).isEqualTo(Map.of(AWS_SECRET_KEY.getPropertyName(), 
"sk2"));
+    
assertThat(c.extraProperties()).isEqualTo(Map.of(AWS_ENDPOINT.getPropertyName(),
 "ep1"));
+    assertThat(c.get(AWS_SECRET_KEY)).isEqualTo("sk2");
+    assertThat(c.get(AWS_ENDPOINT)).isEqualTo("ep1");
+  }
+
+  @Test
+  public void testGetExtraProperty() {
+    AccessConfig.Builder b = AccessConfig.builder();
+    b.putExtraProperty(AWS_ENDPOINT.getPropertyName(), "extra");
+    AccessConfig c = b.build();
+    
assertThat(c.extraProperties()).isEqualTo(Map.of(AWS_ENDPOINT.getPropertyName(),
 "extra"));
+    assertThat(c.get(AWS_ENDPOINT)).isEqualTo("extra");
+  }
+
+  @Test
+  public void testGetInternalProperty() {
+    AccessConfig.Builder b = AccessConfig.builder();
+    b.putExtraProperty(AWS_ENDPOINT.getPropertyName(), "extra");
+    b.putInternalProperty(AWS_ENDPOINT.getPropertyName(), "ep1");
+    AccessConfig c = b.build();
+    
assertThat(c.extraProperties()).isEqualTo(Map.of(AWS_ENDPOINT.getPropertyName(),
 "extra"));
+    
assertThat(c.internalProperties()).isEqualTo(Map.of(AWS_ENDPOINT.getPropertyName(),
 "ep1"));
+    assertThat(c.get(AWS_ENDPOINT)).isEqualTo("ep1");
+  }
+
+  @Test
+  public void testNoCredentialOverride() {
+    AccessConfig.Builder b = AccessConfig.builder();
+    b.put(AWS_SECRET_KEY, "sk-test");
+    b.putExtraProperty(AWS_SECRET_KEY.getPropertyName(), "sk-extra");
+    b.putInternalProperty(AWS_SECRET_KEY.getPropertyName(), "sk-internal");
+    AccessConfig c = b.build();
+    assertThat(c.get(AWS_SECRET_KEY)).isEqualTo("sk-test");
+    
assertThat(c.extraProperties()).isEqualTo(Map.of(AWS_SECRET_KEY.getPropertyName(),
 "sk-extra"));
+    assertThat(c.internalProperties())
+        .isEqualTo(Map.of(AWS_SECRET_KEY.getPropertyName(), "sk-internal"));
+  }
+
+  @Test
+  public void testExpiresAt() {
+    AccessConfig.Builder b = AccessConfig.builder();
+    assertThat(b.build().expiresAt()).isEmpty();
+    b.put(GCS_ACCESS_TOKEN_EXPIRES_AT, "111");
+    assertThat(b.build().expiresAt()).hasValue(Instant.ofEpochMilli(111));
+    b.put(AWS_SESSION_TOKEN_EXPIRES_AT_MS, "222");
+    assertThat(b.build().expiresAt()).hasValue(Instant.ofEpochMilli(222));
+    b.put(EXPIRATION_TIME, "333");
+    assertThat(b.build().expiresAt()).hasValue(Instant.ofEpochMilli(333));
+    b.expiresAt(Instant.ofEpochMilli(444));
+    assertThat(b.build().expiresAt()).hasValue(Instant.ofEpochMilli(444));
+  }
+}
diff --git 
a/polaris-core/src/test/java/org/apache/polaris/core/storage/InMemoryStorageIntegrationTest.java
 
b/polaris-core/src/test/java/org/apache/polaris/core/storage/InMemoryStorageIntegrationTest.java
index d0bf9de8a..0842a132b 100644
--- 
a/polaris-core/src/test/java/org/apache/polaris/core/storage/InMemoryStorageIntegrationTest.java
+++ 
b/polaris-core/src/test/java/org/apache/polaris/core/storage/InMemoryStorageIntegrationTest.java
@@ -21,7 +21,6 @@ package org.apache.polaris.core.storage;
 import jakarta.annotation.Nonnull;
 import jakarta.annotation.Nullable;
 import java.time.Clock;
-import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -212,7 +211,7 @@ class InMemoryStorageIntegrationTest {
     }
 
     @Override
-    public EnumMap<StorageAccessProperty, String> getSubscopedCreds(
+    public AccessConfig getSubscopedCreds(
         @Nonnull CallContext callContext,
         @Nonnull PolarisStorageConfigurationInfo storageConfig,
         boolean allowListOperation,
diff --git 
a/polaris-core/src/test/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegrationTest.java
 
b/polaris-core/src/test/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegrationTest.java
new file mode 100644
index 000000000..89b60dba5
--- /dev/null
+++ 
b/polaris-core/src/test/java/org/apache/polaris/core/storage/azure/AzureCredentialsStorageIntegrationTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.polaris.core.storage.azure;
+
+import static 
org.apache.polaris.core.storage.azure.AzureCredentialsStorageIntegration.toAccessConfig;
+
+import java.time.Instant;
+import org.apache.polaris.core.storage.AccessConfig;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class AzureCredentialsStorageIntegrationTest {
+
+  @Test
+  public void testAzureCredentialFormatting() {
+    Instant expiresAt = Instant.ofEpochMilli(Long.MAX_VALUE);
+
+    AccessConfig noSuffixResult = toAccessConfig("sasToken", "some_account", 
expiresAt);
+    Assertions.assertThat(noSuffixResult.credentials()).hasSize(2);
+    
Assertions.assertThat(noSuffixResult.credentials()).containsKey("adls.sas-token.some_account");
+
+    AccessConfig adlsSuffixResult =
+        toAccessConfig("sasToken", "some_account." + 
AzureLocation.ADLS_ENDPOINT, expiresAt);
+    Assertions.assertThat(adlsSuffixResult.credentials()).hasSize(3);
+    Assertions.assertThat(adlsSuffixResult.credentials())
+        .containsKey("adls.sas-token.some_account");
+    Assertions.assertThat(adlsSuffixResult.credentials())
+        .containsKey("adls.sas-token.some_account." + 
AzureLocation.ADLS_ENDPOINT);
+
+    AccessConfig blobSuffixResult =
+        toAccessConfig("sasToken", "some_account." + 
AzureLocation.BLOB_ENDPOINT, expiresAt);
+    Assertions.assertThat(blobSuffixResult.credentials()).hasSize(3);
+    Assertions.assertThat(blobSuffixResult.credentials())
+        .containsKey("adls.sas-token.some_account");
+    Assertions.assertThat(blobSuffixResult.credentials())
+        .containsKey("adls.sas-token.some_account." + 
AzureLocation.BLOB_ENDPOINT);
+  }
+}
diff --git 
a/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java
 
b/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java
index bf1cf1fcf..39095f08b 100644
--- 
a/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java
+++ 
b/polaris-core/src/test/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheTest.java
@@ -20,11 +20,9 @@ package org.apache.polaris.core.storage.cache;
 
 import static 
org.apache.polaris.core.persistence.PrincipalSecretsGenerator.RANDOM_SECRETS;
 
-import com.google.common.collect.ImmutableMap;
 import jakarta.annotation.Nonnull;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -46,7 +44,6 @@ import 
org.apache.polaris.core.persistence.transactional.TreeMapMetaStore;
 import 
org.apache.polaris.core.persistence.transactional.TreeMapTransactionalPersistenceImpl;
 import org.apache.polaris.core.storage.AccessConfig;
 import org.apache.polaris.core.storage.StorageAccessProperty;
-import org.apache.polaris.core.storage.azure.AzureLocation;
 import org.assertj.core.api.Assertions;
 import org.junit.jupiter.api.RepeatedTest;
 import org.junit.jupiter.api.Test;
@@ -398,30 +395,26 @@ public class StorageCredentialCacheTest {
               : String.valueOf(Long.MAX_VALUE);
       res.add(
           new ScopedCredentialsResult(
-              new EnumMap<>(
-                  ImmutableMap.<StorageAccessProperty, String>builder()
-                      .put(StorageAccessProperty.AWS_KEY_ID, "key_id_" + 
finalI)
-                      .put(StorageAccessProperty.AWS_SECRET_KEY, "key_secret_" 
+ finalI)
-                      
.put(StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS, expireTime)
-                      .put(StorageAccessProperty.EXPIRATION_TIME, expireTime)
-                      .buildOrThrow())));
+              AccessConfig.builder()
+                  .put(StorageAccessProperty.AWS_KEY_ID, "key_id_" + finalI)
+                  .put(StorageAccessProperty.AWS_SECRET_KEY, "key_secret_" + 
finalI)
+                  .put(StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS, 
expireTime)
+                  .put(StorageAccessProperty.EXPIRATION_TIME, expireTime)
+                  .build()));
       if (res.size() == number) return res;
       res.add(
           new ScopedCredentialsResult(
-              new EnumMap<>(
-                  ImmutableMap.<StorageAccessProperty, String>builder()
-                      .put(StorageAccessProperty.AZURE_SAS_TOKEN, "sas_token_" 
+ finalI)
-                      .put(StorageAccessProperty.AZURE_ACCOUNT_HOST, 
"account_host")
-                      .put(StorageAccessProperty.EXPIRATION_TIME, expireTime)
-                      .buildOrThrow())));
+              AccessConfig.builder()
+                  .put(StorageAccessProperty.AZURE_SAS_TOKEN, "sas_token_" + 
finalI)
+                  .put(StorageAccessProperty.EXPIRATION_TIME, expireTime)
+                  .build()));
       if (res.size() == number) return res;
       res.add(
           new ScopedCredentialsResult(
-              new EnumMap<>(
-                  ImmutableMap.<StorageAccessProperty, String>builder()
-                      .put(StorageAccessProperty.GCS_ACCESS_TOKEN, 
"gcs_token_" + finalI)
-                      .put(StorageAccessProperty.GCS_ACCESS_TOKEN_EXPIRES_AT, 
expireTime)
-                      .buildOrThrow())));
+              AccessConfig.builder()
+                  .put(StorageAccessProperty.GCS_ACCESS_TOKEN, "gcs_token_" + 
finalI)
+                  .put(StorageAccessProperty.GCS_ACCESS_TOKEN_EXPIRES_AT, 
expireTime)
+                  .build()));
     }
     return res;
   }
@@ -444,106 +437,16 @@ public class StorageCredentialCacheTest {
     return Arrays.asList(polarisEntity1, polarisEntity2, polarisEntity3);
   }
 
-  @Test
-  public void testAzureCredentialFormatting() {
-    storageCredentialCache = newStorageCredentialCache();
-    List<ScopedCredentialsResult> mockedScopedCreds =
-        List.of(
-            new ScopedCredentialsResult(
-                new EnumMap<>(
-                    ImmutableMap.<StorageAccessProperty, String>builder()
-                        .put(StorageAccessProperty.AZURE_SAS_TOKEN, 
"sas_token_azure_1")
-                        .put(StorageAccessProperty.AZURE_ACCOUNT_HOST, 
"some_account")
-                        .put(StorageAccessProperty.EXPIRATION_TIME, 
String.valueOf(Long.MAX_VALUE))
-                        .buildOrThrow())),
-            new ScopedCredentialsResult(
-                new EnumMap<>(
-                    ImmutableMap.<StorageAccessProperty, String>builder()
-                        .put(StorageAccessProperty.AZURE_SAS_TOKEN, 
"sas_token_azure_2")
-                        .put(
-                            StorageAccessProperty.AZURE_ACCOUNT_HOST,
-                            "some_account." + AzureLocation.ADLS_ENDPOINT)
-                        .put(StorageAccessProperty.EXPIRATION_TIME, 
String.valueOf(Long.MAX_VALUE))
-                        .buildOrThrow())),
-            new ScopedCredentialsResult(
-                new EnumMap<>(
-                    ImmutableMap.<StorageAccessProperty, String>builder()
-                        .put(StorageAccessProperty.AZURE_SAS_TOKEN, 
"sas_token_azure_3")
-                        .put(
-                            StorageAccessProperty.AZURE_ACCOUNT_HOST,
-                            "some_account." + AzureLocation.BLOB_ENDPOINT)
-                        .put(StorageAccessProperty.EXPIRATION_TIME, 
String.valueOf(Long.MAX_VALUE))
-                        .buildOrThrow())));
-
-    Mockito.when(
-            metaStoreManager.getSubscopedCredsForEntity(
-                Mockito.any(),
-                Mockito.anyLong(),
-                Mockito.anyLong(),
-                Mockito.any(),
-                Mockito.anyBoolean(),
-                Mockito.anySet(),
-                Mockito.anySet()))
-        .thenReturn(mockedScopedCreds.get(0))
-        .thenReturn(mockedScopedCreds.get(1))
-        .thenReturn(mockedScopedCreds.get(2));
-    List<PolarisEntity> entityList = getPolarisEntities();
-
-    Map<String, String> noSuffixResult =
-        storageCredentialCache
-            .getOrGenerateSubScopeCreds(
-                metaStoreManager,
-                callCtx,
-                entityList.get(0),
-                true,
-                Set.of("s3://bucket1/path", "s3://bucket2/path"),
-                Set.of("s3://bucket3/path", "s3://bucket4/path"))
-            .credentials();
-    Assertions.assertThat(noSuffixResult.size()).isEqualTo(2);
-    
Assertions.assertThat(noSuffixResult).containsKey("adls.sas-token.some_account");
-
-    Map<String, String> adlsSuffixResult =
-        storageCredentialCache
-            .getOrGenerateSubScopeCreds(
-                metaStoreManager,
-                callCtx,
-                entityList.get(1),
-                true,
-                Set.of("s3://bucket1/path", "s3://bucket2/path"),
-                Set.of("s3://bucket3/path", "s3://bucket4/path"))
-            .credentials();
-    Assertions.assertThat(adlsSuffixResult.size()).isEqualTo(3);
-    
Assertions.assertThat(adlsSuffixResult).containsKey("adls.sas-token.some_account");
-    Assertions.assertThat(adlsSuffixResult)
-        .containsKey("adls.sas-token.some_account." + 
AzureLocation.ADLS_ENDPOINT);
-
-    Map<String, String> blobSuffixResult =
-        storageCredentialCache
-            .getOrGenerateSubScopeCreds(
-                metaStoreManager,
-                callCtx,
-                entityList.get(2),
-                true,
-                Set.of("s3://bucket1/path", "s3://bucket2/path"),
-                Set.of("s3://bucket3/path", "s3://bucket4/path"))
-            .credentials();
-    Assertions.assertThat(blobSuffixResult.size()).isEqualTo(3);
-    
Assertions.assertThat(blobSuffixResult).containsKey("adls.sas-token.some_account");
-    Assertions.assertThat(blobSuffixResult)
-        .containsKey("adls.sas-token.some_account." + 
AzureLocation.BLOB_ENDPOINT);
-  }
-
   @Test
   public void testExtraProperties() {
     storageCredentialCache = newStorageCredentialCache();
     ScopedCredentialsResult properties =
         new ScopedCredentialsResult(
-            new EnumMap<>(
-                ImmutableMap.<StorageAccessProperty, String>builder()
-                    .put(StorageAccessProperty.AWS_SECRET_KEY, 
"super-secret-123")
-                    .put(StorageAccessProperty.AWS_ENDPOINT, "test-endpoint1")
-                    .put(StorageAccessProperty.AWS_PATH_STYLE_ACCESS, "true")
-                    .buildOrThrow()));
+            AccessConfig.builder()
+                .put(StorageAccessProperty.AWS_SECRET_KEY, "super-secret-123")
+                .put(StorageAccessProperty.AWS_ENDPOINT, "test-endpoint1")
+                .put(StorageAccessProperty.AWS_PATH_STYLE_ACCESS, "true")
+                .build());
     Mockito.when(
             metaStoreManager.getSubscopedCredsForEntity(
                 Mockito.any(),
diff --git 
a/polaris-core/src/test/java/org/apache/polaris/service/storage/aws/AwsCredentialsStorageIntegrationTest.java
 
b/polaris-core/src/test/java/org/apache/polaris/service/storage/aws/AwsCredentialsStorageIntegrationTest.java
index 7dee37ebb..cd7729e90 100644
--- 
a/polaris-core/src/test/java/org/apache/polaris/service/storage/aws/AwsCredentialsStorageIntegrationTest.java
+++ 
b/polaris-core/src/test/java/org/apache/polaris/service/storage/aws/AwsCredentialsStorageIntegrationTest.java
@@ -18,13 +18,14 @@
  */
 package org.apache.polaris.service.storage.aws;
 
+import static 
org.apache.polaris.core.storage.PolarisStorageConfigurationInfo.StorageType.S3;
 import static org.assertj.core.api.Assertions.assertThat;
 
 import jakarta.annotation.Nonnull;
 import java.time.Instant;
-import java.util.EnumMap;
 import java.util.List;
 import java.util.Set;
+import org.apache.polaris.core.storage.AccessConfig;
 import org.apache.polaris.core.storage.BaseStorageIntegrationTest;
 import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
 import org.apache.polaris.core.storage.StorageAccessProperty;
@@ -84,26 +85,22 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
               return ASSUME_ROLE_RESPONSE;
             });
     String warehouseDir = scheme + "://bucket/path/to/warehouse";
-    EnumMap<StorageAccessProperty, String> credentials =
+    AccessConfig accessConfig =
         new AwsCredentialsStorageIntegration(stsClient)
             .getSubscopedCreds(
                 newCallContext(),
                 new AwsStorageConfigurationInfo(
-                    PolarisStorageConfigurationInfo.StorageType.S3,
-                    List.of(warehouseDir),
-                    roleARN,
-                    externalId,
-                    null),
+                    S3, List.of(warehouseDir), roleARN, externalId, null),
                 true,
                 Set.of(warehouseDir + "/namespace/table"),
                 Set.of(warehouseDir + "/namespace/table"));
-    assertThat(credentials)
+    assertThat(accessConfig.credentials())
         .isNotEmpty()
-        .containsEntry(StorageAccessProperty.AWS_TOKEN, "sess")
-        .containsEntry(StorageAccessProperty.AWS_KEY_ID, "accessKey")
-        .containsEntry(StorageAccessProperty.AWS_SECRET_KEY, "secretKey")
+        .containsEntry(StorageAccessProperty.AWS_TOKEN.getPropertyName(), 
"sess")
+        .containsEntry(StorageAccessProperty.AWS_KEY_ID.getPropertyName(), 
"accessKey")
+        .containsEntry(StorageAccessProperty.AWS_SECRET_KEY.getPropertyName(), 
"secretKey")
         .containsEntry(
-            StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS,
+            
StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS.getPropertyName(),
             String.valueOf(EXPIRE_TIME.toEpochMilli()));
   }
 
@@ -249,7 +246,7 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
         break;
       case AWS_PARTITION:
       case "aws-us-gov":
-        EnumMap<StorageAccessProperty, String> credentials =
+        AccessConfig accessConfig =
             new AwsCredentialsStorageIntegration(stsClient)
                 .getSubscopedCreds(
                     newCallContext(),
@@ -262,13 +259,13 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
                     true,
                     Set.of(s3Path(bucket, firstPath), s3Path(bucket, 
secondPath)),
                     Set.of(s3Path(bucket, firstPath)));
-        assertThat(credentials)
+        assertThat(accessConfig.credentials())
             .isNotEmpty()
-            .containsEntry(StorageAccessProperty.AWS_TOKEN, "sess")
-            .containsEntry(StorageAccessProperty.AWS_KEY_ID, "accessKey")
-            .containsEntry(StorageAccessProperty.AWS_SECRET_KEY, "secretKey")
+            .containsEntry(StorageAccessProperty.AWS_TOKEN.getPropertyName(), 
"sess")
+            .containsEntry(StorageAccessProperty.AWS_KEY_ID.getPropertyName(), 
"accessKey")
+            
.containsEntry(StorageAccessProperty.AWS_SECRET_KEY.getPropertyName(), 
"secretKey")
             .containsEntry(
-                StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS,
+                
StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS.getPropertyName(),
                 String.valueOf(EXPIRE_TIME.toEpochMilli()));
         break;
       default:
@@ -350,12 +347,12 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
             });
     PolarisStorageConfigurationInfo.StorageType storageType =
         PolarisStorageConfigurationInfo.StorageType.S3;
-    EnumMap<StorageAccessProperty, String> credentials =
+    AccessConfig accessConfig =
         new AwsCredentialsStorageIntegration(stsClient)
             .getSubscopedCreds(
                 newCallContext(),
                 new AwsStorageConfigurationInfo(
-                    PolarisStorageConfigurationInfo.StorageType.S3,
+                    S3,
                     List.of(s3Path(bucket, warehouseKeyPrefix)),
                     roleARN,
                     externalId,
@@ -363,13 +360,13 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
                 false, /* allowList = false*/
                 Set.of(s3Path(bucket, firstPath), s3Path(bucket, secondPath)),
                 Set.of(s3Path(bucket, firstPath)));
-    assertThat(credentials)
+    assertThat(accessConfig.credentials())
         .isNotEmpty()
-        .containsEntry(StorageAccessProperty.AWS_TOKEN, "sess")
-        .containsEntry(StorageAccessProperty.AWS_KEY_ID, "accessKey")
-        .containsEntry(StorageAccessProperty.AWS_SECRET_KEY, "secretKey")
+        .containsEntry(StorageAccessProperty.AWS_TOKEN.getPropertyName(), 
"sess")
+        .containsEntry(StorageAccessProperty.AWS_KEY_ID.getPropertyName(), 
"accessKey")
+        .containsEntry(StorageAccessProperty.AWS_SECRET_KEY.getPropertyName(), 
"secretKey")
         .containsEntry(
-            StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS,
+            
StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS.getPropertyName(),
             String.valueOf(EXPIRE_TIME.toEpochMilli()));
   }
 
@@ -445,7 +442,7 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
             });
     PolarisStorageConfigurationInfo.StorageType storageType =
         PolarisStorageConfigurationInfo.StorageType.S3;
-    EnumMap<StorageAccessProperty, String> credentials =
+    AccessConfig accessConfig =
         new AwsCredentialsStorageIntegration(stsClient)
             .getSubscopedCreds(
                 newCallContext(),
@@ -458,13 +455,13 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
                 true, /* allowList = true */
                 Set.of(s3Path(bucket, firstPath), s3Path(bucket, secondPath)),
                 Set.of());
-    assertThat(credentials)
+    assertThat(accessConfig.credentials())
         .isNotEmpty()
-        .containsEntry(StorageAccessProperty.AWS_TOKEN, "sess")
-        .containsEntry(StorageAccessProperty.AWS_KEY_ID, "accessKey")
-        .containsEntry(StorageAccessProperty.AWS_SECRET_KEY, "secretKey")
+        .containsEntry(StorageAccessProperty.AWS_TOKEN.getPropertyName(), 
"sess")
+        .containsEntry(StorageAccessProperty.AWS_KEY_ID.getPropertyName(), 
"accessKey")
+        .containsEntry(StorageAccessProperty.AWS_SECRET_KEY.getPropertyName(), 
"secretKey")
         .containsEntry(
-            StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS,
+            
StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS.getPropertyName(),
             String.valueOf(EXPIRE_TIME.toEpochMilli()));
   }
 
@@ -510,12 +507,12 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
                       });
               return ASSUME_ROLE_RESPONSE;
             });
-    EnumMap<StorageAccessProperty, String> credentials =
+    AccessConfig accessConfig =
         new AwsCredentialsStorageIntegration(stsClient)
             .getSubscopedCreds(
                 newCallContext(),
                 new AwsStorageConfigurationInfo(
-                    PolarisStorageConfigurationInfo.StorageType.S3,
+                    S3,
                     List.of(s3Path(bucket, warehouseKeyPrefix)),
                     roleARN,
                     externalId,
@@ -523,13 +520,13 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
                 true, /* allowList = true */
                 Set.of(),
                 Set.of());
-    assertThat(credentials)
+    assertThat(accessConfig.credentials())
         .isNotEmpty()
-        .containsEntry(StorageAccessProperty.AWS_TOKEN, "sess")
-        .containsEntry(StorageAccessProperty.AWS_KEY_ID, "accessKey")
-        .containsEntry(StorageAccessProperty.AWS_SECRET_KEY, "secretKey")
+        .containsEntry(StorageAccessProperty.AWS_TOKEN.getPropertyName(), 
"sess")
+        .containsEntry(StorageAccessProperty.AWS_KEY_ID.getPropertyName(), 
"accessKey")
+        .containsEntry(StorageAccessProperty.AWS_SECRET_KEY.getPropertyName(), 
"secretKey")
         .containsEntry(
-            StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS,
+            
StorageAccessProperty.AWS_SESSION_TOKEN_EXPIRES_AT_MS.getPropertyName(),
             String.valueOf(EXPIRE_TIME.toEpochMilli()));
   }
 
@@ -567,12 +564,12 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
         break;
       case AWS_PARTITION:
       case "aws-us-gov":
-        EnumMap<StorageAccessProperty, String> credentials =
+        AccessConfig accessConfig =
             new AwsCredentialsStorageIntegration(stsClient)
                 .getSubscopedCreds(
                     newCallContext(),
                     new AwsStorageConfigurationInfo(
-                        PolarisStorageConfigurationInfo.StorageType.S3,
+                        S3,
                         List.of(s3Path(bucket, warehouseKeyPrefix)),
                         roleARN,
                         externalId,
@@ -580,9 +577,9 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
                     true, /* allowList = true */
                     Set.of(),
                     Set.of());
-        assertThat(credentials)
+        assertThat(accessConfig.credentials())
             .isNotEmpty()
-            .containsEntry(StorageAccessProperty.CLIENT_REGION, clientRegion);
+            
.containsEntry(StorageAccessProperty.CLIENT_REGION.getPropertyName(), 
clientRegion);
         break;
       default:
         throw new IllegalArgumentException("Unknown aws partition: " + 
awsPartition);
@@ -605,20 +602,18 @@ class AwsCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
             });
     switch (awsPartition) {
       case AWS_PARTITION:
-        EnumMap<StorageAccessProperty, String> credentials =
+        AccessConfig accessConfig =
             new AwsCredentialsStorageIntegration(stsClient)
                 .getSubscopedCreds(
                     newCallContext(),
                     new AwsStorageConfigurationInfo(
-                        PolarisStorageConfigurationInfo.StorageType.S3,
-                        List.of(s3Path(bucket, warehouseKeyPrefix)),
-                        roleARN,
-                        externalId,
-                        null),
+                        S3, List.of(s3Path(bucket, warehouseKeyPrefix)), 
roleARN, externalId, null),
                     true, /* allowList = true */
                     Set.of(),
                     Set.of());
-        
assertThat(credentials).isNotEmpty().doesNotContainKey(StorageAccessProperty.CLIENT_REGION);
+        assertThat(accessConfig.credentials())
+            .isNotEmpty()
+            
.doesNotContainKey(StorageAccessProperty.CLIENT_REGION.getPropertyName());
         break;
       case "aws-cn":
       case "aws-us-gov":
diff --git 
a/polaris-core/src/test/java/org/apache/polaris/service/storage/azure/AzureCredentialStorageIntegrationTest.java
 
b/polaris-core/src/test/java/org/apache/polaris/service/storage/azure/AzureCredentialStorageIntegrationTest.java
index 9f43d42bd..14bfcac1c 100644
--- 
a/polaris-core/src/test/java/org/apache/polaris/service/storage/azure/AzureCredentialStorageIntegrationTest.java
+++ 
b/polaris-core/src/test/java/org/apache/polaris/service/storage/azure/AzureCredentialStorageIntegrationTest.java
@@ -42,11 +42,10 @@ import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.EnumMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.stream.Stream;
+import org.apache.polaris.core.storage.AccessConfig;
 import org.apache.polaris.core.storage.BaseStorageIntegrationTest;
 import org.apache.polaris.core.storage.StorageAccessProperty;
 import 
org.apache.polaris.core.storage.azure.AzureCredentialsStorageIntegration;
@@ -120,13 +119,13 @@ public class AzureCredentialStorageIntegrationTest 
extends BaseStorageIntegratio
             String.format(
                 
"abfss://container@icebergdfsstorageacct.%s.core.windows.net/polaris-test/",
                 service));
-    Map<StorageAccessProperty, String> credsMap =
+    AccessConfig accessConfig =
         subscopedCredsForOperations(
             /* allowedReadLoc= */ allowedLoc,
             /* allowedWriteLoc= */ new ArrayList<>(),
             allowListAction);
-    Assertions.assertThat(credsMap).hasSize(2);
-    String sasToken = credsMap.get(StorageAccessProperty.AZURE_SAS_TOKEN);
+    Assertions.assertThat(accessConfig.credentials()).hasSize(2);
+    String sasToken = accessConfig.get(StorageAccessProperty.AZURE_SAS_TOKEN);
     Assertions.assertThat(sasToken).isNotNull();
     String serviceEndpoint =
         String.format("https://icebergdfsstorageacct.%s.core.windows.net";, 
service);
@@ -191,7 +190,7 @@ public class AzureCredentialStorageIntegrationTest extends 
BaseStorageIntegratio
             String.format(
                 
"abfss://container@icebergdfsstorageacct.%s.core.windows.net/%s",
                 service, allowedPrefix));
-    Map<StorageAccessProperty, String> credsMap =
+    AccessConfig accessConfig =
         subscopedCredsForOperations(
             /* allowedReadLoc= */ allowedLoc,
             /* allowedWriteLoc= */ new ArrayList<>(),
@@ -199,7 +198,7 @@ public class AzureCredentialStorageIntegrationTest extends 
BaseStorageIntegratio
 
     BlobClient blobClient =
         createBlobClient(
-            credsMap.get(StorageAccessProperty.AZURE_SAS_TOKEN),
+            accessConfig.get(StorageAccessProperty.AZURE_SAS_TOKEN),
             "https://icebergdfsstorageacct.dfs.core.windows.net";,
             "container",
             allowedPrefix);
@@ -230,7 +229,7 @@ public class AzureCredentialStorageIntegrationTest extends 
BaseStorageIntegratio
     // read fail because container is blocked
     BlobClient blobClientReadFail =
         createBlobClient(
-            credsMap.get(StorageAccessProperty.AZURE_SAS_TOKEN),
+            accessConfig.get(StorageAccessProperty.AZURE_SAS_TOKEN),
             String.format("https://icebergdfsstorageacct.%s.core.windows.net";, 
service),
             "regtest",
             blockedPrefix);
@@ -261,7 +260,7 @@ public class AzureCredentialStorageIntegrationTest extends 
BaseStorageIntegratio
             String.format(
                 
"abfss://container@icebergdfsstorageacct.%s.core.windows.net/%s",
                 service, allowedPrefix));
-    Map<StorageAccessProperty, String> credsMap =
+    AccessConfig accessConfig =
         subscopedCredsForOperations(
             /* allowedReadLoc= */ new ArrayList<>(),
             /* allowedWriteLoc= */ allowedLoc,
@@ -270,13 +269,13 @@ public class AzureCredentialStorageIntegrationTest 
extends BaseStorageIntegratio
         String.format("https://icebergdfsstorageacct.%s.core.windows.net";, 
service);
     BlobClient blobClient =
         createBlobClient(
-            credsMap.get(StorageAccessProperty.AZURE_SAS_TOKEN),
+            accessConfig.get(StorageAccessProperty.AZURE_SAS_TOKEN),
             serviceEndpoint,
             "container",
             allowedPrefix + 
"metadata/00000-65ffa17b-fe64-4c38-bcb9-06f9bd12aa2a.metadata.json");
     DataLakeFileClient fileClient =
         createDatalakeFileClient(
-            credsMap.get(StorageAccessProperty.AZURE_SAS_TOKEN),
+            accessConfig.get(StorageAccessProperty.AZURE_SAS_TOKEN),
             serviceEndpoint,
             "container",
             "polaris-test/scopedcreds/metadata",
@@ -311,13 +310,13 @@ public class AzureCredentialStorageIntegrationTest 
extends BaseStorageIntegratio
     String blockedContainer = "regtest";
     BlobClient blobClientWriteFail =
         createBlobClient(
-            credsMap.get(StorageAccessProperty.AZURE_SAS_TOKEN),
+            accessConfig.get(StorageAccessProperty.AZURE_SAS_TOKEN),
             serviceEndpoint,
             blockedContainer,
             blockedPrefix);
     DataLakeFileClient fileClientFail =
         createDatalakeFileClient(
-            credsMap.get(StorageAccessProperty.AZURE_SAS_TOKEN),
+            accessConfig.get(StorageAccessProperty.AZURE_SAS_TOKEN),
             serviceEndpoint,
             blockedContainer,
             "polaris-test/scopedcreds/metadata",
@@ -338,7 +337,7 @@ public class AzureCredentialStorageIntegrationTest extends 
BaseStorageIntegratio
     }
   }
 
-  private Map<StorageAccessProperty, String> subscopedCredsForOperations(
+  private AccessConfig subscopedCredsForOperations(
       List<String> allowedReadLoc, List<String> allowedWriteLoc, boolean 
allowListAction) {
     List<String> allowedLoc = new ArrayList<>();
     allowedLoc.addAll(allowedReadLoc);
@@ -347,14 +346,12 @@ public class AzureCredentialStorageIntegrationTest 
extends BaseStorageIntegratio
         new AzureStorageConfigurationInfo(allowedLoc, tenantId);
     AzureCredentialsStorageIntegration azureCredsIntegration =
         new AzureCredentialsStorageIntegration();
-    EnumMap<StorageAccessProperty, String> credsMap =
-        azureCredsIntegration.getSubscopedCreds(
-            newCallContext(),
-            azureConfig,
-            allowListAction,
-            new HashSet<>(allowedReadLoc),
-            new HashSet<>(allowedWriteLoc));
-    return credsMap;
+    return azureCredsIntegration.getSubscopedCreds(
+        newCallContext(),
+        azureConfig,
+        allowListAction,
+        new HashSet<>(allowedReadLoc),
+        new HashSet<>(allowedWriteLoc));
   }
 
   private BlobContainerClient createContainerClient(
diff --git 
a/polaris-core/src/test/java/org/apache/polaris/service/storage/gcp/GcpCredentialsStorageIntegrationTest.java
 
b/polaris-core/src/test/java/org/apache/polaris/service/storage/gcp/GcpCredentialsStorageIntegrationTest.java
index 861ad43ac..d3d3daac1 100644
--- 
a/polaris-core/src/test/java/org/apache/polaris/service/storage/gcp/GcpCredentialsStorageIntegrationTest.java
+++ 
b/polaris-core/src/test/java/org/apache/polaris/service/storage/gcp/GcpCredentialsStorageIntegrationTest.java
@@ -43,11 +43,10 @@ import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
-import java.util.EnumMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
+import org.apache.polaris.core.storage.AccessConfig;
 import org.apache.polaris.core.storage.BaseStorageIntegrationTest;
 import org.apache.polaris.core.storage.StorageAccessProperty;
 import org.apache.polaris.core.storage.gcp.GcpCredentialsStorageIntegration;
@@ -136,9 +135,8 @@ class GcpCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
   private Storage setupStorageClient(
       List<String> allowedReadLoc, List<String> allowedWriteLoc, boolean 
allowListAction)
       throws IOException {
-    Map<StorageAccessProperty, String> credsMap =
-        subscopedCredsForOperations(allowedReadLoc, allowedWriteLoc, 
allowListAction);
-    return createStorageClient(credsMap);
+    return createStorageClient(
+        subscopedCredsForOperations(allowedReadLoc, allowedWriteLoc, 
allowListAction));
   }
 
   BlobInfo createStorageBlob(String bucket, String prefix, String fileName) {
@@ -146,19 +144,20 @@ class GcpCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
     return BlobInfo.newBuilder(blobId).build();
   }
 
-  private Storage createStorageClient(Map<StorageAccessProperty, String> 
credsMap) {
+  private Storage createStorageClient(AccessConfig accessConfig) {
     AccessToken accessToken =
         new AccessToken(
-            credsMap.get(StorageAccessProperty.GCS_ACCESS_TOKEN),
+            accessConfig.get(StorageAccessProperty.GCS_ACCESS_TOKEN),
             new Date(
-                
Long.parseLong(credsMap.get(StorageAccessProperty.GCS_ACCESS_TOKEN_EXPIRES_AT))));
+                Long.parseLong(
+                    
accessConfig.get(StorageAccessProperty.GCS_ACCESS_TOKEN_EXPIRES_AT))));
     return StorageOptions.newBuilder()
         .setCredentials(GoogleCredentials.create(accessToken))
         .build()
         .getService();
   }
 
-  private Map<StorageAccessProperty, String> subscopedCredsForOperations(
+  private AccessConfig subscopedCredsForOperations(
       List<String> allowedReadLoc, List<String> allowedWriteLoc, boolean 
allowListAction)
       throws IOException {
     List<String> allowedLoc = new ArrayList<>();
@@ -169,14 +168,12 @@ class GcpCredentialsStorageIntegrationTest extends 
BaseStorageIntegrationTest {
         new GcpCredentialsStorageIntegration(
             GoogleCredentials.getApplicationDefault(),
             ServiceOptions.getFromServiceLoader(HttpTransportFactory.class, 
NetHttpTransport::new));
-    EnumMap<StorageAccessProperty, String> credsMap =
-        gcpCredsIntegration.getSubscopedCreds(
-            newCallContext(),
-            gcpConfig,
-            allowListAction,
-            new HashSet<>(allowedReadLoc),
-            new HashSet<>(allowedWriteLoc));
-    return credsMap;
+    return gcpCredsIntegration.getSubscopedCreds(
+        newCallContext(),
+        gcpConfig,
+        allowListAction,
+        new HashSet<>(allowedReadLoc),
+        new HashSet<>(allowedWriteLoc));
   }
 
   @Test
diff --git 
a/runtime/service/src/test/java/org/apache/polaris/service/quarkus/catalog/AbstractIcebergCatalogTest.java
 
b/runtime/service/src/test/java/org/apache/polaris/service/quarkus/catalog/AbstractIcebergCatalogTest.java
index 4b60434d8..d1d3868ad 100644
--- 
a/runtime/service/src/test/java/org/apache/polaris/service/quarkus/catalog/AbstractIcebergCatalogTest.java
+++ 
b/runtime/service/src/test/java/org/apache/polaris/service/quarkus/catalog/AbstractIcebergCatalogTest.java
@@ -43,7 +43,6 @@ import java.io.UncheckedIOException;
 import java.lang.reflect.Method;
 import java.time.Clock;
 import java.util.Arrays;
-import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -1833,7 +1832,7 @@ public abstract class AbstractIcebergCatalogTest extends 
CatalogTests<IcebergCat
             .getEntities();
     Assertions.assertThat(tasks).hasSize(1);
     TaskEntity taskEntity = TaskEntity.of(tasks.get(0));
-    EnumMap<StorageAccessProperty, String> credentials =
+    Map<String, String> credentials =
         metaStoreManager
             .getSubscopedCredsForEntity(
                 polarisContext,
@@ -1843,13 +1842,14 @@ public abstract class AbstractIcebergCatalogTest 
extends CatalogTests<IcebergCat
                 true,
                 Set.of(tableMetadata.location()),
                 Set.of(tableMetadata.location()))
-            .getCredentials();
+            .getAccessConfig()
+            .credentials();
     Assertions.assertThat(credentials)
         .isNotNull()
         .isNotEmpty()
-        .containsEntry(StorageAccessProperty.AWS_KEY_ID, TEST_ACCESS_KEY)
-        .containsEntry(StorageAccessProperty.AWS_SECRET_KEY, SECRET_ACCESS_KEY)
-        .containsEntry(StorageAccessProperty.AWS_TOKEN, SESSION_TOKEN);
+        .containsEntry(StorageAccessProperty.AWS_KEY_ID.getPropertyName(), 
TEST_ACCESS_KEY)
+        .containsEntry(StorageAccessProperty.AWS_SECRET_KEY.getPropertyName(), 
SECRET_ACCESS_KEY)
+        .containsEntry(StorageAccessProperty.AWS_TOKEN.getPropertyName(), 
SESSION_TOKEN);
     FileIO fileIO =
         new TaskFileIOSupplier(
                 new DefaultFileIOFactory(storageCredentialCache, 
metaStoreManagerFactory))
diff --git 
a/service/common/src/main/java/org/apache/polaris/service/catalog/io/DefaultFileIOFactory.java
 
b/service/common/src/main/java/org/apache/polaris/service/catalog/io/DefaultFileIOFactory.java
index 9f530d4e7..d2c73e268 100644
--- 
a/service/common/src/main/java/org/apache/polaris/service/catalog/io/DefaultFileIOFactory.java
+++ 
b/service/common/src/main/java/org/apache/polaris/service/catalog/io/DefaultFileIOFactory.java
@@ -100,6 +100,7 @@ public class DefaultFileIOFactory implements FileIOFactory {
     if (accessConfig.isPresent()) {
       properties.putAll(accessConfig.get().credentials());
       properties.putAll(accessConfig.get().extraProperties());
+      properties.putAll(accessConfig.get().internalProperties());
     }
 
     return loadFileIOInternal(ioImplClassName, properties);
diff --git 
a/service/common/src/main/java/org/apache/polaris/service/storage/PolarisStorageIntegrationProviderImpl.java
 
b/service/common/src/main/java/org/apache/polaris/service/storage/PolarisStorageIntegrationProviderImpl.java
index 758da2874..6bf3a08c8 100644
--- 
a/service/common/src/main/java/org/apache/polaris/service/storage/PolarisStorageIntegrationProviderImpl.java
+++ 
b/service/common/src/main/java/org/apache/polaris/service/storage/PolarisStorageIntegrationProviderImpl.java
@@ -26,17 +26,16 @@ import jakarta.annotation.Nonnull;
 import jakarta.annotation.Nullable;
 import jakarta.enterprise.context.ApplicationScoped;
 import jakarta.inject.Inject;
-import java.util.EnumMap;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Supplier;
 import org.apache.polaris.core.context.CallContext;
+import org.apache.polaris.core.storage.AccessConfig;
 import org.apache.polaris.core.storage.PolarisStorageActions;
 import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
 import org.apache.polaris.core.storage.PolarisStorageIntegration;
 import org.apache.polaris.core.storage.PolarisStorageIntegrationProvider;
-import org.apache.polaris.core.storage.StorageAccessProperty;
 import org.apache.polaris.core.storage.aws.AwsCredentialsStorageIntegration;
 import org.apache.polaris.core.storage.aws.StsClientProvider;
 import 
org.apache.polaris.core.storage.azure.AzureCredentialsStorageIntegration;
@@ -99,13 +98,13 @@ public class PolarisStorageIntegrationProviderImpl 
implements PolarisStorageInte
         storageIntegration =
             new PolarisStorageIntegration<>("file") {
               @Override
-              public EnumMap<StorageAccessProperty, String> getSubscopedCreds(
+              public AccessConfig getSubscopedCreds(
                   @Nonnull CallContext callContext,
                   @Nonnull T storageConfig,
                   boolean allowListOperation,
                   @Nonnull Set<String> allowedReadLocations,
                   @Nonnull Set<String> allowedWriteLocations) {
-                return new EnumMap<>(StorageAccessProperty.class);
+                return AccessConfig.builder().build();
               }
 
               @Override

Reply via email to