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

sammichen 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 7991b3abb6d HDDS-14538. [STS] Leader OM should generate access key and 
secret and pass to others (#9697)
7991b3abb6d is described below

commit 7991b3abb6de6451ce659885997fbebaeebc6a05
Author: fmorg-git <[email protected]>
AuthorDate: Tue Feb 3 23:01:04 2026 -0800

    HDDS-14538. [STS] Leader OM should generate access key and secret and pass 
to others (#9697)
---
 .../src/main/proto/OmClientProtocol.proto          | 17 +++++
 .../request/s3/security/S3AssumeRoleRequest.java   | 81 ++++++++++++++++++----
 .../s3/security/TestS3AssumeRoleRequest.java       | 71 +++++++++++++------
 3 files changed, 133 insertions(+), 36 deletions(-)

diff --git 
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto 
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 707f8ac567d..173b444e6f1 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -310,6 +310,7 @@ message OMRequest {
   optional AssumeRoleRequest                assumeRoleRequest              = 
144;
   optional RevokeSTSTokenRequest            revokeSTSTokenRequest          = 
145;
   optional DeleteRevokedSTSTokensRequest    deleteRevokedSTSTokensRequest  = 
146;
+  optional UpdateAssumeRoleRequest          updateAssumeRoleRequest        = 
147;
 }
 
 message OMResponse {
@@ -2390,6 +2391,22 @@ message AssumeRoleResponse {
   required string assumedRoleId           = 5;
 }
 
+/**
+  This request will be used internally by OM to replicate credentials 
generated by the leader
+  across the OMs in HA mode. This ensures all OMs have identical audit logs.
+*/
+message UpdateAssumeRoleRequest {
+  required string roleArn                 = 1;
+  required string roleSessionName         = 2;
+  required int32 durationSeconds          = 3;
+  optional string awsIamSessionPolicy     = 4;
+  required string requestId               = 5;
+  // Leader-generated credentials
+  required string tempAccessKeyId         = 6;
+  required string secretAccessKey         = 7;
+  required string roleId                  = 8;
+}
+
 message RevokeSTSTokenRequest {
     required string sessionToken = 1;
 }
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 030a4aeffeb..939fe57efb8 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
@@ -20,6 +20,7 @@
 import static 
org.apache.hadoop.ozone.security.acl.AssumeRoleRequest.OzoneGrant;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.security.SecureRandom;
@@ -28,7 +29,6 @@
 import java.util.Map;
 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.audit.AuditLogger;
@@ -47,6 +47,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.protocol.proto.OzoneManagerProtocolProtos.UpdateAssumeRoleRequest;
 import org.apache.hadoop.ozone.security.acl.iam.IamSessionPolicyResolver;
 import org.apache.hadoop.security.UserGroupInformation;
 
@@ -87,20 +88,78 @@ public S3AssumeRoleRequest(OMRequest omRequest, Clock 
clock) {
     this.clock = clock;
   }
 
+  @Override
+  public OMRequest preExecute(OzoneManager ozoneManager) throws IOException {
+    final AssumeRoleRequest assumeRoleRequest = 
getOmRequest().getAssumeRoleRequest();
+
+    // Brief overview of flow:
+    // The STS Endpoint makes the AssumeRole call, which when received by OM 
leader (via this method),
+    // it will generate the temporary credentials (tempAccessKeyId, 
secretAccessKey) and roleId.
+    // The original AssumeRole request is converted to an 
UpdateAssumeRoleRequest with the generated
+    // credentials. This update request will be submitted to Ratis and the 
credentials
+    // created by the leader will be replicated across all OMs.  All OMs in
+    // HA mode therefore will have identical audit logs with the same 
tempAccessKeyId.
+
+    // Generate temporary AWS credentials using cryptographically strong 
SecureRandom
+    final String tempAccessKeyId = STS_TOKEN_PREFIX + 
generateSecureRandomStringUsingChars(
+        CHARS_FOR_ACCESS_KEY_IDS, CHARS_FOR_ACCESS_KEY_IDS_LENGTH, 
STS_ACCESS_KEY_ID_LENGTH);
+    final String secretAccessKey = generateSecureRandomStringUsingChars(
+        CHARS_FOR_SECRET_ACCESS_KEYS, CHARS_FOR_SECRET_ACCESS_KEYS_LENGTH, 
STS_SECRET_ACCESS_KEY_LENGTH);
+    final String roleId = ASSUME_ROLE_ID_PREFIX + 
generateSecureRandomStringUsingChars(
+        CHARS_FOR_ACCESS_KEY_IDS, CHARS_FOR_ACCESS_KEY_IDS_LENGTH, 
STS_ROLE_ID_LENGTH);
+
+    // Build UpdateAssumeRoleRequest with leader-generated credentials
+    final UpdateAssumeRoleRequest.Builder updateAssumeRoleRequestBuilder =
+        UpdateAssumeRoleRequest.newBuilder()
+            .setRoleArn(assumeRoleRequest.getRoleArn())
+            .setRoleSessionName(assumeRoleRequest.getRoleSessionName())
+            .setDurationSeconds(assumeRoleRequest.getDurationSeconds())
+            .setRequestId(assumeRoleRequest.getRequestId())
+            .setTempAccessKeyId(tempAccessKeyId)
+            .setSecretAccessKey(secretAccessKey)
+            .setRoleId(roleId);
+
+    if (assumeRoleRequest.hasAwsIamSessionPolicy()) {
+      
updateAssumeRoleRequestBuilder.setAwsIamSessionPolicy(assumeRoleRequest.getAwsIamSessionPolicy());
+    }
+
+    // Build new OMRequest with both original and update requests
+    final OMRequest.Builder omRequest = OMRequest.newBuilder()
+        .setUserInfo(getUserInfo())
+        .setCmdType(getOmRequest().getCmdType())
+        .setClientId(getOmRequest().getClientId())
+        .setAssumeRoleRequest(assumeRoleRequest)
+        .setUpdateAssumeRoleRequest(updateAssumeRoleRequestBuilder.build());
+
+    if (getOmRequest().hasS3Authentication()) {
+      omRequest.setS3Authentication(getOmRequest().getS3Authentication());
+    }
+
+    if (getOmRequest().hasTraceID()) {
+      omRequest.setTraceID(getOmRequest().getTraceID());
+    }
+
+    return omRequest.build();
+  }
+
   @Override
   public OMClientResponse validateAndUpdateCache(OzoneManager ozoneManager, 
ExecutionContext context) {
     final OMRequest omRequest = getOmRequest();
     final AssumeRoleRequest assumeRoleRequest = 
omRequest.getAssumeRoleRequest();
+    final UpdateAssumeRoleRequest updateAssumeRoleRequest = 
omRequest.getUpdateAssumeRoleRequest();
+
     final int durationSeconds = assumeRoleRequest.getDurationSeconds();
     final String roleSessionName = assumeRoleRequest.getRoleSessionName();
     final String roleArn = assumeRoleRequest.getRoleArn();
     final String awsIamSessionPolicy = 
assumeRoleRequest.getAwsIamSessionPolicy();
     final String requestId = assumeRoleRequest.getRequestId();
 
+    // Extract leader-generated credentials and roleId from 
UpdateAssumeRoleRequest
+    final String tempAccessKeyId = 
updateAssumeRoleRequest.getTempAccessKeyId();
+    final String secretAccessKey = 
updateAssumeRoleRequest.getSecretAccessKey();
+    final String roleId = updateAssumeRoleRequest.getRoleId();
+
     final Map<String, String> auditMap = new HashMap<>();
-    // In HA environments, only the tempAccessKeyId on the leader is used by 
S3G, so it could be helpful to
-    // have the leader information
-    auditMap.put("omRole", ozoneManager.isLeaderReady() ? "LEADER" : 
"FOLLOWER");
     final AuditLogger auditLogger = ozoneManager.getAuditLogger();
     final OzoneManagerProtocolProtos.UserInfo userInfo = 
omRequest.getUserInfo();
     S3STSUtils.addAssumeRoleAuditParams(
@@ -118,22 +177,18 @@ public OMClientResponse 
validateAndUpdateCache(OzoneManager ozoneManager, Execut
       // Validate role ARN and extract role
       final String targetRoleName = 
AwsRoleArnValidator.validateAndExtractRoleNameFromArn(roleArn);
 
+      // Note: The IamSessionPolicyResolver validates the awsIamPolicy length 
internally
+
       if (!omRequest.hasS3Authentication()) {
         throw new OMException(
             "S3AssumeRoleRequest does not have S3 authentication", 
OMException.ResultCodes.INVALID_REQUEST);
       }
 
-      // Generate temporary AWS credentials using cryptographically strong 
SecureRandom
-      final String tempAccessKeyId = STS_TOKEN_PREFIX + 
generateSecureRandomStringUsingChars(
-          CHARS_FOR_ACCESS_KEY_IDS, CHARS_FOR_ACCESS_KEY_IDS_LENGTH, 
STS_ACCESS_KEY_ID_LENGTH);
-      final String secretAccessKey = generateSecureRandomStringUsingChars(
-          CHARS_FOR_SECRET_ACCESS_KEYS, CHARS_FOR_SECRET_ACCESS_KEYS_LENGTH, 
STS_SECRET_ACCESS_KEY_LENGTH);
+      // Generate session token using leader-generated credentials
       final String sessionToken = generateSessionToken(
           targetRoleName, omRequest, ozoneManager, assumeRoleRequest, 
secretAccessKey, tempAccessKeyId);
 
-      // Generate AssumedRoleId for response
-      final String roleId = ASSUME_ROLE_ID_PREFIX + 
generateSecureRandomStringUsingChars(
-          CHARS_FOR_ACCESS_KEY_IDS, CHARS_FOR_ACCESS_KEY_IDS_LENGTH, 
STS_ROLE_ID_LENGTH);
+      // Generate AssumedRoleId for response using leader-generated roleId
       final String assumedRoleId = roleId + ":" + roleSessionName;
 
       // Calculate expiration of session token
@@ -227,7 +282,7 @@ String getSessionPolicy(OzoneManager ozoneManager, String 
originalAccessKeyId, S
       volumeName = 
HddsClientUtils.getDefaultS3VolumeName(ozoneManager.getConfiguration());
     }
 
-    final Set<OzoneGrant> grants = StringUtils.isBlank(awsIamPolicy) ?
+    final Set<OzoneGrant> grants = Strings.isNullOrEmpty(awsIamPolicy) ?
         null :
         IamSessionPolicyResolver.resolve(awsIamPolicy, volumeName, 
IamSessionPolicyResolver.AuthorizerType.RANGER);
 
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 004a6b0ab69..ba1ac7004de 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
@@ -183,7 +183,7 @@ public void testInvalidDurationTooLong() {
   }
 
   @Test
-  public void testValidDurationMaxBoundary() {
+  public void testValidDurationMaxBoundary() throws IOException {
     final OMRequest omRequest = baseOmRequestBuilder()
         .setAssumeRoleRequest(
             AssumeRoleRequest.newBuilder()
@@ -193,17 +193,20 @@ public void testValidDurationMaxBoundary() {
                 .setRequestId(REQUEST_ID)
         ).build();
 
+    // Call preExecute first to generate credentials
     final S3AssumeRoleRequest request = new S3AssumeRoleRequest(omRequest, 
CLOCK);
-    final OMClientResponse response = 
request.validateAndUpdateCache(ozoneManager, context);
+    final OMRequest preExecutedRequest = request.preExecute(ozoneManager);
+    final S3AssumeRoleRequest requestWithCredentials = new 
S3AssumeRoleRequest(preExecutedRequest, CLOCK);
+    final OMClientResponse response = 
requestWithCredentials.validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
     assertThat(omResponse.getStatus()).isEqualTo(Status.OK);
     assertThat(omResponse.hasAssumeRoleResponse()).isTrue();
-    assertMarkForAuditCalled(request);
+    assertMarkForAuditCalled(requestWithCredentials);
   }
 
   @Test
-  public void testValidDurationMinBoundary() {
+  public void testValidDurationMinBoundary() throws IOException {
     final OMRequest omRequest = baseOmRequestBuilder()
         .setAssumeRoleRequest(
             AssumeRoleRequest.newBuilder()
@@ -213,13 +216,16 @@ public void testValidDurationMinBoundary() {
                 .setRequestId(REQUEST_ID)
         ).build();
 
+    // Call preExecute first to generate credentials
     final S3AssumeRoleRequest request = new S3AssumeRoleRequest(omRequest, 
CLOCK);
-    final OMClientResponse response = 
request.validateAndUpdateCache(ozoneManager, context);
+    final OMRequest preExecutedRequest = request.preExecute(ozoneManager);
+    final S3AssumeRoleRequest requestWithCredentials = new 
S3AssumeRoleRequest(preExecutedRequest, CLOCK);
+    final OMClientResponse response = 
requestWithCredentials.validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
     assertThat(omResponse.getStatus()).isEqualTo(Status.OK);
     assertThat(omResponse.hasAssumeRoleResponse()).isTrue();
-    assertMarkForAuditCalled(request);
+    assertMarkForAuditCalled(requestWithCredentials);
   }
 
   @Test
@@ -246,7 +252,7 @@ public void testMissingS3Authentication() {
   }
 
   @Test
-  public void testSuccessfulAssumeRoleGeneratesCredentials() {
+  public void testSuccessfulAssumeRoleGeneratesCredentials() throws 
IOException {
     final int durationSeconds = 3600;
     final OMRequest omRequest = baseOmRequestBuilder()
         .setAssumeRoleRequest(
@@ -258,7 +264,10 @@ public void testSuccessfulAssumeRoleGeneratesCredentials() 
{
         ).build();
 
     final S3AssumeRoleRequest request = new S3AssumeRoleRequest(omRequest, 
CLOCK);
-    final OMClientResponse clientResponse = 
request.validateAndUpdateCache(ozoneManager, context);
+    // Call preExecute first to generate credentials
+    final OMRequest preExecutedRequest = request.preExecute(ozoneManager);
+    final S3AssumeRoleRequest requestWithCredentials = new 
S3AssumeRoleRequest(preExecutedRequest, CLOCK);
+    final OMClientResponse clientResponse = 
requestWithCredentials.validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = clientResponse.getOMResponse();
 
     assertThat(omResponse.getStatus()).isEqualTo(Status.OK);
@@ -284,7 +293,7 @@ public void testSuccessfulAssumeRoleGeneratesCredentials() {
     // Verify expiration added durationSeconds
     final long expirationEpochSeconds = 
assumeRoleResponse.getExpirationEpochSeconds();
     
assertThat(expirationEpochSeconds).isEqualTo(CLOCK.instant().getEpochSecond() + 
durationSeconds);
-    assertMarkForAuditCalled(request);
+    assertMarkForAuditCalled(requestWithCredentials);
   }
 
   @Test
@@ -307,7 +316,7 @@ public void testGenerateSecureRandomStringUsingChars() {
   }
 
   @Test
-  public void testAssumeRoleCredentialsAreUnique() {
+  public void testAssumeRoleCredentialsAreUnique() throws IOException {
     // Test that multiple calls generate different credentials
     final OMRequest omRequest = baseOmRequestBuilder()
         .setAssumeRoleRequest(
@@ -319,9 +328,16 @@ public void testAssumeRoleCredentialsAreUnique() {
         ).build();
 
     final S3AssumeRoleRequest request1 = new S3AssumeRoleRequest(omRequest, 
CLOCK);
-    final OMClientResponse response1 = 
request1.validateAndUpdateCache(ozoneManager, context);
+    // Call preExecute first to generate credentials
+    final OMRequest preExecutedRequest1 = request1.preExecute(ozoneManager);
+    final S3AssumeRoleRequest requestWithCredentials1 = new 
S3AssumeRoleRequest(preExecutedRequest1, CLOCK);
+    final OMClientResponse response1 = 
requestWithCredentials1.validateAndUpdateCache(ozoneManager, context);
+    
     final S3AssumeRoleRequest request2 = new S3AssumeRoleRequest(omRequest, 
CLOCK);
-    final OMClientResponse response2 = 
request2.validateAndUpdateCache(ozoneManager, context);
+    // Call preExecute again to generate different credentials
+    final OMRequest preExecutedRequest2 = request2.preExecute(ozoneManager);
+    final S3AssumeRoleRequest requestWithCredentials2 = new 
S3AssumeRoleRequest(preExecutedRequest2, CLOCK);
+    final OMClientResponse response2 = 
requestWithCredentials2.validateAndUpdateCache(ozoneManager, context);
 
     final AssumeRoleResponse assumeRoleResponse1 = 
response1.getOMResponse().getAssumeRoleResponse();
     final AssumeRoleResponse assumeRoleResponse2 = 
response2.getOMResponse().getAssumeRoleResponse();
@@ -338,8 +354,8 @@ public void testAssumeRoleCredentialsAreUnique() {
     // Different assumed role IDs
     
assertThat(assumeRoleResponse1.getAssumedRoleId()).isNotEqualTo(assumeRoleResponse2.getAssumedRoleId());
 
-    OMAuditLogger.log(request1.getAuditBuilder());
-    OMAuditLogger.log(request2.getAuditBuilder());
+    OMAuditLogger.log(requestWithCredentials1.getAuditBuilder());
+    OMAuditLogger.log(requestWithCredentials2.getAuditBuilder());
     verify(auditLogger, times(2)).logWrite(any(AuditMessage.class));
   }
 
@@ -409,7 +425,7 @@ public void testInvalidRoleSessionNameTooLong() {
   }
 
   @Test
-  public void testValidRoleSessionNameMaxLengthBoundary() {
+  public void testValidRoleSessionNameMaxLengthBoundary() throws IOException {
     final String roleSessionName = S3SecurityTestUtils.repeat('g', 64);
     final OMRequest omRequest = baseOmRequestBuilder()
         .setAssumeRoleRequest(
@@ -419,17 +435,20 @@ public void testValidRoleSessionNameMaxLengthBoundary() {
                 .setRequestId(REQUEST_ID)
         ).build();
 
+    // Call preExecute first to generate credentials
     final S3AssumeRoleRequest request = new S3AssumeRoleRequest(omRequest, 
CLOCK);
-    final OMClientResponse response = 
request.validateAndUpdateCache(ozoneManager, context);
+    final OMRequest preExecutedRequest = request.preExecute(ozoneManager);
+    final S3AssumeRoleRequest requestWithCredentials = new 
S3AssumeRoleRequest(preExecutedRequest, CLOCK);
+    final OMClientResponse response = 
requestWithCredentials.validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
     assertThat(omResponse.getStatus()).isEqualTo(Status.OK);
     assertThat(omResponse.hasAssumeRoleResponse()).isTrue();
-    assertMarkForAuditCalled(request);
+    assertMarkForAuditCalled(requestWithCredentials);
   }
 
   @Test
-  public void testValidRoleSessionNameMinLengthBoundary() {
+  public void testValidRoleSessionNameMinLengthBoundary() throws IOException {
     final OMRequest omRequest = baseOmRequestBuilder()
         .setAssumeRoleRequest(
             AssumeRoleRequest.newBuilder()
@@ -438,17 +457,20 @@ public void testValidRoleSessionNameMinLengthBoundary() {
                 .setRequestId(REQUEST_ID)
         ).build();
 
+    // Call preExecute first to generate credentials
     final S3AssumeRoleRequest request = new S3AssumeRoleRequest(omRequest, 
CLOCK);
-    final OMClientResponse response = 
request.validateAndUpdateCache(ozoneManager, context);
+    final OMRequest preExecutedRequest = request.preExecute(ozoneManager);
+    final S3AssumeRoleRequest requestWithCredentials = new 
S3AssumeRoleRequest(preExecutedRequest, CLOCK);
+    final OMClientResponse response = 
requestWithCredentials.validateAndUpdateCache(ozoneManager, context);
     final OMResponse omResponse = response.getOMResponse();
 
     assertThat(omResponse.getStatus()).isEqualTo(Status.OK);
     assertThat(omResponse.hasAssumeRoleResponse()).isTrue();
-    assertMarkForAuditCalled(request);
+    assertMarkForAuditCalled(requestWithCredentials);
   }
 
   @Test
-  public void testAssumeRoleWithSessionPolicyPresent() {
+  public void testAssumeRoleWithSessionPolicyPresent() throws IOException {
     final String sessionPolicy = 
"{\"Version\":\"2012-10-17\",\"Statement\":[]}";
     final OMRequest omRequest = baseOmRequestBuilder()
         .setAssumeRoleRequest(
@@ -460,10 +482,13 @@ public void testAssumeRoleWithSessionPolicyPresent() {
                 .setRequestId(REQUEST_ID)
         ).build();
 
+    // Call preExecute first to generate credentials
     final S3AssumeRoleRequest request = new S3AssumeRoleRequest(omRequest, 
CLOCK);
-    final OMClientResponse response = 
request.validateAndUpdateCache(ozoneManager, context);
+    final OMRequest preExecutedRequest = request.preExecute(ozoneManager);
+    final S3AssumeRoleRequest requestWithCredentials = new 
S3AssumeRoleRequest(preExecutedRequest, CLOCK);
+    final OMClientResponse response = 
requestWithCredentials.validateAndUpdateCache(ozoneManager, context);
     assertThat(response.getOMResponse().getStatus()).isEqualTo(Status.OK);
-    assertMarkForAuditCalled(request);
+    assertMarkForAuditCalled(requestWithCredentials);
   }
 
   @Test


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

Reply via email to