This is an automated email from the ASF dual-hosted git repository.

ChenSammi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 54c21025222 HDDS-16583. STS credential validation updates (#11305)
54c21025222 is described below

commit 54c2102522278c26ede7912c3a0328369c2275e9
Author: fmorg-git <[email protected]>
AuthorDate: Wed Sep 23 23:28:21 2026 -0700

    HDDS-16583. STS credential validation updates (#11305)
---
 .../src/main/proto/OmClientProtocol.proto          |   3 +
 .../ozone/om/ratis/OzoneManagerStateMachine.java   |  22 +--
 .../hadoop/ozone/om/request/OMClientRequest.java   |  45 +------
 .../hadoop/ozone/security/S3SecurityUtil.java      |   7 +
 .../hadoop/ozone/security/STSSecurityUtil.java     | 149 ++++++++++++++++++---
 .../om/ratis/TestOzoneManagerStateMachine.java     |  79 +++++++++--
 .../request/TestOMClientRequestWithUserInfo.java   |   8 ++
 .../hadoop/ozone/security/TestS3SecurityUtil.java  |  55 ++++++++
 .../hadoop/ozone/security/TestSTSSecurityUtil.java | 110 ++++++++++++++-
 9 files changed, 378 insertions(+), 100 deletions(-)

diff --git 
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto 
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index bec4a40810f..75aedd475ea 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -2435,6 +2435,9 @@ message S3Authentication {
     // S3 action without the s3: prefix for this request (e.g. GetObject), set 
by S3 Gateway for use
     // in finer-grained STS permissions.
     optional string s3Action = 10;
+    // Resolved so GetCallerIdentity can return the assumed-role identity from 
the session token.
+    optional string resolvedStsAssumedRoleId = 11;
+    optional string resolvedStsAssumedRoleUserArn = 12;
 }
 
 message RecoverLeaseRequest {
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerStateMachine.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerStateMachine.java
index c80a7977a1f..8353df01fb1 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerStateMachine.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerStateMachine.java
@@ -24,7 +24,6 @@
 import com.google.common.base.Preconditions;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import java.io.IOException;
-import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.LinkedHashMap;
@@ -711,24 +710,9 @@ OMResponse runCommand(OMRequest request, TermIndex 
termIndex) {
         OzoneManager.setS3Auth(s3Auth);
         isS3AuthThreadLocalSet = true;
 
-        if (s3Auth.hasSessionToken() && !s3Auth.getSessionToken().isEmpty()) {
-          // ThreadLocal carries session policy for OmMetadataReader
-          // Use Instant.MAX for creationTime so a future revocation check on 
this ThreadLocal
-          // identifier never treats the token as issued before a stored 
cutoff.
-          final STSTokenIdentifier rehydratedTokenIdentifier = new 
STSTokenIdentifier(
-              STSTokenIdentifier.Params.newBuilder()
-                  .setTempAccessKeyId(
-                      s3Auth.hasResolvedStsTempAccessKeyId() ? 
s3Auth.getResolvedStsTempAccessKeyId() : "")
-                  .setOriginalAccessKeyId(
-                      s3Auth.hasResolvedStsOriginalAccessKeyId() ? 
s3Auth.getResolvedStsOriginalAccessKeyId() : "")
-                  .setRoleArn(s3Auth.hasResolvedStsRoleArn() ? 
s3Auth.getResolvedStsRoleArn() : "")
-                  .setCreationTime(Instant.MAX)
-                  .setExpiry(Instant.MAX) // ensure it deterministically is 
not expired
-                  .setSecretAccessKey(null) // no secretAccessKey needed
-                  .setSessionPolicy(
-                      s3Auth.hasResolvedStsSessionPolicy() ? 
s3Auth.getResolvedStsSessionPolicy() : "")
-                  .setManagedSecretKey(null) // no ManagedSecretKey needed
-                  .build());
+        // ThreadLocal carries session policy for OmMetadataReader
+        final STSTokenIdentifier rehydratedTokenIdentifier = 
STSSecurityUtil.rehydrateStsTokenIdentifier(s3Auth);
+        if (rehydratedTokenIdentifier != null) {
           OzoneManager.setStsTokenIdentifier(rehydratedTokenIdentifier);
           isStsThreadLocalSet = true;
         }
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/OMClientRequest.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/OMClientRequest.java
index a3da6ab7e6e..b6cca710d78 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/OMClientRequest.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/OMClientRequest.java
@@ -27,7 +27,6 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Objects;
-import java.util.UUID;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.hdds.utils.TransactionInfo;
 import org.apache.hadoop.ipc_.ProtobufRpcEngine;
@@ -56,7 +55,7 @@
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.LayoutVersion;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMResponse;
-import org.apache.hadoop.ozone.security.STSTokenIdentifier;
+import org.apache.hadoop.ozone.security.STSSecurityUtil;
 import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer;
 import org.apache.hadoop.ozone.security.acl.OzoneObj;
 import org.apache.hadoop.ozone.security.acl.OzoneObjInfo;
@@ -129,52 +128,14 @@ public OMRequest preExecute(OzoneManager ozoneManager)
     }
 
     if (requestBuilder.hasS3Authentication()) {
-      final OzoneManagerProtocolProtos.S3Authentication s3Auth = 
requestBuilder.getS3Authentication();
-      final boolean hasSessionToken = s3Auth.hasSessionToken() && 
!s3Auth.getSessionToken().isEmpty();
-      final STSTokenIdentifier stsTokenIdentifier = 
OzoneManager.getStsTokenIdentifier();
-
-      // This should not happen, so explicitly throw an error.  An existing 
sessionToken
-      // implies prior STS validation must have populated the ThreadLocal.
-      if (ozoneManager.isSecurityEnabled() && hasSessionToken && 
stsTokenIdentifier == null) {
-        throw new OMException(
-            "S3Authentication has session token but no STS token identifier in 
OzoneManager ThreadLocal",
-            OMException.ResultCodes.INVALID_REQUEST);
-      }
-
-      requestBuilder.setS3Authentication(resolveS3Authentication(s3Auth, 
stsTokenIdentifier));
+      requestBuilder.setS3Authentication(
+          
STSSecurityUtil.resolveS3Authentication(requestBuilder.getS3Authentication(), 
ozoneManager));
     }
 
     omRequest = requestBuilder.build();
     return omRequest;
   }
 
-  private static OzoneManagerProtocolProtos.S3Authentication 
resolveS3Authentication(
-      OzoneManagerProtocolProtos.S3Authentication s3Auth, STSTokenIdentifier 
stsTokenIdentifier) {
-    final OzoneManagerProtocolProtos.S3Authentication.Builder s3AuthBuilder = 
s3Auth.toBuilder();
-
-    if (s3Auth.hasSessionToken() && !s3Auth.getSessionToken().isEmpty() && 
stsTokenIdentifier != null) {
-      s3AuthBuilder.setResolvedStsSessionPolicy(
-          StringUtils.defaultString(stsTokenIdentifier.getSessionPolicy()));
-      s3AuthBuilder.setResolvedStsRoleArn(
-          StringUtils.defaultString(stsTokenIdentifier.getRoleArn()));
-      s3AuthBuilder.setResolvedStsOriginalAccessKeyId(
-          
StringUtils.defaultString(stsTokenIdentifier.getOriginalAccessKeyId()));
-      s3AuthBuilder.setResolvedStsTempAccessKeyId(
-          StringUtils.defaultString(stsTokenIdentifier.getTempAccessKeyId()));
-      final UUID secretKeyId = stsTokenIdentifier.getSecretKeyId();
-      s3AuthBuilder.setResolvedStsSecretKeyId(
-          secretKeyId != null ? secretKeyId.toString() : "");
-    } else {
-      s3AuthBuilder.clearResolvedStsSessionPolicy();
-      s3AuthBuilder.clearResolvedStsRoleArn();
-      s3AuthBuilder.clearResolvedStsOriginalAccessKeyId();
-      s3AuthBuilder.clearResolvedStsTempAccessKeyId();
-      s3AuthBuilder.clearResolvedStsSecretKeyId();
-    }
-
-    return s3AuthBuilder.build();
-  }
-
   /**
    * Performs any request specific failure handling during request
    * submission. An example of this would be an undo of any steps
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 6612fff2bad..883e9b38edf 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
@@ -36,6 +36,7 @@
 import org.apache.hadoop.ozone.om.exceptions.OMException;
 import org.apache.hadoop.ozone.om.exceptions.OMLeaderNotReadyException;
 import org.apache.hadoop.ozone.om.exceptions.OMNotLeaderException;
+import org.apache.hadoop.ozone.om.ratis.utils.OzoneManagerRatisUtils;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.S3Authentication;
 import 
org.apache.hadoop.ozone.protocolPB.OzoneManagerProtocolServerSideTranslatorPB;
@@ -71,6 +72,12 @@ public static void validateS3Credential(OMRequest omRequest,
       if (omRequest.getS3Authentication().hasSessionToken()) {
         final String token = omRequest.getS3Authentication().getSessionToken();
         if (!token.isEmpty()) {
+          // The revocation checks below read this OM's local metadata, and 
OzoneManagerProtocolServerSideTranslatorPB
+          // skips its own leader check for any request carrying S3 
authentication. A follower can be arbitrarily
+          // behind the leader, so it may not yet know that a token, or the 
principal that created it, was revoked.
+          // The non-STS path below gets this check from 
OzoneDelegationTokenSecretManager.retrievePassword.
+          OzoneManagerRatisUtils.checkLeaderStatus(ozoneManager);
+
           final STSTokenIdentifier stsTokenIdentifier = 
STSSecurityUtil.constructValidateAndDecryptSTSToken(
               token, ozoneManager.getSecretKeyClient(), CLOCK);
 
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 8862f16d235..5f4c23e37cc 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
@@ -17,6 +17,7 @@
 
 package org.apache.hadoop.ozone.security;
 
+import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_REQUEST;
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_TOKEN;
 import static 
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.TOKEN_EXPIRED;
 
@@ -24,14 +25,16 @@
 import com.google.protobuf.InvalidProtocolBufferException;
 import java.io.IOException;
 import java.time.Clock;
+import java.time.Instant;
 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;
 import org.apache.hadoop.hdds.security.symmetric.SecretKeyClient;
+import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
-import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMTokenProto;
 import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.S3Authentication;
 import org.apache.hadoop.security.token.SecretManager;
@@ -97,11 +100,18 @@ private static STSTokenIdentifier 
verifyAndDecryptToken(Token<STSTokenIdentifier
       throw new SecretManager.InvalidToken("Invalid STS token - secretKeyId 
was not valid: " + proto.getSecretKeyId());
     }
 
+    final ManagedSecretKey secretKey = getValidatedSecretKey(secretKeyId, 
secretKeyClient);
+
+    // Verify the signature over the identifier bytes before anything is 
decrypted or parsed out of them, so
+    // that only bytes this cluster actually issued are ever fed to the 
cipher. Keeping this first also means a
+    // forged token always fails the same way instead of revealing which field 
was rejected.
+    if (!secretKey.isValidSignature(tokenBytes, token.getPassword())) {
+      throw new SecretManager.InvalidToken("Invalid STS token - signature is 
not correct");
+    }
+
     final STSTokenIdentifier tokenId = new STSTokenIdentifier();
-    final ManagedSecretKey secretKey;
+    tokenId.setManagedSecretKey(secretKey);
     try {
-      secretKey = getValidatedSecretKey(secretKeyId, secretKeyClient);
-      tokenId.setManagedSecretKey(secretKey);
       tokenId.readFromByteArray(tokenBytes);
     } catch (OMException e) {
       throw e;
@@ -117,11 +127,6 @@ private static STSTokenIdentifier 
verifyAndDecryptToken(Token<STSTokenIdentifier
       throw new OMException("Invalid STS token - token expired at " + 
tokenId.getExpiry(), TOKEN_EXPIRED);
     }
 
-    // Verify token signature against the original identifier bytes
-    if (!tokenId.isValidSignature(tokenBytes, token.getPassword())) {
-      throw new SecretManager.InvalidToken("Invalid STS token - signature is 
not correct for token: " + tokenId);
-    }
-
     return tokenId;
   }
 
@@ -202,8 +207,7 @@ static void 
ensureEssentialFieldsArePresentInToken(STSTokenIdentifier stsTokenId
    * @param request OM request possibly containing S3 authentication
    * @throws OMException if resolved fields and session token presence are 
inconsistent
    */
-  public static void 
ensureResolvedStsFieldsInvariants(OzoneManagerProtocolProtos.OMRequest request)
-      throws OMException {
+  public static void ensureResolvedStsFieldsInvariants(OMRequest request) 
throws OMException {
     if (!request.hasS3Authentication()) {
       return;
     }
@@ -213,23 +217,128 @@ public static void 
ensureResolvedStsFieldsInvariants(OzoneManagerProtocolProtos.
 
     if (!hasSessionToken) {
       // If sessionToken is missing/empty, resolved fields must be empty.
-      if (s3Auth.hasResolvedStsSessionPolicy() || 
s3Auth.hasResolvedStsRoleArn() ||
-          s3Auth.hasResolvedStsOriginalAccessKeyId() || 
s3Auth.hasResolvedStsTempAccessKeyId() ||
-          s3Auth.hasResolvedStsSecretKeyId()) {
+      if (hasAnyResolvedStsField(s3Auth)) {
         throw new OMException("Resolved STS fields must be empty when 
sessionToken is not present", INVALID_TOKEN);
       }
       return;
     }
 
-    ensureResolvedFieldsArePresent(s3Auth);
+    if (!hasAllResolvedStsFields(s3Auth)) {
+      throw new OMException("Resolved STS fields must be present when 
sessionToken is present", INVALID_TOKEN);
+    }
+
+    if (hasPartialResolvedStsAssumedRoleFields(s3Auth)) {
+      throw new OMException(
+          "Resolved STS assumed-role fields must both be present or both be 
absent when sessionToken is present",
+          INVALID_TOKEN);
+    }
   }
 
-  private static void ensureResolvedFieldsArePresent(S3Authentication s3Auth) 
throws OMException {
-    if (!s3Auth.hasResolvedStsSessionPolicy() || 
!s3Auth.hasResolvedStsRoleArn() ||
-        !s3Auth.hasResolvedStsOriginalAccessKeyId() || 
!s3Auth.hasResolvedStsTempAccessKeyId() ||
-        !s3Auth.hasResolvedStsSecretKeyId()) {
-      throw new OMException("Resolved STS fields must be present when 
sessionToken is present", INVALID_TOKEN);
+  private static boolean hasAnyResolvedStsField(S3Authentication s3Auth) {
+    return s3Auth.hasResolvedStsSessionPolicy() || 
s3Auth.hasResolvedStsRoleArn() ||
+        s3Auth.hasResolvedStsOriginalAccessKeyId() || 
s3Auth.hasResolvedStsTempAccessKeyId() ||
+        s3Auth.hasResolvedStsSecretKeyId() || 
s3Auth.hasResolvedStsAssumedRoleId() ||
+        s3Auth.hasResolvedStsAssumedRoleUserArn();
+  }
+
+  private static boolean hasAllResolvedStsFields(S3Authentication s3Auth) {
+    // Assumed-role id/ARN are optional here: they are used for 
GetCallerIdentity, not apply-path ACL,
+    // and may be absent in Ratis log entries committed by a pre-upgrade OM 
leader during rolling upgrade.
+    return s3Auth.hasResolvedStsSessionPolicy() && 
s3Auth.hasResolvedStsRoleArn() &&
+        s3Auth.hasResolvedStsOriginalAccessKeyId() && 
s3Auth.hasResolvedStsTempAccessKeyId() &&
+        s3Auth.hasResolvedStsSecretKeyId();
+  }
+
+  private static boolean 
hasPartialResolvedStsAssumedRoleFields(S3Authentication s3Auth) {
+    return s3Auth.hasResolvedStsAssumedRoleId() != 
s3Auth.hasResolvedStsAssumedRoleUserArn();
+  }
+
+  /**
+   * Copies the STS state that {@link S3SecurityUtil#validateS3Credential} put 
in the {@link OzoneManager}
+   * thread local onto {@code s3Auth}, or clears the resolved fields when the 
request has no session token.
+   *
+   * <p>The resolved fields are the only STS state visible to the Ratis apply 
thread, because the thread locals
+   * belong to the RPC handler thread and do not follow the request onto a 
Ratis thread. Resolving here, where
+   * the token has just been verified, is what lets the apply thread rebuild 
the request context without
+   * repeating any crypto.</p>
+   *
+   * <p>Returns {@code s3Auth} itself when there is nothing to resolve and 
nothing to clear.</p>
+   *
+   * @throws OMException if a session token is present but the token 
identifier is not, which means the
+   *         request reached this point without passing STS validation
+   */
+  public static S3Authentication resolveS3Authentication(S3Authentication 
s3Auth, OzoneManager ozoneManager)
+      throws OMException {
+    final boolean hasSessionToken = s3Auth.hasSessionToken() && 
!s3Auth.getSessionToken().isEmpty();
+    final STSTokenIdentifier stsTokenIdentifier = 
OzoneManager.getStsTokenIdentifier();
+
+    // This should not happen, so explicitly throw an error.  An existing 
sessionToken
+    // implies prior STS validation must have populated the ThreadLocal.
+    if (ozoneManager.isSecurityEnabled() && hasSessionToken && 
stsTokenIdentifier == null) {
+      throw new OMException(
+          "S3Authentication has session token but no STS token identifier in 
OzoneManager ThreadLocal",
+          INVALID_REQUEST);
+    }
+
+    if (!hasSessionToken || stsTokenIdentifier == null) {
+      return hasAnyResolvedStsField(s3Auth) ? clearResolvedStsFields(s3Auth) : 
s3Auth;
+    }
+
+    final UUID secretKeyId = stsTokenIdentifier.getSecretKeyId();
+    return s3Auth.toBuilder()
+        
.setResolvedStsSessionPolicy(StringUtils.defaultString(stsTokenIdentifier.getSessionPolicy()))
+        
.setResolvedStsRoleArn(StringUtils.defaultString(stsTokenIdentifier.getRoleArn()))
+        
.setResolvedStsOriginalAccessKeyId(StringUtils.defaultString(stsTokenIdentifier.getOriginalAccessKeyId()))
+        
.setResolvedStsTempAccessKeyId(StringUtils.defaultString(stsTokenIdentifier.getTempAccessKeyId()))
+        .setResolvedStsSecretKeyId(secretKeyId != null ? 
secretKeyId.toString() : "")
+        
.setResolvedStsAssumedRoleId(StringUtils.defaultString(stsTokenIdentifier.getAssumedRoleId()))
+        
.setResolvedStsAssumedRoleUserArn(StringUtils.defaultString(stsTokenIdentifier.getAssumedRoleUserArn()))
+        .build();
+  }
+
+  private static S3Authentication clearResolvedStsFields(S3Authentication 
s3Auth) {
+    return s3Auth.toBuilder()
+        .clearResolvedStsSessionPolicy()
+        .clearResolvedStsRoleArn()
+        .clearResolvedStsOriginalAccessKeyId()
+        .clearResolvedStsTempAccessKeyId()
+        .clearResolvedStsSecretKeyId()
+        .clearResolvedStsAssumedRoleId()
+        .clearResolvedStsAssumedRoleUserArn()
+        .build();
+  }
+
+  /**
+   * Rebuilds the {@link STSTokenIdentifier} for a request from the resolved 
fields written by
+   * {@link #resolveS3Authentication(S3Authentication, OzoneManager)}, or 
returns null when the request has
+   * no session token.
+   *
+   * <p>Performs no crypto and does not contact {@link SecretKeyClient}: the 
token was already verified on
+   * the leader RPC path, so only the resolved values are needed for 
authorization on the apply thread.
+   * {@code creationTime} and {@code expiry} are set to {@link Instant#MAX} so 
that a revocation or expiry
+   * check against this identifier is deterministic and never treats it as 
issued before a stored cutoff.</p>
+   *
+   * <p>Call {@link #ensureResolvedStsFieldsInvariants(OMRequest)} first: it 
guarantees the five core resolved
+   * fields (session policy, role ARN, original/temp access key IDs, secret 
key ID) are present. Assumed-role
+   * id/ARN may be absent on legacy Ratis log entries; when present, both must 
be set.</p>
+   */
+  public static STSTokenIdentifier 
rehydrateStsTokenIdentifier(S3Authentication s3Auth) {
+    if (!s3Auth.hasSessionToken() || s3Auth.getSessionToken().isEmpty()) {
+      return null;
     }
+
+    return new STSTokenIdentifier(STSTokenIdentifier.Params.newBuilder()
+        .setTempAccessKeyId(s3Auth.getResolvedStsTempAccessKeyId())
+        .setOriginalAccessKeyId(s3Auth.getResolvedStsOriginalAccessKeyId())
+        .setRoleArn(s3Auth.getResolvedStsRoleArn())
+        .setCreationTime(Instant.MAX)
+        .setExpiry(Instant.MAX)
+        .setSecretAccessKey(null) // no secretAccessKey needed
+        .setSessionPolicy(s3Auth.getResolvedStsSessionPolicy())
+        .setAssumedRoleId(s3Auth.getResolvedStsAssumedRoleId())
+        .setAssumedRoleUserArn(s3Auth.getResolvedStsAssumedRoleUserArn())
+        .setManagedSecretKey(null) // no ManagedSecretKey needed
+        .build());
   }
 }
 
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerStateMachine.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerStateMachine.java
index 072bb84188e..40975d5af61 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerStateMachine.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/ratis/TestOzoneManagerStateMachine.java
@@ -419,19 +419,8 @@ public void testRunCommandRuntimeExceptionTerminates() 
throws Exception {
   public void testRunCommandSetsAndClearsStsThreadLocal() throws Exception {
     when(om.isSecurityEnabled()).thenReturn(true);
 
-    final OzoneManagerProtocolProtos.S3Authentication s3Auth =
-        OzoneManagerProtocolProtos.S3Authentication.newBuilder()
-            .setAccessId("accessId")
-            .setSessionToken("sessionToken")
-            .setResolvedStsSessionPolicy("sessionPolicy")
-            .setResolvedStsRoleArn("roleArn")
-            .setResolvedStsOriginalAccessKeyId("originalAccessKeyId")
-            .setResolvedStsTempAccessKeyId("tempAccessKeyId")
-            .setResolvedStsSecretKeyId("secretKeyId")
-            .build();
-
     final OMRequest request = sampleWriteRequest().toBuilder()
-        .setS3Authentication(s3Auth)
+        .setS3Authentication(stsS3Authentication())
         .build();
     final TermIndex ti = TermIndex.valueOf(1, 5);
 
@@ -452,6 +441,8 @@ public void testRunCommandSetsAndClearsStsThreadLocal() 
throws Exception {
       assertEquals("originalAccessKeyId", 
OzoneManager.getStsTokenIdentifier().getOriginalAccessKeyId());
       assertEquals("roleArn", 
OzoneManager.getStsTokenIdentifier().getRoleArn());
       assertEquals("sessionPolicy", 
OzoneManager.getStsTokenIdentifier().getSessionPolicy());
+      assertEquals("assumedRoleId", 
OzoneManager.getStsTokenIdentifier().getAssumedRoleId());
+      assertEquals("assumedRoleUserArn", 
OzoneManager.getStsTokenIdentifier().getAssumedRoleUserArn());
       return clientResponse;
     }).when(handler).handleWriteRequest(eq(request), any(), eq(doubleBuffer));
 
@@ -464,6 +455,44 @@ public void testRunCommandSetsAndClearsStsThreadLocal() 
throws Exception {
     assertNull(OzoneManager.getStsTokenIdentifier(), "Expected STS ThreadLocal 
to be cleared after runCommand");
   }
 
+  @Test
+  public void testRunCommandSetsStsThreadLocalWithLegacyResolvedFields() 
throws Exception {
+    when(om.isSecurityEnabled()).thenReturn(true);
+
+    final OMRequest request = sampleWriteRequest().toBuilder()
+        .setS3Authentication(legacyStsS3Authentication())
+        .build();
+    final TermIndex ti = TermIndex.valueOf(1, 5);
+
+    final OMResponse expectedResponse = OMResponse.newBuilder()
+        .setCmdType(Type.CreateKey)
+        .setStatus(Status.OK)
+        .setSuccess(true)
+        .build();
+
+    final OMClientResponse clientResponse = mock(OMClientResponse.class);
+    when(clientResponse.getOMResponse()).thenReturn(expectedResponse);
+    when(clientResponse.getOmLockDetails()).thenReturn(null);
+
+    doAnswer(invocation -> {
+      assertNotNull(OzoneManager.getStsTokenIdentifier(),
+          "Expected STS ThreadLocal to be set during 
handler.handleWriteRequest");
+      assertEquals("tempAccessKeyId", 
OzoneManager.getStsTokenIdentifier().getTempAccessKeyId());
+      assertEquals("originalAccessKeyId", 
OzoneManager.getStsTokenIdentifier().getOriginalAccessKeyId());
+      assertEquals("roleArn", 
OzoneManager.getStsTokenIdentifier().getRoleArn());
+      assertEquals("sessionPolicy", 
OzoneManager.getStsTokenIdentifier().getSessionPolicy());
+      assertEquals("", 
OzoneManager.getStsTokenIdentifier().getAssumedRoleId());
+      assertEquals("", 
OzoneManager.getStsTokenIdentifier().getAssumedRoleUserArn());
+      return clientResponse;
+    }).when(handler).handleWriteRequest(eq(request), any(), eq(doubleBuffer));
+
+    OMResponse result = sm.runCommand(request, ti);
+
+    assertNotNull(result);
+    assertTrue(result.getSuccess());
+    assertNull(OzoneManager.getStsTokenIdentifier(), "Expected STS ThreadLocal 
to be cleared after runCommand");
+  }
+
   @Test
   public void testRunCommandMissingResolvedStsFieldsReturnsErrorResponse() 
throws Exception {
     when(om.isSecurityEnabled()).thenReturn(true);
@@ -1288,6 +1317,32 @@ private OMRequest sampleReadRequest() {
         .build();
   }
 
+  private OzoneManagerProtocolProtos.S3Authentication stsS3Authentication() {
+    return OzoneManagerProtocolProtos.S3Authentication.newBuilder()
+        .setAccessId("accessId")
+        .setSessionToken("sessionToken")
+        .setResolvedStsSessionPolicy("sessionPolicy")
+        .setResolvedStsRoleArn("roleArn")
+        .setResolvedStsOriginalAccessKeyId("originalAccessKeyId")
+        .setResolvedStsTempAccessKeyId("tempAccessKeyId")
+        .setResolvedStsSecretKeyId("secretKeyId")
+        .setResolvedStsAssumedRoleId("assumedRoleId")
+        .setResolvedStsAssumedRoleUserArn("assumedRoleUserArn")
+        .build();
+  }
+
+  private OzoneManagerProtocolProtos.S3Authentication 
legacyStsS3Authentication() {
+    return OzoneManagerProtocolProtos.S3Authentication.newBuilder()
+        .setAccessId("accessId")
+        .setSessionToken("sessionToken")
+        .setResolvedStsSessionPolicy("sessionPolicy")
+        .setResolvedStsRoleArn("roleArn")
+        .setResolvedStsOriginalAccessKeyId("originalAccessKeyId")
+        .setResolvedStsTempAccessKeyId("tempAccessKeyId")
+        .setResolvedStsSecretKeyId("secretKeyId")
+        .build();
+  }
+
   private TransactionContext mockTrx(OMRequest request, long term, long index) 
{
     return mockTrx(request, term, index, request);
   }
diff --git 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/TestOMClientRequestWithUserInfo.java
 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/TestOMClientRequestWithUserInfo.java
index 3dd695ee8ff..7a0923eb0e0 100644
--- 
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/TestOMClientRequestWithUserInfo.java
+++ 
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/TestOMClientRequestWithUserInfo.java
@@ -237,6 +237,8 @@ public void testPreExecuteOverwritesResolvedStsFields() 
throws Exception {
       final String originalAccessKeyId = "AKIAORIGINAL";
       final String roleArn = "arn:aws:iam::123456789012:role/test-role";
       final String sessionPolicy = "test-session-policy";
+      final String assumedRoleId = "AROATEST123456789:testsess";
+      final String assumedRoleUserArn = 
"arn:aws:sts::123456789012:assumed-role/test-role/testsess";
       final UUID secretKeyId = UUID.randomUUID();
 
       final STSTokenIdentifier stsTokenIdentifier = 
mock(STSTokenIdentifier.class);
@@ -245,6 +247,8 @@ public void testPreExecuteOverwritesResolvedStsFields() 
throws Exception {
       
when(stsTokenIdentifier.getOriginalAccessKeyId()).thenReturn(originalAccessKeyId);
       when(stsTokenIdentifier.getTempAccessKeyId()).thenReturn(accessId);
       when(stsTokenIdentifier.getSecretKeyId()).thenReturn(secretKeyId);
+      when(stsTokenIdentifier.getAssumedRoleId()).thenReturn(assumedRoleId);
+      
when(stsTokenIdentifier.getAssumedRoleUserArn()).thenReturn(assumedRoleUserArn);
 
       final S3Authentication s3Authentication = S3Authentication.newBuilder()
           .setAccessId(accessId)
@@ -256,6 +260,8 @@ public void testPreExecuteOverwritesResolvedStsFields() 
throws Exception {
           .setResolvedStsOriginalAccessKeyId("client-original-access-key-id")
           .setResolvedStsTempAccessKeyId("client-temp-access-key-id")
           .setResolvedStsSecretKeyId("client-secret-key-id")
+          .setResolvedStsAssumedRoleId("client-assumed-role-id")
+          .setResolvedStsAssumedRoleUserArn("client-assumed-role-user-arn")
           .build();
 
       OzoneManager.setS3Auth(s3Authentication);
@@ -282,6 +288,8 @@ public void testPreExecuteOverwritesResolvedStsFields() 
throws Exception {
         assertEquals(originalAccessKeyId, 
modifiedS3Auth.getResolvedStsOriginalAccessKeyId());
         assertEquals(accessId, modifiedS3Auth.getResolvedStsTempAccessKeyId());
         assertEquals(secretKeyId.toString(), 
modifiedS3Auth.getResolvedStsSecretKeyId());
+        assertEquals(assumedRoleId, 
modifiedS3Auth.getResolvedStsAssumedRoleId());
+        assertEquals(assumedRoleUserArn, 
modifiedS3Auth.getResolvedStsAssumedRoleUserArn());
       } finally {
         OzoneManager.setStsTokenIdentifier(null);
         OzoneManager.setS3Auth(null);
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
index f9d641f60b7..21c24384859 100644
--- 
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
@@ -22,6 +22,7 @@
 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.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -31,9 +32,12 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import com.google.protobuf.ServiceException;
 import java.io.IOException;
 import java.time.Clock;
 import java.time.Duration;
@@ -49,6 +53,7 @@
 import org.apache.hadoop.ozone.om.OzoneManager;
 import org.apache.hadoop.ozone.om.S3SecretManager;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.exceptions.OMNotLeaderException;
 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;
@@ -175,6 +180,56 @@ public void 
testValidateS3CredentialSuccessWhenTokenCreatedAtRevocationCutoff()
             .setExpectedResult(null));
   }
 
+  @Test
+  public void testValidateS3CredentialFailsWhenNotLeader() throws Exception {
+    try (OzoneManager ozoneManager = mock(OzoneManager.class)) {
+      when(ozoneManager.isSecurityEnabled()).thenReturn(true);
+      final OMNotLeaderException notLeaderException = new 
OMNotLeaderException("om1 is not the leader");
+      doThrow(notLeaderException).when(ozoneManager).checkLeaderStatus();
+
+      try (MockedStatic<STSSecurityUtil> stsSecurityUtilMock = 
mockStatic(STSSecurityUtil.class, CALLS_REAL_METHODS)) {
+        final OMRequest omRequest = 
createRequestWithSessionToken(TEMP_ACCESS_KEY_ID, true);
+
+        final ServiceException serviceException = assertThrows(
+            ServiceException.class, () -> 
S3SecurityUtil.validateS3Credential(omRequest, ozoneManager));
+        assertSame(notLeaderException, serviceException.getCause());
+
+        // A follower must reject before it reads its own revocation metadata, 
which may be stale.
+        stsSecurityUtilMock.verify(
+            () -> 
STSSecurityUtil.constructValidateAndDecryptSTSToken(anyString(), 
any(SecretKeyClient.class),
+                any(Clock.class)),
+            never());
+      }
+    }
+  }
+
+  @Test
+  public void testValidateS3CredentialDoesNotCheckLeaderOutsideStsPath() 
throws Exception {
+    try (OzoneManager ozoneManager = mock(OzoneManager.class)) {
+      when(ozoneManager.isSecurityEnabled()).thenReturn(true);
+      doThrow(new OMNotLeaderException("om1 is not the 
leader")).when(ozoneManager).checkLeaderStatus();
+
+      final OzoneDelegationTokenSecretManager delegationTokenMgr = 
mock(OzoneDelegationTokenSecretManager.class);
+      
when(ozoneManager.getDelegationTokenMgr()).thenReturn(delegationTokenMgr);
+
+      final OMRequest omRequest = OMRequest.newBuilder()
+          .setClientId(UUID.randomUUID().toString())
+          .setCmdType(Type.CreateVolume)
+          .setS3Authentication(S3Authentication.newBuilder()
+              .setStringToSign("string-to-sign")
+              .setSignature("signature")
+              .setAccessId(TEMP_ACCESS_KEY_ID)
+              .build())
+          .build();
+
+      // Permanent S3 credentials get their leader check from 
OzoneDelegationTokenSecretManager.retrievePassword,
+      // so validateS3Credential must not add a second one outside the session 
token branch.
+      assertDoesNotThrow(() -> S3SecurityUtil.validateS3Credential(omRequest, 
ozoneManager));
+      verify(ozoneManager, never()).checkLeaderStatus();
+      
verify(delegationTokenMgr).retrievePassword(any(OzoneTokenIdentifier.class));
+    }
+  }
+
   private void validateS3CredentialHelper(TestConfig config) throws Exception {
     try (OzoneManager ozoneManager = mock(OzoneManager.class)) {
       when(ozoneManager.isSecurityEnabled()).thenReturn(true);
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 769ae0da5f1..676a286111c 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
@@ -204,9 +204,7 @@ public void 
testConstructValidateAndDecryptSTSTokenSecretKeyNotFound() throws Ex
     assertThatThrownBy(() ->
         STSSecurityUtil.constructValidateAndDecryptSTSToken(validTokenString, 
mockKeyClient, clock))
         .isInstanceOf(OMException.class)
-        .hasMessage(
-            "Invalid STS token format: Invalid STS token - could not 
readFromByteArray: Secret key not found for " +
-            "STS token secretKeyId: " + secretKeyId);
+        .hasMessage("Invalid STS token format: Secret key not found for STS 
token secretKeyId: " + secretKeyId);
   }
 
   @Test
@@ -269,9 +267,7 @@ public void 
testConstructValidateAndDecryptSTSTokenSecretKeyRetrievalException()
     assertThatThrownBy(() ->
         STSSecurityUtil.constructValidateAndDecryptSTSToken(validTokenString, 
mockKeyClient, clock))
         .isInstanceOf(OMException.class)
-        .hasMessage(
-            "Invalid STS token format: Invalid STS token - could not 
readFromByteArray: Failed to retrieve secret " +
-            "key: something went wrong");
+        .hasMessage("Invalid STS token format: Failed to retrieve secret key: 
something went wrong");
   }
 
   @Test
@@ -293,7 +289,33 @@ public void 
testConstructValidateAndDecryptSTSTokenInvalidSignature() throws Exc
     assertThatThrownBy(() ->
         
STSSecurityUtil.constructValidateAndDecryptSTSToken(invalidTokenString, 
secretKeyClient, clock))
         .isInstanceOf(OMException.class)
-        .hasMessageContaining("Invalid STS token format: Invalid STS token - 
signature is not correct for token");
+        .hasMessage("Invalid STS token format: Invalid STS token - signature 
is not correct");
+  }
+
+  @Test
+  public void 
testConstructValidateAndDecryptSTSTokenRejectsTamperedIdentifierBeforeDecrypting()
 throws Exception {
+    final String validTokenString = createStsTokenString();
+
+    final Token<STSTokenIdentifier> validToken = new Token<>();
+    validToken.decodeFromUrlString(validTokenString);
+
+    // Corrupt the encrypted secretAccessKey while keeping the original 
signature. Decrypting these bytes would
+    // fail inside the cipher, so the signature check must reject them before 
readFromByteArray is ever reached.
+    final OMTokenProto proto = 
OMTokenProto.parseFrom(validToken.getIdentifier());
+    final byte[] tamperedSecret = 
proto.getSecretAccessKey().getBytes(StandardCharsets.UTF_8);
+    tamperedSecret[tamperedSecret.length - 1] ^= 0x01;
+    final OMTokenProto tamperedProto = proto.toBuilder()
+        .setSecretAccessKey(new String(tamperedSecret, StandardCharsets.UTF_8))
+        .build();
+
+    final Token<STSTokenIdentifier> tamperedToken = new Token<>(
+        tamperedProto.toByteArray(), validToken.getPassword(), 
validToken.getKind(), validToken.getService());
+
+    assertThatThrownBy(() ->
+        
STSSecurityUtil.constructValidateAndDecryptSTSToken(tamperedToken.encodeToUrlString(),
 secretKeyClient, clock))
+        .isInstanceOf(OMException.class)
+        .satisfies(exception -> assertThat(((OMException) 
exception).getResult()).isEqualTo(INVALID_TOKEN))
+        .hasMessage("Invalid STS token format: Invalid STS token - signature 
is not correct");
   }
 
   @Test
@@ -414,6 +436,8 @@ public void testEnsureResolvedStsFieldsInvariantsSuccess() 
throws Exception {
         .setResolvedStsOriginalAccessKeyId(ORIGINAL_ACCESS_KEY)
         .setResolvedStsTempAccessKeyId(TEMP_ACCESS_KEY)
         .setResolvedStsSecretKeyId(secretKeyId.toString())
+        .setResolvedStsAssumedRoleId(ASSUMED_ROLE_ID)
+        .setResolvedStsAssumedRoleUserArn(ASSUMED_ROLE_USER_ARN)
         .build();
 
     final OMRequest request = OMRequest.newBuilder()
@@ -425,6 +449,78 @@ public void testEnsureResolvedStsFieldsInvariantsSuccess() 
throws Exception {
     STSSecurityUtil.ensureResolvedStsFieldsInvariants(request);
   }
 
+  @Test
+  public void 
testEnsureResolvedStsFieldsInvariantsSuccessWithLegacyResolvedFields() throws 
Exception {
+    final String tokenString = createStsTokenString();
+
+    final S3Authentication s3Auth = S3Authentication.newBuilder()
+        .setSessionToken(tokenString)
+        .setResolvedStsSessionPolicy(SESSION_POLICY)
+        .setResolvedStsRoleArn(ROLE_ARN)
+        .setResolvedStsOriginalAccessKeyId(ORIGINAL_ACCESS_KEY)
+        .setResolvedStsTempAccessKeyId(TEMP_ACCESS_KEY)
+        .setResolvedStsSecretKeyId(secretKeyId.toString())
+        .build();
+
+    final OMRequest request = OMRequest.newBuilder()
+        .setCmdType(Type.CreateBucket)
+        .setClientId("client-id")
+        .setS3Authentication(s3Auth)
+        .build();
+
+    STSSecurityUtil.ensureResolvedStsFieldsInvariants(request);
+  }
+
+  @Test
+  public void 
testEnsureResolvedStsFieldsInvariantsRejectsPartialAssumedRoleFields() throws 
Exception {
+    final String tokenString = createStsTokenString();
+
+    final S3Authentication s3Auth = S3Authentication.newBuilder()
+        .setSessionToken(tokenString)
+        .setResolvedStsSessionPolicy(SESSION_POLICY)
+        .setResolvedStsRoleArn(ROLE_ARN)
+        .setResolvedStsOriginalAccessKeyId(ORIGINAL_ACCESS_KEY)
+        .setResolvedStsTempAccessKeyId(TEMP_ACCESS_KEY)
+        .setResolvedStsSecretKeyId(secretKeyId.toString())
+        .setResolvedStsAssumedRoleId(ASSUMED_ROLE_ID)
+        .build();
+
+    final OMRequest request = OMRequest.newBuilder()
+        .setCmdType(Type.CreateBucket)
+        .setClientId("client-id")
+        .setS3Authentication(s3Auth)
+        .build();
+
+    assertThatThrownBy(() -> 
STSSecurityUtil.ensureResolvedStsFieldsInvariants(request))
+        .isInstanceOf(OMException.class)
+        .hasMessageContaining("Resolved STS assumed-role fields must both be 
present or both be absent");
+  }
+
+  @Test
+  public void 
testEnsureResolvedStsFieldsInvariantsRejectsPartialAssumedRoleUserArnOnly() 
throws Exception {
+    final String tokenString = createStsTokenString();
+
+    final S3Authentication s3Auth = S3Authentication.newBuilder()
+        .setSessionToken(tokenString)
+        .setResolvedStsSessionPolicy(SESSION_POLICY)
+        .setResolvedStsRoleArn(ROLE_ARN)
+        .setResolvedStsOriginalAccessKeyId(ORIGINAL_ACCESS_KEY)
+        .setResolvedStsTempAccessKeyId(TEMP_ACCESS_KEY)
+        .setResolvedStsSecretKeyId(secretKeyId.toString())
+        .setResolvedStsAssumedRoleUserArn(ASSUMED_ROLE_USER_ARN)
+        .build();
+
+    final OMRequest request = OMRequest.newBuilder()
+        .setCmdType(Type.CreateBucket)
+        .setClientId("client-id")
+        .setS3Authentication(s3Auth)
+        .build();
+
+    assertThatThrownBy(() -> 
STSSecurityUtil.ensureResolvedStsFieldsInvariants(request))
+        .isInstanceOf(OMException.class)
+        .hasMessageContaining("Resolved STS assumed-role fields must both be 
present or both be absent");
+  }
+
   @Test
   public void testEnsureResolvedStsFieldsInvariantsMissingSessionToken() {
     final S3Authentication s3Auth = S3Authentication.newBuilder()


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

Reply via email to