Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import org.apache.polaris.core.persistence.pagination.PageToken;
import org.apache.polaris.core.policy.PolicyEntity;
import org.apache.polaris.core.policy.PolicyType;
import org.apache.polaris.core.storage.CredentialVendingContext;
import org.apache.polaris.persistence.nosql.metastore.privs.SecurableGranteePrivilegeTuple;

record NoSqlMetaStoreManager(
Expand Down Expand Up @@ -776,7 +777,8 @@ public ScopedCredentialsResult getSubscopedCredsForEntity(
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
@Nonnull PolarisPrincipal polarisPrincipal,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
@Nonnull CredentialVendingContext credentialVendingContext) {

checkArgument(
!allowedReadLocations.isEmpty() || !allowedWriteLocations.isEmpty(),
Expand Down Expand Up @@ -807,7 +809,8 @@ public ScopedCredentialsResult getSubscopedCredsForEntity(
allowedReadLocations,
allowedWriteLocations,
polarisPrincipal,
refreshCredentialsEndpoint);
refreshCredentialsEndpoint,
credentialVendingContext);
return new ScopedCredentialsResult(creds);
} catch (Exception ex) {
return new ScopedCredentialsResult(SUBSCOPE_CREDS_ERROR, ex.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ public static void enforceFeatureEnabledOrThrow(
.defaultValue(false)
.buildFeatureConfiguration();

public static final FeatureConfiguration<Boolean> INCLUDE_SESSION_TAGS_IN_SUBSCOPED_CREDENTIAL =
PolarisConfiguration.<Boolean>builder()
.key("INCLUDE_SESSION_TAGS_IN_SUBSCOPED_CREDENTIAL")
.description(
"If set to true, session tags (catalog, namespace, table, principal, request-id) will be included\n"
+ "in AWS STS AssumeRole requests for credential vending. These tags appear in CloudTrail events,\n"
+ "enabling correlation between catalog operations and S3 data access.\n"
+ "Requires the IAM role trust policy to allow sts:TagSession action.\n"
+ "Note that enabling this feature leads to degradation in temporary credential caching as \n"
+ "catalog will no longer be able to reuse credentials for different tables/namespaces/requests.")
.defaultValue(false)
.buildFeatureConfiguration();

public static final FeatureConfiguration<Boolean> ALLOW_SETTING_S3_ENDPOINTS =
PolarisConfiguration.<Boolean>builder()
.key("ALLOW_SETTING_S3_ENDPOINTS")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
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.CredentialVendingContext;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
import org.apache.polaris.core.storage.PolarisStorageIntegration;
import org.apache.polaris.core.storage.StorageAccessConfig;
Expand Down Expand Up @@ -1602,7 +1603,8 @@ public void deletePrincipalSecrets(
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
@Nonnull PolarisPrincipal polarisPrincipal,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
@Nonnull CredentialVendingContext credentialVendingContext) {

// get meta store session we should be using
BasePersistence ms = callCtx.getMetaStore();
Expand Down Expand Up @@ -1644,7 +1646,8 @@ public void deletePrincipalSecrets(
allowedReadLocations,
allowedWriteLocations,
polarisPrincipal,
refreshCredentialsEndpoint);
refreshCredentialsEndpoint,
credentialVendingContext);
return new ScopedCredentialsResult(storageAccessConfig);
} catch (Exception ex) {
return new ScopedCredentialsResult(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.apache.polaris.core.persistence.pagination.PageToken;
import org.apache.polaris.core.policy.PolicyEntity;
import org.apache.polaris.core.policy.PolicyType;
import org.apache.polaris.core.storage.CredentialVendingContext;

/**
* Wraps an existing impl of PolarisMetaStoreManager and delegates expected "read" operations
Expand Down Expand Up @@ -326,7 +327,8 @@ public void deletePrincipalSecrets(
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
@Nonnull PolarisPrincipal polarisPrincipal,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
@Nonnull CredentialVendingContext credentialVendingContext) {
return delegate.getSubscopedCredsForEntity(
callCtx,
catalogId,
Expand All @@ -336,7 +338,8 @@ public void deletePrincipalSecrets(
allowedReadLocations,
allowedWriteLocations,
polarisPrincipal,
refreshCredentialsEndpoint);
refreshCredentialsEndpoint,
credentialVendingContext);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
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.CredentialVendingContext;
import org.apache.polaris.core.storage.PolarisStorageConfigurationInfo;
import org.apache.polaris.core.storage.PolarisStorageIntegration;
import org.apache.polaris.core.storage.StorageAccessConfig;
Expand Down Expand Up @@ -2096,7 +2097,8 @@ private PolarisEntityResolver resolveSecurableToRoleGrant(
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
@Nonnull PolarisPrincipal polarisPrincipal,
Optional<String> refreshCredentialsEndpoint) {
Optional<String> refreshCredentialsEndpoint,
@Nonnull CredentialVendingContext credentialVendingContext) {

// get meta store session we should be using
TransactionalPersistence ms = ((TransactionalPersistence) callCtx.getMetaStore());
Expand Down Expand Up @@ -2133,7 +2135,8 @@ private PolarisEntityResolver resolveSecurableToRoleGrant(
allowedReadLocations,
allowedWriteLocations,
polarisPrincipal,
refreshCredentialsEndpoint);
refreshCredentialsEndpoint,
credentialVendingContext);
return new ScopedCredentialsResult(storageAccessConfig);
} catch (Exception ex) {
return new ScopedCredentialsResult(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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 java.util.Optional;
import org.apache.polaris.immutables.PolarisImmutable;

/**
* Context information for credential vending operations. This context is used to provide metadata
* that can be attached to credentials as session tags (e.g., AWS STS session tags) for audit and
* correlation purposes in CloudTrail and similar logging systems.
*
* <p>When session tags are enabled, this context provides:
*
* <ul>
* <li>{@code catalogName} - The name of the catalog vending credentials
* <li>{@code namespace} - The namespace/database being accessed (e.g., "db.schema")
* <li>{@code tableName} - The name of the table being accessed
* <li>{@code requestId} - A unique request identifier for correlation with catalog audit logs
* </ul>
*
* <p>These values appear in cloud provider audit logs (e.g., AWS CloudTrail), enabling
* deterministic correlation between catalog operations and data access events.
*/
@PolarisImmutable
public interface CredentialVendingContext {
Comment thread
obelix74 marked this conversation as resolved.

// Default session tag keys for cloud provider credentials (e.g., AWS STS session tags).
// These appear in cloud audit logs (e.g., CloudTrail) for correlation purposes.
String TAG_KEY_CATALOG = "polaris:catalog";
String TAG_KEY_NAMESPACE = "polaris:namespace";
String TAG_KEY_TABLE = "polaris:table";
String TAG_KEY_PRINCIPAL = "polaris:principal";
String TAG_KEY_REQUEST_ID = "polaris:request-id";

/** The name of the catalog that is vending credentials. */
Optional<String> catalogName();

/**
* The namespace being accessed, represented as a dot-separated string (e.g., "database.schema").
*/
Optional<String> namespace();

/** The name of the table being accessed. */
Optional<String> tableName();

/**
* A unique request identifier that can be used to correlate this credential vending operation
* with catalog audit logs.
*/
Optional<String> requestId();

/**
* Creates a new builder for CredentialVendingContext.
*
* @return a new builder instance
*/
static Builder builder() {
return ImmutableCredentialVendingContext.builder();
}

/**
* Creates an empty context with no metadata. This is useful when session tags are disabled or
* when context information is not available.
*
* @return an empty context instance
*/
static CredentialVendingContext empty() {
return ImmutableCredentialVendingContext.builder().build();
}

interface Builder {
Builder catalogName(Optional<String> catalogName);

Builder namespace(Optional<String> namespace);

Builder tableName(Optional<String> tableName);

Builder requestId(Optional<String> requestId);

CredentialVendingContext build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,65 @@ public interface PolarisCredentialVendor {
* @param callCtx the polaris call context
* @param catalogId the catalog id
* @param entityId the entity id
* @param entityType the type of entity
* @param allowListOperation whether to allow LIST operation on the allowedReadLocations and
* allowedWriteLocations
* @param allowedReadLocations a set of allowed to read locations
* @param allowedWriteLocations a set of allowed to write locations
* @param polarisPrincipal the principal requesting credentials
* @param refreshCredentialsEndpoint an optional endpoint to use for refreshing credentials. If
* supported by the storage type it will be returned to the client in the appropriate
* properties. The endpoint may be relative to the base URI and the client is responsible for
* handling the relative path
* @return an enum map containing the scoped credentials
*/
@Nonnull
default ScopedCredentialsResult getSubscopedCredsForEntity(
Comment thread
obelix74 marked this conversation as resolved.
@Nonnull PolarisCallContext callCtx,
long catalogId,
long entityId,
@Nonnull PolarisEntityType entityType,
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
@Nonnull PolarisPrincipal polarisPrincipal,
Optional<String> refreshCredentialsEndpoint) {
return getSubscopedCredsForEntity(
callCtx,
catalogId,
entityId,
entityType,
allowListOperation,
allowedReadLocations,
allowedWriteLocations,
polarisPrincipal,
refreshCredentialsEndpoint,
CredentialVendingContext.empty());
}

/**
* Get a sub-scoped credentials for an entity against the provided allowed read and write
* locations, with credential vending context for session tags.
*
* @param callCtx the polaris call context
* @param catalogId the catalog id
* @param entityId the entity id
* @param entityType the type of entity
* @param allowListOperation whether to allow LIST operation on the allowedReadLocations and
* allowedWriteLocations
* @param allowedReadLocations a set of allowed to read locations
* @param allowedWriteLocations a set of allowed to write locations
* @param polarisPrincipal the principal requesting credentials
* @param refreshCredentialsEndpoint an optional endpoint to use for refreshing credentials. If
* supported by the storage type it will be returned to the client in the appropriate
* properties. The endpoint may be relative to the base URI and the client is responsible for
* handling the relative path
* @param credentialVendingContext context containing metadata for session tags (catalog,
* namespace, table, request-id) that can be attached to credentials for audit/correlation
* purposes
* @return an enum map containing the scoped credentials
*/
@Nonnull
ScopedCredentialsResult getSubscopedCredsForEntity(
Comment thread
obelix74 marked this conversation as resolved.
@Nonnull PolarisCallContext callCtx,
long catalogId,
Expand All @@ -55,5 +103,6 @@ ScopedCredentialsResult getSubscopedCredsForEntity(
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
@Nonnull PolarisPrincipal polarisPrincipal,
Optional<String> refreshCredentialsEndpoint);
Optional<String> refreshCredentialsEndpoint,
@Nonnull CredentialVendingContext credentialVendingContext);
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,14 @@ public String getStorageIdentifierOrId() {
* locations
* @param allowedReadLocations a set of allowed to read locations
* @param allowedWriteLocations a set of allowed to write locations
* @param polarisPrincipal the principal requesting credentials
* @param refreshCredentialsEndpoint an optional endpoint to use for refreshing credentials. If
* supported by the storage type it will be returned to the client in the appropriate
* properties. The endpoint may be relative to the base URI and the client is responsible for
* handling the relative path
* @param credentialVendingContext context containing metadata for session tags (catalog,
* namespace, table, request-id) that can be attached to credentials for audit/correlation
* purposes
* @return An enum map including the scoped credentials
*/
public abstract StorageAccessConfig getSubscopedCreds(
Expand All @@ -69,7 +73,8 @@ public abstract StorageAccessConfig getSubscopedCreds(
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
@Nonnull PolarisPrincipal polarisPrincipal,
Optional<String> refreshCredentialsEndpoint);
Optional<String> refreshCredentialsEndpoint,
@Nonnull CredentialVendingContext credentialVendingContext);

/**
* Validate access for the provided operation actions and locations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public RealmConfig getRealmConfig() {
* allowedWriteLocations
* @param allowedReadLocations a set of allowed to read locations
* @param allowedWriteLocations a set of allowed to write locations
* @param polarisPrincipal the principal requesting credentials
* @param refreshCredentialsEndpoint an optional endpoint to use for refreshing credentials. If
* supported by the storage type it will be returned to the client in the appropriate
* properties. The endpoint may be relative to the base URI and the client is responsible for
Expand All @@ -70,6 +71,44 @@ public ScopedCredentialsResult getSubscopedCredsForEntity(
@Nonnull Set<String> allowedWriteLocations,
@Nonnull PolarisPrincipal polarisPrincipal,
Optional<String> refreshCredentialsEndpoint) {
return getSubscopedCredsForEntity(
entity,
allowListOperation,
allowedReadLocations,
allowedWriteLocations,
polarisPrincipal,
refreshCredentialsEndpoint,
CredentialVendingContext.empty());
}

/**
* Get sub-scoped credentials for an entity against the provided allowed read and write locations,
* with credential vending context for session tags.
*
* @param entity the entity
* @param allowListOperation whether to allow LIST operation on the allowedReadLocations and
* allowedWriteLocations
* @param allowedReadLocations a set of allowed to read locations
* @param allowedWriteLocations a set of allowed to write locations
* @param polarisPrincipal the principal requesting credentials
* @param refreshCredentialsEndpoint an optional endpoint to use for refreshing credentials. If
* supported by the storage type it will be returned to the client in the appropriate
* properties. The endpoint may be relative to the base URI and the client is responsible for
* handling the relative path
* @param credentialVendingContext context containing metadata for session tags (catalog,
* namespace, table, request-id) that can be attached to credentials for audit/correlation
* purposes
* @return an enum map containing the scoped credentials
*/
@Nonnull
public ScopedCredentialsResult getSubscopedCredsForEntity(
@Nonnull PolarisEntity entity,
boolean allowListOperation,
@Nonnull Set<String> allowedReadLocations,
@Nonnull Set<String> allowedWriteLocations,
@Nonnull PolarisPrincipal polarisPrincipal,
Optional<String> refreshCredentialsEndpoint,
@Nonnull CredentialVendingContext credentialVendingContext) {
return polarisCredentialVendor.getSubscopedCredsForEntity(
callContext.getPolarisCallContext(),
entity.getCatalogId(),
Expand All @@ -79,6 +118,7 @@ public ScopedCredentialsResult getSubscopedCredsForEntity(
allowedReadLocations,
allowedWriteLocations,
polarisPrincipal,
refreshCredentialsEndpoint);
refreshCredentialsEndpoint,
credentialVendingContext);
}
}
Loading