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

sodonnell pushed a commit to branch HDDS-13323-sts
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/HDDS-13323-sts by this push:
     new 08186522ad5 HDDS-14011. [STS] Ranger interactions for STS tokens 
(assumeRole and S3 api calls) (#9484)
08186522ad5 is described below

commit 08186522ad5bb1974ee07790d943b683e1f02ec1
Author: fmorg-git <[email protected]>
AuthorDate: Fri Dec 19 08:45:38 2025 -0800

    HDDS-14011. [STS] Ranger interactions for STS tokens (assumeRole and S3 api 
calls) (#9484)
---
 .../hadoop/ozone/security/acl/RequestContext.java  |  13 ++
 .../apache/hadoop/ozone/om/OmMetadataReader.java   |  30 +++-
 .../request/s3/security/S3AssumeRoleRequest.java   |  46 ++++--
 .../hadoop/ozone/om/TestOMMetadataReader.java      | 133 +++++++++++++++
 .../s3/security/TestS3AssumeRoleRequest.java       | 181 +++++++++++++++++++--
 .../ozone/security/acl/TestRequestContext.java     |  66 ++++++++
 .../hadoop/ozone/security/acl/package-info.java    |  21 +++
 7 files changed, 458 insertions(+), 32 deletions(-)

diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/RequestContext.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/RequestContext.java
index f2d25c2ad23..18dbe94a4a8 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/RequestContext.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/security/acl/RequestContext.java
@@ -238,4 +238,17 @@ public boolean isRecursiveAccessCheck() {
   public String getSessionPolicy() {
     return sessionPolicy;
   }
+
+  public Builder toBuilder() {
+    return newBuilder()
+        .setHost(host)
+        .setIp(ip)
+        .setClientUgi(clientUgi)
+        .setServiceId(serviceId)
+        .setAclType(aclType)
+        .setAclRights(aclRights)
+        .setOwnerName(ownerName)
+        .setRecursiveAccessCheck(recursiveAccessCheck)
+        .setSessionPolicy(sessionPolicy);
+  }
 }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
index 2fac369e3a2..e25cd3d4271 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataReader.java
@@ -54,6 +54,7 @@
 import org.apache.hadoop.ozone.om.helpers.OzoneFileStatusLight;
 import org.apache.hadoop.ozone.om.helpers.S3VolumeContext;
 import org.apache.hadoop.ozone.om.protocolPB.grpc.GrpcClientConstants;
+import org.apache.hadoop.ozone.security.STSTokenIdentifier;
 import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
 import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLIdentityType;
 import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType;
@@ -589,8 +590,10 @@ public boolean checkAcls(ResourceType resType, StoreType 
storeType,
   public boolean checkAcls(OzoneObj obj, RequestContext context,
       boolean throwIfPermissionDenied) throws OMException {
 
+    final RequestContext normalizedRequestContext = 
maybeAttachSessionPolicyFromThreadLocal(context);
+
     if (!captureLatencyNs(perfMetrics::setCheckAccessLatencyNs,
-        () -> accessAuthorizer.checkAccess(obj, context))) {
+        () -> accessAuthorizer.checkAccess(obj, normalizedRequestContext))) {
       if (throwIfPermissionDenied) {
         String volumeName = obj.getVolumeName() != null ?
                 "Volume:" + obj.getVolumeName() + " " : "";
@@ -599,11 +602,12 @@ public boolean checkAcls(OzoneObj obj, RequestContext 
context,
         String keyName = obj.getKeyName() != null ?
                 "Key:" + obj.getKeyName() : "";
         log.warn("User {} doesn't have {} permission to access {} {}{}{}",
-            context.getClientUgi().getShortUserName(), context.getAclRights(),
+            normalizedRequestContext.getClientUgi().getShortUserName(),
+            normalizedRequestContext.getAclRights(),
             obj.getResourceType(), volumeName, bucketName, keyName);
         throw new OMException(
-            "User " + context.getClientUgi().getShortUserName() +
-            " doesn't have " + context.getAclRights() +
+            "User " + 
normalizedRequestContext.getClientUgi().getShortUserName() +
+            " doesn't have " + normalizedRequestContext.getAclRights() +
             " permission to access " + obj.getResourceType() + " " +
             volumeName  + bucketName + keyName, ResultCodes.PERMISSION_DENIED);
       }
@@ -613,6 +617,24 @@ public boolean checkAcls(OzoneObj obj, RequestContext 
context,
     }
   }
 
+  /**
+   * Attaches session policy to RequestContext if an STSTokenIdentifier is 
found in the Ozone Manager thread local
+   * (meaning this is an STS request), and the STSTokenIdentifier has a 
session policy.  Otherwise, returns the
+   * RequestContext as it was before.
+   * @param context the original RequestContext
+   * @return RequestContext as before or with sessionPolicy embedded
+   */
+  private RequestContext 
maybeAttachSessionPolicyFromThreadLocal(RequestContext context) {
+    final STSTokenIdentifier stsTokenIdentifier = 
OzoneManager.getStsTokenIdentifier();
+    if (stsTokenIdentifier == null) {
+      return context;
+    }
+
+    return context.toBuilder()
+        .setSessionPolicy(stsTokenIdentifier.getSessionPolicy())
+        .build();
+  }
+
   static String getClientAddress() {
     String clientMachine = Server.getRemoteAddress();
     if (clientMachine == null) { //not a RPC client
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3AssumeRoleRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3AssumeRoleRequest.java
index b02f78e5643..aecba45f32c 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3AssumeRoleRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3AssumeRoleRequest.java
@@ -17,14 +17,17 @@
 
 package org.apache.hadoop.ozone.om.request.s3.security;
 
+import static 
org.apache.hadoop.ozone.security.acl.AssumeRoleRequest.OzoneGrant;
+
 import com.google.common.annotations.VisibleForTesting;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.security.SecureRandom;
 import java.time.Clock;
-import java.time.Instant;
-import java.time.ZoneOffset;
+import java.util.Optional;
+import java.util.Set;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.hdds.scm.client.HddsClientUtils;
 import org.apache.hadoop.ipc.ProtobufRpcEngine;
 import org.apache.hadoop.ozone.om.OzoneAclUtils;
 import org.apache.hadoop.ozone.om.OzoneManager;
@@ -37,6 +40,7 @@
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AssumeRoleRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AssumeRoleResponse;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver;
 import org.apache.hadoop.security.UserGroupInformation;
 
 /**
@@ -71,12 +75,13 @@ public class S3AssumeRoleRequest extends OMClientRequest {
   private static final String CHARS_FOR_SECRET_ACCESS_KEYS = 
CHARS_FOR_ACCESS_KEY_IDS +
       "abcdefghijklmnopqrstuvwxyz/+";
   private static final int CHARS_FOR_SECRET_ACCESS_KEYS_LENGTH = 
CHARS_FOR_SECRET_ACCESS_KEYS.length();
-  private static final Clock CLOCK = Clock.system(ZoneOffset.UTC);
-
   public static final String STS_TOKEN_PREFIX = "ASIA";
 
-  public S3AssumeRoleRequest(OMRequest omRequest) {
+  private final Clock clock;
+
+  public S3AssumeRoleRequest(OMRequest omRequest, Clock clock) {
     super(omRequest);
+    this.clock = clock;
   }
 
   @Override
@@ -127,7 +132,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager 
ozoneManager, Execut
       final String assumedRoleId = roleId + ":" + roleSessionName;
 
       // Calculate expiration of session token
-      final long expirationEpochSeconds = 
Instant.now().plusSeconds(durationSeconds).getEpochSecond();
+      final long expirationEpochSeconds = 
clock.instant().plusSeconds(durationSeconds).getEpochSecond();
 
       final AssumeRoleResponse.Builder responseBuilder = 
AssumeRoleResponse.newBuilder()
           .setAccessKeyId(tempAccessKeyId)
@@ -201,7 +206,7 @@ private String generateSessionToken(String targetRoleName, 
OMRequest omRequest,
 
     return ozoneManager.getSTSTokenSecretManager().createSTSTokenString(
         tempAccessKeyId, originalAccessKeyId, roleArn, 
assumeRoleRequest.getDurationSeconds(), secretAccessKey,
-        sessionPolicy, CLOCK);
+        sessionPolicy, clock);
   }
 
   /**
@@ -209,10 +214,31 @@ private String generateSessionToken(String 
targetRoleName, OMRequest omRequest,
    * to IAccessAuthorizer.generateAssumeRoleSessionPolicy() which is currently 
only implemented
    * by RangerOzoneAuthorizer.
    */
-  private String getSessionPolicy(OzoneManager ozoneManager, String 
originalAccessKeyId, String awsIamPolicy,
+  @VisibleForTesting
+  String getSessionPolicy(OzoneManager ozoneManager, String 
originalAccessKeyId, String awsIamPolicy,
       String hostName, InetAddress remoteIp, UserGroupInformation ugi, String 
targetRoleName) throws IOException {
-    // TODO sts - implement in a future PR
-    return null;
+
+    final String volumeName;
+    if (ozoneManager.isS3MultiTenancyEnabled()) {
+      final Optional<String> tenantOpt = ozoneManager.getMultiTenantManager()
+          .getTenantForAccessID(originalAccessKeyId);
+      if (tenantOpt.isPresent()) {
+        volumeName = ozoneManager.getMultiTenantManager()
+            .getTenantVolumeName(tenantOpt.get());
+      } else {
+        volumeName = 
HddsClientUtils.getDefaultS3VolumeName(ozoneManager.getConfiguration());
+      }
+    } else {
+      volumeName = 
HddsClientUtils.getDefaultS3VolumeName(ozoneManager.getConfiguration());
+    }
+
+    final Set<OzoneGrant> grants = StringUtils.isBlank(awsIamPolicy) ?
+        null :
+        IamSessionPolicyResolver.resolve(awsIamPolicy, volumeName, 
IamSessionPolicyResolver.AuthorizerType.RANGER);
+
+    return ozoneManager.getAccessAuthorizer().generateAssumeRoleSessionPolicy(
+        new org.apache.hadoop.ozone.security.acl.AssumeRoleRequest(
+            hostName, remoteIp, ugi, targetRoleName, grants));
   }
 
   /**
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMMetadataReader.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMMetadataReader.java
index 00a94a538c3..a6dc8e78ca6 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMMetadataReader.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOMMetadataReader.java
@@ -18,20 +18,41 @@
 package org.apache.hadoop.ozone.om;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import io.grpc.Context;
+import java.net.InetAddress;
 import org.apache.hadoop.ipc.Server;
+import org.apache.hadoop.ozone.audit.AuditLogger;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.security.STSTokenIdentifier;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.ozone.security.acl.OzoneObj;
+import org.apache.hadoop.ozone.security.acl.OzoneObjInfo;
+import org.apache.hadoop.ozone.security.acl.RequestContext;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.MockedStatic;
+import org.slf4j.Logger;
 
 /**
  * Test ozone metadata reader.
  */
 public class TestOMMetadataReader {
 
+  @AfterEach
+  public void clearStsThreadLocal() {
+    OzoneManager.setStsTokenIdentifier(null);
+  }
+
   @Test
   public void testGetClientAddress() {
     try (
@@ -69,4 +90,116 @@ public void testGetClientAddress() {
     }
   }
 
+  @Test
+  public void testCheckAclsAttachesSessionPolicyFromThreadLocal() throws 
Exception {
+    final String sessionPolicy = "session-policy-from-thread-local";
+    setupStsTokenIdentifier(sessionPolicy);
+
+    final IAccessAuthorizer accessAuthorizer = 
createMockIAccessAuthorizerReturningTrue();
+    final OmMetadataReader omMetadataReader = 
createMetadataReader(accessAuthorizer);
+
+    final RequestContext contextWithoutSessionPolicy = 
createTestRequestContext(null);
+    final OzoneObj obj = createTestOzoneObj();
+
+    assertTrue(omMetadataReader.checkAcls(obj, contextWithoutSessionPolicy, 
true));
+
+    verifySessionPolicyPassedToAuthorizer(accessAuthorizer, obj, 
sessionPolicy);
+  }
+
+  @Test
+  public void testNoSessionPolicyWhenThreadLocalIsNull() throws Exception {
+    // No STS token identifier in thread local
+    OzoneManager.setStsTokenIdentifier(null);
+
+    final IAccessAuthorizer accessAuthorizer = 
createMockIAccessAuthorizerReturningTrue();
+    final OmMetadataReader omMetadataReader = 
createMetadataReader(accessAuthorizer);
+
+    final RequestContext contextWithoutSessionPolicy = 
createTestRequestContext(null);
+    final OzoneObj obj = createTestOzoneObj();
+
+    assertTrue(omMetadataReader.checkAcls(obj, contextWithoutSessionPolicy, 
true));
+
+    verifySessionPolicyPassedToAuthorizer(accessAuthorizer, obj, null);
+  }
+
+  private OmMetadataReader createMetadataReader(IAccessAuthorizer 
accessAuthorizer) {
+    final OzoneManager ozoneManager = mock(OzoneManager.class);
+    
when(ozoneManager.getBucketManager()).thenReturn(mock(BucketManager.class));
+    
when(ozoneManager.getVolumeManager()).thenReturn(mock(VolumeManager.class));
+    when(ozoneManager.getAclsEnabled()).thenReturn(true);
+    
when(ozoneManager.getPerfMetrics()).thenReturn(mock(OMPerformanceMetrics.class));
+
+    return new OmMetadataReader(
+        mock(KeyManager.class), mock(PrefixManager.class), ozoneManager, 
mock(Logger.class), mock(AuditLogger.class),
+        mock(OmMetadataReaderMetrics.class), accessAuthorizer);
+  }
+
+  /**
+   * Creates and sets a mock STSTokenIdentifier with the given session policy 
in the thread-local.
+   * @param sessionPolicy the session policy to return, or null
+   */
+  private void setupStsTokenIdentifier(String sessionPolicy) {
+    final STSTokenIdentifier stsTokenIdentifier = 
mock(STSTokenIdentifier.class);
+    when(stsTokenIdentifier.getSessionPolicy()).thenReturn(sessionPolicy);
+    OzoneManager.setStsTokenIdentifier(stsTokenIdentifier);
+  }
+
+  /**
+   * Creates a mock IAccessAuthorizer that returns the specified result for 
checkAccess.
+   * @return the mocked IAccessAuthorizer
+   */
+  private IAccessAuthorizer createMockIAccessAuthorizerReturningTrue() throws 
OMException {
+    final IAccessAuthorizer accessAuthorizer = mock(IAccessAuthorizer.class);
+    when(accessAuthorizer.checkAccess(any(OzoneObj.class), 
any(RequestContext.class)))
+        .thenReturn(true);
+    return accessAuthorizer;
+  }
+
+  /**
+   * Creates a test RequestContext with the given session policy.
+   * @param sessionPolicy the session policy to set, or null
+   * @return the constructed RequestContext
+   */
+  private RequestContext createTestRequestContext(String sessionPolicy) {
+    RequestContext.Builder builder = RequestContext.newBuilder()
+        .setClientUgi(UserGroupInformation.createRemoteUser("testUser"))
+        .setIp(InetAddress.getLoopbackAddress())
+        .setHost("localhost")
+        .setAclType(IAccessAuthorizer.ACLIdentityType.USER)
+        .setAclRights(IAccessAuthorizer.ACLType.READ)
+        .setOwnerName("owner");
+
+    if (sessionPolicy != null) {
+      builder.setSessionPolicy(sessionPolicy);
+    }
+
+    return builder.build();
+  }
+
+  /**
+   * Creates a test OzoneObj representing a key.
+   * @return the constructed OzoneObj
+   */
+  private OzoneObj createTestOzoneObj() {
+    return OzoneObjInfo.Builder.newBuilder()
+        .setResType(OzoneObj.ResourceType.KEY)
+        .setStoreType(OzoneObj.StoreType.OZONE)
+        .setVolumeName("vol")
+        .setBucketName("bucket")
+        .setKeyName("key")
+        .build();
+  }
+
+  /**
+   * Verifies that the accessAuthorizer received a call to checkAccess with 
the expected session policy.
+   * @param accessAuthorizer the mock authorizer to verify
+   * @param expectedObj the expected OzoneObj
+   * @param expectedSessionPolicy the expected session policy (may be null)
+   */
+  private void verifySessionPolicyPassedToAuthorizer(IAccessAuthorizer 
accessAuthorizer, OzoneObj expectedObj,
+      String expectedSessionPolicy) throws OMException {
+    final ArgumentCaptor<RequestContext> captor = 
ArgumentCaptor.forClass(RequestContext.class);
+    verify(accessAuthorizer).checkAccess(eq(expectedObj), captor.capture());
+    assertEquals(expectedSessionPolicy, captor.getValue().getSessionPolicy());
+  }
 }
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/security/TestS3AssumeRoleRequest.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/security/TestS3AssumeRoleRequest.java
index 0940bbc5545..3ae3c3c7599 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/security/TestS3AssumeRoleRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/security/TestS3AssumeRoleRequest.java
@@ -17,21 +17,32 @@
 
 package org.apache.hadoop.ozone.om.request.s3.security;
 
+import static java.util.Collections.emptySet;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
+import java.time.ZoneOffset;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
 import java.util.UUID;
 import java.util.regex.Pattern;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
+import org.apache.hadoop.hdds.conf.OzoneConfiguration;
 import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
 import org.apache.hadoop.hdds.security.symmetric.SecretKeySignerClient;
+import org.apache.hadoop.ozone.om.OMMultiTenantManager;
 import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.execution.flowcontrol.ExecutionContext;
 import org.apache.hadoop.ozone.om.response.OMClientResponse;
@@ -43,9 +54,16 @@
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status;
 import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type;
 import org.apache.hadoop.ozone.security.STSTokenSecretManager;
+import org.apache.hadoop.ozone.security.acl.AssumeRoleRequest.OzoneGrant;
+import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
+import org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver;
+import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.hadoop.security.token.TokenIdentifier;
+import org.apache.ozone.test.TestClock;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.MockedStatic;
 
 /**
  * Unit tests for S3AssumeRoleRequest.
@@ -55,14 +73,33 @@ public class TestS3AssumeRoleRequest {
   private static final String ROLE_ARN_1 = 
"arn:aws:iam::123456789012:role/MyRole1";
   private static final String SESSION_NAME = "testSessionName";
   private static final String ORIGINAL_ACCESS_KEY_ID = "origAccessKeyId";
+  private static final String TARGET_ROLE_NAME = "targetRole";
+  private static final String SESSION_POLICY_VALUE = "session-policy";
+  private static final String AWS_IAM_POLICY = "{\n" +
+      "  \"Statement\": [{\n" +
+      "    \"Effect\": \"Allow\",\n" +
+      "    \"Action\": \"s3:*\",\n" +
+      "    \"Resource\": \"arn:aws:s3:::*/*\"\n" +
+      "  }]\n" +
+      "}";
+
+  private static final TestClock CLOCK = new 
TestClock(Instant.ofEpochMilli(1764819000), ZoneOffset.UTC);
+  private static final String OM_HOST = "om-host";
+  private static final InetAddress LOOPBACK_IP = 
InetAddress.getLoopbackAddress();
+  private static final Set<OzoneGrant> EMPTY_GRANTS = 
Collections.singleton(new OzoneGrant(emptySet(), emptySet()));
 
   private OzoneManager ozoneManager;
   private ExecutionContext context;
+  private IAccessAuthorizer accessAuthorizer;
 
   @BeforeEach
   public void setup() throws IOException {
     ozoneManager = mock(OzoneManager.class);
 
+    final OzoneConfiguration configuration = new OzoneConfiguration();
+    when(ozoneManager.getConfiguration()).thenReturn(configuration);
+    when(ozoneManager.isS3MultiTenancyEnabled()).thenReturn(false);
+
     final SecretKeySignerClient secretKeyClient = 
mock(SecretKeySignerClient.class);
     final ManagedSecretKey managedSecretKey = mock(ManagedSecretKey.class);
     final SecretKey secretKey = new SecretKeySpec(
@@ -80,6 +117,13 @@ public void setup() throws IOException {
     when(ozoneManager.getOmRpcServerAddr()).thenReturn(
         new InetSocketAddress("localhost", 9876));
     
when(ozoneManager.getSTSTokenSecretManager()).thenReturn(stsTokenSecretManager);
+
+    accessAuthorizer = mock(IAccessAuthorizer.class);
+    when(ozoneManager.getAccessAuthorizer()).thenReturn(accessAuthorizer);
+    when(accessAuthorizer.generateAssumeRoleSessionPolicy(any(
+        org.apache.hadoop.ozone.security.acl.AssumeRoleRequest.class)))
+        .thenReturn(SESSION_POLICY_VALUE);
+
     context = ExecutionContext.of(1L, null);
   }
 
@@ -93,7 +137,7 @@ public void testInvalidDurationTooShort() {
                 .setDurationSeconds(899)  // less than 900
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
@@ -112,7 +156,7 @@ public void testInvalidDurationTooLong() {
                 .setDurationSeconds(43201)  // more than 43200
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
@@ -131,7 +175,7 @@ public void testValidDurationMaxBoundary() {
                 .setDurationSeconds(43200)  // exactly max
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
@@ -149,7 +193,7 @@ public void testValidDurationMinBoundary() {
                 .setDurationSeconds(900)  // exactly min
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
@@ -169,7 +213,7 @@ public void testMissingS3Authentication() {
                 .setDurationSeconds(3600)
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
@@ -189,8 +233,7 @@ public void testSuccessfulAssumeRoleGeneratesCredentials() {
                 .setDurationSeconds(durationSeconds)
         ).build();
 
-    final long before = Instant.now().getEpochSecond();
-    final OMClientResponse clientResponse = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse clientResponse = new S3AssumeRoleRequest(omRequest, 
CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = clientResponse.getOMResponse();
 
@@ -214,10 +257,9 @@ public void testSuccessfulAssumeRoleGeneratesCredentials() 
{
     final int expectedAssumedRoleIdLength = 4 + 16 + 1 + 
SESSION_NAME.length(); // 4 for AROA, 16 chars, 1 for ":"
     
assertThat(assumeRoleResponse.getAssumedRoleId().length()).isEqualTo(expectedAssumedRoleIdLength);
 
-    // Expiration around now + durationSeconds (allow small skew)
-    final long after = Instant.now().getEpochSecond();
+    // Verify expiration added durationSeconds
     final long expirationEpochSeconds = 
assumeRoleResponse.getExpirationEpochSeconds();
-    assertThat(expirationEpochSeconds).isBetween(before + durationSeconds - 1, 
after + durationSeconds + 1);
+    
assertThat(expirationEpochSeconds).isEqualTo(CLOCK.instant().getEpochSecond() + 
durationSeconds);
   }
 
   @Test
@@ -250,9 +292,9 @@ public void testAssumeRoleCredentialsAreUnique() {
                 .setDurationSeconds(3600)
         ).build();
 
-    final OMClientResponse response1 = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response1 = new S3AssumeRoleRequest(omRequest, 
CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
-    final OMClientResponse response2 = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response2 = new S3AssumeRoleRequest(omRequest, 
CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
 
     final AssumeRoleResponse assumeRoleResponse1 = 
response1.getOMResponse().getAssumeRoleResponse();
@@ -281,7 +323,7 @@ public void testAssumeRoleWithEmptySessionName() {
                 .setDurationSeconds(3600)
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     
assertThat(response.getOMResponse().getStatus()).isEqualTo(Status.INVALID_REQUEST);
     
assertThat(response.getOMResponse().getMessage()).isEqualTo("RoleSessionName is 
required");
@@ -296,7 +338,7 @@ public void testInvalidAssumeRoleSessionNameTooShort() {
                 .setRoleSessionName("T")   // Less than 2 characters
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
@@ -315,7 +357,7 @@ public void testInvalidRoleSessionNameTooLong() {
                 .setRoleSessionName(tooLongRoleSessionName)
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
@@ -334,7 +376,7 @@ public void testValidRoleSessionNameMaxLengthBoundary() {
                 .setRoleSessionName(roleSessionName)  // exactly max length
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
@@ -351,7 +393,7 @@ public void testValidRoleSessionNameMinLengthBoundary() {
                 .setRoleSessionName("TT")   // exactly min length
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
@@ -371,11 +413,114 @@ public void testAssumeRoleWithSessionPolicyPresent() {
                 .setAwsIamSessionPolicy(sessionPolicy)
         ).build();
 
-    final OMClientResponse response = new S3AssumeRoleRequest(omRequest)
+    final OMClientResponse response = new S3AssumeRoleRequest(omRequest, CLOCK)
         .validateAndUpdateCache(ozoneManager, context);
     assertThat(response.getOMResponse().getStatus()).isEqualTo(Status.OK);
   }
 
+  @Test
+  public void testGetSessionPolicyUsesDefaultVolumeWhenMultiTenantDisabled() 
throws Exception {
+    when(ozoneManager.isS3MultiTenancyEnabled()).thenReturn(false);
+
+    // Ensure s3v default volume was captured in the method invocation
+    final org.apache.hadoop.ozone.security.acl.AssumeRoleRequest 
capturedAssumeRoleRequest =
+        captureAssumeRoleRequest("s3v", "userNameA");
+
+    assertThat(capturedAssumeRoleRequest.getHost()).isEqualTo(OM_HOST);
+    assertThat(capturedAssumeRoleRequest.getIp()).isEqualTo(LOOPBACK_IP);
+    
assertThat(capturedAssumeRoleRequest.getTargetRoleName()).isEqualTo(TARGET_ROLE_NAME);
+    assertThat(capturedAssumeRoleRequest.getGrants()).isEqualTo(EMPTY_GRANTS);
+  }
+
+  @Test
+  public void testGetSessionPolicyResolvesIamPolicyWithTenantVolume() throws 
Exception {
+    when(ozoneManager.isS3MultiTenancyEnabled()).thenReturn(true);
+
+    final OMMultiTenantManager multiTenantManager = 
mock(OMMultiTenantManager.class);
+    when(ozoneManager.getMultiTenantManager()).thenReturn(multiTenantManager);
+    
when(multiTenantManager.getTenantForAccessID(ORIGINAL_ACCESS_KEY_ID)).thenReturn(Optional.of("tenant-a"));
+    
when(multiTenantManager.getTenantVolumeName("tenant-a")).thenReturn("tenant-a-volume");
+
+    // Ensure "tenant-a-volume" was captured in the method invocation
+    final org.apache.hadoop.ozone.security.acl.AssumeRoleRequest 
capturedAssumeRoleRequest =
+        captureAssumeRoleRequest("tenant-a-volume", "userNameA");
+
+    assertThat(capturedAssumeRoleRequest.getHost()).isEqualTo(OM_HOST);
+    assertThat(capturedAssumeRoleRequest.getIp()).isEqualTo(LOOPBACK_IP);
+    
assertThat(capturedAssumeRoleRequest.getTargetRoleName()).isEqualTo(TARGET_ROLE_NAME);
+    assertThat(capturedAssumeRoleRequest.getGrants()).isEqualTo(EMPTY_GRANTS);
+  }
+
+  @Test
+  public void testGetSessionPolicyFallsBackToDefaultVolumeWhenTenantMissing() 
throws Exception {
+    when(ozoneManager.isS3MultiTenancyEnabled()).thenReturn(true);
+
+    final OMMultiTenantManager multiTenantManager = 
mock(OMMultiTenantManager.class);
+    when(ozoneManager.getMultiTenantManager()).thenReturn(multiTenantManager);
+    
when(multiTenantManager.getTenantForAccessID(ORIGINAL_ACCESS_KEY_ID)).thenReturn(Optional.empty());
+
+    // Ensure s3v default volume was captured in the method invocation since 
tenant was missing
+    final org.apache.hadoop.ozone.security.acl.AssumeRoleRequest 
capturedAssumeRoleRequest =
+        captureAssumeRoleRequest("s3v", "userNameB");
+
+    verify(multiTenantManager, never()).getTenantVolumeName(any());
+    assertThat(capturedAssumeRoleRequest.getHost()).isEqualTo(OM_HOST);
+    assertThat(capturedAssumeRoleRequest.getIp()).isEqualTo(LOOPBACK_IP);
+    
assertThat(capturedAssumeRoleRequest.getTargetRoleName()).isEqualTo(TARGET_ROLE_NAME);
+    assertThat(capturedAssumeRoleRequest.getGrants()).isEqualTo(EMPTY_GRANTS);
+  }
+
+  @Test
+  public void testGetSessionPolicyWithBlankAwsPolicyCapturesNullGrants() 
throws Exception {
+    when(ozoneManager.isS3MultiTenancyEnabled()).thenReturn(false);
+
+    final String awsIamPolicy = null;
+    try (MockedStatic<IamSessionPolicyResolver> resolverMock = 
mockStatic(IamSessionPolicyResolver.class)) {
+      final String result = new 
S3AssumeRoleRequest(baseOmRequestBuilder().build(), CLOCK)
+          .getSessionPolicy(
+              ozoneManager, ORIGINAL_ACCESS_KEY_ID, awsIamPolicy, OM_HOST, 
LOOPBACK_IP,
+              UserGroupInformation.createRemoteUser("userNameC"), 
TARGET_ROLE_NAME);
+
+      assertThat(result).isEqualTo(SESSION_POLICY_VALUE);
+
+      // Ensure IamSessionPolicyResolver was never invoked since awsIamPolicy 
is null
+      resolverMock.verifyNoInteractions();
+    }
+
+    final 
ArgumentCaptor<org.apache.hadoop.ozone.security.acl.AssumeRoleRequest> captor =
+        
ArgumentCaptor.forClass(org.apache.hadoop.ozone.security.acl.AssumeRoleRequest.class);
+    verify(accessAuthorizer).generateAssumeRoleSessionPolicy(captor.capture());
+
+    final org.apache.hadoop.ozone.security.acl.AssumeRoleRequest 
capturedAssumeRoleRequest = captor.getValue();
+    assertThat(capturedAssumeRoleRequest.getHost()).isEqualTo(OM_HOST);
+    assertThat(capturedAssumeRoleRequest.getIp()).isEqualTo(LOOPBACK_IP);
+    
assertThat(capturedAssumeRoleRequest.getTargetRoleName()).isEqualTo(TARGET_ROLE_NAME);
+    assertThat(capturedAssumeRoleRequest.getGrants()).isNull();
+  }
+
+  private org.apache.hadoop.ozone.security.acl.AssumeRoleRequest 
captureAssumeRoleRequest(String volumeName,
+      String userName) throws Exception {
+    try (MockedStatic<IamSessionPolicyResolver> resolverMock = 
mockStatic(IamSessionPolicyResolver.class)) {
+      resolverMock.when(() -> IamSessionPolicyResolver.resolve(
+              AWS_IAM_POLICY, volumeName, 
IamSessionPolicyResolver.AuthorizerType.RANGER))
+          .thenReturn(EMPTY_GRANTS);
+
+      final String result = new 
S3AssumeRoleRequest(baseOmRequestBuilder().build(), CLOCK)
+          .getSessionPolicy(
+              ozoneManager, ORIGINAL_ACCESS_KEY_ID, AWS_IAM_POLICY, OM_HOST, 
LOOPBACK_IP,
+              UserGroupInformation.createRemoteUser(userName), 
TARGET_ROLE_NAME);
+
+      assertThat(result).isEqualTo(SESSION_POLICY_VALUE);
+      resolverMock.verify(() -> IamSessionPolicyResolver.resolve(
+          AWS_IAM_POLICY, volumeName, 
IamSessionPolicyResolver.AuthorizerType.RANGER));
+    }
+
+    final 
ArgumentCaptor<org.apache.hadoop.ozone.security.acl.AssumeRoleRequest> captor =
+        
ArgumentCaptor.forClass(org.apache.hadoop.ozone.security.acl.AssumeRoleRequest.class);
+    verify(accessAuthorizer).generateAssumeRoleSessionPolicy(captor.capture());
+    return captor.getValue();
+  }
+
   private static OMRequest.Builder baseOmRequestBuilder() {
     return OMRequest.newBuilder()
         .setCmdType(Type.AssumeRole)
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestRequestContext.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestRequestContext.java
index cb05c2ef626..937c69f5874 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestRequestContext.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/TestRequestContext.java
@@ -131,4 +131,70 @@ private RequestContext getUserRequestContext(String 
username,
             UserGroupInformation.createRemoteUser(username), null, null,
             type, ownerName).build();
   }
+
+  @Test
+  public void testToBuilderWithNoModifications() {
+    // Create a RequestContext with all fields set
+    final UserGroupInformation ugi = 
UserGroupInformation.createRemoteUser("testUser");
+    final String host = "testHost";
+    final String serviceId = "testServiceId";
+    final String ownerName = "testOwner";
+    final String sessionPolicy = "{\"Statement\":[{\"Effect\":\"Allow\"}]}";
+
+    final RequestContext original = new RequestContext(
+        host, null, ugi, serviceId, IAccessAuthorizer.ACLIdentityType.USER, 
IAccessAuthorizer.ACLType.READ, ownerName,
+        true, sessionPolicy);
+
+    // Use toBuilder to create a new builder
+    final RequestContext.Builder builder = original.toBuilder();
+    final RequestContext requestCtxFromToBuilder = builder.build();
+
+    // Verify all fields are preserved
+    assertEquals(original.getHost(), requestCtxFromToBuilder.getHost(), "Host 
should be preserved");
+    assertNull(original.getIp(), "IP should be preserved");
+    assertEquals(original.getClientUgi(), 
requestCtxFromToBuilder.getClientUgi(), "ClientUgi should be preserved");
+    assertEquals(original.getServiceId(), 
requestCtxFromToBuilder.getServiceId(), "ServiceId should be preserved");
+    assertEquals(original.getAclType(), requestCtxFromToBuilder.getAclType(), 
"AclType should be preserved");
+    assertEquals(original.getAclRights(), 
requestCtxFromToBuilder.getAclRights(), "AclRights should be preserved");
+    assertEquals(original.getOwnerName(), 
requestCtxFromToBuilder.getOwnerName(), "OwnerName should be preserved");
+    assertTrue(original.isRecursiveAccessCheck(), "RecursiveAccessCheck should 
be preserved");
+    assertEquals(original.getSessionPolicy(), 
requestCtxFromToBuilder.getSessionPolicy(),
+        "SessionPolicy should be preserved");
+  }
+
+  @Test
+  public void testToBuilderWithModifications() {
+    // Create an original RequestContext
+    final UserGroupInformation originalUgi = 
UserGroupInformation.createRemoteUser("user1");
+    final RequestContext original = new RequestContext(
+        "host1", null, originalUgi, "service1", 
IAccessAuthorizer.ACLIdentityType.USER, IAccessAuthorizer.ACLType.READ,
+        "owner1", false, null);
+
+    // Use toBuilder and modify some fields
+    final UserGroupInformation newUgi = 
UserGroupInformation.createRemoteUser("user2");
+    final RequestContext modified = original.toBuilder()
+        .setHost("host2")
+        .setClientUgi(newUgi)
+        .setAclRights(IAccessAuthorizer.ACLType.WRITE)
+        .setOwnerName("owner2")
+        .setRecursiveAccessCheck(true)
+        .setSessionPolicy("{\"Statement\":[]}")
+        .build();
+
+    // Verify original is unchanged
+    assertEquals("host1", original.getHost(), "Original should be unchanged");
+    assertEquals(originalUgi, original.getClientUgi(), "Original UGI should be 
unchanged");
+    assertEquals(IAccessAuthorizer.ACLType.READ, original.getAclRights(), 
"Original ACL rights should be unchanged");
+    assertEquals("owner1", original.getOwnerName(), "Original owner name 
should be unchanged");
+    assertFalse(original.isRecursiveAccessCheck(), "Original recursive flag 
should be unchanged");
+    assertNull(original.getSessionPolicy(), "Original session policy should be 
unchanged");
+
+    // Verify modified has new values
+    assertEquals("host2", modified.getHost(), "Modified host should be 
updated");
+    assertEquals(newUgi, modified.getClientUgi(), "Modified UGI should be 
updated");
+    assertEquals(IAccessAuthorizer.ACLType.WRITE, modified.getAclRights(), 
"Modified ACL rights should be updated");
+    assertEquals("owner2", modified.getOwnerName(), "Modified owner should be 
updated");
+    assertTrue(modified.isRecursiveAccessCheck(), "Modified recursive flag 
should be updated");
+    assertEquals("{\"Statement\":[]}", modified.getSessionPolicy(), "Modified 
session policy should be updated");
+  }
 }
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/package-info.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/package-info.java
new file mode 100644
index 00000000000..7feb73bd2e6
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/acl/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Ozone security acl tests.
+ */
+package org.apache.hadoop.ozone.security.acl;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to