This is an automated email from the ASF dual-hosted git repository.
sodonnell 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 9d3c32fba57 HDDS-13950. [STS] Introduce STSTokenSecretManager to sign
STS tokens (#9323)
9d3c32fba57 is described below
commit 9d3c32fba57e80c36c767c8da3853ff58ba2c0a1
Author: fmorg-git <[email protected]>
AuthorDate: Wed Nov 19 12:39:30 2025 -0800
HDDS-13950. [STS] Introduce STSTokenSecretManager to sign STS tokens (#9323)
---
.../org/apache/hadoop/ozone/om/OzoneManager.java | 20 ++++
.../request/s3/security/S3AssumeRoleRequest.java | 23 ++--
.../hadoop/ozone/security/STSTokenIdentifier.java | 7 +-
.../ozone/security/STSTokenSecretManager.java | 84 +++++++++++++++
.../s3/security/TestS3AssumeRoleRequest.java | 28 ++++-
.../ozone/security/TestSTSTokenIdentifier.java | 88 ++++++++++-----
.../ozone/security/TestSTSTokenSecretManager.java | 119 +++++++++++++++++++++
7 files changed, 323 insertions(+), 46 deletions(-)
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
index 3cf263e5013..5fe753871a1 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OzoneManager.java
@@ -307,6 +307,7 @@
import org.apache.hadoop.ozone.security.OMCertificateClient;
import org.apache.hadoop.ozone.security.OzoneDelegationTokenSecretManager;
import org.apache.hadoop.ozone.security.OzoneTokenIdentifier;
+import org.apache.hadoop.ozone.security.STSTokenSecretManager;
import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLIdentityType;
import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType;
@@ -381,6 +382,7 @@ public final class OzoneManager extends
ServiceRuntimeInfoImpl
private final ReconfigurationHandler reconfigurationHandler;
private OzoneDelegationTokenSecretManager delegationTokenMgr;
+ private STSTokenSecretManager stsTokenSecretManager;
private OzoneBlockTokenSecretManager blockTokenMgr;
private CertificateClient certClient;
private SecretKeyClient secretKeyClient;
@@ -965,6 +967,7 @@ private void instantiateServices(boolean withNewSnapshot)
throws IOException {
if (secConfig.isSecurityEnabled() || testSecureOmFlag) {
try {
delegationTokenMgr = createDelegationTokenSecretManager(configuration);
+ stsTokenSecretManager = createSTSTokenSecretManager();
} catch (IllegalArgumentException e) {
if (metadataManager != null) {
// to avoid the unit test leak report failure
@@ -1240,6 +1243,10 @@ private OzoneBlockTokenSecretManager
createBlockTokenSecretManager() {
return new OzoneBlockTokenSecretManager(expiryTime, secretKeyClient);
}
+ private STSTokenSecretManager createSTSTokenSecretManager() {
+ return new STSTokenSecretManager(secretKeyClient);
+ }
+
private void stopSecretManager() {
if (secretKeyClient != null) {
LOG.info("Stopping secret key client.");
@@ -1254,6 +1261,12 @@ private void stopSecretManager() {
LOG.error("Failed to stop delegation token manager", e);
}
}
+
+ if (stsTokenSecretManager != null) {
+ // STS token secret manager doesn't need explicit stop method
+ // as it uses the shared secret key client
+ LOG.info("Stopping OM STS token secret manager.");
+ }
}
@Override
@@ -1332,6 +1345,9 @@ public void setSecretKeyClient(SecretKeyClient
secretKeyClient) {
if (delegationTokenMgr != null) {
delegationTokenMgr.setSecretKeyClient(secretKeyClient);
}
+ if (stsTokenSecretManager != null) {
+ stsTokenSecretManager.setSecretKeyClient(secretKeyClient);
+ }
}
/**
@@ -4502,6 +4518,10 @@ public OzoneDelegationTokenSecretManager
getDelegationTokenMgr() {
return delegationTokenMgr;
}
+ public STSTokenSecretManager getSTSTokenSecretManager() {
+ return stsTokenSecretManager;
+ }
+
/**
* Return the list of Ozone administrators in effect.
*/
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 dc31644c806..31f71204dc4 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
@@ -103,7 +103,7 @@ public OMClientResponse validateAndUpdateCache(OzoneManager
ozoneManager, Execut
final String secretAccessKey = generateSecureRandomStringUsingChars(
CHARS_FOR_SECRET_ACCESS_KEYS, CHARS_FOR_SECRET_ACCESS_KEYS_LENGTH,
STS_SECRET_ACCESS_KEY_LENGTH);
final String sessionToken = generateSessionToken(
- targetRoleName, omRequest, ozoneManager, assumeRoleRequest,
secretAccessKey);
+ targetRoleName, omRequest, ozoneManager, assumeRoleRequest,
secretAccessKey, tempAccessKeyId);
// Generate AssumedRoleId for response
final String roleId = ASSUME_ROLE_ID_PREFIX +
generateSecureRandomStringUsingChars(
@@ -159,15 +159,15 @@ private S3AssumeRoleResponse
validateRoleSessionName(String roleSessionName, OMR
* Generates session token using components from the AssumeRoleRequest.
*/
private String generateSessionToken(String targetRoleName, OMRequest
omRequest,
- OzoneManager ozoneManager, AssumeRoleRequest assumeRoleRequest, String
secretAccessKey) throws IOException {
+ OzoneManager ozoneManager, AssumeRoleRequest assumeRoleRequest, String
secretAccessKey,
+ String tempAccessKeyId) throws IOException {
InetAddress remoteIp = ProtobufRpcEngine.Server.getRemoteIp();
if (remoteIp == null) {
remoteIp = ozoneManager.getOmRpcServerAddr().getAddress();
}
- final String hostName = remoteIp != null ? remoteIp.getHostName() :
- ozoneManager.getOmRpcServerAddr().getHostName();
+ final String hostName = remoteIp != null ? remoteIp.getHostName() :
ozoneManager.getOmRpcServerAddr().getHostName();
// Determine the caller's access key ID - this will be referred to as the
original
// access key id. When STS tokens are used, the tokens will be authorized
as
@@ -183,18 +183,9 @@ private String generateSessionToken(String targetRoleName,
OMRequest omRequest,
ozoneManager, originalAccessKeyId,
assumeRoleRequest.getAwsIamSessionPolicy(), hostName, remoteIp, ugi,
targetRoleName);
- // TODO sts - generate a real STS token in a future PR that incorporates
the components above
- final StringBuilder builder = new StringBuilder();
- builder.append(originalAccessKeyId);
- builder.append(':');
- builder.append(roleArn);
- builder.append(':');
- builder.append(assumeRoleRequest.getDurationSeconds());
- builder.append(':');
- builder.append(secretAccessKey);
- builder.append(':');
- builder.append(sessionPolicy);
- return builder.toString();
+ return ozoneManager.getSTSTokenSecretManager().createSTSTokenString(
+ tempAccessKeyId, originalAccessKeyId, roleArn,
assumeRoleRequest.getDurationSeconds(), secretAccessKey,
+ sessionPolicy);
}
/**
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSTokenIdentifier.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSTokenIdentifier.java
index 071d7d21a9a..1f2e8d300ae 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSTokenIdentifier.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSTokenIdentifier.java
@@ -46,6 +46,9 @@ public class STSTokenIdentifier extends
ShortLivedTokenIdentifier {
private String secretAccessKey;
private String sessionPolicy;
+ // Encryption key derived from ManagedSecretKey for this token
+ private transient byte[] encryptionKey;
+
// Service name for STS tokens
public static final String STS_SERVICE = "STS";
@@ -66,14 +69,16 @@ public STSTokenIdentifier() {
* @param secretAccessKey the secret access key associated with the
temporary access key ID
* @param sessionPolicy an optional opaque identifier that further
limits the scope of
* the permissions granted by the role
+ * @param encryptionKey the key bytes for encrypting sensitive fields
*/
public STSTokenIdentifier(String tempAccessKeyId, String
originalAccessKeyId, String roleArn, Instant expiry,
- String secretAccessKey, String sessionPolicy) {
+ String secretAccessKey, String sessionPolicy, byte[] encryptionKey) {
super(tempAccessKeyId, expiry);
this.originalAccessKeyId = originalAccessKeyId;
this.roleArn = roleArn;
this.secretAccessKey = secretAccessKey;
this.sessionPolicy = sessionPolicy;
+ this.encryptionKey = encryptionKey != null ? encryptionKey.clone() : null;
}
@Override
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSTokenSecretManager.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSTokenSecretManager.java
new file mode 100644
index 00000000000..b418beea4c3
--- /dev/null
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSTokenSecretManager.java
@@ -0,0 +1,84 @@
+/*
+ * 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 java.io.IOException;
+import java.time.Instant;
+import org.apache.hadoop.hdds.annotation.InterfaceAudience;
+import org.apache.hadoop.hdds.annotation.InterfaceStability;
+import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
+import org.apache.hadoop.hdds.security.symmetric.SecretKeySignerClient;
+import org.apache.hadoop.hdds.security.token.ShortLivedTokenSecretManager;
+import org.apache.hadoop.security.token.Token;
+
+/**
+ * Secret manager for STS (Security Token Service) tokens.
+ * This class extends ShortLivedTokenSecretManager to make use of
functionality such as signing tokens, etc.
+ */
[email protected]
[email protected]
+public class STSTokenSecretManager extends
ShortLivedTokenSecretManager<STSTokenIdentifier> {
+
+ private static final long TOKEN_MAX_LIFETIME = 43200 * 1000L; // 12 hours in
milliseconds
+
+ // Store reference to secret key client for encryption key access
+ private final SecretKeySignerClient secretKeyClient;
+
+ /**
+ * Create a new STS token secret manager.
+ *
+ * @param secretKeyClient client for accessing secret keys from SCM
+ */
+ public STSTokenSecretManager(SecretKeySignerClient secretKeyClient) {
+ super(TOKEN_MAX_LIFETIME, secretKeyClient);
+ this.secretKeyClient = secretKeyClient;
+ }
+
+ /**
+ * Create an STS token and return it as an encoded string.
+ *
+ * @param tempAccessKeyId the temporary access key ID
+ * @param originalAccessKeyId the original long-lived access key ID
+ * @param roleArn the ARN of the assumed role
+ * @param durationSeconds how long the token should be valid for
+ * @param secretAccessKey the secret access key associated with the
temporary access key ID
+ * @param sessionPolicy an optional opaque identifier that further
limits the scope of
+ * the permissions granted by the role
+ * @return base64 encoded token string
+ */
+ public String createSTSTokenString(String tempAccessKeyId, String
originalAccessKeyId, String roleArn,
+ int durationSeconds, String secretAccessKey, String sessionPolicy)
throws IOException {
+ final Instant expiration = Instant.now().plusSeconds(durationSeconds);
+
+ // Get the current secret key for encryption
+ final ManagedSecretKey currentSecretKey =
secretKeyClient.getCurrentSecretKey();
+ final byte[] encryptionKey = currentSecretKey.getSecretKey().getEncoded();
+
+ // Note - the encryptionKey will NOT be encoded in the token. When
generateToken() is called, it eventually calls
+ // the write() method in STSTokenIdentifier which calls toProtoBuf(), and
the encryptionKey is not
+ // serialized there.
+ // TODO sts - use the encryptionKey in a future PR to encrypt/decrypt the
secretAccessKey
+ final STSTokenIdentifier identifier = new STSTokenIdentifier(
+ tempAccessKeyId, originalAccessKeyId, roleArn, expiration,
secretAccessKey, sessionPolicy, encryptionKey);
+
+ final Token<STSTokenIdentifier> token = generateToken(identifier);
+ return token.encodeToUrlString();
+ }
+}
+
+
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 9a826393a5f..0940bbc5545 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
@@ -18,12 +18,20 @@
package org.apache.hadoop.ozone.om.request.s3.security;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import java.io.IOException;
import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
import java.time.Instant;
+import java.util.UUID;
import java.util.regex.Pattern;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
+import org.apache.hadoop.hdds.security.symmetric.SecretKeySignerClient;
import org.apache.hadoop.ozone.om.OzoneManager;
import org.apache.hadoop.ozone.om.execution.flowcontrol.ExecutionContext;
import org.apache.hadoop.ozone.om.response.OMClientResponse;
@@ -34,6 +42,8 @@
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.S3Authentication;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Status;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type;
+import org.apache.hadoop.ozone.security.STSTokenSecretManager;
+import org.apache.hadoop.security.token.TokenIdentifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -50,10 +60,26 @@ public class TestS3AssumeRoleRequest {
private ExecutionContext context;
@BeforeEach
- public void setup() {
+ public void setup() throws IOException {
ozoneManager = mock(OzoneManager.class);
+
+ final SecretKeySignerClient secretKeyClient =
mock(SecretKeySignerClient.class);
+ final ManagedSecretKey managedSecretKey = mock(ManagedSecretKey.class);
+ final SecretKey secretKey = new SecretKeySpec(
+ "testSecretKey".getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+ final UUID secretKeyId = UUID.randomUUID();
+
+ when(secretKeyClient.getCurrentSecretKey()).thenReturn(managedSecretKey);
+ when(managedSecretKey.getSecretKey()).thenReturn(secretKey);
+ when(managedSecretKey.getId()).thenReturn(secretKeyId);
+ when(managedSecretKey.sign(any(TokenIdentifier.class))).thenReturn(
+ "signature".getBytes(StandardCharsets.UTF_8));
+
+ final STSTokenSecretManager stsTokenSecretManager = new
STSTokenSecretManager(secretKeyClient);
+
when(ozoneManager.getOmRpcServerAddr()).thenReturn(
new InetSocketAddress("localhost", 9876));
+
when(ozoneManager.getSTSTokenSecretManager()).thenReturn(stsTokenSecretManager);
context = ExecutionContext.of(1L, null);
}
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 37909703a7f..ada9c756104 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
@@ -24,6 +24,7 @@
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
+import java.security.SecureRandom;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.UUID;
@@ -35,11 +36,17 @@
*/
public class TestSTSTokenIdentifier {
+ private static final byte[] ENCRYPTION_KEY = new byte[5];
+
+ {
+ new SecureRandom().nextBytes(ENCRYPTION_KEY);
+ }
+
@Test
public void testKindAndService() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn",
- Instant.now().plusSeconds(3600), "secretAccessKey", "sessionPolicy");
+ Instant.now().plusSeconds(3600), "secretAccessKey", "sessionPolicy",
ENCRYPTION_KEY);
assertEquals("STSToken", stsTokenIdentifier.getKind().toString());
assertEquals("STS", stsTokenIdentifier.getService());
@@ -53,7 +60,7 @@ public void testProtoBufRoundTrip() throws IOException {
final Instant expiry =
Instant.now().plusSeconds(7200).truncatedTo(ChronoUnit.MILLIS);
final STSTokenIdentifier originalTokenIdentifier = new STSTokenIdentifier(
"tempAccess", "origAccess", "arn:aws:iam::123456789012:role/RoleY",
- expiry, "secretKey", "sessionPolicy");
+ expiry, "secretKey", "sessionPolicy", ENCRYPTION_KEY);
final UUID secretKeyId = UUID.randomUUID();
originalTokenIdentifier.setSecretKeyId(secretKeyId);
@@ -92,7 +99,7 @@ public void testFromProtoBufInvalidSecretKeyId() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", Instant.now(),
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
final IOException ex = assertThrows(IOException.class, () ->
stsTokenIdentifier.fromProtoBuf(invalid));
assertThat(ex.getMessage()).isEqualTo("Invalid secretKeyId format in STS
token: not-a-uuid");
@@ -103,7 +110,7 @@ public void testProtobufRoundTripWithNullSessionPolicy()
throws IOException {
final Instant expiry = Instant.now().plusSeconds(7200);
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccess", "origAccess", "arn:aws:iam::123456789012:role/RoleX",
- expiry, "secretKey", null);
+ expiry, "secretKey", null, ENCRYPTION_KEY);
final OMTokenProto proto = stsTokenIdentifier.toProtoBuf();
assertThat(proto.getSessionPolicy()).isEmpty();
@@ -119,7 +126,7 @@ public void testProtobufRoundTripWithEmptySessionPolicy()
throws IOException {
final Instant expiry = Instant.now().plusSeconds(4000);
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccess", "origAccess", "arn:aws:iam::123456789012:role/RoleZ",
- expiry, "secretKey", "");
+ expiry, "secretKey", "", ENCRYPTION_KEY);
final OMTokenProto proto = stsTokenIdentifier.toProtoBuf();
assertThat(proto.getSessionPolicy()).isEmpty();
@@ -140,7 +147,7 @@ public void testFromProtoBufInvalidTokenType() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "origAccessKeyId", "roleArn", Instant.now(),
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
final IllegalArgumentException ex = assertThrows(
IllegalArgumentException.class, () ->
stsTokenIdentifier.fromProtoBuf(invalidType));
@@ -156,7 +163,7 @@ public void testWriteToAndReadFromByteArray() throws
Exception {
Instant.now().plusSeconds(1000).truncatedTo(ChronoUnit.MILLIS);
final STSTokenIdentifier originalTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
originalTokenIdentifier.setSecretKeyId(UUID.randomUUID());
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -182,7 +189,7 @@ public void
testWriteToAndReadFromByteArrayWithDifferentSecretKeyIds() throws Ex
final Instant expiry = Instant.now().plusSeconds(1500);
final STSTokenIdentifier originalTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
originalTokenIdentifier.setSecretKeyId(uuid1);
final ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
@@ -192,7 +199,7 @@ public void
testWriteToAndReadFromByteArrayWithDifferentSecretKeyIds() throws Ex
final STSTokenIdentifier anotherTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
anotherTokenIdentifier.setSecretKeyId(uuid2);
final ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
@@ -211,7 +218,7 @@ public void
testWriteToAndReadFromByteArrayWithSameSecretKeyIds() throws Excepti
final STSTokenIdentifier originalTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
originalTokenIdentifier.setSecretKeyId(uuid);
final ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
@@ -221,7 +228,7 @@ public void
testWriteToAndReadFromByteArrayWithSameSecretKeyIds() throws Excepti
final STSTokenIdentifier anotherTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
anotherTokenIdentifier.setSecretKeyId(uuid);
final ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
@@ -243,7 +250,7 @@ public void testGettersReturnCorrectValues() {
final String sessionPolicy = "myPolicy";
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
- tempAccessKeyId, originalAccessKeyId, roleArn, expiry,
secretAccessKey, sessionPolicy);
+ tempAccessKeyId, originalAccessKeyId, roleArn, expiry,
secretAccessKey, sessionPolicy, ENCRYPTION_KEY);
assertThat(stsTokenIdentifier.getOwnerId()).isEqualTo(tempAccessKeyId);
assertThat(stsTokenIdentifier.getTempAccessKeyId()).isEqualTo(tempAccessKeyId);
@@ -261,12 +268,12 @@ public void testEqualsAndHashCode() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
stsTokenIdentifier.setSecretKeyId(uuid);
final STSTokenIdentifier stsTokenIdentifier2 = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
stsTokenIdentifier2.setSecretKeyId(uuid);
assertThat(stsTokenIdentifier).isEqualTo(stsTokenIdentifier2);
@@ -279,11 +286,11 @@ public void testNotEqualsWhenTempAccessKeyIdDiffers() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId1", "originalAccessKeyId", "roleArn",
- expiry, "secretAccessKey", "sessionPolicy");
+ expiry, "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
final STSTokenIdentifier stsTokenIdentifier2 = new STSTokenIdentifier(
"tempAccessKeyId2", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
assertThat(stsTokenIdentifier).isNotEqualTo(stsTokenIdentifier2);
}
@@ -294,11 +301,11 @@ public void testNotEqualsWhenOriginalAccessKeyIdDiffers()
{
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId1", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
final STSTokenIdentifier stsTokenIdentifier2 = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId2", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
assertThat(stsTokenIdentifier).isNotEqualTo(stsTokenIdentifier2);
}
@@ -309,11 +316,11 @@ public void testNotEqualsWhenRoleArnDiffers() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn1", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
final STSTokenIdentifier stsTokenIdentifier2 = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn2", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
assertThat(stsTokenIdentifier).isNotEqualTo(stsTokenIdentifier2);
}
@@ -322,11 +329,11 @@ public void testNotEqualsWhenRoleArnDiffers() {
public void testNotEqualsWhenExpirationDiffers() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn",
- Instant.now().plusSeconds(3600), "secretAccessKey", "sessionPolicy");
+ Instant.now().plusSeconds(3600), "secretAccessKey", "sessionPolicy",
ENCRYPTION_KEY);
final STSTokenIdentifier stsTokenIdentifier2 = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn",
- Instant.now().plusSeconds(7600), "secretAccessKey", "sessionPolicy");
+ Instant.now().plusSeconds(7600), "secretAccessKey", "sessionPolicy",
ENCRYPTION_KEY);
assertThat(stsTokenIdentifier).isNotEqualTo(stsTokenIdentifier2);
}
@@ -337,11 +344,11 @@ public void testNotEqualsWhenSecretAccessKeyDiffers() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey1", "sessionPolicy");
+ "secretAccessKey1", "sessionPolicy", ENCRYPTION_KEY);
final STSTokenIdentifier stsTokenIdentifier2 = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey2", "sessionPolicy");
+ "secretAccessKey2", "sessionPolicy", ENCRYPTION_KEY);
assertThat(stsTokenIdentifier).isNotEqualTo(stsTokenIdentifier2);
}
@@ -352,11 +359,11 @@ public void testNotEqualsWhenSessionPolicyDiffers() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy1");
+ "secretAccessKey", "sessionPolicy1", ENCRYPTION_KEY);
final STSTokenIdentifier stsTokenIdentifier2 = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy2");
+ "secretAccessKey", "sessionPolicy2", ENCRYPTION_KEY);
assertThat(stsTokenIdentifier).isNotEqualTo(stsTokenIdentifier2);
}
@@ -368,7 +375,7 @@ public void testToString() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
stsTokenIdentifier.setSecretKeyId(uuid);
final String stsTokenIdentifierStr = stsTokenIdentifier.toString();
@@ -383,10 +390,35 @@ public void testToString() {
public void testNotEqualsWithNull() {
final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
"tempAccessKeyId", "originalAccessKeyId", "roleArn", Instant.now(),
- "secretAccessKey", "sessionPolicy");
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
assertThat(stsTokenIdentifier).isNotEqualTo(null);
}
+
+ @Test
+ public void testEqualsWithDifferentEncryptionKeys() {
+ final Instant expiry =
Instant.now().plusSeconds(3600).truncatedTo(ChronoUnit.MILLIS);
+ final UUID uuid = UUID.randomUUID();
+
+ // Create first identifier with the default key
+ final STSTokenIdentifier stsTokenIdentifier = new STSTokenIdentifier(
+ "tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
+ "secretAccessKey", "sessionPolicy", ENCRYPTION_KEY);
+ stsTokenIdentifier.setSecretKeyId(uuid);
+
+ // Create second identifier with a different encryption key but otherwise
same parameters
+ byte[] differentKey = new byte[5];
+ new SecureRandom().nextBytes(differentKey);
+
+ final STSTokenIdentifier stsTokenIdentifier2 = new STSTokenIdentifier(
+ "tempAccessKeyId", "originalAccessKeyId", "roleArn", expiry,
+ "secretAccessKey", "sessionPolicy", differentKey);
+ stsTokenIdentifier2.setSecretKeyId(uuid);
+
+ // They should still be equal because encryptionKey is transient/ignored
for identity
+ assertThat(stsTokenIdentifier).isEqualTo(stsTokenIdentifier2);
+
assertThat(stsTokenIdentifier.hashCode()).isEqualTo(stsTokenIdentifier2.hashCode());
+ }
}
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSTokenSecretManager.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSTokenSecretManager.java
new file mode 100644
index 00000000000..f35fc902460
--- /dev/null
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSTokenSecretManager.java
@@ -0,0 +1,119 @@
+/*
+ * 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.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.UUID;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.hadoop.hdds.security.symmetric.ManagedSecretKey;
+import org.apache.hadoop.hdds.security.symmetric.SecretKeySignerClient;
+import org.apache.hadoop.io.Text;
+import org.apache.hadoop.security.token.Token;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for STSTokenSecretManager.
+ */
+public class TestSTSTokenSecretManager {
+ private STSTokenSecretManager secretManager;
+ private static final String TEMP_ACCESS_KEY = "temp-access-key";
+ private static final String ORIGINAL_ACCESS_KEY = "original-access-key";
+ private static final String ROLE_ARN =
"arn:aws:iam::123456789012:role/test-role";
+ 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 SecretKey sharedSecretKey;
+
+ @BeforeAll
+ public static void setUpClass() {
+ final byte[] keyBytes =
"01234567890123456789012345678901".getBytes(StandardCharsets.US_ASCII);
+ sharedSecretKey = new SecretKeySpec(keyBytes, "HmacSHA256");
+ }
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ final SecretKeySignerClient mockSecretKeyClient =
mock(SecretKeySignerClient.class);
+ final ManagedSecretKey mockSecretKey = mock(ManagedSecretKey.class);
+
+ final UUID keyId = UUID.fromString("00000000-0000-0000-0000-000000000000");
+ when(mockSecretKey.getId()).thenReturn(keyId);
+ when(mockSecretKey.getSecretKey()).thenReturn(sharedSecretKey);
+ when(mockSecretKey.sign(any(STSTokenIdentifier.class)))
+ .thenReturn("mock-signature".getBytes(StandardCharsets.UTF_8));
+ when(mockSecretKeyClient.getCurrentSecretKey()).thenReturn(mockSecretKey);
+
+ secretManager = new STSTokenSecretManager(mockSecretKeyClient);
+ }
+
+ @Test
+ public void testCreateSTSTokenStringContainsCorrectFields() throws
IOException {
+ final Instant beforeCreation = Instant.now();
+
+ final String tokenString = secretManager.createSTSTokenString(
+ TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN, DURATION_SECONDS,
SECRET_ACCESS_KEY, SESSION_POLICY);
+
+ // Decode the token
+ final Token<STSTokenIdentifier> token = new Token<>();
+ token.decodeFromUrlString(tokenString);
+
+ // Verify the token identifier fields
+ final STSTokenIdentifier identifier = new STSTokenIdentifier();
+ identifier.readFromByteArray(token.getIdentifier());
+ final Instant afterCreation = Instant.now();
+ final Instant expiration = identifier.getExpiry();
+
+ assertEquals(TEMP_ACCESS_KEY, identifier.getTempAccessKeyId());
+ assertEquals(ORIGINAL_ACCESS_KEY, identifier.getOriginalAccessKeyId());
+ assertEquals(ROLE_ARN, identifier.getRoleArn());
+ assertEquals(SECRET_ACCESS_KEY, identifier.getSecretAccessKey());
+ assertEquals(SESSION_POLICY, identifier.getSessionPolicy());
+ assertNotNull(identifier.getSecretKeyId());
+ assertEquals(new Text("STSToken"), identifier.getKind());
+ assertEquals("STS", identifier.getService());
+ // Verify expiration is approximately durationSeconds in the future
+ assertTrue(expiration.isAfter(beforeCreation.plusSeconds(DURATION_SECONDS
- 1)));
+ assertTrue(expiration.isBefore(afterCreation.plusSeconds(DURATION_SECONDS
+ 1)));
+ }
+
+ @Test
+ public void testCreateSTSTokenStringWithNullSessionPolicy() throws
IOException {
+ final String tokenString = secretManager.createSTSTokenString(
+ TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN, DURATION_SECONDS,
SECRET_ACCESS_KEY, null);
+
+ // Decode the token
+ final Token<STSTokenIdentifier> token = new Token<>();
+ token.decodeFromUrlString(tokenString);
+
+ final STSTokenIdentifier identifier = new STSTokenIdentifier();
+ identifier.readFromByteArray(token.getIdentifier());
+ assertTrue(identifier.getSessionPolicy().isEmpty());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]