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 e96df8e2003 HDDS-13887. [STS] Protobuf Plumbing for AssumeRole 
Requests (#9254)
e96df8e2003 is described below

commit e96df8e20039b8d7e81dcfcc9c40b6cd9f184abc
Author: fmorg-git <[email protected]>
AuthorDate: Wed Nov 12 11:38:06 2025 -0800

    HDDS-13887. [STS] Protobuf Plumbing for AssumeRole Requests (#9254)
---
 .../apache/hadoop/ozone/client/ObjectStore.java    |  20 ++
 .../ozone/client/protocol/ClientProtocol.java      |  17 ++
 .../apache/hadoop/ozone/client/rpc/RpcClient.java  |  11 +
 .../main/java/org/apache/hadoop/ozone/OmUtils.java |   1 +
 .../ozone/om/helpers/AssumeRoleResponseInfo.java   | 133 ++++++++++
 .../ozone/om/protocol/OzoneManagerProtocol.java    |  22 ++
 ...OzoneManagerProtocolClientSideTranslatorPB.java |  24 ++
 .../om/helpers/TestAssumeRoleResponseInfo.java     | 286 +++++++++++++++++++++
 .../src/main/proto/OmClientProtocol.proto          |  27 ++
 .../org/apache/hadoop/ozone/audit/OMAction.java    |   2 +
 .../hadoop/ozone/client/ClientProtocolStub.java    |  11 +
 11 files changed, 554 insertions(+)

diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
index 456dc916214..18e28f387c6 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java
@@ -36,6 +36,7 @@
 import org.apache.hadoop.ozone.OzoneFsServerDefaults;
 import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
 import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
 import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
@@ -750,6 +751,25 @@ public Iterator<OzoneSnapshotDiff> listSnapshotDiffJobs(
     return new SnapshotDiffJobIterator(volumeName, bucketName, jobStatus, 
listAllStatus, prevSnapshotDiffJob);
   }
 
+  /**
+   * Process the AssumeRole operation.
+   *
+   * @param roleArn                 The ARN of the role to assume
+   * @param roleSessionName         The session name (should be unique) for 
this operation
+   * @param durationSeconds         The duration in seconds for the token 
validity
+   * @param awsIamSessionPolicy     The AWS IAM JSON session policy
+   * @return AssumeRoleResponseInfo The AssumeRole response information 
containing temporary credentials
+   * @throws IOException            if an error occurs during the AssumeRole 
operation
+   */
+  public AssumeRoleResponseInfo assumeRole(
+      String roleArn,
+      String roleSessionName,
+      int durationSeconds,
+      String awsIamSessionPolicy
+  ) throws IOException {
+    return proxy.assumeRole(roleArn, roleSessionName, durationSeconds, 
awsIamSessionPolicy);
+  }
+
   /**
    * An Iterator to iterate over {@link SnapshotDiffJobIterator} list.
    */
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
index e3a57589634..7560f2efc61 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java
@@ -46,6 +46,7 @@
 import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
 import org.apache.hadoop.ozone.om.OMConfigKeys;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
 import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
 import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
 import org.apache.hadoop.ozone.om.helpers.LeaseKeyInfo;
@@ -1359,4 +1360,20 @@ void putObjectTagging(String volumeName, String 
bucketName, String keyName,
   void deleteObjectTagging(String volumeName, String bucketName, String 
keyName)
       throws IOException;
 
+  /**
+   * Process the AssumeRole operation.
+   *
+   * @param roleArn                 The ARN of the role to assume
+   * @param roleSessionName         The session name (should be unique) for 
this operation
+   * @param durationSeconds         The duration in seconds for the token 
validity
+   * @param awsIamSessionPolicy     The AWS IAM JSON session policy
+   * @return AssumeRoleResponseInfo The AssumeRole response information 
containing temporary credentials
+   * @throws IOException            if an error occurs during the AssumeRole 
operation
+   */
+  AssumeRoleResponseInfo assumeRole(
+      String roleArn,
+      String roleSessionName,
+      int durationSeconds,
+      String awsIamSessionPolicy
+  ) throws IOException;
 }
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
index d4ebf0be1b3..115aa0bd20c 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
@@ -127,6 +127,7 @@
 import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
 import org.apache.hadoop.ozone.om.OmConfig;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
 import org.apache.hadoop.ozone.om.helpers.BasicOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.BucketEncryptionKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.BucketLayout;
@@ -2790,6 +2791,16 @@ public void deleteObjectTagging(String volumeName, 
String bucketName,
     ozoneManagerClient.deleteObjectTagging(keyArgs);
   }
 
+  @Override
+  public AssumeRoleResponseInfo assumeRole(
+      String roleArn,
+      String roleSessionName,
+      int durationSeconds,
+      String awsIamSessionPolicy
+  ) throws IOException {
+    return ozoneManagerClient.assumeRole(roleArn, roleSessionName, 
durationSeconds, awsIamSessionPolicy);
+  }
+
   private static ExecutorService createThreadPoolExecutor(
        int corePoolSize, int maximumPoolSize, String threadNameFormat) {
     return new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
index 4c20b380865..be1c422711a 100644
--- a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
+++ b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/OmUtils.java
@@ -300,6 +300,7 @@ public static boolean isReadOnly(
     case CompleteMultiPartUpload:
     case AbortMultiPartUpload:
     case GetS3Secret:
+    case AssumeRole:
     case GetDelegationToken:
     case RenewDelegationToken:
     case CancelDelegationToken:
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/AssumeRoleResponseInfo.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/AssumeRoleResponseInfo.java
new file mode 100644
index 00000000000..08bf14ef4a2
--- /dev/null
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/AssumeRoleResponseInfo.java
@@ -0,0 +1,133 @@
+/*
+ * 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.om.helpers;
+
+import java.util.Objects;
+import net.jcip.annotations.Immutable;
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AssumeRoleResponse;
+
+/**
+ * Utility class to handle AssumeRoleResponse protobuf message.
+ */
+@Immutable
+public class AssumeRoleResponseInfo {
+
+  private final String accessKeyId;
+  private final String secretAccessKey;
+  private final String sessionToken;
+  private final long expirationEpochSeconds;
+  private final String assumedRoleId;
+
+  public String getAccessKeyId() {
+    return accessKeyId;
+  }
+
+  public String getSecretAccessKey() {
+    return secretAccessKey;
+  }
+
+  public String getSessionToken() {
+    return sessionToken;
+  }
+
+  public long getExpirationEpochSeconds() {
+    return expirationEpochSeconds;
+  }
+
+  public String getAssumedRoleId() {
+    return assumedRoleId;
+  }
+
+  public AssumeRoleResponseInfo(
+      String accessKeyId,
+      String secretAccessKey,
+      String sessionToken,
+      long expirationEpochSeconds,
+      String assumedRoleId
+  ) {
+    this.accessKeyId = accessKeyId;
+    this.secretAccessKey = secretAccessKey;
+    this.sessionToken = sessionToken;
+    this.expirationEpochSeconds = expirationEpochSeconds;
+    this.assumedRoleId = assumedRoleId;
+  }
+
+  public static AssumeRoleResponseInfo fromProtobuf(
+      AssumeRoleResponse response
+  ) {
+    return new AssumeRoleResponseInfo(
+        response.getAccessKeyId(),
+        response.getSecretAccessKey(),
+        response.getSessionToken(),
+        response.getExpirationEpochSeconds(),
+        response.getAssumedRoleId()
+    );
+  }
+
+  public AssumeRoleResponse getProtobuf() {
+    return AssumeRoleResponse.newBuilder()
+        .setAccessKeyId(accessKeyId)
+        .setSecretAccessKey(secretAccessKey)
+        .setSessionToken(sessionToken)
+        .setExpirationEpochSeconds(expirationEpochSeconds)
+        .setAssumedRoleId(assumedRoleId)
+        .build();
+  }
+
+  @Override
+  public String toString() {
+    return "AssumeRoleResponseInfo{" +
+        "accessKeyId='" + accessKeyId + '\'' +
+        ", secretAccessKey='" + secretAccessKey + '\'' +
+        ", sessionToken='" + sessionToken + '\'' +
+        ", expirationEpochSeconds=" + expirationEpochSeconds +
+        ", assumedRoleId='" + assumedRoleId + '\'' +
+        '}';
+  }
+
+  @Override
+  public boolean equals(
+      Object o
+  ) {
+    if (this == o) {
+      return true;
+    }
+
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    final AssumeRoleResponseInfo that = (AssumeRoleResponseInfo) o;
+    return expirationEpochSeconds == that.expirationEpochSeconds &&
+        Objects.equals(accessKeyId, that.accessKeyId) &&
+        Objects.equals(secretAccessKey, that.secretAccessKey) &&
+        Objects.equals(sessionToken, that.sessionToken) &&
+        Objects.equals(assumedRoleId, that.assumedRoleId);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(
+        accessKeyId,
+        secretAccessKey,
+        sessionToken,
+        expirationEpochSeconds,
+        assumedRoleId
+    );
+  }
+}
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
index 3bcf190662a..b41510bb2bf 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java
@@ -29,6 +29,7 @@
 import org.apache.hadoop.ozone.om.IOmMetadataReader;
 import org.apache.hadoop.ozone.om.OMConfigKeys;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
 import org.apache.hadoop.ozone.om.helpers.DBUpdates;
 import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
 import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
@@ -1175,4 +1176,25 @@ default void deleteObjectTagging(OmKeyArgs args) throws 
IOException {
    * @throws IOException
    */
   void startQuotaRepair(List<String> buckets) throws IOException;
+
+  /**
+   * Process the AssumeRole operation.
+   *
+   * @param roleArn                 The ARN of the role to assume
+   * @param roleSessionName         The session name (should be unique) for 
this operation
+   * @param durationSeconds         The duration in seconds for the token 
validity
+   * @param awsIamSessionPolicy     The AWS IAM JSON session policy
+   * @return AssumeRoleResponseInfo The AssumeRole response information 
containing temporary credentials
+   * @throws IOException            if an error occurs during the AssumeRole 
operation
+   */
+  default AssumeRoleResponseInfo assumeRole(
+      String roleArn,
+      String roleSessionName,
+      int durationSeconds,
+      String awsIamSessionPolicy
+  ) throws IOException {
+    throw new UnsupportedOperationException(
+        "OzoneManager does not require this to be implemented"
+    );
+  }
 }
diff --git 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
index 671a93a486e..222f4bc1f48 100644
--- 
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
+++ 
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java
@@ -53,6 +53,7 @@
 import org.apache.hadoop.ozone.ClientVersion;
 import org.apache.hadoop.ozone.OzoneAcl;
 import org.apache.hadoop.ozone.om.exceptions.OMException;
+import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
 import org.apache.hadoop.ozone.om.helpers.BasicOmKeyInfo;
 import org.apache.hadoop.ozone.om.helpers.DBUpdates;
 import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
@@ -2650,6 +2651,29 @@ public void deleteObjectTagging(OmKeyArgs args) throws 
IOException {
     handleError(omResponse);
   }
 
+  @Override
+  public AssumeRoleResponseInfo assumeRole(
+      String roleArn,
+      String roleSessionName,
+      int durationSeconds,
+      String awsIamSessionPolicy
+  ) throws IOException {
+    final OzoneManagerProtocolProtos.AssumeRoleRequest.Builder request =
+        OzoneManagerProtocolProtos.AssumeRoleRequest.newBuilder()
+            .setRoleArn(roleArn)
+            .setRoleSessionName(roleSessionName)
+            .setDurationSeconds(durationSeconds)
+            .setAwsIamSessionPolicy(awsIamSessionPolicy != null ? 
awsIamSessionPolicy : "");
+
+    final OMRequest omRequest = createOMRequest(Type.AssumeRole)
+        .setAssumeRoleRequest(request)
+        .build();
+
+    return AssumeRoleResponseInfo.fromProtobuf(
+        handleError(submitRequest(omRequest)).getAssumeRoleResponse()
+    );
+  }
+
   private SafeMode toProtoBuf(SafeModeAction action) {
     switch (action) {
     case ENTER:
diff --git 
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestAssumeRoleResponseInfo.java
 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestAssumeRoleResponseInfo.java
new file mode 100644
index 00000000000..db5a409864d
--- /dev/null
+++ 
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestAssumeRoleResponseInfo.java
@@ -0,0 +1,286 @@
+/*
+ * 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.om.helpers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import 
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AssumeRoleResponse;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test AssumeRoleResponseInfo.
+ */
+public class TestAssumeRoleResponseInfo {
+
+  private static final String ACCESS_KEY_ID = "ASIA7O1AJD8VV4KCEAX5";
+  private static final String SECRET_ACCESS_KEY = 
"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
+  private static final String SESSION_TOKEN = 
"jgIDCAMaI2lkYnJva2VyL2lkYnJva2VyY2xpZW50QEVYQU1QTEUuQ09" +
+      
"NOLDJ8bClM2IjaWRicm9rZXIvaWRicm9rZXJjbGllbnRARVhBTVBMRS5DT02CASRjMGM5YTk2NS00YTU1LTRmMjQtYTUxMi0"
 +
+      
"3MGQ3M2JiMzg0ZDSKARRBU0lBN08xQUpEOFdWNEtDRUFYNZIBKGFybjphd3M6aWFtOjoxMjM0NTY3ODkwMTI6cm9sZS9mbS1"
 +
+      
"kd3JvbGWaAXAvdmNOSjNqRW5zc3UyWklKYWxJbGRXZSswV1VmYkRvSmwxdXV1eDBPVExQalVzZ0VqOVE5T0FZVUZTd2JtUGo"
 +
+      
"zZHNhaXpjMytacEJiVXJDNWRSV1FOTE4xcWJsVkhSdEZiZFBPTXp4NU5YY1pXdz09ogHQAVt7InJvbGVOYW1lIjoiZm0tZHd"
 +
+      
"yb2xlIiwiZ3JhbnRzIjpbeyJvYmplY3RzIjpbImtleTogL3Mzdi9idWNrZXQxLyoiXSwicGVybWlzc2lvbnMiOlsicmVhZCJ"
 +
+      
"dfSx7Im9iamVjdHMiOlsidm9sdW1lOiAvczN2Il0sInBlcm1pc3Npb25zIjpbInJlYWQiXX0seyJvYmplY3RzIjpbImJ1Y2t"
 +
+      
"ldDogL3Mzdi9idWNrZXQxIl0sInBlcm1pc3Npb25zIjpbInJlYWQiXX1dfV0gCil_LhVjpP4hfMez4L5wNZDeqEubSeBfEow"
 +
+      "VoRnSQ-wIU1RTVG9rZW4DU1RT";
+  private static final long EXPIRATION_EPOCH_SECONDS = 1577836800L;
+  private static final String ASSUMED_ROLE_ID = 
"arn:aws:iam::123456789012:role/MyRole";
+
+  @Test
+  public void testConstructor() {
+    final AssumeRoleResponseInfo response = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    assertEquals(ACCESS_KEY_ID, response.getAccessKeyId());
+    assertEquals(SECRET_ACCESS_KEY, response.getSecretAccessKey());
+    assertEquals(SESSION_TOKEN, response.getSessionToken());
+    assertEquals(EXPIRATION_EPOCH_SECONDS, 
response.getExpirationEpochSeconds());
+    assertEquals(ASSUMED_ROLE_ID, response.getAssumedRoleId());
+  }
+
+  @Test
+  public void testProtobufConversion() {
+    final AssumeRoleResponseInfo response = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    final AssumeRoleResponse proto = response.getProtobuf();
+
+    assertNotNull(proto);
+    assertEquals(ACCESS_KEY_ID, proto.getAccessKeyId());
+    assertEquals(SECRET_ACCESS_KEY, proto.getSecretAccessKey());
+    assertEquals(SESSION_TOKEN, proto.getSessionToken());
+    assertEquals(EXPIRATION_EPOCH_SECONDS, proto.getExpirationEpochSeconds());
+    assertEquals(ASSUMED_ROLE_ID, proto.getAssumedRoleId());
+  }
+
+  @Test
+  public void testFromProtobuf() {
+    final AssumeRoleResponse proto = AssumeRoleResponse.newBuilder()
+        .setAccessKeyId(ACCESS_KEY_ID)
+        .setSecretAccessKey(SECRET_ACCESS_KEY)
+        .setSessionToken(SESSION_TOKEN)
+        .setExpirationEpochSeconds(EXPIRATION_EPOCH_SECONDS)
+        .setAssumedRoleId(ASSUMED_ROLE_ID)
+        .build();
+
+    final AssumeRoleResponseInfo response = 
AssumeRoleResponseInfo.fromProtobuf(proto);
+
+    assertEquals(ACCESS_KEY_ID, response.getAccessKeyId());
+    assertEquals(SECRET_ACCESS_KEY, response.getSecretAccessKey());
+    assertEquals(SESSION_TOKEN, response.getSessionToken());
+    assertEquals(EXPIRATION_EPOCH_SECONDS, 
response.getExpirationEpochSeconds());
+    assertEquals(ASSUMED_ROLE_ID, response.getAssumedRoleId());
+  }
+
+  @Test
+  public void testProtobufRoundTrip() {
+    final AssumeRoleResponseInfo originalResponse = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    final AssumeRoleResponse proto = originalResponse.getProtobuf();
+    final AssumeRoleResponseInfo recoveredResponse = 
AssumeRoleResponseInfo.fromProtobuf(proto);
+
+    assertEquals(originalResponse, recoveredResponse);
+  }
+
+  @Test
+  public void testEqualsAndHashCodeWithIdenticalObjects() {
+    final AssumeRoleResponseInfo response1 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    final AssumeRoleResponseInfo response2 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    assertEquals(response1, response2);
+    assertEquals(response1.hashCode(), response2.hashCode());
+  }
+
+  @Test
+  public void testNotEqualsAndHashCodeWithDifferentAccessKeyId() {
+    final AssumeRoleResponseInfo response1 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    final AssumeRoleResponseInfo response2 = new AssumeRoleResponseInfo(
+        "DIFFERENT_KEY_ID",
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    assertNotEquals(response1, response2);
+    assertNotEquals(response1.hashCode(), response2.hashCode());
+  }
+
+  @Test
+  public void testNotEqualsAndHashCodeWithDifferentSecretAccessKey() {
+    final AssumeRoleResponseInfo response1 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    final AssumeRoleResponseInfo response2 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        "DIFFERENT_SECRET_KEY",
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    assertNotEquals(response1, response2);
+    assertNotEquals(response1.hashCode(), response2.hashCode());
+  }
+
+  @Test
+  public void testNotEqualsAndHashCodeWithDifferentSessionToken() {
+    final AssumeRoleResponseInfo response1 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    final AssumeRoleResponseInfo response2 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        "DIFFERENT_TOKEN",
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    assertNotEquals(response1, response2);
+    assertNotEquals(response1.hashCode(), response2.hashCode());
+  }
+
+  @Test
+  public void testNotEqualsAndHashCodeWithDifferentExpirationEpochSeconds() {
+    final AssumeRoleResponseInfo response1 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    final AssumeRoleResponseInfo response2 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        9999999999L,
+        ASSUMED_ROLE_ID
+    );
+
+    assertNotEquals(response1, response2);
+    assertNotEquals(response1.hashCode(), response2.hashCode());
+  }
+
+  @Test
+  public void testNotEqualsAndHashCodeWithDifferentAssumedRoleId() {
+    final AssumeRoleResponseInfo response1 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    final AssumeRoleResponseInfo response2 = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        "DIFFERENT_ROLE_ID"
+    );
+
+    assertNotEquals(response1, response2);
+    assertNotEquals(response1.hashCode(), response2.hashCode());
+  }
+
+  @Test
+  public void testNotEqualsWithNull() {
+    final AssumeRoleResponseInfo response = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    assertNotEquals(null, response);
+  }
+
+  @Test
+  public void testToString() {
+    final AssumeRoleResponseInfo response = new AssumeRoleResponseInfo(
+        ACCESS_KEY_ID,
+        SECRET_ACCESS_KEY,
+        SESSION_TOKEN,
+        EXPIRATION_EPOCH_SECONDS,
+        ASSUMED_ROLE_ID
+    );
+
+    final String toString = response.toString();
+    final String expectedString = "AssumeRoleResponseInfo{" +
+        "accessKeyId='" + ACCESS_KEY_ID + '\'' +
+        ", secretAccessKey='" + SECRET_ACCESS_KEY + '\'' +
+        ", sessionToken='" + SESSION_TOKEN + '\'' +
+        ", expirationEpochSeconds=" + EXPIRATION_EPOCH_SECONDS +
+        ", assumedRoleId='" + ASSUMED_ROLE_ID + '\'' +
+        '}';
+
+    assertNotNull(toString);
+    assertEquals(expectedString, toString);
+  }
+}
+
diff --git 
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto 
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 1e5675f612e..8e455e70342 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -156,6 +156,7 @@ enum Type {
   PutObjectTagging = 140;
   GetObjectTagging = 141;
   DeleteObjectTagging = 142;
+  AssumeRole = 143;
 }
 
 enum SafeMode {
@@ -304,6 +305,7 @@ message OMRequest {
   optional PutObjectTaggingRequest          putObjectTaggingRequest        = 
141;
   optional DeleteObjectTaggingRequest       deleteObjectTaggingRequest     = 
142;
   repeated SetSnapshotPropertyRequest       SetSnapshotPropertyRequests    = 
143;
+  optional AssumeRoleRequest                assumeRoleRequest              = 
144;
 }
 
 message OMResponse {
@@ -437,6 +439,7 @@ message OMResponse {
   optional GetObjectTaggingResponse          getObjectTaggingResponse      = 
140;
   optional PutObjectTaggingResponse          putObjectTaggingResponse      = 
141;
   optional DeleteObjectTaggingResponse       deleteObjectTaggingResponse   = 
142;
+  optional AssumeRoleResponse                assumeRoleResponse            = 
143;
 }
 
 enum Status {
@@ -1494,6 +1497,7 @@ message OMTokenProto {
     enum Type {
       DELEGATION_TOKEN = 1;
       S3AUTHINFO = 2;
+      S3_STS_TOKEN = 3;
     };
     required Type   type           = 1;
     optional uint32 version        = 2;
@@ -1511,6 +1515,11 @@ message OMTokenProto {
     optional string strToSign      = 14;
     optional string omServiceId    = 15 [deprecated = true];
     optional string secretKeyId    = 16;
+    // STS-specific fields
+    optional string roleArn             = 17;
+    optional string originalAccessKeyId = 18;
+    optional string secretAccessKey     = 19;
+    optional string sessionPolicy       = 20;
 }
 
 message SecretKeyProto {
@@ -2263,6 +2272,9 @@ message S3Authentication {
     optional string stringToSign = 1;
     optional string signature = 2;
     optional string accessId = 3;
+    // If present, indicates this request uses STS temporary credentials
+    // and carries the base64-encoded session token for validation.
+    optional string sessionToken = 4;
 }
 
 message RecoverLeaseRequest {
@@ -2354,6 +2366,21 @@ message DeleteObjectTaggingRequest {
 message DeleteObjectTaggingResponse {
 }
 
+message AssumeRoleRequest {
+  required string roleArn                 = 1;
+  required string roleSessionName         = 2;
+  optional int32 durationSeconds          = 3 [default = 3600];
+  optional string awsIamSessionPolicy     = 4;
+}
+
+message AssumeRoleResponse {
+  required string accessKeyId             = 1;
+  required string secretAccessKey         = 2;
+  required string sessionToken            = 3;
+  required uint64 expirationEpochSeconds  = 4;
+  required string assumedRoleId           = 5;
+}
+
 /**
  The OM service that takes care of Ozone namespace.
 */
diff --git 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
index f728a88b2b3..9be2bdea709 100644
--- 
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
+++ 
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/audit/OMAction.java
@@ -83,6 +83,8 @@ public enum OMAction implements AuditAction {
   SET_S3_SECRET,
   REVOKE_S3_SECRET,
 
+  S3_ASSUME_ROLE,
+
   CREATE_TENANT,
   DELETE_TENANT,
   LIST_TENANT,
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
index 739babce1d0..ef0d32e2387 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java
@@ -37,6 +37,7 @@
 import org.apache.hadoop.ozone.client.io.OzoneInputStream;
 import org.apache.hadoop.ozone.client.io.OzoneOutputStream;
 import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
+import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
 import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
 import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
 import org.apache.hadoop.ozone.om.helpers.LeaseKeyInfo;
@@ -803,4 +804,14 @@ public void deleteObjectTagging(String volumeName, String 
bucketName, String key
     getBucket(volumeName, bucketName).deleteObjectTagging(keyName);
   }
 
+  @Override
+  public AssumeRoleResponseInfo assumeRole(
+      String roleArn,
+      String roleSessionName,
+      int durationSeconds,
+      String awsIamSessionPolicy
+  ) throws IOException {
+    return null;
+  }
+
 }


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

Reply via email to