You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by nk...@apache.org on 2026/07/03 06:31:17 UTC

(polaris) branch main updated: Add session policy parameter to SigV4 connections (#4954)

This is an automated email from the ASF dual-hosted git repository.

nandorKollar 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 e17a43ee51 Add session policy parameter to SigV4 connections (#4954)
e17a43ee51 is described below

commit e17a43ee510dbf34f715931d591cf1eb34fdd440
Author: Ayush Saxena <ay...@apache.org>
AuthorDate: Fri Jul 3 12:01:12 2026 +0530

    Add session policy parameter to SigV4 connections (#4954)
---
 CHANGELOG.md                                       |  1 +
 .../connection/AuthenticationParametersDpo.java    |  3 +-
 .../SigV4AuthenticationParametersDpo.java          | 15 ++++++-
 .../SigV4ConnectionCredentialVendor.java           |  7 ++-
 .../DefaultPolarisCredentialManagerTest.java       |  2 +-
 .../SigV4ConnectionCredentialVendorTest.java       | 50 ++++++++++++++++++++--
 spec/polaris-management-service.yml                |  8 ++++
 7 files changed, 78 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a77a60d337..d31d18423d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,7 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
 - Added `maintenance` support in Helm chart.
 - Added support for publishing histogram buckets for HTTP server request duration as configured SLO boundaries.
 - Added an OpenTelemetry event listener for emitting Polaris audit events as OpenTelemetry log records.
+- Added optional `sessionPolicy` field to `SigV4AuthenticationParameters` for catalog federation. When set, the IAM session policy JSON is attached to the STS AssumeRole request, allowing administrators to restrict vended credentials to only the required AWS services and actions (Principle of Least Privilege).
 
 ### Changes
 - Added REPL support to Polaris CLI.
diff --git a/polaris-core/src/main/java/org/apache/polaris/core/connection/AuthenticationParametersDpo.java b/polaris-core/src/main/java/org/apache/polaris/core/connection/AuthenticationParametersDpo.java
index 8d47a61f37..1205fb1d34 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/connection/AuthenticationParametersDpo.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/connection/AuthenticationParametersDpo.java
@@ -105,7 +105,8 @@ public abstract class AuthenticationParametersDpo implements IcebergCatalogPrope
                 sigV4AuthenticationParametersModel.getRoleSessionName(),
                 sigV4AuthenticationParametersModel.getExternalId(),
                 sigV4AuthenticationParametersModel.getSigningRegion(),
-                sigV4AuthenticationParametersModel.getSigningName());
+                sigV4AuthenticationParametersModel.getSigningName(),
+                sigV4AuthenticationParametersModel.getSessionPolicy());
         break;
       default:
         throw new IllegalStateException(
diff --git a/polaris-core/src/main/java/org/apache/polaris/core/connection/SigV4AuthenticationParametersDpo.java b/polaris-core/src/main/java/org/apache/polaris/core/connection/SigV4AuthenticationParametersDpo.java
index 16a448b76c..bf45e5f804 100644
--- a/polaris-core/src/main/java/org/apache/polaris/core/connection/SigV4AuthenticationParametersDpo.java
+++ b/polaris-core/src/main/java/org/apache/polaris/core/connection/SigV4AuthenticationParametersDpo.java
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.iceberg.aws.AwsProperties;
 import org.apache.iceberg.rest.auth.AuthProperties;
 import org.apache.polaris.core.admin.model.AuthenticationParameters;
@@ -57,18 +58,24 @@ public class SigV4AuthenticationParametersDpo extends AuthenticationParametersDp
   @JsonProperty(value = "signingName")
   private final String signingName;
 
+  // An optional IAM session policy (JSON) attached to the STS AssumeRole request
+  @JsonProperty(value = "sessionPolicy")
+  private final String sessionPolicy;
+
   public SigV4AuthenticationParametersDpo(
       @JsonProperty(value = "roleArn", required = true) String roleArn,
       @JsonProperty(value = "roleSessionName", required = false) String roleSessionName,
       @JsonProperty(value = "externalId", required = false) String externalId,
       @JsonProperty(value = "signingRegion", required = true) String signingRegion,
-      @JsonProperty(value = "signingName", required = false) String signingName) {
+      @JsonProperty(value = "signingName", required = false) String signingName,
+      @JsonProperty(value = "sessionPolicy", required = false) String sessionPolicy) {
     super(AuthenticationType.SIGV4.getCode());
     this.roleArn = roleArn;
     this.roleSessionName = roleSessionName;
     this.externalId = externalId;
     this.signingRegion = signingRegion;
     this.signingName = signingName;
+    this.sessionPolicy = sessionPolicy;
   }
 
   public @NonNull String getRoleArn() {
@@ -91,6 +98,10 @@ public class SigV4AuthenticationParametersDpo extends AuthenticationParametersDp
     return signingName;
   }
 
+  public @Nullable String getSessionPolicy() {
+    return sessionPolicy;
+  }
+
   @NonNull
   @Override
   public Map<String, String> asIcebergCatalogProperties(
@@ -114,6 +125,7 @@ public class SigV4AuthenticationParametersDpo extends AuthenticationParametersDp
         .setExternalId(getExternalId())
         .setSigningRegion(getSigningRegion())
         .setSigningName(getSigningName())
+        .setSessionPolicy(getSessionPolicy())
         .build();
   }
 
@@ -126,6 +138,7 @@ public class SigV4AuthenticationParametersDpo extends AuthenticationParametersDp
         .add("externalId", getExternalId())
         .add("signingRegion", getSigningRegion())
         .add("signingName", getSigningName())
+        .add("hasSessionPolicy", StringUtils.isNotEmpty(getSessionPolicy()))
         .toString();
   }
 }
diff --git a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java
index e7fb131c2a..769de39b12 100644
--- a/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java
+++ b/runtime/service/src/main/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendor.java
@@ -26,6 +26,7 @@ import jakarta.inject.Inject;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.polaris.core.connection.AuthenticationType;
 import org.apache.polaris.core.connection.ConnectionConfigInfoDpo;
 import org.apache.polaris.core.connection.SigV4AuthenticationParametersDpo;
@@ -110,7 +111,6 @@ public class SigV4ConnectionCredentialVendor implements ConnectionCredentialVend
     StsClient stsClient = getStsClient(sigv4Params);
 
     // Build the AssumeRole request with Polaris's credentials
-    // TODO: Generate service-level scoping policy to restrict permissions
     AssumeRoleRequest.Builder requestBuilder =
         AssumeRoleRequest.builder()
             .roleArn(sigv4Params.getRoleArn())
@@ -119,6 +119,11 @@ public class SigV4ConnectionCredentialVendor implements ConnectionCredentialVend
                     .orElse(DEFAULT_ROLE_SESSION_NAME))
             .externalId(sigv4Params.getExternalId());
 
+    // Attach user-provided session policy to restrict assumed role permissions (PoLP)
+    if (StringUtils.isNotEmpty(sigv4Params.getSessionPolicy())) {
+      requestBuilder.policy(sigv4Params.getSessionPolicy());
+    }
+
     // Configure the request to use Polaris's service identity credentials
     requestBuilder.overrideConfiguration(
         config -> config.credentialsProvider(awsCredential.getAwsCredentialsProvider()));
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManagerTest.java b/runtime/service/src/test/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManagerTest.java
index 1d17ee8352..a93ef7258b 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManagerTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/credentials/DefaultPolarisCredentialManagerTest.java
@@ -121,7 +121,7 @@ public class DefaultPolarisCredentialManagerTest {
     // Create SIGV4 auth parameters
     SigV4AuthenticationParametersDpo authParams =
         new SigV4AuthenticationParametersDpo(
-            "arn:aws:iam::123456789012:role/test-role", null, null, "us-west-2", "glue");
+            "arn:aws:iam::123456789012:role/test-role", null, null, "us-west-2", "glue", null);
 
     // Create connection config
     IcebergRestConnectionConfigInfoDpo connectionConfig =
diff --git a/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendorTest.java b/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendorTest.java
index 8fb18706b4..517b18b8b9 100644
--- a/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendorTest.java
+++ b/runtime/service/src/test/java/org/apache/polaris/service/credentials/connection/SigV4ConnectionCredentialVendorTest.java
@@ -110,7 +110,8 @@ public class SigV4ConnectionCredentialVendorTest {
             "my-session",
             "external-id-123",
             "us-west-2",
-            "glue");
+            "glue",
+            null);
 
     // Create connection config with service identity and auth params
     IcebergRestConnectionConfigInfoDpo connectionConfig =
@@ -141,6 +142,8 @@ public class SigV4ConnectionCredentialVendorTest {
         .isEqualTo("arn:aws:iam::123456789012:role/customer-role");
     Assertions.assertThat(capturedRequest.roleSessionName()).isEqualTo("my-session");
     Assertions.assertThat(capturedRequest.externalId()).isEqualTo("external-id-123");
+    // No session policy → no policy attached
+    Assertions.assertThat(capturedRequest.policy()).isNull();
   }
 
   @Test
@@ -153,7 +156,7 @@ public class SigV4ConnectionCredentialVendorTest {
     // SigV4 auth without explicit session name
     SigV4AuthenticationParametersDpo authParams =
         new SigV4AuthenticationParametersDpo(
-            "arn:aws:iam::123456789012:role/customer-role", null, null, "us-west-2", "glue");
+            "arn:aws:iam::123456789012:role/customer-role", null, null, "us-west-2", "glue", null);
 
     // Create connection config with service identity and auth params
     IcebergRestConnectionConfigInfoDpo connectionConfig =
@@ -191,7 +194,8 @@ public class SigV4ConnectionCredentialVendorTest {
             "my-session",
             "external-id-123",
             "eu-west-1",
-            "glue");
+            "glue",
+            null);
 
     IcebergRestConnectionConfigInfoDpo connectionConfig =
         createConnectionConfig(authParams, serviceIdentity);
@@ -203,6 +207,39 @@ public class SigV4ConnectionCredentialVendorTest {
     Assertions.assertThat(capturedDestination.get().endpoint()).isEmpty();
   }
 
+  @Test
+  public void testSessionPolicyPassedToAssumeRole() {
+    ServiceIdentityInfoDpo serviceIdentity =
+        new AwsIamServiceIdentityInfoDpo(
+            new SecretReference("urn:polaris-secret:test:my-realm:AWS_IAM", Map.of()));
+
+    String customPolicy =
+        "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\","
+            + "\"Action\":[\"glue:*\",\"s3:GetObject\",\"s3:ListBucket\"],"
+            + "\"Resource\":\"*\"}]}";
+
+    SigV4AuthenticationParametersDpo authParams =
+        new SigV4AuthenticationParametersDpo(
+            "arn:aws:iam::123456789012:role/customer-role",
+            "my-session",
+            "external-id-123",
+            "us-west-2",
+            "glue",
+            customPolicy);
+
+    IcebergRestConnectionConfigInfoDpo connectionConfig =
+        createConnectionConfig(authParams, serviceIdentity);
+
+    vendor.getConnectionCredentials(connectionConfig);
+
+    // Verify the session policy is passed to AssumeRole
+    ArgumentCaptor<AssumeRoleRequest> requestCaptor =
+        ArgumentCaptor.forClass(AssumeRoleRequest.class);
+    Mockito.verify(mockStsClient).assumeRole(requestCaptor.capture());
+
+    Assertions.assertThat(requestCaptor.getValue().policy()).isEqualTo(customPolicy);
+  }
+
   @Test
   public void testStsDestinationUsesDifferentRegions() {
     ServiceIdentityInfoDpo serviceIdentity =
@@ -211,7 +248,12 @@ public class SigV4ConnectionCredentialVendorTest {
 
     SigV4AuthenticationParametersDpo authParams =
         new SigV4AuthenticationParametersDpo(
-            "arn:aws:iam::123456789012:role/customer-role", null, null, "ap-southeast-1", null);
+            "arn:aws:iam::123456789012:role/customer-role",
+            null,
+            null,
+            "ap-southeast-1",
+            null,
+            null);
 
     IcebergRestConnectionConfigInfoDpo connectionConfig =
         createConnectionConfig(authParams, serviceIdentity);
diff --git a/spec/polaris-management-service.yml b/spec/polaris-management-service.yml
index 4cbad147e5..78bc232713 100644
--- a/spec/polaris-management-service.yml
+++ b/spec/polaris-management-service.yml
@@ -1073,6 +1073,14 @@ components:
           type: string
           description: The service name to be used by the SigV4 protocol for signing requests, the default signing name is "execute-api" is if not provided
           example: "glue"
+        sessionPolicy:
+          type: string
+          description: >-
+            An optional IAM session policy (JSON) attached to the STS AssumeRole request.
+            This policy intersects with the IAM role's permissions to further restrict
+            the vended credentials. When omitted, no session policy is applied and the
+            credentials inherit the full permissions of the assumed role.
+          example: '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["glue:*","s3:GetObject","s3:ListBucket"],"Resource":"*"}]}'
       required:
         - roleArn
         - signingRegion