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 f9bec4bde8d HDDS-14711. [STS] Ensure accessKeyId is valid for 
sessionToken (#9820)
f9bec4bde8d is described below

commit f9bec4bde8ddc51126e1fd52cf9b64c518b2d65e
Author: fmorg-git <[email protected]>
AuthorDate: Wed Feb 25 18:15:11 2026 -0800

    HDDS-14711. [STS] Ensure accessKeyId is valid for sessionToken (#9820)
---
 .../hadoop/ozone/security/S3SecurityUtil.java      |  8 +++
 .../hadoop/ozone/security/TestS3SecurityUtil.java  | 62 ++++++++++++++++++----
 2 files changed, 61 insertions(+), 9 deletions(-)

diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/S3SecurityUtil.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/S3SecurityUtil.java
index 17b74bb7417..923bf9d9a78 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/S3SecurityUtil.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/S3SecurityUtil.java
@@ -86,6 +86,14 @@ public static void validateS3Credential(OMRequest omRequest,
             throw new OMException("STS token no longer valid: 
OriginalAccessKeyId principal revoked", REVOKED_TOKEN);
           }
 
+          // Ensure the access key ID in the request matches the one encoded 
in the token.
+          // This prevents using a valid token and secretKey to authenticate 
arbitrary accessKeyIds.
+          final String requestAccessId = 
omRequest.getS3Authentication().getAccessId();
+          if 
(!requestAccessId.equals(stsTokenIdentifier.getTempAccessKeyId())) {
+            throw new OMException(
+                "STS token validation failed - accessKeyId is invalid for 
session token", INVALID_TOKEN);
+          }
+
           // HMAC signature and expiration were validated above.  Now validate 
AWS signature.
           validateSTSTokenAwsSignature(stsTokenIdentifier, omRequest);
           OzoneManager.setStsTokenIdentifier(stsTokenIdentifier);
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestS3SecurityUtil.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestS3SecurityUtil.java
index d642c6e5ace..d82b338ce8f 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestS3SecurityUtil.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestS3SecurityUtil.java
@@ -18,6 +18,7 @@
 package org.apache.hadoop.ozone.security;
 
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INTERNAL_ERROR;
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_TOKEN;
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.REVOKED_TOKEN;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -57,6 +58,7 @@
 public class TestS3SecurityUtil {
   private static final byte[] ENCRYPTION_KEY = new byte[5];
   private static final TestClock CLOCK = TestClock.newInstance();
+  private static final String TEMP_ACCESS_KEY_ID = "temp-access-key-id";
 
   {
     ThreadLocalRandom.current().nextBytes(ENCRYPTION_KEY);
@@ -132,6 +134,33 @@ public void 
testValidateS3CredentialFailsWhenOriginalAccessKeyIdCheckThrows() th
             .setExpectedMessage("Could not determine if original principal is 
revoked"));
   }
 
+  @Test
+  public void 
testValidateS3CredentialFailsWhenRequestAccessIdDoesNotMatchTokenOwner() throws 
Exception {
+    validateS3CredentialHelper(
+        new TestConfig()
+            .setRequestAccessId("some-other-access-id")
+            .setExpectedResult(INVALID_TOKEN)
+            .setExpectedMessage("STS token validation failed - accessKeyId is 
invalid for session token"));
+  }
+
+  @Test
+  public void testValidateS3CredentialFailsWhenRequestAccessIdMissing() throws 
Exception {
+    validateS3CredentialHelper(
+        new TestConfig()
+            .setIncludeAccessId(false)
+            .setExpectedResult(INVALID_TOKEN)
+            .setExpectedMessage("STS token validation failed - accessKeyId is 
invalid for session token"));
+  }
+
+  @Test
+  public void testValidateS3CredentialFailsWhenRequestAccessIdEmpty() throws 
Exception {
+    validateS3CredentialHelper(
+        new TestConfig()
+            .setRequestAccessId("")
+            .setExpectedResult(INVALID_TOKEN)
+            .setExpectedMessage("STS token validation failed - accessKeyId is 
invalid for session token"));
+  }
+
   private void validateS3CredentialHelper(TestConfig config) throws Exception {
     try (OzoneManager ozoneManager = mock(OzoneManager.class)) {
       when(ozoneManager.isSecurityEnabled()).thenReturn(true);
@@ -178,7 +207,8 @@ private void validateS3CredentialHelper(TestConfig config) 
throws Exception {
         awsV4AuthValidatorMock.when(() -> 
AWSV4AuthValidator.validateRequest(anyString(), anyString(), anyString()))
             .thenReturn(true);
 
-        final OMRequest omRequest = 
createRequestWithSessionToken(sessionToken);
+        final OMRequest omRequest = createRequestWithSessionToken(
+            config.requestAccessId, config.includeAccessId);
 
         if (config.expectedResult != null) {
           final OMException omException = assertThrows(
@@ -199,19 +229,20 @@ private void validateS3CredentialHelper(TestConfig 
config) throws Exception {
 
   private STSTokenIdentifier createSTSTokenIdentifier() {
     return new STSTokenIdentifier(
-        "temp-access-key-id", "original-access-key-id", 
"arn:aws:iam::123456789012:role/test-role",
+        TEMP_ACCESS_KEY_ID, "original-access-key-id", 
"arn:aws:iam::123456789012:role/test-role",
         CLOCK.instant().plusSeconds(3600), "secret-access-key", 
"session-policy",
         ENCRYPTION_KEY);
   }
 
-  @SuppressWarnings("SameParameterValue")
-  private static OMRequest createRequestWithSessionToken(String sessionToken) {
-    final S3Authentication s3Authentication = S3Authentication.newBuilder()
-        .setAccessId("accessKeyId")
+  private static OMRequest createRequestWithSessionToken(String accessId, 
boolean includeAccessId) {
+    final S3Authentication.Builder s3AuthenticationBuilder = 
S3Authentication.newBuilder()
         .setStringToSign("string-to-sign")
         .setSignature("signature")
-        .setSessionToken(sessionToken)
-        .build();
+        .setSessionToken("session-token");
+    if (includeAccessId) {
+      s3AuthenticationBuilder.setAccessId(accessId);
+    }
+    final S3Authentication s3Authentication = s3AuthenticationBuilder.build();
 
     return OMRequest.newBuilder()
         .setClientId(UUID.randomUUID().toString())
@@ -223,12 +254,14 @@ private static OMRequest 
createRequestWithSessionToken(String sessionToken) {
   /**
    * Helper class to create various scenarios for testing.
    */
-  private static class TestConfig {
+  private static final class TestConfig {
     private OMMetadataManager metadataManager = mock(OMMetadataManager.class);
     private Table<String, Long> revokedSTSTokenTable = new 
InMemoryTestTable<>();
     private boolean isTokenRevoked = false;
     private boolean isOriginalAccessKeyIdRevoked = false;
     private boolean shouldOriginalAccessKeyIdCheckThrowError = false;
+    private String requestAccessId = TEMP_ACCESS_KEY_ID;
+    private boolean includeAccessId = true;
     private OMException.ResultCodes expectedResult = null;
     private String expectedMessage = null;
 
@@ -261,6 +294,17 @@ TestConfig 
setShouldOriginalAccessKeyIdCheckThrowError(boolean isError) {
       return this;
     }
 
+    TestConfig setRequestAccessId(String requestAccessId) {
+      this.requestAccessId = requestAccessId;
+      return this;
+    }
+
+    @SuppressWarnings("SameParameterValue")
+    TestConfig setIncludeAccessId(boolean includeAccessId) {
+      this.includeAccessId = includeAccessId;
+      return this;
+    }
+
     TestConfig setExpectedResult(OMException.ResultCodes result) {
       this.expectedResult = result;
       return this;


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

Reply via email to