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]