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 ae4bdb30974 HDDS-14091. [STS] Deny access if STS token is found in 
revoked table (#9445)
ae4bdb30974 is described below

commit ae4bdb30974f0ad5aa36024bbad770fab0901d35
Author: fmorg-git <[email protected]>
AuthorDate: Wed Dec 17 06:00:36 2025 -0800

    HDDS-14091. [STS] Deny access if STS token is found in revoked table (#9445)
---
 .../hadoop/ozone/om/exceptions/OMException.java    |   2 +
 .../src/main/proto/OmClientProtocol.proto          |   2 +
 .../hadoop/ozone/om/OmMetadataManagerImpl.java     |   4 +-
 .../hadoop/ozone/security/S3SecurityUtil.java      |  45 +++++
 .../hadoop/ozone/security/STSSecurityUtil.java     |  25 +++
 .../hadoop/ozone/om/TestOmMetadataManager.java     |  30 +++-
 .../hadoop/ozone/security/TestS3SecurityUtil.java  | 186 +++++++++++++++++++++
 .../hadoop/ozone/security/TestSTSSecurityUtil.java |  58 ++++++-
 .../ozone/security/TestSTSTokenIdentifier.java     |   3 +-
 9 files changed, 343 insertions(+), 12 deletions(-)

diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java
index 596eb127656..70acadefed8 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/exceptions/OMException.java
@@ -275,5 +275,7 @@ public enum ResultCodes {
     KEY_UNDER_LEASE_SOFT_LIMIT_PERIOD,
 
     TOO_MANY_SNAPSHOTS,
+
+    REVOKED_TOKEN,
   }
 }
diff --git 
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto 
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 6e36be5ca48..b24aff586ce 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -572,6 +572,8 @@ enum Status {
     KEY_UNDER_LEASE_SOFT_LIMIT_PERIOD = 97;
 
     TOO_MANY_SNAPSHOTS = 98;
+
+    REVOKED_TOKEN = 99;
 }
 
 /**
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
index b28f8bcb9d6..3439e04c063 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java
@@ -490,7 +490,9 @@ protected void initializeOmTables(CacheType cacheType,
     compactionLogTable = 
initializer.get(OMDBDefinition.COMPACTION_LOG_TABLE_DEF);
 
     // temporaryAccessKeyId -> sessionToken
-    s3RevokedStsTokenTable = 
initializer.get(OMDBDefinition.S3_REVOKED_STS_TOKEN_TABLE_DEF);
+    // FULL_CACHE keeps revocations in memory as there are not expected to be 
many revoked tokens
+    s3RevokedStsTokenTable = initializer.get(
+        OMDBDefinition.S3_REVOKED_STS_TOKEN_TABLE_DEF, CacheType.FULL_CACHE);
   }
 
   /**
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 e31f822b2fb..aeb4a2e189a 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
@@ -17,7 +17,9 @@
 
 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.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMTokenProto.Type.S3AUTHINFO;
 
 import com.google.protobuf.ServiceException;
@@ -25,7 +27,9 @@
 import java.time.ZoneOffset;
 import org.apache.hadoop.hdds.annotation.InterfaceAudience;
 import org.apache.hadoop.hdds.annotation.InterfaceStability;
+import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.io.Text;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
 import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.exceptions.OMLeaderNotReadyException;
@@ -34,6 +38,8 @@
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.S3Authentication;
 import 
org.apache.hadoop.ozone.protocolPB.OzoneManagerProtocolServerSideTranslatorPB;
 import org.apache.hadoop.security.token.SecretManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Utility class which holds methods required for parse/validation of
@@ -44,6 +50,7 @@
 public final class S3SecurityUtil {
 
   private static final Clock CLOCK = Clock.system(ZoneOffset.UTC);
+  private static final Logger LOG = 
LoggerFactory.getLogger(S3SecurityUtil.class);
 
   private S3SecurityUtil() {
   }
@@ -64,6 +71,13 @@ public static void validateS3Credential(OMRequest omRequest,
         if (!token.isEmpty()) {
           final STSTokenIdentifier stsTokenIdentifier = 
STSSecurityUtil.constructValidateAndDecryptSTSToken(
               token, ozoneManager.getSecretKeyClient(), CLOCK);
+
+          // Ensure the token is not revoked
+          if (isRevokedStsTempAccessKeyId(stsTokenIdentifier, ozoneManager)) {
+            LOG.info("Session token has been revoked: {}, {}", 
stsTokenIdentifier.getTempAccessKeyId(), token);
+            throw new OMException("STS token has been revoked", REVOKED_TOKEN);
+          }
+
           // HMAC signature and expiration were validated above.  Now validate 
AWS signature.
           validateSTSTokenAwsSignature(stsTokenIdentifier, omRequest);
           OzoneManager.setStsTokenIdentifier(stsTokenIdentifier);
@@ -124,4 +138,35 @@ private static void 
validateSTSTokenAwsSignature(STSTokenIdentifier stsTokenIden
     throw new OMException(
         "STS token validation failed for token: " + 
omRequest.getS3Authentication().getSessionToken(), INVALID_TOKEN);
   }
+
+  /**
+   * Returns true if the STS token's temporary access key ID is present in the 
revoked STS token table.
+   */
+  private static boolean isRevokedStsTempAccessKeyId(STSTokenIdentifier 
stsTokenIdentifier, OzoneManager ozoneManager)
+      throws OMException {
+    try {
+      final OMMetadataManager metadataManager = 
ozoneManager.getMetadataManager();
+      if (metadataManager == null) {
+        final String msg = "Could not determine STS revocation: 
metadataManager is null";
+        LOG.warn(msg);
+        throw new OMException(msg, INTERNAL_ERROR);
+      }
+
+      final Table<String, String> revokedStsTokenTable = 
metadataManager.getS3RevokedStsTokenTable();
+      if (revokedStsTokenTable == null) {
+        final String msg = "Could not determine STS revocation: 
revokedStsTokenTable is null";
+        LOG.warn(msg);
+        throw new OMException(msg, INTERNAL_ERROR);
+      }
+
+      // When the STSTokenIdentifier is validated, it ensures the temp access 
key id is not null/empty
+      final String tempAccessKeyId = stsTokenIdentifier.getTempAccessKeyId();
+
+      return revokedStsTokenTable.getIfExist(tempAccessKeyId) != null;
+    } catch (Exception e) {
+      final String msg = "Could not determine STS revocation because of 
Exception: " + e.getMessage();
+      LOG.warn(msg, e);
+      throw new OMException(msg, e, INTERNAL_ERROR);
+    }
+  }
 }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSSecurityUtil.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSSecurityUtil.java
index c3fb14d24b1..44d8b63b973 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSSecurityUtil.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSSecurityUtil.java
@@ -19,10 +19,12 @@
 
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_TOKEN;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.protobuf.InvalidProtocolBufferException;
 import java.io.IOException;
 import java.time.Clock;
 import java.util.UUID;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.hdds.annotation.InterfaceAudience;
 import org.apache.hadoop.hdds.annotation.InterfaceStability;
 import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
@@ -102,6 +104,9 @@ private static STSTokenIdentifier 
verifyAndDecryptToken(Token<STSTokenIdentifier
       throw new SecretManager.InvalidToken("Invalid STS token - could not 
readFromByteArray: " + e.getMessage());
     }
 
+    // Ensure essential fields are present in the token
+    ensureEssentialFieldsArePresentInToken(tokenId);
+
     // Check expiration
     if (tokenId.isExpired(clock.instant())) {
       throw new SecretManager.InvalidToken("Invalid STS token - token expired 
at " + tokenId.getExpiry());
@@ -149,5 +154,25 @@ private static Token<STSTokenIdentifier> 
decodeTokenFromString(String encodedTok
       throw new SecretManager.InvalidToken("Failed to decode STS token string: 
" + e);
     }
   }
+
+  @VisibleForTesting
+  static void ensureEssentialFieldsArePresentInToken(STSTokenIdentifier 
stsTokenIdentifier)
+      throws SecretManager.InvalidToken {
+    if (StringUtils.isEmpty(stsTokenIdentifier.getTempAccessKeyId())) {
+      throw new SecretManager.InvalidToken("Invalid STS token - 
tempAccessKeyId is null/empty");
+    }
+    if (stsTokenIdentifier.getExpiry() == null) {
+      throw new SecretManager.InvalidToken("Invalid STS token - expiry is 
null");
+    }
+    if (StringUtils.isEmpty(stsTokenIdentifier.getRoleArn())) {
+      throw new SecretManager.InvalidToken("Invalid STS token - roleArn is 
null/empty");
+    }
+    if (StringUtils.isEmpty(stsTokenIdentifier.getOriginalAccessKeyId())) {
+      throw new SecretManager.InvalidToken("Invalid STS token - 
originalAccessKeyId is null/empty");
+    }
+    if (StringUtils.isEmpty(stsTokenIdentifier.getSecretAccessKey())) {
+      throw new SecretManager.InvalidToken("Invalid STS token - 
secretAccessKey is null/empty");
+    }
+  }
 }
 
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmMetadataManager.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmMetadataManager.java
index 6f37afd0674..3541d303b24 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmMetadataManager.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmMetadataManager.java
@@ -79,6 +79,7 @@
 import org.apache.hadoop.hdds.protocol.StorageType;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos;
 import org.apache.hadoop.hdds.utils.TransactionInfo;
+import org.apache.hadoop.hdds.utils.db.Table;
 import org.apache.hadoop.hdds.utils.db.cache.CacheKey;
 import org.apache.hadoop.hdds.utils.db.cache.CacheValue;
 import org.apache.hadoop.ozone.om.codec.OMDBDefinition;
@@ -1303,18 +1304,29 @@ public void testS3RevokedStsTokenTablePutAndGet() 
throws Exception {
     final String tempAccessKeyId2 = "ASIA904E65QIGL9ON305";
     final String sessionToken2 = "test-session-token-2";
 
-    omMetadataManager.getS3RevokedStsTokenTable()
-        .put(tempAccessKeyId1, sessionToken1);
-    omMetadataManager.getS3RevokedStsTokenTable()
-        .put(tempAccessKeyId2, sessionToken2);
+    final Table<String, String> table = 
omMetadataManager.getS3RevokedStsTokenTable();
+
+    // This table is configured as FULL_CACHE in OmMetadataManagerImpl.
+    // A put() writes to RocksDB but does not update the table cache, so get() 
and getIfExist() will return null unless
+    // the cache is updated with addCacheEntry().  getSkipCache() will read 
the DB instead of the cache.
+    table.put(tempAccessKeyId1, sessionToken1);
+    table.put(tempAccessKeyId2, sessionToken2);
+
+    // Verify the values are persisted in RocksDB.
+    assertEquals(sessionToken1, table.getSkipCache(tempAccessKeyId1));
+    assertEquals(sessionToken2, table.getSkipCache(tempAccessKeyId2));
+
+    // Update cache to make get/getIfExist reflect the write for FULL_CACHE 
tables.
+    table.addCacheEntry(tempAccessKeyId1, sessionToken1, 1L);
+    table.addCacheEntry(tempAccessKeyId2, sessionToken2, 1L);
 
     // Verify get and getIfExist return the stored value
-    assertEquals(sessionToken1, 
omMetadataManager.getS3RevokedStsTokenTable().get(tempAccessKeyId1));
-    assertEquals(sessionToken1, 
omMetadataManager.getS3RevokedStsTokenTable().getIfExist(tempAccessKeyId1));
-    assertEquals(sessionToken2, 
omMetadataManager.getS3RevokedStsTokenTable().get(tempAccessKeyId2));
-    assertEquals(sessionToken2, 
omMetadataManager.getS3RevokedStsTokenTable().getIfExist(tempAccessKeyId2));
+    assertEquals(sessionToken1, table.get(tempAccessKeyId1));
+    assertEquals(sessionToken1, table.getIfExist(tempAccessKeyId1));
+    assertEquals(sessionToken2, table.get(tempAccessKeyId2));
+    assertEquals(sessionToken2, table.getIfExist(tempAccessKeyId2));
 
     // Unknown key should return null for getIfExist
-    
assertNull(omMetadataManager.getS3RevokedStsTokenTable().getIfExist("ASIA_UNKNOWN_ACCESS_KEY"));
+    assertNull(table.getIfExist("ASIA_UNKNOWN_ACCESS_KEY"));
   }
 }
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
new file mode 100644
index 00000000000..c5cce385071
--- /dev/null
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestS3SecurityUtil.java
@@ -0,0 +1,186 @@
+/*
+ * 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.
+ */
+
+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.REVOKED_TOKEN;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.time.Clock;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.hadoop.hdds.security.symmetric.SecretKeyClient;
+import org.apache.hadoop.hdds.utils.db.InMemoryTestTable;
+import org.apache.hadoop.hdds.utils.db.Table;
+import org.apache.hadoop.ozone.om.OMMetadataManager;
+import org.apache.hadoop.ozone.om.OzoneManager;
+import org.apache.hadoop.ozone.om.exceptions.OMException;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.S3Authentication;
+import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type;
+import org.apache.ozone.test.TestClock;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+/**
+ * Tests for STS revocation handling in {@link S3SecurityUtil}.
+ */
+public class TestS3SecurityUtil {
+  private static final byte[] ENCRYPTION_KEY = new byte[5];
+  private static final TestClock CLOCK = TestClock.newInstance();
+
+  {
+    ThreadLocalRandom.current().nextBytes(ENCRYPTION_KEY);
+  }
+
+  @Test
+  public void testValidateS3CredentialFailsWhenTokenRevoked() throws Exception 
{
+    // If the revoked STS token table contains an entry for the temporary 
access key id extracted from the session
+    // token, validateS3Credential should reject the request with REVOKED_TOKEN
+    final OMMetadataManager metadataManager = mock(OMMetadataManager.class);
+    final Table<String, String> revokedSTSTokenTable = new 
InMemoryTestTable<>();
+    validateS3CredentialHelper(
+        "session-token-a", metadataManager, revokedSTSTokenTable, true, 
createSTSTokenIdentifier(),
+        REVOKED_TOKEN, "STS token has been revoked");
+  }
+
+  @Test
+  public void testValidateS3CredentialWhenMetadataUnavailable() throws 
Exception {
+    // If the metadata manager is not available, throws INTERNAL_ERROR
+    validateS3CredentialHelper(
+        "session-token-b", null, null, false, createSTSTokenIdentifier(),
+        INTERNAL_ERROR, "Could not determine STS revocation: metadataManager 
is null");
+  }
+
+  @Test
+  public void testValidateS3CredentialSuccessWhenNotRevoked() throws Exception 
{
+    // Normal case: token is NOT revoked and request is accepted
+    final OMMetadataManager metadataManager = mock(OMMetadataManager.class);
+    final Table<String, String> revokedSTSTokenTable = new 
InMemoryTestTable<>();
+    validateS3CredentialHelper(
+        "session-token-c", metadataManager, revokedSTSTokenTable, false, 
createSTSTokenIdentifier(),
+        null, null);
+  }
+
+  @Test
+  public void 
testValidateS3CredentialWhenMetadataManagerAvailableButRevokedTableNull() 
throws Exception {
+    // If the revoked STS token table is not available, throws INTERNAL_ERROR
+    final OMMetadataManager metadataManager = mock(OMMetadataManager.class);
+    validateS3CredentialHelper(
+        "session-token-d", metadataManager, null, false, 
createSTSTokenIdentifier(),
+        INTERNAL_ERROR, "Could not determine STS revocation: 
revokedStsTokenTable is null");
+  }
+
+  @Test
+  public void testValidateS3CredentialWhenTableThrowsException() throws 
Exception {
+    // If the revoked STS token table lookup throws, throws INTERNAL_ERROR 
(wrapped)
+    final OMMetadataManager metadataManager = mock(OMMetadataManager.class);
+    final Table<String, String> revokedSTSTokenTable = spy(new 
InMemoryTestTable<>());
+    doThrow(new RuntimeException("lookup 
failed")).when(revokedSTSTokenTable).getIfExist(anyString());
+    validateS3CredentialHelper(
+        "session-token-g", metadataManager, revokedSTSTokenTable, false, 
createSTSTokenIdentifier(),
+        INTERNAL_ERROR, "Could not determine STS revocation because of 
Exception: lookup failed");
+  }
+
+  private void validateS3CredentialHelper(String sessionToken, 
OMMetadataManager metadataManager,
+      Table<String, String> revokedSTSTokenTable, boolean isRevoked, 
STSTokenIdentifier stsTokenIdentifier,
+      OMException.ResultCodes expectedResult, String expectedMessageContents) 
throws Exception {
+
+    try (OzoneManager ozoneManager = mock(OzoneManager.class)) {
+      when(ozoneManager.isSecurityEnabled()).thenReturn(true);
+      
when(ozoneManager.getSecretKeyClient()).thenReturn(mock(SecretKeyClient.class));
+
+      when(ozoneManager.getMetadataManager()).thenReturn(metadataManager);
+      if (metadataManager != null) {
+        
when(metadataManager.getS3RevokedStsTokenTable()).thenReturn(revokedSTSTokenTable);
+      }
+
+      final String tempAccessKeyId = "temp-access-key-id";
+      if (isRevoked) {
+        if (revokedSTSTokenTable == null) {
+          throw new IllegalArgumentException("revokedSTSTokenTable must not be 
null when isRevoked=true");
+        }
+        revokedSTSTokenTable.put(tempAccessKeyId, sessionToken);
+      }
+
+      try (MockedStatic<STSSecurityUtil> stsSecurityUtilMock = 
mockStatic(STSSecurityUtil.class, CALLS_REAL_METHODS);
+           MockedStatic<AWSV4AuthValidator> awsV4AuthValidatorMock = 
mockStatic(
+               AWSV4AuthValidator.class, CALLS_REAL_METHODS)) {
+
+        stsSecurityUtilMock.when(
+            () -> STSSecurityUtil.constructValidateAndDecryptSTSToken(
+                eq(sessionToken), any(SecretKeyClient.class), 
any(Clock.class)))
+            .thenReturn(stsTokenIdentifier);
+
+        // Mock AWS V4 signature validation
+        awsV4AuthValidatorMock.when(() -> 
AWSV4AuthValidator.validateRequest(anyString(), anyString(), anyString()))
+            .thenReturn(true);
+
+        final OMRequest omRequest = 
createRequestWithSessionToken(sessionToken);
+
+        if (expectedResult != null) {
+          final OMException omException = assertThrows(
+              OMException.class, () -> 
S3SecurityUtil.validateS3Credential(omRequest, ozoneManager));
+          assertEquals(expectedResult, omException.getResult());
+          if (expectedMessageContents != null) {
+            assertTrue(
+                omException.getMessage().contains(expectedMessageContents),
+                "Expected exception message to contain: '" + 
expectedMessageContents + "' but was: '" +
+                omException.getMessage() + "'");
+          }
+        } else {
+          assertDoesNotThrow(() -> 
S3SecurityUtil.validateS3Credential(omRequest, ozoneManager));
+        }
+      }
+    }
+  }
+
+  private STSTokenIdentifier createSTSTokenIdentifier() {
+    return new STSTokenIdentifier(
+        "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);
+  }
+
+  private static OMRequest createRequestWithSessionToken(String sessionToken) {
+    final S3Authentication s3Authentication = S3Authentication.newBuilder()
+        .setAccessId("accessKeyId")
+        .setStringToSign("string-to-sign")
+        .setSignature("signature")
+        .setSessionToken(sessionToken)
+        .build();
+
+    return OMRequest.newBuilder()
+        .setClientId(UUID.randomUUID().toString())
+        .setCmdType(Type.CreateVolume)
+        .setS3Authentication(s3Authentication)
+        .build();
+  }
+}
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSSecurityUtil.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSSecurityUtil.java
index 96c83287705..6cf19b182ee 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSSecurityUtil.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSSecurityUtil.java
@@ -28,12 +28,14 @@
 import java.time.Instant;
 import java.time.ZoneOffset;
 import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
 import org.apache.hadoop.hdds.security.exception.SCMSecurityException;
 import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
 import org.apache.hadoop.hdds.security.symmetric.SecretKeyClient;
 import org.apache.hadoop.io.Text;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMTokenProto;
+import org.apache.hadoop.security.token.SecretManager;
 import org.apache.hadoop.security.token.Token;
 import org.apache.ozone.test.TestClock;
 import org.junit.jupiter.api.Test;
@@ -48,12 +50,17 @@ public class TestSTSSecurityUtil {
   private static final String SECRET_ACCESS_KEY = "test-secret-access-key";
   private static final String SESSION_POLICY = "test-session-policy";
   private static final int DURATION_SECONDS = 3600;
+  private static final byte[] ENCRYPTION_KEY = new byte[5];
 
   private final SecretKeyTestClient secretKeyClient = new 
SecretKeyTestClient();
   private final STSTokenSecretManager tokenSecretManager = new 
STSTokenSecretManager(secretKeyClient);
   private final UUID secretKeyId = 
secretKeyClient.getCurrentSecretKey().getId();
   private final TestClock clock = new 
TestClock(Instant.ofEpochMilli(1764819000), ZoneOffset.UTC);
 
+  {
+    ThreadLocalRandom.current().nextBytes(ENCRYPTION_KEY);
+  }
+
   @Test
   public void testConstructValidateAndDecryptSTSTokenInvalidProtobuf() throws 
IOException {
     // Create a token whose identifier bytes are not a valid OMTokenProto
@@ -314,5 +321,54 @@ public void 
testConstructValidateAndDecryptMultipleTokens() throws Exception {
     assertThat(result2.getOwnerId()).isEqualTo("temp-key-2");
     assertThat(result2.getOriginalAccessKeyId()).isEqualTo("orig-key-2");
   }
-}
 
+  @Test
+  public void testEnsureEssentialFieldsArePresentInTokenMissingExpiry() {
+    final STSTokenIdentifier tokenIdentifier = new STSTokenIdentifier(
+        TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN, null, 
SECRET_ACCESS_KEY, SESSION_POLICY, ENCRYPTION_KEY);
+
+    assertThatThrownBy(() -> 
STSSecurityUtil.ensureEssentialFieldsArePresentInToken(tokenIdentifier))
+        .isInstanceOf(SecretManager.InvalidToken.class)
+        .hasMessage("Invalid STS token - expiry is null");
+  }
+
+  @Test
+  public void 
testEnsureEssentialFieldsArePresentInTokenMissingTempAccessKeyId() {
+    final STSTokenIdentifier tokenIdentifier = new STSTokenIdentifier(
+        null, ORIGINAL_ACCESS_KEY, ROLE_ARN, clock.instant(), 
SECRET_ACCESS_KEY, SESSION_POLICY, ENCRYPTION_KEY);
+
+    assertThatThrownBy(() -> 
STSSecurityUtil.ensureEssentialFieldsArePresentInToken(tokenIdentifier))
+        .isInstanceOf(SecretManager.InvalidToken.class)
+        .hasMessage("Invalid STS token - tempAccessKeyId is null/empty");
+  }
+
+  @Test
+  public void testEnsureEssentialFieldsArePresentInTokenMissingRoleArn() {
+    final STSTokenIdentifier tokenIdentifier = new STSTokenIdentifier(
+        TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, null, clock.instant(), 
SECRET_ACCESS_KEY, SESSION_POLICY, ENCRYPTION_KEY);
+
+    assertThatThrownBy(() -> 
STSSecurityUtil.ensureEssentialFieldsArePresentInToken(tokenIdentifier))
+        .isInstanceOf(SecretManager.InvalidToken.class)
+        .hasMessage("Invalid STS token - roleArn is null/empty");
+  }
+
+  @Test
+  public void 
testEnsureEssentialFieldsArePresentInTokenMissingOriginalAccessKeyId() {
+    final STSTokenIdentifier tokenIdentifier = new STSTokenIdentifier(
+        TEMP_ACCESS_KEY, null, ROLE_ARN, clock.instant(), SECRET_ACCESS_KEY, 
SESSION_POLICY, ENCRYPTION_KEY);
+
+    assertThatThrownBy(() -> 
STSSecurityUtil.ensureEssentialFieldsArePresentInToken(tokenIdentifier))
+        .isInstanceOf(SecretManager.InvalidToken.class)
+        .hasMessage("Invalid STS token - originalAccessKeyId is null/empty");
+  }
+
+  @Test
+  public void 
testEnsureEssentialFieldsArePresentInTokenMissingSecretAccessKey() {
+    final STSTokenIdentifier tokenIdentifier = new STSTokenIdentifier(
+        TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN, clock.instant(), null, 
SESSION_POLICY, ENCRYPTION_KEY);
+
+    assertThatThrownBy(() -> 
STSSecurityUtil.ensureEssentialFieldsArePresentInToken(tokenIdentifier))
+        .isInstanceOf(SecretManager.InvalidToken.class)
+        .hasMessage("Invalid STS token - secretAccessKey is null/empty");
+  }
+}
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSTokenIdentifier.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSTokenIdentifier.java
index 549d473a49d..09a786faaea 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSTokenIdentifier.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSTokenIdentifier.java
@@ -28,6 +28,7 @@
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
 import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMTokenProto;
 import org.junit.jupiter.api.Test;
 
@@ -39,7 +40,7 @@ public class TestSTSTokenIdentifier {
   private static final byte[] ENCRYPTION_KEY = new byte[5];
 
   {
-    new SecureRandom().nextBytes(ENCRYPTION_KEY);
+    ThreadLocalRandom.current().nextBytes(ENCRYPTION_KEY);
   }
 
   @Test


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

Reply via email to