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]