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();
+  }
 }

Reply via email to