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 596eb1276560..70acadefed8b 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 6e36be5ca48d..b24aff586cea 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 b28f8bcb9d6e..3439e04c0636 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 e31f822b2fb7..aeb4a2e189a3 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 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 c3fb14d24b16..44d8b63b973f 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 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 6f37afd0674c..3541d303b246 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 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 000000000000..c5cce385071b --- /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 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 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 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 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 stsSecurityUtilMock = mockStatic(STSSecurityUtil.class, CALLS_REAL_METHODS); + MockedStatic 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 96c832877059..6cf19b182eee 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 549d473a49d2..09a786faaea3 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