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 f1b51c72e feat: Add trace_id to AWS STS session tags for end-to-end
correlation (#3414)
f1b51c72e is described below
commit f1b51c72e7355a5fd7aca92723e63b22267fc189
Author: Anand K Sankaran <[email protected]>
AuthorDate: Fri Jan 16 11:22:04 2026 -0800
feat: Add trace_id to AWS STS session tags for end-to-end correlation
(#3414)
* feat: Add trace_id to AWS STS session tags for end-to-end correlation
This change enables deterministic correlation between:
- Catalog operations (Polaris events)
- Credential vending (AWS CloudTrail via STS session tags)
- Metrics reports from compute engines (Spark, Trino, etc.)
Changes:
1. Add traceId field to CredentialVendingContext
- Marked with @Value.Auxiliary to exclude from cache key comparison
- Every request has unique trace ID, so including it in equals/hashCode
would prevent all cache hits
- Trace ID is for correlation/audit only, not authorization
2. Extract OpenTelemetry trace ID in StorageAccessConfigProvider
- getCurrentTraceId() extracts trace ID from current span context
- Populates CredentialVendingContext.traceId for each request
3. Add trace_id to AWS STS session tags
- AwsSessionTagsBuilder includes trace_id in session tags
- Appears in CloudTrail logs for correlation with catalog operations
- Uses 'unknown' placeholder when trace ID is not available
4. Update tests to verify trace_id is included in session tags
This enables operators to correlate:
- Which catalog operation triggered credential vending
- Which data access events in CloudTrail correspond to catalog operations
- Which metrics reports correspond to specific catalog operations
* Update AwsCredentialsStorageIntegrationTest.java
* Review comments
1. Feature Flag to Disable Trace IDs in Session Tags
Added a new feature configuration flag INCLUDE_TRACE_ID_IN_SESSION_TAGS
in FeatureConfiguration.java:
polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
(EXCERPT)
public static final FeatureConfiguration<Boolean>
INCLUDE_TRACE_ID_IN_SESSION_TAGS =
PolarisConfiguration.<Boolean>builder()
.key("INCLUDE_TRACE_ID_IN_SESSION_TAGS")
.description("If set to true (and
INCLUDE_SESSION_TAGS_IN_SUBSCOPED_CREDENTIAL is also true), ...")
.defaultValue(false)
.buildFeatureConfiguration();
2. Cache Key Correctness Solution
The solution ensures cache correctness by including trace IDs in cache
keys only when they affect the vended credentials:
Key changes:
1. `StorageCredentialCacheKey` - Added a new traceIdForCaching() field
that is populated only when trace IDs affect credentials:
polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheKey.java
(EXCERPT)
@Value.Parameter(order = 10)
Optional<String> traceIdForCaching();
2. `StorageCredentialCache` - Reads both flags and includes trace ID
in cache key only when both are enabled:
polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCache.java
(EXCERPT)
boolean includeTraceIdInCacheKey = includeSessionTags &&
includeTraceIdInSessionTags;
StorageCredentialCacheKey key = StorageCredentialCacheKey.of(...,
includeTraceIdInCacheKey);
3. `AwsSessionTagsBuilder` - Conditionally includes trace ID based on
the new flag.
4. Tests - Updated existing tests and added a new test
testSessionTagsWithTraceIdWhenBothFlagsEnabled.
How This Resolves the Cache Correctness vs. Efficiency Trade-off
| Configuration | Trace ID in Session Tags | Trace ID in Cache Key |
Caching Behavior |
|---------------|--------------------------|----------------------|------------------|
| Session tags disabled | No | No | Efficient caching |
| Session tags enabled, trace ID disabled (default) | No | No |
Efficient caching |
| Session tags enabled, trace ID enabled | Yes | Yes | Correct but no
caching across requests |
This design ensures:
• Correctness: When trace IDs affect credentials, they're included in
the cache key
• Efficiency: When trace IDs don't affect credentials, they're
excluded from the cache key, allowing cache hits across requests
* Update CHANGELOG.md
Co-authored-by: Anand Kumar Sankaran <[email protected]>
---
CHANGELOG.md | 1 +
.../polaris/core/config/FeatureConfiguration.java | 14 +++
.../core/storage/CredentialVendingContext.java | 22 +++-
.../aws/AwsCredentialsStorageIntegration.java | 4 +-
.../core/storage/aws/AwsSessionTagsBuilder.java | 20 +++-
.../core/storage/cache/StorageCredentialCache.java | 6 +-
.../storage/cache/StorageCredentialCacheKey.java | 11 +-
.../aws/AwsCredentialsStorageIntegrationTest.java | 112 ++++++++++++++++++++-
.../catalog/io/StorageAccessConfigProvider.java | 28 ++++++
9 files changed, 206 insertions(+), 12 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 910456ee6..93e51deab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -61,6 +61,7 @@ request adding CHANGELOG notes for breaking (!) changes and
possibly other secti
- Added `topologySpreadConstraints` support in Helm chart.
- Added `priorityClassName` support in Helm chart.
- Added support for including principal name in subscoped credentials.
`INCLUDE_PRINCIPAL_NAME_IN_SUBSCOPED_CREDENTIAL` (default: false) can be used
to toggle this feature. If enabled, cached credentials issued to one principal
will no longer be available for others.
+- Added support for including OpenTelemetry trace IDs in AWS STS session tags.
This requires session tags to be enabled via
`INCLUDE_SESSION_TAGS_IN_SUBSCOPED_CREDENTIAL` and can be toggled with
`INCLUDE_TRACE_ID_IN_SESSION_TAGS` (default: false). Note: enabling trace IDs
disables credential caching (each request has a unique trace ID), which may
increase STS calls and latency.
- Added support for [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/)
to the Helm Chart.
- Added `hierarchical` flag to `AzureStorageConfigInfo` to allow more precise
SAS token down-scoping in ADLS when
the [hierarchical
namespace](https://learn.microsoft.com/en-us/azure/storage/blobs/data-lake-storage-namespace)
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
b/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
index 5c6858ceb..1eb121c49 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java
@@ -104,6 +104,20 @@ public class FeatureConfiguration<T> extends
PolarisConfiguration<T> {
.defaultValue(false)
.buildFeatureConfiguration();
+ public static final FeatureConfiguration<Boolean>
INCLUDE_TRACE_ID_IN_SESSION_TAGS =
+ PolarisConfiguration.<Boolean>builder()
+ .key("INCLUDE_TRACE_ID_IN_SESSION_TAGS")
+ .description(
+ "If set to true (and
INCLUDE_SESSION_TAGS_IN_SUBSCOPED_CREDENTIAL is also true), the OpenTelemetry\n"
+ + "trace ID will be included as a session tag in AWS STS
AssumeRole requests. This enables\n"
+ + "end-to-end correlation between catalog operations
(Polaris events), credential vending (CloudTrail),\n"
+ + "and metrics reports from compute engines.\n"
+ + "WARNING: Enabling this feature completely disables
credential caching because every request\n"
+ + "has a unique trace ID. This may significantly increase
latency and STS API costs.\n"
+ + "Consider leaving this disabled unless end-to-end tracing
correlation is critical.")
+ .defaultValue(false)
+ .buildFeatureConfiguration();
+
public static final FeatureConfiguration<Boolean> ALLOW_SETTING_S3_ENDPOINTS
=
PolarisConfiguration.<Boolean>builder()
.key("ALLOW_SETTING_S3_ENDPOINTS")
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/CredentialVendingContext.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/CredentialVendingContext.java
index 718062c41..40c262726 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/CredentialVendingContext.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/CredentialVendingContext.java
@@ -33,10 +33,11 @@ import org.apache.polaris.immutables.PolarisImmutable;
* <li>{@code namespace} - The namespace/database being accessed (e.g.,
"db.schema")
* <li>{@code tableName} - The name of the table being accessed
* <li>{@code activatedRoles} - Comma-separated list of activated principal
roles
+ * <li>{@code traceId} - OpenTelemetry trace ID for end-to-end correlation
* </ul>
*
- * <p>These values appear in cloud provider audit logs (e.g., AWS CloudTrail),
enabling correlation
- * between catalog operations and data access events.
+ * <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 {
@@ -48,6 +49,7 @@ public interface CredentialVendingContext {
String TAG_KEY_TABLE = "polaris:table";
String TAG_KEY_PRINCIPAL = "polaris:principal";
String TAG_KEY_ROLES = "polaris:roles";
+ String TAG_KEY_TRACE_ID = "polaris:trace_id";
/** The name of the catalog that is vending credentials. */
Optional<String> catalogName();
@@ -67,6 +69,20 @@ public interface CredentialVendingContext {
*/
Optional<String> activatedRoles();
+ /**
+ * The OpenTelemetry trace ID for end-to-end correlation. This enables
correlation between
+ * credential vending (CloudTrail), catalog operations (Polaris events), and
metrics reports from
+ * compute engines.
+ *
+ * <p>This field is only populated when the {@code
INCLUDE_TRACE_ID_IN_SESSION_TAGS} feature flag
+ * is enabled. When populated, the trace ID is included in AWS STS session
tags and becomes part
+ * of the credential cache key (since it affects the vended credentials).
+ *
+ * <p>When the flag is disabled (default), this field is left empty ({@code
Optional.empty()}),
+ * which allows efficient credential caching across requests with different
trace IDs.
+ */
+ Optional<String> traceId();
+
/**
* Creates a new builder for CredentialVendingContext.
*
@@ -95,6 +111,8 @@ public interface CredentialVendingContext {
Builder activatedRoles(Optional<String> activatedRoles);
+ Builder traceId(Optional<String> traceId);
+
CredentialVendingContext build();
}
}
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 d59568252..8fce267af 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
@@ -125,7 +125,9 @@ public class AwsCredentialsStorageIntegration
.toJson())
.durationSeconds(storageCredentialDurationSeconds);
- // Add session tags when the feature is enabled
+ // Add session tags when the feature is enabled.
+ // Note: The trace ID is controlled at the source
(StorageAccessConfigProvider).
+ // If INCLUDE_TRACE_ID_IN_SESSION_TAGS is enabled, the context will
contain the trace ID.
if (includeSessionTags) {
List<Tag> sessionTags =
buildSessionTags(polarisPrincipal.getName(),
credentialVendingContext);
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsSessionTagsBuilder.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsSessionTagsBuilder.java
index 7403ca9f1..7931c7204 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsSessionTagsBuilder.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/aws/AwsSessionTagsBuilder.java
@@ -43,8 +43,13 @@ public final class AwsSessionTagsBuilder {
* Builds a list of AWS STS session tags from the principal name and
credential vending context.
* These tags will appear in CloudTrail events for correlation purposes.
*
+ * <p>The trace ID tag is only included if {@link
CredentialVendingContext#traceId()} is present.
+ * This is controlled at the source (StorageAccessConfigProvider) based on
the
+ * INCLUDE_TRACE_ID_IN_SESSION_TAGS feature flag.
+ *
* @param principalName the name of the principal requesting credentials
- * @param context the credential vending context containing catalog,
namespace, table, and roles
+ * @param context the credential vending context containing catalog,
namespace, table, roles, and
+ * optionally trace ID
* @return a list of STS Tags to attach to the AssumeRole request
*/
public static List<Tag> buildSessionTags(String principalName,
CredentialVendingContext context) {
@@ -78,6 +83,19 @@ public final class AwsSessionTagsBuilder {
.value(truncateTagValue(context.tableName().orElse(TAG_VALUE_UNKNOWN)))
.build());
+ // Only include trace ID if it's present in the context.
+ // The context's traceId is only populated when
INCLUDE_TRACE_ID_IN_SESSION_TAGS is enabled.
+ // This allows efficient credential caching when trace IDs are not needed
in session tags.
+ context
+ .traceId()
+ .ifPresent(
+ traceId ->
+ tags.add(
+ Tag.builder()
+ .key(CredentialVendingContext.TAG_KEY_TRACE_ID)
+ .value(truncateTagValue(traceId))
+ .build()));
+
return tags;
}
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 a2ce3b2b0..125ab8ea3 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
@@ -132,10 +132,14 @@ public class StorageCredentialCache {
// When session tags are enabled, the cache key needs to include:
// 1. The credential vending context to avoid returning cached credentials
with different
- // session tags (catalog/namespace/table/roles)
+ // session tags (catalog/namespace/table/roles/traceId)
// 2. The principal, because the polaris:principal session tag is included
in AWS credentials
// and we must not serve credentials tagged for principal A to
principal B
// When session tags are disabled, we only include principal if explicitly
configured.
+ //
+ // Note: The trace ID is controlled at the source
(StorageAccessConfigProvider). When
+ // INCLUDE_TRACE_ID_IN_SESSION_TAGS is disabled, the context's traceId is
left empty,
+ // which allows efficient caching across requests with different trace IDs.
boolean includePrincipalInCacheKey =
includePrincipalNameInSubscopedCredential || includeSessionTags;
// When session tags are disabled, use empty context to ensure consistent
cache key behavior
diff --git
a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheKey.java
b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheKey.java
index b062d1a78..1e46c2357 100644
---
a/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheKey.java
+++
b/polaris-core/src/main/java/org/apache/polaris/core/storage/cache/StorageCredentialCacheKey.java
@@ -58,12 +58,19 @@ public interface StorageCredentialCacheKey {
/**
* The credential vending context for session tags. When session tags are
enabled, this contains
- * the catalog, namespace, table, and roles information. When session tags
are disabled, this
- * should be {@link CredentialVendingContext#empty()} to ensure consistent
cache key behavior.
+ * the catalog, namespace, table, roles, and optionally trace ID
information. When session tags
+ * are disabled, this should be {@link CredentialVendingContext#empty()} to
ensure consistent
+ * cache key behavior.
+ *
+ * <p>The trace ID in the context is only populated when the {@code
+ * INCLUDE_TRACE_ID_IN_SESSION_TAGS} feature flag is enabled. When
populated, it becomes part of
+ * the cache key comparison (since it affects the vended credentials via
session tags). When
+ * empty, credentials can be cached efficiently across requests with
different trace IDs.
*/
@Value.Parameter(order = 9)
CredentialVendingContext credentialVendingContext();
+ /** Creates a cache key from the provided parameters. */
static StorageCredentialCacheKey of(
String realmId,
PolarisEntity entity,
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 254aea4d3..082eed3ec 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
@@ -1014,7 +1014,9 @@ class AwsCredentialsStorageIntegrationTest extends
BaseStorageIntegrationTest {
ArgumentCaptor.forClass(AssumeRoleRequest.class);
Mockito.when(stsClient.assumeRole(requestCaptor.capture())).thenReturn(ASSUME_ROLE_RESPONSE);
- // Roles are included in context (not extracted from principal) to be part
of cache key
+ // Roles are included in context (not extracted from principal) to be part
of cache key.
+ // Note: traceId is NOT set because INCLUDE_TRACE_ID_IN_SESSION_TAGS is
disabled (default).
+ // In production, StorageAccessConfigProvider only populates traceId when
that flag is enabled.
CredentialVendingContext context =
CredentialVendingContext.builder()
.catalogName(Optional.of("test-catalog"))
@@ -1040,7 +1042,8 @@ class AwsCredentialsStorageIntegrationTest extends
BaseStorageIntegrationTest {
context);
AssumeRoleRequest capturedRequest = requestCaptor.getValue();
- Assertions.assertThat(capturedRequest.tags()).isNotEmpty();
+ // 5 tags are included when session tags enabled but trace_id not in
context
+ Assertions.assertThat(capturedRequest.tags()).hasSize(5);
Assertions.assertThat(capturedRequest.tags())
.anyMatch(tag -> tag.key().equals("polaris:catalog") &&
tag.value().equals("test-catalog"));
Assertions.assertThat(capturedRequest.tags())
@@ -1053,8 +1056,11 @@ class AwsCredentialsStorageIntegrationTest extends
BaseStorageIntegrationTest {
// Roles are sorted alphabetically and joined with comma
Assertions.assertThat(capturedRequest.tags())
.anyMatch(tag -> tag.key().equals("polaris:roles") &&
tag.value().equals("admin,reader"));
+ // Verify trace_id is NOT included when not present in context
+ Assertions.assertThat(capturedRequest.tags())
+ .noneMatch(tag -> tag.key().equals("polaris:trace_id"));
- // Verify transitive tag keys are set
+ // Verify transitive tag keys are set (without trace_id)
Assertions.assertThat(capturedRequest.transitiveTagKeys())
.containsExactlyInAnyOrder(
"polaris:catalog",
@@ -1064,6 +1070,96 @@ class AwsCredentialsStorageIntegrationTest extends
BaseStorageIntegrationTest {
"polaris:roles");
}
+ @Test
+ public void testSessionTagsWithTraceIdWhenBothFlagsEnabled() {
+ StsClient stsClient = Mockito.mock(StsClient.class);
+ String roleARN = "arn:aws:iam::012345678901:role/jdoe";
+ String externalId = "externalId";
+ String bucket = "bucket";
+ String warehouseKeyPrefix = "path/to/warehouse";
+
+ // Create a realm config with both session tags AND trace_id enabled
+ RealmConfig sessionTagsAndTraceIdEnabledConfig =
+ new RealmConfigImpl(
+ new PolarisConfigurationStore() {
+ @SuppressWarnings("unchecked")
+ @Override
+ public String getConfiguration(@Nonnull RealmContext ctx, String
configName) {
+ if (configName.equals(
+
FeatureConfiguration.INCLUDE_SESSION_TAGS_IN_SUBSCOPED_CREDENTIAL.key())) {
+ return "true";
+ }
+ if (configName.equals(
+
FeatureConfiguration.INCLUDE_TRACE_ID_IN_SESSION_TAGS.key())) {
+ return "true";
+ }
+ return null;
+ }
+ },
+ () -> "realm");
+
+ ArgumentCaptor<AssumeRoleRequest> requestCaptor =
+ ArgumentCaptor.forClass(AssumeRoleRequest.class);
+
Mockito.when(stsClient.assumeRole(requestCaptor.capture())).thenReturn(ASSUME_ROLE_RESPONSE);
+
+ // When INCLUDE_TRACE_ID_IN_SESSION_TAGS is enabled,
StorageAccessConfigProvider populates
+ // traceId in the context. The presence of traceId in the context
determines whether it's
+ // included in session tags (and in the cache key, since it's a normal
field).
+ CredentialVendingContext context =
+ CredentialVendingContext.builder()
+ .catalogName(Optional.of("test-catalog"))
+ .namespace(Optional.of("db.schema"))
+ .tableName(Optional.of("my_table"))
+ .activatedRoles(Optional.of("admin,reader"))
+ .traceId(Optional.of("abc123def456"))
+ .build();
+
+ new AwsCredentialsStorageIntegration(
+ AwsStorageConfigurationInfo.builder()
+ .addAllowedLocation(s3Path(bucket, warehouseKeyPrefix))
+ .roleARN(roleARN)
+ .externalId(externalId)
+ .build(),
+ stsClient)
+ .getSubscopedCreds(
+ sessionTagsAndTraceIdEnabledConfig,
+ true,
+ Set.of(s3Path(bucket, warehouseKeyPrefix)),
+ Set.of(s3Path(bucket, warehouseKeyPrefix)),
+ POLARIS_PRINCIPAL,
+ Optional.empty(),
+ context);
+
+ AssumeRoleRequest capturedRequest = requestCaptor.getValue();
+ // All 6 tags are included when both session tags AND trace_id are enabled
+ Assertions.assertThat(capturedRequest.tags()).hasSize(6);
+ Assertions.assertThat(capturedRequest.tags())
+ .anyMatch(tag -> tag.key().equals("polaris:catalog") &&
tag.value().equals("test-catalog"));
+ Assertions.assertThat(capturedRequest.tags())
+ .anyMatch(tag -> tag.key().equals("polaris:namespace") &&
tag.value().equals("db.schema"));
+ Assertions.assertThat(capturedRequest.tags())
+ .anyMatch(tag -> tag.key().equals("polaris:table") &&
tag.value().equals("my_table"));
+ Assertions.assertThat(capturedRequest.tags())
+ .anyMatch(
+ tag -> tag.key().equals("polaris:principal") &&
tag.value().equals("test-principal"));
+ Assertions.assertThat(capturedRequest.tags())
+ .anyMatch(tag -> tag.key().equals("polaris:roles") &&
tag.value().equals("admin,reader"));
+ // Verify trace_id IS included when INCLUDE_TRACE_ID_IN_SESSION_TAGS is
true
+ Assertions.assertThat(capturedRequest.tags())
+ .anyMatch(
+ tag -> tag.key().equals("polaris:trace_id") &&
tag.value().equals("abc123def456"));
+
+ // Verify transitive tag keys include trace_id
+ Assertions.assertThat(capturedRequest.transitiveTagKeys())
+ .containsExactlyInAnyOrder(
+ "polaris:catalog",
+ "polaris:namespace",
+ "polaris:table",
+ "polaris:principal",
+ "polaris:roles",
+ "polaris:trace_id");
+ }
+
@Test
public void testSessionTagsNotIncludedWhenFeatureDisabled() {
StsClient stsClient = Mockito.mock(StsClient.class);
@@ -1154,7 +1250,7 @@ class AwsCredentialsStorageIntegrationTest extends
BaseStorageIntegrationTest {
context);
AssumeRoleRequest capturedRequest = requestCaptor.getValue();
- // All 5 tags are always included; missing values use "unknown" placeholder
+ // 5 tags are included when session tags enabled but trace_id disabled
(default)
Assertions.assertThat(capturedRequest.tags()).hasSize(5);
Assertions.assertThat(capturedRequest.tags())
.anyMatch(tag -> tag.key().equals("polaris:catalog") &&
tag.value().equals("test-catalog"));
@@ -1168,6 +1264,9 @@ class AwsCredentialsStorageIntegrationTest extends
BaseStorageIntegrationTest {
.anyMatch(tag -> tag.key().equals("polaris:table") &&
tag.value().equals("unknown"));
Assertions.assertThat(capturedRequest.tags())
.anyMatch(tag -> tag.key().equals("polaris:roles") &&
tag.value().equals("unknown"));
+ // trace_id is NOT included when INCLUDE_TRACE_ID_IN_SESSION_TAGS is not
set (defaults to false)
+ Assertions.assertThat(capturedRequest.tags())
+ .noneMatch(tag -> tag.key().equals("polaris:trace_id"));
}
@Test
@@ -1276,7 +1375,7 @@ class AwsCredentialsStorageIntegrationTest extends
BaseStorageIntegrationTest {
CredentialVendingContext.empty());
AssumeRoleRequest capturedRequest = requestCaptor.getValue();
- // All 5 tags are always included; missing values use "unknown" placeholder
+ // 5 tags are included when session tags enabled but trace_id disabled
(default)
Assertions.assertThat(capturedRequest.tags()).hasSize(5);
Assertions.assertThat(capturedRequest.tags())
.anyMatch(
@@ -1290,6 +1389,9 @@ class AwsCredentialsStorageIntegrationTest extends
BaseStorageIntegrationTest {
.anyMatch(tag -> tag.key().equals("polaris:table") &&
tag.value().equals("unknown"));
Assertions.assertThat(capturedRequest.tags())
.anyMatch(tag -> tag.key().equals("polaris:roles") &&
tag.value().equals("unknown"));
+ // trace_id is NOT included when INCLUDE_TRACE_ID_IN_SESSION_TAGS is not
set (defaults to false)
+ Assertions.assertThat(capturedRequest.tags())
+ .noneMatch(tag -> tag.key().equals("polaris:trace_id"));
}
/**
diff --git
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/io/StorageAccessConfigProvider.java
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/io/StorageAccessConfigProvider.java
index c92204fe7..76f5a53f9 100644
---
a/runtime/service/src/main/java/org/apache/polaris/service/catalog/io/StorageAccessConfigProvider.java
+++
b/runtime/service/src/main/java/org/apache/polaris/service/catalog/io/StorageAccessConfigProvider.java
@@ -19,6 +19,8 @@
package org.apache.polaris.service.catalog.io;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.SpanContext;
import jakarta.annotation.Nonnull;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
@@ -187,6 +189,32 @@ public class StorageAccessConfigProvider {
builder.activatedRoles(Optional.of(rolesString));
}
+ // Only include trace ID when the feature flag is enabled.
+ // When enabled, trace IDs are included in AWS STS session tags and become
part of the
+ // credential cache key (since they affect the vended credentials).
+ // When disabled (default), trace IDs are not included, allowing efficient
credential
+ // caching across requests with different trace IDs.
+ boolean includeTraceIdInSessionTags =
+ storageCredentialsVendor
+ .getRealmConfig()
+ .getConfig(FeatureConfiguration.INCLUDE_TRACE_ID_IN_SESSION_TAGS);
+ if (includeTraceIdInSessionTags) {
+ builder.traceId(getCurrentTraceId());
+ }
+
return builder.build();
}
+
+ /**
+ * Extracts the current OpenTelemetry trace ID from the active span context.
+ *
+ * @return the trace ID if a valid span context exists, empty otherwise
+ */
+ private Optional<String> getCurrentTraceId() {
+ SpanContext spanContext = Span.current().getSpanContext();
+ if (spanContext.isValid()) {
+ return Optional.of(spanContext.getTraceId());
+ }
+ return Optional.empty();
+ }
}