This is an automated email from the ASF dual-hosted git repository.
ChenSammi 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 5bc5a1e362f HDDS-16290. [STS] Implement GetCallerIdentity API (#11121)
5bc5a1e362f is described below
commit 5bc5a1e362f5143198683b511a32984f6319a8fb
Author: fmorg-git <[email protected]>
AuthorDate: Thu Sep 3 18:10:08 2026 -0700
HDDS-16290. [STS] Implement GetCallerIdentity API (#11121)
---
hadoop-hdds/docs/content/design/ozone-sts.md | 12 +-
.../apache/hadoop/ozone/client/ObjectStore.java | 10 ++
.../ozone/client/protocol/ClientProtocol.java | 8 +
.../apache/hadoop/ozone/client/rpc/RpcClient.java | 6 +
.../main/java/org/apache/hadoop/ozone/OmUtils.java | 2 +
.../ozone/om/helpers/CallerIdentityInfo.java | 88 +++++++++
.../apache/hadoop/ozone/om/helpers/S3STSUtils.java | 34 +++-
.../ozone/om/protocol/OzoneManagerProtocol.java | 10 ++
...OzoneManagerProtocolClientSideTranslatorPB.java | 10 ++
.../ozone/om/helpers/TestCallerIdentityInfo.java | 107 +++++++++++
.../om/helpers/TestS3STSUtilsCallerIdentity.java | 59 ++++++
.../smoketest/security/ozone-secure-sts.resource | 37 ++++
.../main/smoketest/security/ozone-secure-sts.robot | 32 ++++
.../src/main/proto/OmClientProtocol.proto | 14 ++
.../request/s3/security/S3AssumeRoleRequest.java | 199 +++++++++++++++++++--
.../protocolPB/OzoneManagerRequestHandler.java | 24 +++
.../hadoop/ozone/security/STSTokenIdentifier.java | 53 +++++-
.../ozone/security/STSTokenSecretManager.java | 158 ++++++++++++++--
.../s3/security/TestS3AssumeRoleRequest.java | 41 ++++-
.../hadoop/ozone/security/TestSTSSecurityUtil.java | 84 +++++----
.../ozone/security/TestSTSTokenIdentifier.java | 14 ++
.../ozone/security/TestSTSTokenSecretManager.java | 25 ++-
.../org/apache/hadoop/ozone/audit/S3GAction.java | 1 +
.../hadoop/ozone/s3/util/S3GActionIamMapper.java | 1 +
.../ozone/s3sts/S3AssumeRoleResponseXml.java | 23 +--
.../s3sts/S3GetCallerIdentityResponseXml.java | 92 ++++++++++
.../apache/hadoop/ozone/s3sts/S3STSEndpoint.java | 126 +++++++++----
.../hadoop/ozone/s3sts/S3STSResponseMetadata.java | 42 +++++
.../hadoop/ozone/client/ClientProtocolStub.java | 6 +
.../ozone/s3/util/TestS3GActionIamMapper.java | 1 +
.../hadoop/ozone/s3sts/TestS3STSEndpoint.java | 79 ++++++++
31 files changed, 1261 insertions(+), 137 deletions(-)
diff --git a/hadoop-hdds/docs/content/design/ozone-sts.md
b/hadoop-hdds/docs/content/design/ozone-sts.md
index 9963534d848..ccca985ae57 100644
--- a/hadoop-hdds/docs/content/design/ozone-sts.md
+++ b/hadoop-hdds/docs/content/design/ozone-sts.md
@@ -41,8 +41,9 @@ solutions that want to aggregate data across multiple cloud
providers.
# 3. How Ozone STS Works
-The initial implementation of Ozone STS supports only the
[AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html)
-API from the AWS specification. A new STS endpoint on port `9880` (port
`9881` for https) will be created to service STS requests in the S3 Gateway at
the root path (`/`).
+The initial implementation of Ozone STS supports the
[AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html)
+and
[GetCallerIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html)
+APIs from the AWS specification. A new STS endpoint on port `9880` (port
`9881` for https) will be created to service STS requests in the S3 Gateway at
the root path (`/`).
We use a separate port for STS to align with AWS so we don't have conflicts at
a later time. This means we have:
- Admin port for Ozone specific S3 admin operations
- STS port for STS APIs, analogous to AWS' separate STS endpoint
@@ -66,6 +67,11 @@ return value of the AssumeRole call will be temporary
credentials consisting of
an IAM policy is specified, the temporary credential will have the permissions
comprising the intersection of the role permissions
and the IAM policy permissions. **Note:** If the IAM policy is specified and
does not grant any permissions, then
the generated temporary credentials won't have any permissions and will
essentially be useless.
+-
[GetCallerIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetCallerIdentity.html)
returns the account,
+ARN, and user ID for the caller credentials used to sign the request. Ozone
uses a static account ID of `123456789012`.
+For permanent S3 credentials, `UserId` is the resolved Kerberos principal and
`Arn` is `arn:aws:iam::123456789012:user/<kerberosShortName>`
+where `<kerberosShortName>` is the short username of the Kerberos principal.
For STS temporary credentials, `UserId` is
+the `AssumedRoleId` and `Arn` is the assumed-role user ARN from the session
token.
## 3.2 Limitations in AssumeRole API Support
@@ -151,6 +157,8 @@ credential will have the permissions and actions comprising
the intersection of
- creation time of the token (via `OMTokenProto#issueDate`, exposed as
`STSTokenIdentifier#getCreationTime()`)
- expiration time of the token (via `ShortLivedTokenIdentifier#getExpiry()`)
- UUID of the OzoneManager secret key used to sign the sessionToken and
encrypt the secretAccessKey (via `ShortLivedTokenIdentifier#getSecretKeyId()`)
+- assumedRoleId - the generated identifier of the role from the AssumeRole
call response (this is used for GetCallerIdentity api)
+- assumedRoleUserArn - the arn from the AssumeRole call response (this is used
for GetCallerIdentity api)
## 3.5 STS Token Revocation
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 ce0f780b72d..2cc610d4007 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
@@ -37,6 +37,7 @@
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.CallerIdentityInfo;
import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
import org.apache.hadoop.ozone.om.helpers.OmVolumeArgs;
import org.apache.hadoop.ozone.om.helpers.S3SecretValue;
@@ -812,6 +813,15 @@ public AssumeRoleResponseInfo assumeRole(String roleArn,
String roleSessionName,
return proxy.assumeRole(roleArn, roleSessionName, durationSeconds,
awsIamSessionPolicy, requestId);
}
+ /**
+ * Returns the caller identity for the current S3-authenticated request.
+ * @return CallerIdentityInfo containing account, arn, and userId
+ * @throws IOException if an error occurs during the GetCallerIdentity
operation
+ */
+ public CallerIdentityInfo getCallerIdentity() throws IOException {
+ return proxy.getCallerIdentity();
+ }
+
/**
* Revokes STS tokens for the given original access key ID.
* @param originalAccessKeyId The original long-lived access key ID
whose STS tokens to revoke
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 b5f7baa0ef2..73f099879bc 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
@@ -48,6 +48,7 @@
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.CallerIdentityInfo;
import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
import org.apache.hadoop.ozone.om.helpers.LeaseKeyInfo;
@@ -1647,6 +1648,13 @@ void deleteObjectTagging(String volumeName, String
bucketName, String keyName)
AssumeRoleResponseInfo assumeRole(String roleArn, String roleSessionName,
int durationSeconds,
String awsIamSessionPolicy, String requestId) throws IOException;
+ /**
+ * Returns the caller identity for the current S3-authenticated request.
+ * @return CallerIdentityInfo containing account, arn, and userId
+ * @throws IOException if an error occurs during the GetCallerIdentity
operation
+ */
+ CallerIdentityInfo getCallerIdentity() throws IOException;
+
/**
* Revokes STS tokens for the given original access key ID.
* @param originalAccessKeyId The original long-lived access key ID
whose STS tokens to revoke
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 9ca47013462..dcba71af525 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
@@ -133,6 +133,7 @@
import org.apache.hadoop.ozone.om.helpers.BasicOmKeyInfo;
import org.apache.hadoop.ozone.om.helpers.BucketEncryptionKeyInfo;
import org.apache.hadoop.ozone.om.helpers.BucketLayout;
+import org.apache.hadoop.ozone.om.helpers.CallerIdentityInfo;
import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
import org.apache.hadoop.ozone.om.helpers.KeyInfoWithVolumeContext;
@@ -3021,6 +3022,11 @@ public AssumeRoleResponseInfo assumeRole(String roleArn,
String roleSessionName,
return ozoneManagerClient.assumeRole(roleArn, roleSessionName,
durationSeconds, awsIamSessionPolicy, requestId);
}
+ @Override
+ public CallerIdentityInfo getCallerIdentity() throws IOException {
+ return ozoneManagerClient.getCallerIdentity();
+ }
+
@Override
public void revokeSTSToken(String originalAccessKeyId) throws IOException {
ozoneManagerClient.revokeSTSToken(originalAccessKeyId);
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 5da80215fad..31530900cf2 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
@@ -238,6 +238,7 @@ public static boolean isReadOnly(OMRequest omRequest) {
case FinalizeUpgradeProgress:
case PrepareStatus:
case GetS3VolumeContext:
+ case GetCallerIdentity:
case ListTenant:
case TenantGetUserInfo:
case TenantListUser:
@@ -383,6 +384,7 @@ public static boolean shouldSendToFollower(OMRequest
omRequest) {
case FinalizeUpgradeProgress:
case PrepareStatus:
case GetS3VolumeContext:
+ case GetCallerIdentity:
case ListTenant:
case TenantGetUserInfo:
case TenantListUser:
diff --git
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/CallerIdentityInfo.java
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/CallerIdentityInfo.java
new file mode 100644
index 00000000000..5a014f19fcf
--- /dev/null
+++
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/CallerIdentityInfo.java
@@ -0,0 +1,88 @@
+/*
+ * 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.GetCallerIdentityResponse;
+
+/**
+ * Utility class to handle GetCallerIdentityResponse protobuf message.
+ */
+@Immutable
+public class CallerIdentityInfo {
+
+ private final String account;
+ private final String arn;
+ private final String userId;
+
+ public CallerIdentityInfo(String account, String arn, String userId) {
+ this.account = account;
+ this.arn = arn;
+ this.userId = userId;
+ }
+
+ public String getAccount() {
+ return account;
+ }
+
+ public String getArn() {
+ return arn;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public static CallerIdentityInfo fromProtobuf(GetCallerIdentityResponse
response) {
+ return new CallerIdentityInfo(response.getAccount(), response.getArn(),
response.getUserId());
+ }
+
+ public GetCallerIdentityResponse getProtobuf() {
+ return GetCallerIdentityResponse.newBuilder()
+ .setAccount(account)
+ .setArn(arn)
+ .setUserId(userId)
+ .build();
+ }
+
+ @Override
+ public String toString() {
+ return "CallerIdentityInfo{" + "account='" + account + "', arn='" + arn +
"', userId='" + userId + "'}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final CallerIdentityInfo that = (CallerIdentityInfo) o;
+ return Objects.equals(account, that.account) && Objects.equals(arn,
that.arn) &&
+ Objects.equals(userId, that.userId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(account, arn, userId);
+ }
+}
diff --git
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/S3STSUtils.java
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/S3STSUtils.java
index 64221089655..17bf084f9b3 100644
---
a/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/S3STSUtils.java
+++
b/hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/S3STSUtils.java
@@ -44,11 +44,43 @@ public final class S3STSUtils {
public static final String STS_ACCESS_KEY_ID_ALLOWED_CHARS =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final int STS_ACCESS_KEY_ID_ALLOWED_CHARS_LENGTH =
STS_ACCESS_KEY_ID_ALLOWED_CHARS.length();
public static final int STS_ACCESS_KEY_ID_RANDOM_LENGTH = 20;
- public static final int STS_ACCESS_KEY_ID_LENGTH = STS_TOKEN_PREFIX.length()
+ STS_ACCESS_KEY_ID_RANDOM_LENGTH;
+
+ public static final String OZONE_STATIC_ACCOUNT_ID = "123456789012";
private S3STSUtils() {
}
+ /**
+ * Builds an IAM user ARN for the given Kerberos short name.
+ */
+ public static String toIamUserArn(String kerberosShortName) {
+ return "arn:aws:iam::" + OZONE_STATIC_ACCOUNT_ID + ":user/" +
kerberosShortName;
+ }
+
+ /**
+ * Resolves the caller identity for GetCallerIdentity with permanent S3
credentials.
+ *
+ * @param resolvedPrincipal full Kerberos principal of the caller
+ * @param kerberosShortName short username
+ * @return caller identity with account, arn, and userId
+ */
+ public static CallerIdentityInfo
resolveCallerIdentityForPermanentCredentials(String resolvedPrincipal,
+ String kerberosShortName) {
+ return new CallerIdentityInfo(OZONE_STATIC_ACCOUNT_ID,
toIamUserArn(kerberosShortName), resolvedPrincipal);
+ }
+
+ /**
+ * Resolves the caller identity for GetCallerIdentity with temporary STS
credentials.
+ *
+ * @param assumedRoleId assumed role ID from the STS token
+ * @param assumedRoleUserArn assumed role user ARN from the STS token
+ * @return caller identity with account, arn, and userId
+ */
+ public static CallerIdentityInfo
resolveCallerIdentityForStsCredentials(String assumedRoleId,
+ String assumedRoleUserArn) {
+ return new CallerIdentityInfo(OZONE_STATIC_ACCOUNT_ID, assumedRoleUserArn,
assumedRoleId);
+ }
+
/**
* Adds standard AssumeRole audit params.
*/
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 46254e3d6f6..7cea19d7a90 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
@@ -30,6 +30,7 @@
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.CallerIdentityInfo;
import org.apache.hadoop.ozone.om.helpers.DBUpdates;
import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
@@ -1335,6 +1336,15 @@ default AssumeRoleResponseInfo assumeRole(String
roleArn, String roleSessionName
throw new UnsupportedOperationException("OzoneManager does not require
this to be implemented");
}
+ /**
+ * Returns the caller identity for the current S3-authenticated request.
+ * @return CallerIdentityInfo containing account, arn, and userId
+ * @throws IOException if an error occurs during the GetCallerIdentity
operation
+ */
+ default CallerIdentityInfo getCallerIdentity() throws IOException {
+ throw new UnsupportedOperationException("OzoneManager does not require
this to be implemented");
+ }
+
/**
* Revokes STS tokens for the given original access key ID.
* @param originalAccessKeyId The original long-lived access key ID
whose STS tokens to revoke
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 7077fd1b02c..b5a9e3a2bbc 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
@@ -60,6 +60,7 @@
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.CallerIdentityInfo;
import org.apache.hadoop.ozone.om.helpers.DBUpdates;
import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
@@ -2980,6 +2981,15 @@ public AssumeRoleResponseInfo assumeRole(String roleArn,
String roleSessionName,
handleError(submitRequest(omRequest)).getAssumeRoleResponse());
}
+ @Override
+ public CallerIdentityInfo getCallerIdentity() throws IOException {
+ final OMRequest omRequest = createOMRequest(Type.GetCallerIdentity)
+
.setGetCallerIdentityRequest(OzoneManagerProtocolProtos.GetCallerIdentityRequest.newBuilder().build())
+ .build();
+
+ return
CallerIdentityInfo.fromProtobuf(handleError(submitRequest(omRequest)).getGetCallerIdentityResponse());
+ }
+
@Override
public void revokeSTSToken(String originalAccessKeyId) throws IOException {
final OzoneManagerProtocolProtos.RevokeSTSTokenRequest request =
diff --git
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestCallerIdentityInfo.java
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestCallerIdentityInfo.java
new file mode 100644
index 00000000000..f6bc0334acf
--- /dev/null
+++
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestCallerIdentityInfo.java
@@ -0,0 +1,107 @@
+/*
+ * 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.GetCallerIdentityResponse;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test CallerIdentityInfo.
+ */
+public class TestCallerIdentityInfo {
+
+ private static final String ACCOUNT = "123456789012";
+ private static final String ARN = "arn:aws:iam::123456789012:user/om";
+ private static final String USER_ID = "om/[email protected]";
+
+ @Test
+ public void testConstructor() {
+ final CallerIdentityInfo identity = new CallerIdentityInfo(ACCOUNT, ARN,
USER_ID);
+
+ assertEquals(ACCOUNT, identity.getAccount());
+ assertEquals(ARN, identity.getArn());
+ assertEquals(USER_ID, identity.getUserId());
+ }
+
+ @Test
+ public void testProtobufConversion() {
+ final CallerIdentityInfo identity = new CallerIdentityInfo(ACCOUNT, ARN,
USER_ID);
+
+ final GetCallerIdentityResponse proto = identity.getProtobuf();
+
+ assertNotNull(proto);
+ assertEquals(ACCOUNT, proto.getAccount());
+ assertEquals(ARN, proto.getArn());
+ assertEquals(USER_ID, proto.getUserId());
+ }
+
+ @Test
+ public void testFromProtobuf() {
+ final GetCallerIdentityResponse proto =
GetCallerIdentityResponse.newBuilder()
+ .setAccount(ACCOUNT)
+ .setArn(ARN)
+ .setUserId(USER_ID)
+ .build();
+
+ final CallerIdentityInfo identity = CallerIdentityInfo.fromProtobuf(proto);
+
+ assertEquals(ACCOUNT, identity.getAccount());
+ assertEquals(ARN, identity.getArn());
+ assertEquals(USER_ID, identity.getUserId());
+ }
+
+ @Test
+ public void testProtobufRoundTrip() {
+ final CallerIdentityInfo original = new CallerIdentityInfo(ACCOUNT, ARN,
USER_ID);
+
+ final CallerIdentityInfo recovered =
CallerIdentityInfo.fromProtobuf(original.getProtobuf());
+
+ assertEquals(original, recovered);
+ }
+
+ @Test
+ public void testEqualsAndHashCodeWithIdenticalObjects() {
+ final CallerIdentityInfo identity1 = new CallerIdentityInfo(ACCOUNT, ARN,
USER_ID);
+ final CallerIdentityInfo identity2 = new CallerIdentityInfo(ACCOUNT, ARN,
USER_ID);
+
+ assertEquals(identity1, identity2);
+ assertEquals(identity1.hashCode(), identity2.hashCode());
+ }
+
+ @Test
+ public void testNotEqualsWithDifferentArn() {
+ final CallerIdentityInfo identity1 = new CallerIdentityInfo(ACCOUNT, ARN,
USER_ID);
+ final CallerIdentityInfo identity2 = new CallerIdentityInfo(
+ ACCOUNT, "arn:aws:iam::123456789012:user/other", USER_ID);
+
+ assertNotEquals(identity1, identity2);
+ assertNotEquals(identity1.hashCode(), identity2.hashCode());
+ }
+
+ @Test
+ public void testToString() {
+ final CallerIdentityInfo identity = new CallerIdentityInfo(ACCOUNT, ARN,
USER_ID);
+
+ assertEquals(
+ "CallerIdentityInfo{account='123456789012', arn='" + ARN + "',
userId='" + USER_ID + "'}", identity.toString());
+ }
+}
diff --git
a/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestS3STSUtilsCallerIdentity.java
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestS3STSUtilsCallerIdentity.java
new file mode 100644
index 00000000000..f991bd7bffd
--- /dev/null
+++
b/hadoop-ozone/common/src/test/java/org/apache/hadoop/ozone/om/helpers/TestS3STSUtilsCallerIdentity.java
@@ -0,0 +1,59 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+
+/**
+ * Test caller identity resolution helpers in S3STSUtils.
+ */
+public class TestS3STSUtilsCallerIdentity {
+
+ private static final String PRINCIPAL = "om/[email protected]";
+ private static final String KERBEROS_SHORT_NAME = "om";
+ private static final String ASSUMED_ROLE_ID = "AROATEST123456789:testsess";
+ private static final String ASSUMED_ROLE_USER_ARN =
+ "arn:aws:sts::123456789012:assumed-role/test-role/testsess";
+
+ @Test
+ public void testToIamUserArn() {
+ assertEquals("arn:aws:iam::123456789012:user/om",
S3STSUtils.toIamUserArn(KERBEROS_SHORT_NAME));
+ }
+
+ @Test
+ public void testResolveCallerIdentityForPermanentCredentials() {
+ final CallerIdentityInfo identity =
S3STSUtils.resolveCallerIdentityForPermanentCredentials(
+ PRINCIPAL, KERBEROS_SHORT_NAME);
+
+ assertEquals(S3STSUtils.OZONE_STATIC_ACCOUNT_ID, identity.getAccount());
+ assertEquals("arn:aws:iam::123456789012:user/om", identity.getArn());
+ assertEquals(PRINCIPAL, identity.getUserId());
+ }
+
+ @Test
+ public void testResolveCallerIdentityForStsCredentials() {
+ final CallerIdentityInfo identity =
S3STSUtils.resolveCallerIdentityForStsCredentials(
+ ASSUMED_ROLE_ID, ASSUMED_ROLE_USER_ARN);
+
+ assertEquals(S3STSUtils.OZONE_STATIC_ACCOUNT_ID, identity.getAccount());
+ assertEquals(ASSUMED_ROLE_USER_ARN, identity.getArn());
+ assertEquals(ASSUMED_ROLE_ID, identity.getUserId());
+ }
+}
diff --git
a/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.resource
b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.resource
index ebfbe55a653..4ebf2ec1b3b 100644
--- a/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.resource
+++ b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.resource
@@ -111,10 +111,14 @@ Assume Role And Get Temporary Credentials
${stsAccessKeyId} = Execute echo '${json}'
| jq -r '.Credentials.AccessKeyId'
${stsSecretKey} = Execute echo '${json}'
| jq -r '.Credentials.SecretAccessKey'
${stsSessionToken} = Execute echo '${json}'
| jq -r '.Credentials.SessionToken'
+ ${assumedRoleId} = Execute echo '${json}'
| jq -r '.AssumedRoleUser.AssumedRoleId'
+ ${assumedRoleUserArn} = Execute echo '${json}'
| jq -r '.AssumedRoleUser.Arn'
Should Start With ${stsAccessKeyId} ASIA
Set Global Variable ${STS_ACCESS_KEY_ID}
${stsAccessKeyId}
Set Global Variable ${STS_SECRET_KEY} ${stsSecretKey}
Set Global Variable ${STS_SESSION_TOKEN}
${stsSessionToken}
+ Set Global Variable ${STS_ASSUMED_ROLE_ID}
${assumedRoleId}
+ Set Global Variable ${STS_ASSUMED_ROLE_USER_ARN}
${assumedRoleUserArn}
${expected_duration} = Set Variable
${duration_seconds}
# Ensure the expected duration defaults to 3600 seconds (1 hour) if not
specified
@@ -336,3 +340,36 @@ Assert Listed Keys Should Equal
Sort List ${expected_sorted}
${actual_list} = Evaluate
json.loads($actual_keys_json) modules=json
Lists Should Be Equal ${actual_list}
${expected_sorted}
+
+Get Caller Identity
+ [Arguments] ${profile}
+ ${json} = Execute aws sts
get-caller-identity --endpoint-url ${STS_ENDPOINT_URL} --output json --profile
${profile}
+ ${account} = Execute echo '${json}'
| jq -r '.Account'
+ ${arn} = Execute echo '${json}'
| jq -r '.Arn'
+ ${userId} = Execute echo '${json}'
| jq -r '.UserId'
+ [Return] ${json} ${account}
${arn} ${userId}
+
+Get Caller Identity Using Curl
+ # AWS CLI always sends Version=2011-06-15 and rejects unknown flags, so
use curl to test version validation.
+ # curl 7.76.1's --aws-sigv4 omits the port from the signed canonical
"host" header (it signs "host:s3g"
+ # while sending "Host: s3g:9880"), so Ozone's SigV4 validation for the
non-default STS port rejects it.
+ # Send an explicit Host header without the port so the sent and signed
host values match.
+ # This should also work for newer versions of curl as well
+ [Arguments] ${perm_access_key_id} ${perm_secret_key}
${api_version}=2011-06-15 ${extra_curl_params}=${EMPTY}
+ ${sts_host} = Evaluate
urllib.parse.urlparse("${STS_ENDPOINT_URL}").hostname modules=urllib.parse
+ ${cmd} = Set Variable curl --silent
--show-error --include --request POST --aws-sigv4 "aws:amz:us-east-1:sts"
--user '${perm_access_key_id}:${perm_secret_key}' --header "Host: ${sts_host}"
--header "Content-Type: application/x-www-form-urlencoded" --data-urlencode
"Action=GetCallerIdentity"
+ ${cmd} = Set Variable If
'${api_version}' != '${EMPTY}' ${cmd} --data-urlencode
"Version=${api_version}" ${cmd}
+ ${cmd} = Set Variable If
'${extra_curl_params}' != '${EMPTY}' ${cmd} ${extra_curl_params} ${cmd}
+ ${cmd} = Set Variable ${cmd}
${STS_ENDPOINT_URL}
+ ${output} = Execute And Ignore Error ${cmd}
+ [Return] ${output}
+
+Get Caller Identity Should Fail
+ [Arguments] ${expected_error_contains}
${api_version}=${EMPTY}
+ ${output} = Get Caller Identity Using Curl
${PERMANENT_ACCESS_KEY_ID} ${PERMANENT_SECRET_KEY} api_version=${api_version}
+ Should Contain ${output}
${expected_error_contains}
+ @{http_codes} = Get Regexp Matches ${output}
(?m)^HTTP/[0-9.]+ ([0-9]{3}) 1
+ ${code_count} = Get Length ${http_codes}
+ Should Be True ${code_count} > 0 Expected to
find an HTTP status code in curl output, but none was found.
+ ${http_code} = Get From List ${http_codes}
-1
+ Should Be Equal As Strings ${http_code} 400
diff --git
a/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.robot
b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.robot
index b07a38e1a23..2368b1da68e 100644
--- a/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.robot
+++ b/hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.robot
@@ -1367,6 +1367,38 @@ STS Role On Chained Linked Bucket Grants GetObject On
Source Bucket
Head Bucket Should Succeed ${STS_LINK_BUCKET_CHAIN_FINAL}
Get Object Should Succeed ${STS_LINK_BUCKET_CHAIN_FINAL}
${STS_LINK_BUCKET_CHAIN_TESTFILE}
+Get Caller Identity With Permanent Credentials Should Succeed
+ Configure AWS Profile permanent ${PERMANENT_ACCESS_KEY_ID}
${PERMANENT_SECRET_KEY}
+ ${json} ${account} ${arn} ${userId} = Get Caller
Identity permanent
+ Should Be Equal ${account} 123456789012
+ ${principal} = Execute klist | awk
'/Default principal/ {print $3}'
+ Should Be Equal ${userId} ${principal}
+ Should Be Equal ${arn}
arn:aws:iam::123456789012:user/${ICEBERG_SVC_CATALOG_USER}
+
+Get Caller Identity With STS Credentials Should Succeed
+ Assume Role And Configure STS Profile
perm_access_key_id=${PERMANENT_ACCESS_KEY_ID}
perm_secret_key=${PERMANENT_SECRET_KEY}
role_arn=${ICEBERG_ALL_ACCESS_ROLE_OBS_ARN}
+ ${json} ${account} ${arn} ${userId} = Get Caller
Identity sts
+ Should Be Equal ${account} 123456789012
+ Should Be Equal ${userId}
${STS_ASSUMED_ROLE_ID}
+ Should Be Equal ${arn}
${STS_ASSUMED_ROLE_USER_ARN}
+
+Get Caller Identity Ignores Extra Parameters
+ # curl 7.76.1 produces an invalid SigV4 signature for STS GET requests
with --get --data-urlencode.
+ ${extra_curl_params} = Set Variable
--data-urlencode "RoleArn=${ICEBERG_ALL_ACCESS_ROLE_OBS_ARN}" --data-urlencode
"RoleSessionName=${ROLE_SESSION_NAME}" --data-urlencode "DurationSeconds=3600"
+ ${output} = Get Caller Identity Using Curl
${PERMANENT_ACCESS_KEY_ID} ${PERMANENT_SECRET_KEY}
extra_curl_params=${extra_curl_params}
+ Should Contain ${output} 123456789012
+ @{http_codes} = Get Regexp Matches ${output}
(?m)^HTTP/[0-9.]+ ([0-9]{3}) 1
+ ${code_count} = Get Length ${http_codes}
+ Should Be True ${code_count} > 0 Expected to
find an HTTP status code in curl output, but none was found.
+ ${http_code} = Get From List ${http_codes}
-1
+ Should Be Equal As Strings ${http_code} 200
+
+Get Caller Identity Rejects Missing Version
+ Get Caller Identity Should Fail InvalidAction
+
+Get Caller Identity Rejects Invalid Version
+ Get Caller Identity Should Fail InvalidAction
api_version=2020-01-01
+
Expired STS temporary credentials must return ExpiredToken on S3 APIs
# Increase timeout to account for 15 minute STS token expiration plus the
time to execute the api calls
[Timeout] 25 minutes
diff --git
a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
index 53f7c20e5d9..3702b021ac2 100644
--- a/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
+++ b/hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto
@@ -169,6 +169,7 @@ enum Type {
AssumeRole = 153;
RevokeSTSToken = 154;
DeleteRevokedSTSTokens = 155;
+ GetCallerIdentity = 156;
}
enum SafeMode {
@@ -333,6 +334,7 @@ message OMRequest {
optional RevokeSTSTokenRequest revokeSTSTokenRequest =
155;
optional DeleteRevokedSTSTokensRequest deleteRevokedSTSTokensRequest =
156;
optional UpdateAssumeRoleRequest updateAssumeRoleRequest =
157;
+ optional GetCallerIdentityRequest getCallerIdentityRequest =
158;
}
message OMResponse {
@@ -481,6 +483,7 @@ message OMResponse {
optional AssumeRoleResponse assumeRoleResponse =
153;
optional RevokeSTSTokenResponse revokeSTSTokenResponse =
154;
optional DeleteRevokedSTSTokensResponse deleteRevokedSTSTokensResponse =
155;
+ optional GetCallerIdentityResponse getCallerIdentityResponse =
156;
}
enum Status {
@@ -1600,6 +1603,8 @@ message OMTokenProto {
optional string originalAccessKeyId = 18;
optional string secretAccessKey = 19;
optional string sessionPolicy = 20;
+ optional string assumedRoleId = 21;
+ optional string assumedRoleUserArn = 22;
}
message SecretKeyProto {
@@ -2555,6 +2560,15 @@ message DeleteRevokedSTSTokensRequest {
message DeleteRevokedSTSTokensResponse {
}
+message GetCallerIdentityRequest {
+}
+
+message GetCallerIdentityResponse {
+ optional string account = 1;
+ optional string arn = 2;
+ optional string userId = 3;
+}
+
enum ReadConsistencyProto {
// Unspecified consistency, the read consistency behavior is decided
// by the OM
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 693d280419b..173ae19461f 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
@@ -27,6 +27,9 @@
import com.google.common.base.Strings;
import java.io.IOException;
import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Clock;
import java.time.Instant;
@@ -59,6 +62,7 @@
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.AssumeRoleResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.OMRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.UpdateAssumeRoleRequest;
+import org.apache.hadoop.ozone.security.STSTokenSecretManager;
import org.apache.hadoop.ozone.security.acl.IAccessAuthorizer.ACLType;
import org.apache.hadoop.ozone.security.acl.IOzoneObj;
import org.apache.hadoop.ozone.security.acl.OzoneObj;
@@ -131,23 +135,31 @@ public OMRequest preExecute(OzoneManager ozoneManager)
throws IOException {
S3STSUtils.validateDuration(durationSeconds);
S3STSUtils.validateRoleSessionName(roleSessionName);
final String targetRoleName =
AwsRoleArnValidator.validateAndExtractRoleNameFromArn(roleArn);
-
- // Generate temporary AWS credentials using cryptographically strong
SecureRandom
+
+ // Generate temporary AWS credentials using cryptographically strong
SecureRandom, and a
+ // deterministic roleId derived from the role ARN.
final String tempAccessKeyId = STS_TOKEN_PREFIX +
generateSecureRandomStringUsingChars(
STS_ACCESS_KEY_ID_ALLOWED_CHARS,
STS_ACCESS_KEY_ID_ALLOWED_CHARS_LENGTH,
STS_ACCESS_KEY_ID_RANDOM_LENGTH);
final String secretAccessKey = generateSecureRandomStringUsingChars(
CHARS_FOR_SECRET_ACCESS_KEYS, CHARS_FOR_SECRET_ACCESS_KEYS_LENGTH,
STS_SECRET_ACCESS_KEY_LENGTH);
- final String roleId = ASSUME_ROLE_ID_PREFIX +
generateSecureRandomStringUsingChars(
- STS_ACCESS_KEY_ID_ALLOWED_CHARS,
STS_ACCESS_KEY_ID_ALLOWED_CHARS_LENGTH,
- STS_ROLE_ID_LENGTH);
+ final String roleId = generateDeterministicRoleId(roleArn);
+ final String assumedRoleId = roleId + ":" + roleSessionName;
+ final String assumedRoleUserArn =
S3STSUtils.toAssumedRoleUserArn(roleArn, roleSessionName);
final Instant creationInstant = clock.instant();
- final String sessionToken = generateSessionToken(
- targetRoleName, omRequest, ozoneManager, assumeRoleRequest,
secretAccessKey, tempAccessKeyId,
- creationInstant);
+ final String sessionToken =
generateSessionToken(GenerateSessionTokenParams.newBuilder()
+ .setTargetRoleName(targetRoleName)
+ .setOmRequest(omRequest)
+ .setOzoneManager(ozoneManager)
+ .setAssumeRoleRequest(assumeRoleRequest)
+ .setSecretAccessKey(secretAccessKey)
+ .setTempAccessKeyId(tempAccessKeyId)
+ .setAssumedRoleId(assumedRoleId)
+ .setAssumedRoleUserArn(assumedRoleUserArn)
+ .setCreationTime(creationInstant)
+ .build());
final long expirationEpochSeconds =
creationInstant.plusSeconds(durationSeconds).getEpochSecond();
-
auditMap.put(OzoneConsts.S3_STS_TEMP_ACCESS_KEY_ID, tempAccessKeyId);
// Build UpdateAssumeRoleRequest with leader-generated credentials and
session token
@@ -245,9 +257,10 @@ public OMClientResponse
validateAndUpdateCache(OzoneManager ozoneManager, Execut
/**
* Generates session token using components from the AssumeRoleRequest.
*/
- private String generateSessionToken(String targetRoleName, OMRequest
omRequest,
- OzoneManager ozoneManager, AssumeRoleRequest assumeRoleRequest, String
secretAccessKey,
- String tempAccessKeyId, Instant creationInstant) throws IOException {
+ private String generateSessionToken(GenerateSessionTokenParams params)
throws IOException {
+ final OzoneManager ozoneManager = params.getOzoneManager();
+ final OMRequest omRequest = params.getOmRequest();
+ final AssumeRoleRequest assumeRoleRequest = params.getAssumeRoleRequest();
InetAddress remoteIp = ProtobufRpcEngine.Server.getRemoteIp();
if (remoteIp == null) {
@@ -268,11 +281,148 @@ private String generateSessionToken(String
targetRoleName, OMRequest omRequest,
final String roleArn = assumeRoleRequest.getRoleArn();
final String sessionPolicy = getSessionPolicy(
ozoneManager, originalAccessKeyId,
assumeRoleRequest.getAwsIamSessionPolicy(), hostName, remoteIp, ugi,
- targetRoleName);
+ params.getTargetRoleName());
return ozoneManager.getSTSTokenSecretManager().createSTSTokenString(
- tempAccessKeyId, originalAccessKeyId, roleArn,
assumeRoleRequest.getDurationSeconds(), secretAccessKey,
- sessionPolicy, creationInstant);
+ STSTokenSecretManager.CreateSTSTokenParams.newBuilder()
+ .setTempAccessKeyId(params.getTempAccessKeyId())
+ .setOriginalAccessKeyId(originalAccessKeyId)
+ .setRoleArn(roleArn)
+ .setDurationSeconds(assumeRoleRequest.getDurationSeconds())
+ .setSecretAccessKey(params.getSecretAccessKey())
+ .setSessionPolicy(sessionPolicy)
+ .setAssumedRoleId(params.getAssumedRoleId())
+ .setAssumedRoleUserArn(params.getAssumedRoleUserArn())
+ .setCreationTime(params.getCreationTime())
+ .build());
+ }
+
+ /**
+ * Parameters for {@link #generateSessionToken(GenerateSessionTokenParams)}.
+ */
+ private static final class GenerateSessionTokenParams {
+ private final String targetRoleName;
+ private final OMRequest omRequest;
+ private final OzoneManager ozoneManager;
+ private final AssumeRoleRequest assumeRoleRequest;
+ private final String secretAccessKey;
+ private final String tempAccessKeyId;
+ private final String assumedRoleId;
+ private final String assumedRoleUserArn;
+ private final Instant creationInstant;
+
+ private GenerateSessionTokenParams(Builder builder) {
+ this.targetRoleName = builder.targetRoleName;
+ this.omRequest = builder.omRequest;
+ this.ozoneManager = builder.ozoneManager;
+ this.assumeRoleRequest = builder.assumeRoleRequest;
+ this.secretAccessKey = builder.secretAccessKey;
+ this.tempAccessKeyId = builder.tempAccessKeyId;
+ this.assumedRoleId = builder.assumedRoleId;
+ this.assumedRoleUserArn = builder.assumedRoleUserArn;
+ this.creationInstant = builder.creationInstant;
+ }
+
+ static Builder newBuilder() {
+ return new Builder();
+ }
+
+ String getTargetRoleName() {
+ return targetRoleName;
+ }
+
+ OMRequest getOmRequest() {
+ return omRequest;
+ }
+
+ OzoneManager getOzoneManager() {
+ return ozoneManager;
+ }
+
+ AssumeRoleRequest getAssumeRoleRequest() {
+ return assumeRoleRequest;
+ }
+
+ String getSecretAccessKey() {
+ return secretAccessKey;
+ }
+
+ String getTempAccessKeyId() {
+ return tempAccessKeyId;
+ }
+
+ String getAssumedRoleId() {
+ return assumedRoleId;
+ }
+
+ String getAssumedRoleUserArn() {
+ return assumedRoleUserArn;
+ }
+
+ Instant getCreationTime() {
+ return creationInstant;
+ }
+
+ private static final class Builder {
+ private String targetRoleName;
+ private OMRequest omRequest;
+ private OzoneManager ozoneManager;
+ private AssumeRoleRequest assumeRoleRequest;
+ private String secretAccessKey;
+ private String tempAccessKeyId;
+ private String assumedRoleId;
+ private String assumedRoleUserArn;
+ private Instant creationInstant;
+
+ Builder setTargetRoleName(String value) {
+ this.targetRoleName = value;
+ return this;
+ }
+
+ Builder setOmRequest(OMRequest value) {
+ this.omRequest = value;
+ return this;
+ }
+
+ Builder setOzoneManager(OzoneManager value) {
+ this.ozoneManager = value;
+ return this;
+ }
+
+ Builder setAssumeRoleRequest(AssumeRoleRequest value) {
+ this.assumeRoleRequest = value;
+ return this;
+ }
+
+ Builder setSecretAccessKey(String value) {
+ this.secretAccessKey = value;
+ return this;
+ }
+
+ Builder setTempAccessKeyId(String value) {
+ this.tempAccessKeyId = value;
+ return this;
+ }
+
+ Builder setAssumedRoleId(String value) {
+ this.assumedRoleId = value;
+ return this;
+ }
+
+ Builder setAssumedRoleUserArn(String value) {
+ this.assumedRoleUserArn = value;
+ return this;
+ }
+
+ Builder setCreationTime(Instant instant) {
+ this.creationInstant = instant;
+ return this;
+ }
+
+ GenerateSessionTokenParams build() {
+ return new GenerateSessionTokenParams(this);
+ }
+ }
}
/**
@@ -428,6 +578,25 @@ interface BucketLinkResolver {
ResolvedBucket resolve(String volumeName, String bucketName) throws
IOException;
}
+ /**
+ * Generates a deterministic role ID from the role ARN so the same role
returns the same ID on every
+ * AssumeRole invocation, matching AWS behavior where RoleId is stable for a
given role.
+ */
+ @VisibleForTesting
+ static String generateDeterministicRoleId(String roleArn) {
+ try {
+ final MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ final byte[] hash =
digest.digest(roleArn.getBytes(StandardCharsets.UTF_8));
+ final StringBuilder sb = new StringBuilder(STS_ROLE_ID_LENGTH);
+ for (int i = 0; i < STS_ROLE_ID_LENGTH; i++) {
+ sb.append(STS_ACCESS_KEY_ID_ALLOWED_CHARS.charAt((hash[i] & 0xFF) %
STS_ACCESS_KEY_ID_ALLOWED_CHARS_LENGTH));
+ }
+ return ASSUME_ROLE_ID_PREFIX + sb;
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("SHA-256 not available", e);
+ }
+ }
+
/**
* Generates a cryptographically strong String of the supplied stringLength
using supplied chars.
*/
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
index 21afb1e42f7..0ff43231856 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/protocolPB/OzoneManagerRequestHandler.java
@@ -61,6 +61,7 @@
import org.apache.hadoop.hdds.scm.protocolPB.OzonePBHelper;
import org.apache.hadoop.hdds.utils.FaultInjector;
import org.apache.hadoop.ozone.OzoneAcl;
+import org.apache.hadoop.ozone.om.OzoneAclUtils;
import org.apache.hadoop.ozone.om.OzoneManager;
import org.apache.hadoop.ozone.om.OzoneManagerPrepareState;
import org.apache.hadoop.ozone.om.exceptions.OMException;
@@ -86,6 +87,7 @@
import org.apache.hadoop.ozone.om.helpers.OpenKeySession;
import org.apache.hadoop.ozone.om.helpers.OzoneFileStatus;
import org.apache.hadoop.ozone.om.helpers.OzoneFileStatusLight;
+import org.apache.hadoop.ozone.om.helpers.S3STSUtils;
import org.apache.hadoop.ozone.om.helpers.ServiceInfo;
import org.apache.hadoop.ozone.om.helpers.ServiceInfoEx;
import org.apache.hadoop.ozone.om.helpers.SnapshotDiffJob;
@@ -113,6 +115,7 @@
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.FinalizeUpgradeProgressResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetBucketTaggingRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetBucketTaggingResponse;
+import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetCallerIdentityResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetFileStatusRequest;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetFileStatusResponse;
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.GetKeyInfoRequest;
@@ -171,11 +174,13 @@
import
org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.TenantListUserResponse;
import org.apache.hadoop.ozone.protocol.proto.OzoneManagerProtocolProtos.Type;
import org.apache.hadoop.ozone.request.validation.RequestProcessingPhase;
+import org.apache.hadoop.ozone.security.STSTokenIdentifier;
import org.apache.hadoop.ozone.security.acl.OzoneObjInfo;
import org.apache.hadoop.ozone.snapshot.ListSnapshotResponse;
import org.apache.hadoop.ozone.upgrade.UpgradeFinalization.StatusAndMessages;
import org.apache.hadoop.ozone.util.PayloadUtils;
import org.apache.hadoop.ozone.util.ProtobufUtils;
+import org.apache.hadoop.security.UserGroupInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -316,6 +321,9 @@ public OMResponse handleReadRequest(OMRequest request) {
getS3VolumeContext();
responseBuilder.setGetS3VolumeContextResponse(s3VolumeContextResponse);
break;
+ case GetCallerIdentity:
+ responseBuilder.setGetCallerIdentityResponse(getCallerIdentity());
+ break;
case TenantGetUserInfo:
impl.checkS3MultiTenancyEnabled();
TenantGetUserInfoResponse getUserInfoResponse = tenantGetUserInfo(
@@ -1448,6 +1456,22 @@ private GetS3VolumeContextResponse getS3VolumeContext()
return impl.getS3VolumeContext().getProtobuf();
}
+ private GetCallerIdentityResponse getCallerIdentity() throws OMException {
+ impl.checkS3STSEnabled();
+ if (OzoneManager.getS3Auth() == null) {
+ throw new OMException(
+ "GetCallerIdentity does not have S3 authentication",
OMException.ResultCodes.INVALID_REQUEST);
+ }
+ final STSTokenIdentifier stsTokenIdentifier =
OzoneManager.getStsTokenIdentifier();
+ if (stsTokenIdentifier != null) {
+ return S3STSUtils.resolveCallerIdentityForStsCredentials(
+ stsTokenIdentifier.getAssumedRoleId(),
stsTokenIdentifier.getAssumedRoleUserArn()).getProtobuf();
+ }
+ final String resolvedPrincipal =
OzoneAclUtils.accessIdToUserPrincipal(OzoneManager.getS3AuthEffectiveAccessId());
+ final String kerberosShortName =
UserGroupInformation.createRemoteUser(resolvedPrincipal).getShortUserName();
+ return
S3STSUtils.resolveCallerIdentityForPermanentCredentials(resolvedPrincipal,
kerberosShortName).getProtobuf();
+ }
+
@DisallowedUntilLayoutVersion(FILESYSTEM_SNAPSHOT)
private SnapshotDiffResponse snapshotDiff(
SnapshotDiffRequest snapshotDiffRequest) throws IOException {
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 e5629073c5a..c0928d93a2c 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
@@ -48,6 +48,8 @@ public class STSTokenIdentifier extends
ShortLivedTokenIdentifier {
private String originalAccessKeyId;
private String secretAccessKey;
private String sessionPolicy;
+ private String assumedRoleId;
+ private String assumedRoleUserArn;
private Instant creationTime;
// SCM secret key used for encrypting sensitive fields and signing this token
@@ -85,6 +87,8 @@ public STSTokenIdentifier(Params params) {
throw new IllegalArgumentException("ManagedSecretKey is not set");
}
}
+ this.assumedRoleId = params.getAssumedRoleId();
+ this.assumedRoleUserArn = params.getAssumedRoleUserArn();
}
/**
@@ -99,6 +103,8 @@ public static final class Params {
private final String secretAccessKey;
private final String sessionPolicy;
private final ManagedSecretKey managedSecretKey;
+ private final String assumedRoleId;
+ private final String assumedRoleUserArn;
private Params(Builder builder) {
this.tempAccessKeyId = builder.tempAccessKeyId;
@@ -109,6 +115,8 @@ private Params(Builder builder) {
this.secretAccessKey = builder.secretAccessKey;
this.sessionPolicy = builder.sessionPolicy;
this.managedSecretKey = builder.managedSecretKey;
+ this.assumedRoleId = builder.assumedRoleId;
+ this.assumedRoleUserArn = builder.assumedRoleUserArn;
}
public static Builder newBuilder() {
@@ -147,6 +155,14 @@ public ManagedSecretKey getManagedSecretKey() {
return managedSecretKey;
}
+ public String getAssumedRoleId() {
+ return assumedRoleId;
+ }
+
+ public String getAssumedRoleUserArn() {
+ return assumedRoleUserArn;
+ }
+
/**
* Builder for {@link Params}.
*/
@@ -159,6 +175,8 @@ public static final class Builder {
private String secretAccessKey;
private String sessionPolicy;
private ManagedSecretKey managedSecretKey;
+ private String assumedRoleId;
+ private String assumedRoleUserArn;
public Builder setTempAccessKeyId(String value) {
this.tempAccessKeyId = value;
@@ -200,6 +218,16 @@ public Builder setManagedSecretKey(ManagedSecretKey value)
{
return this;
}
+ public Builder setAssumedRoleId(String value) {
+ this.assumedRoleId = value;
+ return this;
+ }
+
+ public Builder setAssumedRoleUserArn(String value) {
+ this.assumedRoleUserArn = value;
+ return this;
+ }
+
public Params build() {
return new Params(this);
}
@@ -249,7 +277,9 @@ public OMTokenProto toProtoBuf() throws IOException {
.setRoleArn(roleArn != null ? roleArn : "")
.setSecretAccessKey(secretAccessKey != null ?
encryptSensitiveField(secretAccessKey) : "")
.setSecretKeyId(managedSecretKey.getId().toString())
- .setSessionPolicy(sessionPolicy != null ? sessionPolicy : "");
+ .setSessionPolicy(sessionPolicy != null ? sessionPolicy : "")
+ .setAssumedRoleId(assumedRoleId != null ? assumedRoleId : "")
+ .setAssumedRoleUserArn(assumedRoleUserArn != null ? assumedRoleUserArn
: "");
return builder.build();
}
@@ -292,6 +322,12 @@ public void fromProtoBuf(OMTokenProto token) throws
IOException {
if (token.hasSessionPolicy()) {
this.sessionPolicy = token.getSessionPolicy();
}
+ if (token.hasAssumedRoleId()) {
+ this.assumedRoleId = token.getAssumedRoleId();
+ }
+ if (token.hasAssumedRoleUserArn()) {
+ this.assumedRoleUserArn = token.getAssumedRoleUserArn();
+ }
}
/**
@@ -359,6 +395,14 @@ public String getSessionPolicy() {
return sessionPolicy;
}
+ public String getAssumedRoleId() {
+ return assumedRoleId;
+ }
+
+ public String getAssumedRoleUserArn() {
+ return assumedRoleUserArn;
+ }
+
public Instant getCreationTime() {
return creationTime;
}
@@ -415,13 +459,15 @@ public boolean equals(Object o) {
final STSTokenIdentifier that = (STSTokenIdentifier) o;
return Objects.equals(roleArn, that.roleArn) &&
Objects.equals(secretAccessKey, that.secretAccessKey) &&
Objects.equals(originalAccessKeyId, that.originalAccessKeyId) &&
- Objects.equals(sessionPolicy, that.sessionPolicy) &&
Objects.equals(creationTime, that.creationTime);
+ Objects.equals(sessionPolicy, that.sessionPolicy) &&
Objects.equals(assumedRoleId, that.assumedRoleId) &&
+ Objects.equals(assumedRoleUserArn, that.assumedRoleUserArn) &&
Objects.equals(creationTime, that.creationTime);
}
@Override
public int hashCode() {
return Objects.hash(
- super.hashCode(), roleArn, secretAccessKey, originalAccessKeyId,
sessionPolicy, creationTime);
+ super.hashCode(), roleArn, secretAccessKey, originalAccessKeyId,
sessionPolicy, assumedRoleId,
+ assumedRoleUserArn, creationTime);
}
@Override
@@ -429,6 +475,7 @@ public String toString() {
// Intentionally left off secretAccessKey
return "STSTokenIdentifier{" + "tempAccessKeyId='" + getOwnerId() + "'" +
", originalAccessKeyId='" + originalAccessKeyId + "', roleArn='" +
roleArn + "'" +
+ ", assumedRoleId='" + assumedRoleId + "', assumedRoleUserArn='" +
assumedRoleUserArn + "'" +
", creationTime='" + creationTime + "', expiry='" + getExpiry() + "',
secretKeyId='" + getSecretKeyId() +
"', sessionPolicy='" + sessionPolicy + "'}";
}
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
index d9e5c4caf76..2a7b7b1feb2 100644
---
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
@@ -75,34 +75,160 @@ public Token<STSTokenIdentifier>
generateToken(STSTokenIdentifier tokenIdentifie
/**
* 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
- * @param creationTime token creation time
+ * @param params the STS token creation parameters
* @return base64 encoded token string
*/
- public String createSTSTokenString(String tempAccessKeyId, String
originalAccessKeyId, String roleArn,
- int durationSeconds, String secretAccessKey, String sessionPolicy,
Instant creationTime) throws IOException {
- final Instant expiration = creationTime.plusSeconds(durationSeconds);
+ public String createSTSTokenString(CreateSTSTokenParams params) throws
IOException {
+ final Instant creationTime = params.getCreationTime();
+ final Instant expiration =
creationTime.plusSeconds(params.getDurationSeconds());
final STSTokenIdentifier identifier = new
STSTokenIdentifier(STSTokenIdentifier.Params.newBuilder()
- .setTempAccessKeyId(tempAccessKeyId)
- .setOriginalAccessKeyId(originalAccessKeyId)
- .setRoleArn(roleArn)
+ .setTempAccessKeyId(params.getTempAccessKeyId())
+ .setOriginalAccessKeyId(params.getOriginalAccessKeyId())
+ .setRoleArn(params.getRoleArn())
.setCreationTime(creationTime)
.setExpiry(expiration)
- .setSecretAccessKey(secretAccessKey)
- .setSessionPolicy(sessionPolicy)
+ .setSecretAccessKey(params.getSecretAccessKey())
+ .setSessionPolicy(params.getSessionPolicy())
.setManagedSecretKey(secretKeyClient.getCurrentSecretKey())
+ .setAssumedRoleId(params.getAssumedRoleId())
+ .setAssumedRoleUserArn(params.getAssumedRoleUserArn())
.build());
final Token<STSTokenIdentifier> token = generateToken(identifier);
return token.encodeToUrlString();
}
+
+ /**
+ * Parameters for {@link #createSTSTokenString(CreateSTSTokenParams)}.
+ */
+ public static final class CreateSTSTokenParams {
+ private final String tempAccessKeyId;
+ private final String originalAccessKeyId;
+ private final String roleArn;
+ private final int durationSeconds;
+ private final String secretAccessKey;
+ private final String sessionPolicy;
+ private final String assumedRoleId;
+ private final String assumedRoleUserArn;
+ private final Instant creationTime;
+
+ private CreateSTSTokenParams(Builder builder) {
+ this.tempAccessKeyId = builder.tempAccessKeyId;
+ this.originalAccessKeyId = builder.originalAccessKeyId;
+ this.roleArn = builder.roleArn;
+ this.durationSeconds = builder.durationSeconds;
+ this.secretAccessKey = builder.secretAccessKey;
+ this.sessionPolicy = builder.sessionPolicy;
+ this.assumedRoleId = builder.assumedRoleId;
+ this.assumedRoleUserArn = builder.assumedRoleUserArn;
+ this.creationTime = builder.creationTime;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public String getTempAccessKeyId() {
+ return tempAccessKeyId;
+ }
+
+ public String getOriginalAccessKeyId() {
+ return originalAccessKeyId;
+ }
+
+ public String getRoleArn() {
+ return roleArn;
+ }
+
+ public int getDurationSeconds() {
+ return durationSeconds;
+ }
+
+ public String getSecretAccessKey() {
+ return secretAccessKey;
+ }
+
+ public String getSessionPolicy() {
+ return sessionPolicy;
+ }
+
+ public String getAssumedRoleId() {
+ return assumedRoleId;
+ }
+
+ public String getAssumedRoleUserArn() {
+ return assumedRoleUserArn;
+ }
+
+ public Instant getCreationTime() {
+ return creationTime;
+ }
+
+ /**
+ * Builder for {@link CreateSTSTokenParams}.
+ */
+ public static final class Builder {
+ private String tempAccessKeyId;
+ private String originalAccessKeyId;
+ private String roleArn;
+ private int durationSeconds;
+ private String secretAccessKey;
+ private String sessionPolicy;
+ private String assumedRoleId;
+ private String assumedRoleUserArn;
+ private Instant creationTime;
+
+ public Builder setTempAccessKeyId(String value) {
+ this.tempAccessKeyId = value;
+ return this;
+ }
+
+ public Builder setOriginalAccessKeyId(String value) {
+ this.originalAccessKeyId = value;
+ return this;
+ }
+
+ public Builder setRoleArn(String value) {
+ this.roleArn = value;
+ return this;
+ }
+
+ public Builder setDurationSeconds(int value) {
+ this.durationSeconds = value;
+ return this;
+ }
+
+ public Builder setSecretAccessKey(String value) {
+ this.secretAccessKey = value;
+ return this;
+ }
+
+ public Builder setSessionPolicy(String value) {
+ this.sessionPolicy = value;
+ return this;
+ }
+
+ public Builder setAssumedRoleId(String value) {
+ this.assumedRoleId = value;
+ return this;
+ }
+
+ public Builder setAssumedRoleUserArn(String value) {
+ this.assumedRoleUserArn = value;
+ return this;
+ }
+
+ public Builder setCreationTime(Instant creationTime) {
+ this.creationTime = creationTime;
+ return this;
+ }
+
+ public CreateSTSTokenParams build() {
+ return new CreateSTSTokenParams(this);
+ }
+ }
+ }
}
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 57068d1a169..f779a349d0f 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
@@ -88,6 +88,7 @@
public class TestS3AssumeRoleRequest {
private static final String ROLE_ARN_1 =
"arn:aws:iam::123456789012:role/MyRole1";
+ private static final String ROLE_ARN_2 =
"arn:aws:iam::123456789012:role/MyRole2";
private static final String SESSION_NAME = "testSessionName";
private static final String ORIGINAL_ACCESS_KEY_ID = "origAccessKeyId";
private static final String TARGET_ROLE_NAME = "targetRole";
@@ -306,11 +307,9 @@ public void testSuccessfulAssumeRoleGeneratesCredentials()
throws IOException {
assertThat(assumeRoleResponse.getSecretAccessKey().length()).isEqualTo(40);
// AssumedRoleId: prefix AROA + 16 chars, followed by ":" and sessionName
+ final String expectedRoleId =
S3AssumeRoleRequest.generateDeterministicRoleId(ROLE_ARN_1);
assertThat(assumeRoleResponse.getAssumedRoleId())
- .startsWith("AROA")
- .contains(":" + SESSION_NAME);
- final int expectedAssumedRoleIdLength = 4 + 16 + 1 +
SESSION_NAME.length(); // 4 for AROA, 16 chars, 1 for ":"
-
assertThat(assumeRoleResponse.getAssumedRoleId().length()).isEqualTo(expectedAssumedRoleIdLength);
+ .isEqualTo(expectedRoleId + ":" + SESSION_NAME);
// Verify expiration added durationSeconds
final long expirationEpochSeconds =
assumeRoleResponse.getExpirationEpochSeconds();
@@ -318,6 +317,17 @@ public void testSuccessfulAssumeRoleGeneratesCredentials()
throws IOException {
assertMarkForAuditCalled(requestWithCredentials);
}
+ @Test
+ public void testGenerateDeterministicRoleId() {
+ final String roleId1 =
S3AssumeRoleRequest.generateDeterministicRoleId(ROLE_ARN_1);
+ final String roleId2 =
S3AssumeRoleRequest.generateDeterministicRoleId(ROLE_ARN_1);
+ final String roleId3 =
S3AssumeRoleRequest.generateDeterministicRoleId(ROLE_ARN_2);
+
+ assertThat(roleId1).startsWith("AROA").hasSize(4 + 16);
+ assertThat(roleId1).isEqualTo(roleId2);
+ assertThat(roleId1).isNotEqualTo(roleId3);
+ }
+
@Test
public void testGenerateSecureRandomStringUsingChars() {
final String chars = "ABC";
@@ -373,12 +383,29 @@ public void testAssumeRoleCredentialsAreUnique() throws
IOException {
// Different session tokens
assertThat(assumeRoleResponse1.getSessionToken()).isNotEqualTo(assumeRoleResponse2.getSessionToken());
- // Different assumed role IDs
-
assertThat(assumeRoleResponse1.getAssumedRoleId()).isNotEqualTo(assumeRoleResponse2.getAssumedRoleId());
+ // Same assumed role ID for the same role and session name
+
assertThat(assumeRoleResponse1.getAssumedRoleId()).isEqualTo(assumeRoleResponse2.getAssumedRoleId());
+
+ // Different role ARN yields a different assumed role ID
+ final OMRequest omRequestDifferentRole = baseOmRequestBuilder()
+ .setAssumeRoleRequest(
+ AssumeRoleRequest.newBuilder()
+ .setRoleArn(ROLE_ARN_2)
+ .setRoleSessionName(SESSION_NAME)
+ .setDurationSeconds(3600)
+ .setRequestId(REQUEST_ID)
+ ).build();
+ final S3AssumeRoleRequest request3 = new
S3AssumeRoleRequest(omRequestDifferentRole, CLOCK);
+ final OMRequest preExecutedRequest3 = request3.preExecute(ozoneManager);
+ final S3AssumeRoleRequest requestWithCredentials3 = new
S3AssumeRoleRequest(preExecutedRequest3, CLOCK);
+ final OMClientResponse response3 =
requestWithCredentials3.validateAndUpdateCache(ozoneManager, context);
+ final AssumeRoleResponse assumeRoleResponse3 =
response3.getOMResponse().getAssumeRoleResponse();
+
assertThat(assumeRoleResponse1.getAssumedRoleId()).isNotEqualTo(assumeRoleResponse3.getAssumedRoleId());
OMAuditLogger.log(requestWithCredentials1.getAuditBuilder());
OMAuditLogger.log(requestWithCredentials2.getAuditBuilder());
- verify(auditLogger, times(2)).logWrite(any(AuditMessage.class));
+ OMAuditLogger.log(requestWithCredentials3.getAuditBuilder());
+ verify(auditLogger, times(3)).logWrite(any(AuditMessage.class));
}
@Test
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 2f3a0350b57..769ae0da5f1 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
@@ -53,6 +53,9 @@ public class TestSTSSecurityUtil {
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 String ASSUMED_ROLE_ID = "AROATEST123456789:testsess";
+ private static final String ASSUMED_ROLE_USER_ARN =
+ "arn:aws:sts::123456789012:assumed-role/test-role/testsess";
private static final int DURATION_SECONDS = 3600;
private static final ManagedSecretKey MANAGED_SECRET_KEY = new
SecretKeyTestClient().getCurrentSecretKey();
private final SecretKeyTestClient secretKeyClient = new
SecretKeyTestClient();
@@ -80,8 +83,7 @@ public void
testConstructValidateAndDecryptSTSTokenInvalidProtobuf() throws IOEx
@Test
public void testConstructValidateAndDecryptSTSTokenSuccess() throws
IOException {
// Create a valid token
- final String tokenString =
tokenSecretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String tokenString = createStsTokenString();
// Validate and decrypt the token
final STSTokenIdentifier result =
STSSecurityUtil.constructValidateAndDecryptSTSToken(
@@ -102,8 +104,7 @@ public void
testConstructValidateAndDecryptSTSTokenSuccess() throws IOException
@Test
public void
testConstructValidateAndDecryptSTSTokenSuccessWithNullSessionPolicy() throws
Exception {
// Create a valid token with null session policy
- final String tokenString = tokenSecretManager.createSTSTokenString(
- TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN, DURATION_SECONDS,
SECRET_ACCESS_KEY, null, clock.instant());
+ final String tokenString = createStsTokenString(DURATION_SECONDS, null);
// Validate and decrypt the token
final STSTokenIdentifier result =
STSSecurityUtil.constructValidateAndDecryptSTSToken(
@@ -135,8 +136,7 @@ public void
testConstructValidateAndDecryptSTSTokenRuntimeDecodeFailure() {
@Test
public void testConstructValidateAndDecryptSTSTokenInvalidKind() throws
Exception {
// Create a valid identifier to use as base
- final String validTokenString =
tokenSecretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String validTokenString = createStsTokenString();
final Token<STSTokenIdentifier> validToken = new Token<>();
validToken.decodeFromUrlString(validTokenString);
@@ -158,8 +158,7 @@ public void
testConstructValidateAndDecryptSTSTokenInvalidKind() throws Exceptio
@Test
public void testConstructValidateAndDecryptSTSTokenInvalidService() throws
Exception {
// Create a token with incorrect service
- final String validTokenString =
tokenSecretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String validTokenString = createStsTokenString();
final Token<STSTokenIdentifier> validToken = new Token<>();
validToken.decodeFromUrlString(validTokenString);
@@ -179,8 +178,7 @@ public void
testConstructValidateAndDecryptSTSTokenInvalidService() throws Excep
@Test
public void testConstructValidateAndDecryptSTSTokenExpired() throws
Exception {
// Create a token that expires immediately (durationSeconds of 0)
- final String tokenString = tokenSecretManager.createSTSTokenString(
- TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN, 0, SECRET_ACCESS_KEY,
SESSION_POLICY, clock.instant());
+ final String tokenString = createStsTokenString(0, SESSION_POLICY);
// Fast-forward time to ensure token is expired
clock.fastForward(100);
@@ -196,8 +194,7 @@ public void
testConstructValidateAndDecryptSTSTokenExpired() throws Exception {
@Test
public void testConstructValidateAndDecryptSTSTokenSecretKeyNotFound()
throws Exception {
// Create a valid token string
- final String validTokenString =
tokenSecretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String validTokenString = createStsTokenString();
// Create a mock secret key client that returns null for the key
final SecretKeyClient mockKeyClient = mock(SecretKeyClient.class);
@@ -215,8 +212,7 @@ public void
testConstructValidateAndDecryptSTSTokenSecretKeyNotFound() throws Ex
@Test
public void testConstructValidateAndDecryptSTSTokenInvalidSecretKeyId()
throws Exception {
// Create a valid identifier to use as base
- final String validTokenString =
tokenSecretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String validTokenString = createStsTokenString();
final Token<STSTokenIdentifier> validToken = new Token<>();
validToken.decodeFromUrlString(validTokenString);
@@ -241,8 +237,7 @@ public void
testConstructValidateAndDecryptSTSTokenInvalidSecretKeyId() throws E
@Test
public void testConstructValidateAndDecryptSTSTokenExpiredSecretKey() throws
Exception {
// Create a valid token string
- final String validTokenString =
tokenSecretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String validTokenString = createStsTokenString();
// Create a mock secret key that is expired
final ManagedSecretKey expiredSecretKey = mock(ManagedSecretKey.class);
@@ -264,8 +259,7 @@ public void
testConstructValidateAndDecryptSTSTokenExpiredSecretKey() throws Exc
@Test
public void
testConstructValidateAndDecryptSTSTokenSecretKeyRetrievalException() throws
Exception {
// Create a valid token string
- final String validTokenString =
tokenSecretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String validTokenString = createStsTokenString();
// Create a mock secret key client that throws an exception
final SecretKeyClient mockKeyClient = mock(SecretKeyClient.class);
@@ -283,8 +277,7 @@ public void
testConstructValidateAndDecryptSTSTokenSecretKeyRetrievalException()
@Test
public void testConstructValidateAndDecryptSTSTokenInvalidSignature() throws
Exception {
// Create a valid token string
- final String validTokenString =
tokenSecretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String validTokenString = createStsTokenString();
final Token<STSTokenIdentifier> validToken = new Token<>();
validToken.decodeFromUrlString(validTokenString);
@@ -305,9 +298,7 @@ public void
testConstructValidateAndDecryptSTSTokenInvalidSignature() throws Exc
@Test
public void testConstructValidateAndDecryptSTSTokenRejectsDoubledToken()
throws Exception {
- final String tokenString = tokenSecretManager.createSTSTokenString(
- TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN, DURATION_SECONDS,
- SECRET_ACCESS_KEY, SESSION_POLICY, clock.instant());
+ final String tokenString = createStsTokenString();
assertThatThrownBy(() ->
STSSecurityUtil.constructValidateAndDecryptSTSToken(tokenString +
tokenString, secretKeyClient, clock))
@@ -318,9 +309,7 @@ public void
testConstructValidateAndDecryptSTSTokenRejectsDoubledToken() throws
@Test
public void testConstructValidateAndDecryptSTSTokenRejectsTokenWithSuffix()
throws Exception {
- final String tokenString = tokenSecretManager.createSTSTokenString(
- TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN, DURATION_SECONDS,
- SECRET_ACCESS_KEY, SESSION_POLICY, clock.instant());
+ final String tokenString = createStsTokenString();
assertThatThrownBy(() ->
STSSecurityUtil.constructValidateAndDecryptSTSToken(tokenString +
"garbage", secretKeyClient, clock))
@@ -342,13 +331,11 @@ public void
testConstructValidateAndDecryptSTSTokenEmptyString() {
@Test
public void testConstructValidateAndDecryptMultipleTokens() throws Exception
{
// Create multiple tokens and validate them all
- final String token1 = tokenSecretManager.createSTSTokenString(
- "temp-key-1", "orig-key-1", "role-arn-1", DURATION_SECONDS,
- "secret-key-1", "policy-1", clock.instant());
+ final String token1 = createStsTokenString(DURATION_SECONDS,
"secret-key-1", "policy-1",
+ "temp-key-1", "orig-key-1", "role-arn-1");
- final String token2 = tokenSecretManager.createSTSTokenString(
- "temp-key-2", "orig-key-2", "role-arn-2", DURATION_SECONDS,
- "secret-key-2", "policy-2", clock.instant());
+ final String token2 = createStsTokenString(DURATION_SECONDS,
"secret-key-2", "policy-2",
+ "temp-key-2", "orig-key-2", "role-arn-2");
final STSTokenIdentifier result1 =
STSSecurityUtil.constructValidateAndDecryptSTSToken(
token1, secretKeyClient, clock);
@@ -418,8 +405,7 @@ public void
testEnsureEssentialFieldsArePresentInTokenMissingCreationTime() {
@Test
public void testEnsureResolvedStsFieldsInvariantsSuccess() throws Exception {
- final String tokenString =
tokenSecretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String tokenString = createStsTokenString();
final S3Authentication s3Auth = S3Authentication.newBuilder()
.setSessionToken(tokenString)
@@ -458,9 +444,7 @@ public void
testEnsureResolvedStsFieldsInvariantsMissingSessionToken() {
@Test
public void testEnsureResolvedStsFieldsInvariantsMissingResolvedFields()
throws Exception {
- final String tokenString = tokenSecretManager.createSTSTokenString(
- TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN, DURATION_SECONDS,
- SECRET_ACCESS_KEY, SESSION_POLICY, clock.instant());
+ final String tokenString = createStsTokenString();
final S3Authentication s3Auth = S3Authentication.newBuilder()
.setSessionToken(tokenString)
@@ -488,6 +472,32 @@ public void
testEnsureResolvedStsFieldsInvariantsNoS3Auth() throws Exception {
STSSecurityUtil.ensureResolvedStsFieldsInvariants(request);
}
+ private String createStsTokenString() throws IOException {
+ return createStsTokenString(DURATION_SECONDS, SECRET_ACCESS_KEY,
SESSION_POLICY,
+ TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN);
+ }
+
+ private String createStsTokenString(int durationSeconds, String
sessionPolicy)
+ throws IOException {
+ return createStsTokenString(durationSeconds,
TestSTSSecurityUtil.SECRET_ACCESS_KEY, sessionPolicy,
+ TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY, ROLE_ARN);
+ }
+
+ private String createStsTokenString(int durationSeconds, String
secretAccessKey, String sessionPolicy,
+ String tempAccessKey, String originalAccessKey, String roleArn) throws
IOException {
+ return
tokenSecretManager.createSTSTokenString(STSTokenSecretManager.CreateSTSTokenParams.newBuilder()
+ .setTempAccessKeyId(tempAccessKey)
+ .setOriginalAccessKeyId(originalAccessKey)
+ .setRoleArn(roleArn)
+ .setDurationSeconds(durationSeconds)
+ .setSecretAccessKey(secretAccessKey)
+ .setSessionPolicy(sessionPolicy)
+ .setAssumedRoleId(ASSUMED_ROLE_ID)
+ .setAssumedRoleUserArn(ASSUMED_ROLE_USER_ARN)
+ .setCreationTime(clock.instant())
+ .build());
+ }
+
private STSTokenIdentifier.Params.Builder paramsBuilder() {
return STSTokenIdentifier.Params.newBuilder()
.setTempAccessKeyId(TEMP_ACCESS_KEY)
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 c2136388e2a..ee2863e6259 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
@@ -77,6 +77,8 @@ public void testProtoBufRoundTrip() throws IOException {
.setSecretAccessKey("secretKey")
.setSessionPolicy("sessionPolicy")
.setManagedSecretKey(MANAGED_SECRET_KEY)
+ .setAssumedRoleId("AROATEST123456789:testsess")
+
.setAssumedRoleUserArn("arn:aws:sts::123456789012:assumed-role/RoleY/testsess")
.build());
final UUID secretKeyId = MANAGED_SECRET_KEY.getId();
@@ -89,6 +91,9 @@ public void testProtoBufRoundTrip() throws IOException {
assertThat(proto.getRoleArn()).isEqualTo("arn:aws:iam::123456789012:role/RoleY");
assertThat(proto.getSecretAccessKey()).isNotEqualTo("secretKey"); //
must be encrypted
assertThat(proto.getSessionPolicy()).isEqualTo("sessionPolicy");
+
assertThat(proto.getAssumedRoleId()).isEqualTo("AROATEST123456789:testsess");
+ assertThat(proto.getAssumedRoleUserArn())
+ .isEqualTo("arn:aws:sts::123456789012:assumed-role/RoleY/testsess");
assertThat(proto.getSecretKeyId()).isEqualTo(secretKeyId.toString());
final STSTokenIdentifier parsedTokenIdentifier = new STSTokenIdentifier();
@@ -103,6 +108,9 @@ public void testProtoBufRoundTrip() throws IOException {
assertThat(parsedTokenIdentifier.getSecretAccessKey()).isEqualTo("secretKey");
assertThat(parsedTokenIdentifier.getSecretKeyId()).isEqualTo(secretKeyId);
assertThat(parsedTokenIdentifier.getSessionPolicy()).isEqualTo("sessionPolicy");
+
assertThat(parsedTokenIdentifier.getAssumedRoleId()).isEqualTo("AROATEST123456789:testsess");
+ assertThat(parsedTokenIdentifier.getAssumedRoleUserArn())
+ .isEqualTo("arn:aws:sts::123456789012:assumed-role/RoleY/testsess");
assertThat(parsedTokenIdentifier).isEqualTo(originalTokenIdentifier);
assertThat(parsedTokenIdentifier.hashCode()).isEqualTo(originalTokenIdentifier.hashCode());
}
@@ -211,6 +219,8 @@ public void testWriteToAndReadFromByteArray() throws
Exception {
.setSecretAccessKey("secretAccessKey")
.setSessionPolicy("sessionPolicy")
.setManagedSecretKey(MANAGED_SECRET_KEY)
+ .setAssumedRoleId("AROATEST123456789:testsess")
+
.setAssumedRoleUserArn("arn:aws:sts::123456789012:assumed-role/test-role/testsess")
.build());
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -541,12 +551,16 @@ public void testToString() {
.setExpiry(expiry)
.setSecretAccessKey("secretAccessKey")
.setSessionPolicy("sessionPolicy")
+ .setAssumedRoleId("AROATEST123456789:testsess")
+
.setAssumedRoleUserArn("arn:aws:sts::123456789012:assumed-role/test-role/testsess")
.build());
stsTokenIdentifier.setSecretKeyId(uuid);
final String stsTokenIdentifierStr = stsTokenIdentifier.toString();
final String expectedString = "STSTokenIdentifier{" +
"tempAccessKeyId='tempAccessKeyId'" +
", originalAccessKeyId='originalAccessKeyId'" + ", roleArn='roleArn'" +
+ ", assumedRoleId='AROATEST123456789:testsess'" +
+ ",
assumedRoleUserArn='arn:aws:sts::123456789012:assumed-role/test-role/testsess'"
+
", creationTime='" + CREATION_TIME + "', expiry='" + expiry +
"', secretKeyId='" + uuid + "', sessionPolicy='sessionPolicy'" + '}';
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
index fe20c9dd2a4..022dab45508 100644
---
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
@@ -56,6 +56,8 @@ public class TestSTSTokenSecretManager {
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 String ASSUMED_ROLE_ID = "AROATEST123456789:testsess";
+ private static final String ASSUMED_ROLE_USER_ARN =
"arn:aws:sts::123456789012:assumed-role/test-role/testsess";
private static final int DURATION_SECONDS = 3600;
private static SecretKey sharedSecretKey;
@@ -84,8 +86,7 @@ public void setUp() throws Exception {
@Test
public void testCreateSTSTokenStringContainsCorrectFields() throws
IOException {
- final String tokenString =
secretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String tokenString =
secretManager.createSTSTokenString(createStsTokenParamsBuilder().build());
// Decode the token
final Token<STSTokenIdentifier> token = new Token<>();
@@ -104,6 +105,8 @@ public void testCreateSTSTokenStringContainsCorrectFields()
throws IOException {
assertEquals(ROLE_ARN, identifier.getRoleArn());
assertEquals(SECRET_ACCESS_KEY, identifier.getSecretAccessKey());
assertEquals(SESSION_POLICY, identifier.getSessionPolicy());
+ assertEquals(ASSUMED_ROLE_ID, identifier.getAssumedRoleId());
+ assertEquals(ASSUMED_ROLE_USER_ARN, identifier.getAssumedRoleUserArn());
assertEquals(clock.instant(), identifier.getCreationTime());
assertNotNull(identifier.getSecretKeyId());
assertEquals(new Text("STSToken"), identifier.getKind());
@@ -114,7 +117,7 @@ public void testCreateSTSTokenStringContainsCorrectFields()
throws IOException {
@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, clock.instant());
+ createStsTokenParamsBuilder().setSessionPolicy(null).build());
// Decode the token
final Token<STSTokenIdentifier> token = new Token<>();
@@ -150,8 +153,7 @@ public void
testCreateSTSTokenStringValidatesWhenSecretKeyRotatesDuringCreation(
encryptionKey, signingKey);
final STSTokenSecretManager rotatingSecretManager = new
STSTokenSecretManager(rotatingSecretKeyClient);
- final String tokenString =
rotatingSecretManager.createSTSTokenString(TEMP_ACCESS_KEY, ORIGINAL_ACCESS_KEY,
- ROLE_ARN, DURATION_SECONDS, SECRET_ACCESS_KEY, SESSION_POLICY,
clock.instant());
+ final String tokenString =
rotatingSecretManager.createSTSTokenString(createStsTokenParamsBuilder().build());
final STSTokenIdentifier result =
STSSecurityUtil.constructValidateAndDecryptSTSToken(
tokenString, rotatingSecretKeyClient, clock);
@@ -160,6 +162,19 @@ public void
testCreateSTSTokenStringValidatesWhenSecretKeyRotatesDuringCreation(
assertEquals(1, rotatingSecretKeyClient.getCurrentSecretKeyCallCount());
}
+ private STSTokenSecretManager.CreateSTSTokenParams.Builder
createStsTokenParamsBuilder() {
+ return STSTokenSecretManager.CreateSTSTokenParams.newBuilder()
+ .setTempAccessKeyId(TEMP_ACCESS_KEY)
+ .setOriginalAccessKeyId(ORIGINAL_ACCESS_KEY)
+ .setRoleArn(ROLE_ARN)
+ .setDurationSeconds(DURATION_SECONDS)
+ .setSecretAccessKey(SECRET_ACCESS_KEY)
+ .setSessionPolicy(SESSION_POLICY)
+ .setAssumedRoleId(ASSUMED_ROLE_ID)
+ .setAssumedRoleUserArn(ASSUMED_ROLE_USER_ARN)
+ .setCreationTime(clock.instant());
+ }
+
private static ManagedSecretKey createManagedSecretKey(UUID id, byte[]
keyBytes, Instant creationTime) {
final SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA256");
return new ManagedSecretKey(id, creationTime,
creationTime.plus(Duration.ofHours(1)), secretKey);
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java
index abdb64e5ff2..f215cd6cb0f 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java
@@ -66,6 +66,7 @@ public enum S3GAction implements AuditAction {
// STS endpoint
ASSUME_ROLE,
+ GET_CALLER_IDENTITY,
GET_OBJECT_ATTRIBUTES;
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3GActionIamMapper.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3GActionIamMapper.java
index 9953ebe2020..223b057b5bb 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3GActionIamMapper.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3GActionIamMapper.java
@@ -85,6 +85,7 @@ private S3GActionIamMapper() {
case GENERATE_SECRET:
case REVOKE_SECRET:
case ASSUME_ROLE:
+ case GET_CALLER_IDENTITY:
default:
return null;
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3AssumeRoleResponseXml.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3AssumeRoleResponseXml.java
index bd4be9a7eaf..6c8b73906a7 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3AssumeRoleResponseXml.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3AssumeRoleResponseXml.java
@@ -33,7 +33,7 @@ public class S3AssumeRoleResponseXml {
private AssumeRoleResult assumeRoleResult;
@XmlElement(name = "ResponseMetadata")
- private ResponseMetadata responseMetadata;
+ private S3STSResponseMetadata responseMetadata;
public AssumeRoleResult getAssumeRoleResult() {
return assumeRoleResult;
@@ -43,11 +43,11 @@ public void setAssumeRoleResult(AssumeRoleResult
assumeRoleResult) {
this.assumeRoleResult = assumeRoleResult;
}
- public ResponseMetadata getResponseMetadata() {
+ public S3STSResponseMetadata getResponseMetadata() {
return responseMetadata;
}
- public void setResponseMetadata(ResponseMetadata responseMetadata) {
+ public void setResponseMetadata(S3STSResponseMetadata responseMetadata) {
this.responseMetadata = responseMetadata;
}
@@ -157,23 +157,6 @@ public void setArn(String arn) {
this.arn = arn;
}
}
-
- /**
- * ResponseMetadata element.
- */
- @XmlAccessorType(XmlAccessType.FIELD)
- public static class ResponseMetadata {
- @XmlElement(name = "RequestId")
- private String requestId;
-
- public String getRequestId() {
- return requestId;
- }
-
- public void setRequestId(String requestId) {
- this.requestId = requestId;
- }
- }
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3GetCallerIdentityResponseXml.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3GetCallerIdentityResponseXml.java
new file mode 100644
index 00000000000..594ed12e55d
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3GetCallerIdentityResponseXml.java
@@ -0,0 +1,92 @@
+/*
+ * 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.s3sts;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * JAXB model for AWS STS GetCallerIdentityResponse.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlRootElement(name = "GetCallerIdentityResponse", namespace =
"https://sts.amazonaws.com/doc/2011-06-15/")
+public class S3GetCallerIdentityResponseXml {
+
+ @XmlElement(name = "GetCallerIdentityResult")
+ private GetCallerIdentityResult getCallerIdentityResult;
+
+ @XmlElement(name = "ResponseMetadata")
+ private S3STSResponseMetadata responseMetadata;
+
+ public GetCallerIdentityResult getGetCallerIdentityResult() {
+ return getCallerIdentityResult;
+ }
+
+ public void setGetCallerIdentityResult(GetCallerIdentityResult
getCallerIdentityResult) {
+ this.getCallerIdentityResult = getCallerIdentityResult;
+ }
+
+ public S3STSResponseMetadata getResponseMetadata() {
+ return responseMetadata;
+ }
+
+ public void setResponseMetadata(S3STSResponseMetadata responseMetadata) {
+ this.responseMetadata = responseMetadata;
+ }
+
+ /**
+ * GetCallerIdentityResult element.
+ */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class GetCallerIdentityResult {
+ @XmlElement(name = "Arn")
+ private String arn;
+
+ @XmlElement(name = "UserId")
+ private String userId;
+
+ @XmlElement(name = "Account")
+ private String account;
+
+ public String getArn() {
+ return arn;
+ }
+
+ public void setArn(String arn) {
+ this.arn = arn;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getAccount() {
+ return account;
+ }
+
+ public void setAccount(String account) {
+ this.account = account;
+ }
+ }
+}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
index d6ed5339a44..2c6200d1e66 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSEndpoint.java
@@ -60,6 +60,7 @@
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
import org.apache.hadoop.ozone.om.helpers.AwsRoleArnValidator;
+import org.apache.hadoop.ozone.om.helpers.CallerIdentityInfo;
import org.apache.hadoop.ozone.om.helpers.S3STSUtils;
import org.apache.hadoop.ozone.s3.RequestIdentifier;
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
@@ -73,8 +74,8 @@
* This endpoint provides temporary security credentials compatible with
* AWS STS API, exposed on port 9880 or 9881 at the root path ({@code /}).
* <p>
- * Currently supports only AssumeRole operation. Other STS operations will
- * return appropriate error responses.
+ * Currently supports AssumeRole and GetCallerIdentity operations. Other STS
+ * operations will return appropriate error responses.
*
* @see <a href="https://docs.aws.amazon.com/STS/latest/APIReference/">AWS STS
API Reference</a>
*/
@@ -113,7 +114,8 @@ public class S3STSEndpoint extends S3STSEndpointBase {
static {
try {
- JAXB_CONTEXT = JAXBContext.newInstance(S3AssumeRoleResponseXml.class);
+ JAXB_CONTEXT = JAXBContext.newInstance(
+ S3AssumeRoleResponseXml.class, S3GetCallerIdentityResponseXml.class,
S3STSResponseMetadata.class);
} catch (JAXBException e) {
throw new RuntimeException("Failed to initialize JAXBContext: " + e, e);
}
@@ -193,11 +195,12 @@ private Response handleSTSRequest(Set<String>
paramNamesToValidate, String actio
case ASSUME_ROLE_ACTION:
return handleAssumeRole(
paramNamesToValidate, roleArn, roleSessionName, durationSeconds,
awsIamSessionPolicy, version, requestId);
+ case GET_CALLER_IDENTITY_ACTION:
+ return handleGetCallerIdentity(version, requestId);
// These operations are not supported yet
case GET_SESSION_TOKEN_ACTION:
case ASSUME_ROLE_WITH_SAML_ACTION:
case ASSUME_ROLE_WITH_WEB_IDENTITY_ACTION:
- case GET_CALLER_IDENTITY_ACTION:
case DECODE_AUTHORIZATION_MESSAGE_ACTION:
case GET_ACCESS_KEY_INFO_ACTION:
throw new OSTSException(STS_INVALID_ACTION_NOT_IMPLEMENTED)
@@ -307,39 +310,76 @@ private Response handleAssumeRole(Set<String>
paramNamesToValidate, String roleA
.header("Content-Type", "text/xml")
.build();
} catch (IOException e) {
- LOG.error("Error during AssumeRole processing", e);
-
+ throw toStsProcessingException(
+ S3GAction.ASSUME_ROLE, auditParams, e, action, "User is not
authorized to perform: sts:AssumeRole on " +
+ "resource: " + roleArn);
+ } catch (Exception e) {
getAuditLogger().logWriteFailure(buildAuditMessageForFailure(S3GAction.ASSUME_ROLE,
auditParams, e));
+ throw e;
+ }
+ }
- if (e instanceof OMException) {
- final OMException omException = (OMException) e;
- if (omException.getResult() == OMException.ResultCodes.ACCESS_DENIED ||
- omException.getResult() ==
OMException.ResultCodes.PERMISSION_DENIED ||
- omException.getResult() == OMException.ResultCodes.TOKEN_EXPIRED) {
- throw new OSTSException(ACCESS_DENIED)
- .withMessage("User is not authorized to perform: sts:AssumeRole
on resource: " + roleArn);
- }
- if (omException.getResult() == OMException.ResultCodes.INVALID_TOKEN) {
- throw new OSTSException(STS_INVALID_CLIENT_TOKEN_ID);
- }
- if (omException.getResult() ==
OMException.ResultCodes.NOT_SUPPORTED_OPERATION ||
- omException.getResult() ==
OMException.ResultCodes.FEATURE_NOT_ENABLED) {
- throw new
OSTSException(STS_UNSUPPORTED_OPERATION).withMessage(omException.getMessage());
- }
- if (omException.getResult() ==
OMException.ResultCodes.INVALID_REQUEST) {
- throw new
OSTSException(STS_VALIDATION_ERROR).withMessage(omException.getMessage());
- }
- if (omException.getResult() ==
OMException.ResultCodes.MALFORMED_POLICY_DOCUMENT) {
- throw new
OSTSException(STS_MALFORMED_POLICY_DOCUMENT).withMessage(omException.getMessage());
- }
- }
- throw new OSTSException(STS_INTERNAL_FAILURE, e).withType("Receiver");
+ private Response handleGetCallerIdentity(String version, String requestId)
throws OSTSException {
+ final String action = GET_CALLER_IDENTITY_ACTION;
+ final Map<String, String> auditParams = getAuditParameters();
+ auditParams.put("action", action);
+ auditParams.put("requestId", requestId);
+
+ if (version == null || !version.equals(EXPECTED_VERSION)) {
+ final OSTSException exception = new OSTSException(STS_INVALID_ACTION)
+ .withMessage("Could not find operation " + action + " for version " +
+ (version == null ? "NO_VERSION_SPECIFIED. Expected version is:
" + EXPECTED_VERSION : version));
+ getAuditLogger().logWriteFailure(buildAuditMessageForFailure(
+ S3GAction.GET_CALLER_IDENTITY, auditParams, exception));
+ throw exception;
+ }
+
+ try {
+ final CallerIdentityInfo identityInfo =
getClient().getObjectStore().getCallerIdentity();
+ final String responseXml =
generateGetCallerIdentityResponse(identityInfo, requestId);
+
getAuditLogger().logWriteSuccess(buildAuditMessageForSuccess(S3GAction.GET_CALLER_IDENTITY,
auditParams));
+ return Response.ok(responseXml)
+ .header("Content-Type", "text/xml")
+ .build();
+ } catch (IOException e) {
+ throw toStsProcessingException(
+ S3GAction.GET_CALLER_IDENTITY, auditParams, e, action, "User is not
authorized to perform: " +
+ "sts:GetCallerIdentity");
} catch (Exception e) {
-
getAuditLogger().logWriteFailure(buildAuditMessageForFailure(S3GAction.ASSUME_ROLE,
auditParams, e));
+
getAuditLogger().logWriteFailure(buildAuditMessageForFailure(S3GAction.GET_CALLER_IDENTITY,
auditParams, e));
throw e;
}
}
+ private OSTSException toStsProcessingException(S3GAction auditAction,
Map<String, String> auditParams, IOException e,
+ String operationName, String accessDeniedMessage) {
+ LOG.error("Error during {} processing", operationName, e);
+ getAuditLogger().logWriteFailure(buildAuditMessageForFailure(auditAction,
auditParams, e));
+
+ if (e instanceof OMException) {
+ final OMException omException = (OMException) e;
+ if (omException.getResult() == OMException.ResultCodes.ACCESS_DENIED ||
+ omException.getResult() == OMException.ResultCodes.PERMISSION_DENIED
||
+ omException.getResult() == OMException.ResultCodes.TOKEN_EXPIRED) {
+ return new
OSTSException(ACCESS_DENIED).withMessage(accessDeniedMessage);
+ }
+ if (omException.getResult() == OMException.ResultCodes.INVALID_TOKEN) {
+ return new OSTSException(STS_INVALID_CLIENT_TOKEN_ID);
+ }
+ if (omException.getResult() ==
OMException.ResultCodes.NOT_SUPPORTED_OPERATION ||
+ omException.getResult() ==
OMException.ResultCodes.FEATURE_NOT_ENABLED) {
+ return new
OSTSException(STS_UNSUPPORTED_OPERATION).withMessage(omException.getMessage());
+ }
+ if (omException.getResult() == OMException.ResultCodes.INVALID_REQUEST) {
+ return new
OSTSException(STS_VALIDATION_ERROR).withMessage(omException.getMessage());
+ }
+ if (omException.getResult() ==
OMException.ResultCodes.MALFORMED_POLICY_DOCUMENT) {
+ return new
OSTSException(STS_MALFORMED_POLICY_DOCUMENT).withMessage(omException.getMessage());
+ }
+ }
+ return new OSTSException(STS_INTERNAL_FAILURE, e).withType("Receiver");
+ }
+
private AssumeRoleParamValidationResult
validateAssumeRoleParameters(Set<String> paramNamesToValidate) {
if (paramNamesToValidate == null || paramNamesToValidate.isEmpty()) {
return AssumeRoleParamValidationResult.empty();
@@ -450,7 +490,7 @@ private String generateAssumeRoleResponse(String
assumedRoleUserArn, AssumeRoleR
user.setArn(assumedRoleUserArn);
result.setAssumedRoleUser(user);
response.setAssumeRoleResult(result);
- final S3AssumeRoleResponseXml.ResponseMetadata meta = new
S3AssumeRoleResponseXml.ResponseMetadata();
+ final S3STSResponseMetadata meta = new S3STSResponseMetadata();
meta.setRequestId(requestId);
response.setResponseMetadata(meta);
@@ -463,5 +503,29 @@ private String generateAssumeRoleResponse(String
assumedRoleUserArn, AssumeRoleR
throw new IOException("Failed to marshal AssumeRole response", e);
}
}
+
+ private String generateGetCallerIdentityResponse(CallerIdentityInfo
identityInfo, String requestId)
+ throws IOException {
+ try {
+ final S3GetCallerIdentityResponseXml response = new
S3GetCallerIdentityResponseXml();
+ final S3GetCallerIdentityResponseXml.GetCallerIdentityResult result =
+ new S3GetCallerIdentityResponseXml.GetCallerIdentityResult();
+ result.setAccount(identityInfo.getAccount());
+ result.setArn(identityInfo.getArn());
+ result.setUserId(identityInfo.getUserId());
+ response.setGetCallerIdentityResult(result);
+ final S3STSResponseMetadata meta = new S3STSResponseMetadata();
+ meta.setRequestId(requestId);
+ response.setResponseMetadata(meta);
+
+ final Marshaller marshaller = JAXB_CONTEXT.createMarshaller();
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+ final StringWriter stringWriter = new StringWriter();
+ marshaller.marshal(response, stringWriter);
+ return stringWriter.toString();
+ } catch (JAXBException e) {
+ throw new IOException("Failed to marshal GetCallerIdentity response", e);
+ }
+ }
}
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSResponseMetadata.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSResponseMetadata.java
new file mode 100644
index 00000000000..a43ec76b443
--- /dev/null
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3sts/S3STSResponseMetadata.java
@@ -0,0 +1,42 @@
+/*
+ * 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.s3sts;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * JAXB model for AWS STS ResponseMetadata element shared across STS responses.
+ */
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "ResponseMetadata", namespace =
"https://sts.amazonaws.com/doc/2011-06-15/")
+public class S3STSResponseMetadata {
+
+ @XmlElement(name = "RequestId")
+ private String requestId;
+
+ public String getRequestId() {
+ return requestId;
+ }
+
+ public void setRequestId(String requestId) {
+ this.requestId = requestId;
+ }
+}
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 abd80cbc1fc..6fd318dc906 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
@@ -39,6 +39,7 @@
import org.apache.hadoop.ozone.client.protocol.ClientProtocol;
import org.apache.hadoop.ozone.client.protocol.ListStatusLightOptions;
import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
+import org.apache.hadoop.ozone.om.helpers.CallerIdentityInfo;
import org.apache.hadoop.ozone.om.helpers.DeleteTenantState;
import org.apache.hadoop.ozone.om.helpers.ErrorInfo;
import org.apache.hadoop.ozone.om.helpers.LeaseKeyInfo;
@@ -898,6 +899,11 @@ public AssumeRoleResponseInfo assumeRole(String roleArn,
String roleSessionName,
return null;
}
+ @Override
+ public CallerIdentityInfo getCallerIdentity() throws IOException {
+ return null;
+ }
+
@Override
public void revokeSTSToken(String originalAccessKeyId) throws IOException {
}
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/util/TestS3GActionIamMapper.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/util/TestS3GActionIamMapper.java
index c7ae9e4e924..82f0134cffe 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/util/TestS3GActionIamMapper.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/util/TestS3GActionIamMapper.java
@@ -62,6 +62,7 @@ public void copyActionsReturnNull() {
@Test
public void nonIamActionsReturnNull() {
assertNull(S3GActionIamMapper.toS3ActionString(S3GAction.ASSUME_ROLE));
+
assertNull(S3GActionIamMapper.toS3ActionString(S3GAction.GET_CALLER_IDENTITY));
assertNull(S3GActionIamMapper.toS3ActionString(S3GAction.GENERATE_SECRET));
assertNull(S3GActionIamMapper.toS3ActionString(S3GAction.REVOKE_SECRET));
}
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
index 36adf2359c4..379bd27eb98 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3sts/TestS3STSEndpoint.java
@@ -51,6 +51,7 @@
import org.apache.hadoop.ozone.client.OzoneClientStub;
import org.apache.hadoop.ozone.om.exceptions.OMException;
import org.apache.hadoop.ozone.om.helpers.AssumeRoleResponseInfo;
+import org.apache.hadoop.ozone.om.helpers.CallerIdentityInfo;
import org.apache.hadoop.ozone.s3.OzoneConfigurationHolder;
import org.apache.hadoop.ozone.s3.RequestIdentifier;
import org.apache.hadoop.ozone.s3.exception.OSTSException;
@@ -103,6 +104,11 @@ public void setup() throws Exception {
"session-token",
Instant.now().plusSeconds(3600).getEpochSecond(),
"AROA1234567890123456:test-session"));
+ when(objectStore.getCallerIdentity())
+ .thenReturn(new CallerIdentityInfo(
+ "123456789012",
+ "arn:aws:iam::123456789012:user/test-user",
+ "test-user"));
when(clientStub.getObjectStore()).thenReturn(objectStore);
endpoint = new S3STSEndpoint();
@@ -560,6 +566,79 @@ public void testStsWhenActionNotImplemented() throws
Exception {
"Operation GetSessionToken is not supported yet.");
}
+ @Test
+ public void testStsGetCallerIdentitySuccessForGetMethod() throws Exception {
+ final Response response = endpoint.get("GetCallerIdentity", null, null,
null, "2011-06-15", null);
+
+ assertEquals(200, response.getStatus());
+ verify(objectStore).getCallerIdentity();
+ verify(auditLogger).logWriteSuccess(any(AuditMessage.class));
+ verify(auditLogger, never()).logWriteFailure(any(AuditMessage.class));
+
+ final Document doc = parseXml((String) response.getEntity());
+ assertEquals("GetCallerIdentityResponse",
doc.getDocumentElement().getLocalName());
+ assertEquals(STS_NS, doc.getDocumentElement().getNamespaceURI());
+ assertEquals(
+ "123456789012", doc.getElementsByTagNameNS(STS_NS,
"Account").item(0).getTextContent());
+ assertEquals(
+ "arn:aws:iam::123456789012:user/test-user",
doc.getElementsByTagNameNS(STS_NS, "Arn").item(0).getTextContent());
+ assertEquals(
+ "test-user", doc.getElementsByTagNameNS(STS_NS,
"UserId").item(0).getTextContent());
+ }
+
+ @Test
+ public void testStsGetCallerIdentityIgnoresExtraParameters() throws
Exception {
+ final Response response = endpoint.get("GetCallerIdentity", ROLE_ARN,
ROLE_SESSION_NAME, 3600, "2011-06-15", null);
+
+ assertEquals(200, response.getStatus());
+ verify(objectStore).getCallerIdentity();
+ }
+
+ @Test
+ public void testStsGetCallerIdentityIgnoresExtraParametersForPostMethod()
throws Exception {
+ formParameters = new Form();
+ formParameters.param("Action", "GetCallerIdentity");
+ formParameters.param("Version", "2011-06-15");
+ formParameters.param("RoleArn", ROLE_ARN);
+ formParameters.param("RoleSessionName", ROLE_SESSION_NAME);
+ formParameters.param("DurationSeconds", "3600");
+
+ final Response response = endpoint.post(formParameters);
+
+ assertEquals(200, response.getStatus());
+ verify(objectStore).getCallerIdentity();
+ }
+
+ @Test
+ public void testStsGetCallerIdentityRejectsMissingVersion() throws Exception
{
+ final OSTSException ex = assertThrows(
+ OSTSException.class, () -> endpoint.get("GetCallerIdentity", null,
null, null, null, null));
+
+ assertEquals(400, ex.getHttpCode());
+ verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+ verify(objectStore, never()).getCallerIdentity();
+
+ ex.setRequestId(REQUEST_ID);
+ assertStsErrorXml(
+ ex.toXml(), AWS_FAULT_NS, "Sender", "InvalidAction",
+ "Could not find operation GetCallerIdentity for version
NO_VERSION_SPECIFIED");
+ }
+
+ @Test
+ public void testStsGetCallerIdentityRejectsInvalidVersion() throws Exception
{
+ final OSTSException ex = assertThrows(
+ OSTSException.class, () -> endpoint.get("GetCallerIdentity", null,
null, null, "2020-01-01", null));
+
+ assertEquals(400, ex.getHttpCode());
+ verify(auditLogger).logWriteFailure(any(AuditMessage.class));
+ verify(objectStore, never()).getCallerIdentity();
+
+ ex.setRequestId(REQUEST_ID);
+ assertStsErrorXml(
+ ex.toXml(), AWS_FAULT_NS, "Sender", "InvalidAction",
+ "Could not find operation GetCallerIdentity for version 2020-01-01");
+ }
+
@Test
public void testStsMissingRoleSessionName() throws Exception {
final OSTSException ex = assertThrows(OSTSException.class, () ->
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]