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]