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]


Reply via email to