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