Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ dependencies {
testImplementation 'org.mockito:mockito-core:5.14.2'
testImplementation 'org.mockito:mockito-junit-jupiter:5.14.2'
testImplementation 'org.apache.httpcomponents.client5:httpclient5:5.4.1'
testImplementation 'com.amazonaws:aws-java-sdk-sts:1.12.766' // for dynamic STS temp credential tests
Comment thread
vijaypdwivedi marked this conversation as resolved.
testImplementation 'com.amazonaws:aws-java-sdk-iam:1.12.766' // for IAM role/user creation in temp credential tests
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testRuntimeOnly 'org.slf4j:jcl-over-slf4j:2.0.16'
testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl:2.24.3'
Expand Down
3 changes: 2 additions & 1 deletion src/test/java/com/emc/object/s3/S3JerseyClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3366,7 +3366,8 @@ public void testGetPutDeleteObjectWithTagging() {
Assert.fail("Fail was expected. Can NOT get tags from a deleted object");
} catch (S3Exception e) {
Assert.assertEquals(404, e.getHttpCode());
Assert.assertEquals("NoSuchKey", e.getErrorCode());
Assert.assertTrue("unexpected error code: " + e.getErrorCode(),
"NoSuchKey".equals(e.getErrorCode()) || "NoSuchVersion".equals(e.getErrorCode()));
}

}
Expand Down
309 changes: 299 additions & 10 deletions src/test/java/com/emc/object/s3/S3TempCredentialsTest.java
Original file line number Diff line number Diff line change
@@ -1,39 +1,251 @@
package com.emc.object.s3;

import com.amazonaws.SDKGlobalConfiguration;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.identitymanagement.AmazonIdentityManagement;
import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClientBuilder;
import com.amazonaws.services.identitymanagement.model.*;
import com.amazonaws.services.securitytoken.AWSSecurityTokenService;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder;
import com.amazonaws.services.securitytoken.model.AssumeRoleRequest;
import com.amazonaws.services.securitytoken.model.AssumeRoleResult;
import com.amazonaws.services.securitytoken.model.Credentials;
import com.emc.object.Method;
import com.emc.object.s3.bean.BucketPolicy;
import com.emc.object.s3.bean.BucketPolicyAction;
import com.emc.object.s3.bean.BucketPolicyStatement;
import com.emc.object.s3.jersey.S3JerseyClient;
import com.emc.object.s3.request.PresignedUrlRequest;
import com.emc.object.util.TestProperties;
import com.emc.util.TestConfig;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Response;
import org.junit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.Date;
import java.util.Properties;

public class S3TempCredentialsTest extends S3JerseyClientTest {
private static final Logger log = LoggerFactory.getLogger(S3TempCredentialsTest.class);

// hardcoded session token used only for pre-signed URL signature verification (unit-test style)
private static final String SESSION_TOKEN = "Cghuc190ZXN0MRIIaWFtX3VzZXIaFEFST0EzQjFGMDc0OUJFQkIzRDlFIiB1cm46ZWNzOmlhbTo6bnNfdGVzdDE6cm9sZS9yb2xlMSoUQVNJQUI1MTEzMzYwN0FBNzg1QjUyUE1hc3RlcktleVJlY29yZC0zZGE0ZTJlNmMyMGNiMzg2NDVlZTJlYjlkNWUxYzUxODJiYTBhYjQ3NWIxMDg4YWE5NDBmMzIyZTAyNWEzY2Q1OKXTrK2VL1IMZWNzLXN0cy10ZW1waL_l44QG";

@Override
protected S3Config createS3Config() throws Exception {
private static final String IAM_USERNAME = "obj-client-temp-cred-test-user";
private static final String IAM_ROLE_NAME = "obj-client-temp-cred-test-role";

// shared across tests (set up once via @BeforeClass, torn down via @AfterClass)
private static AmazonIdentityManagement iamClient;
private static AWSSecurityTokenService stsClient;
private static User iamUser;
private static Role iamRole;
private static String stsEndpoint;
private static String iamEndpoint;
private static String s3AccessKey;
private static String s3SecretKey;
private static boolean dynamicMode;

@BeforeClass
public static void setupStsInfrastructure() throws Exception {
Properties props = TestConfig.getProperties();

String accessKey = TestConfig.getPropertyNotEmpty(props, TestProperties.S3_TEMP_ACCESS_KEY);
String secretKey = TestConfig.getPropertyNotEmpty(props, TestProperties.S3_TEMP_SECRET_KEY);
String securityToken = TestConfig.getPropertyNotEmpty(props, TestProperties.S3_SECURITY_TOKEN);
stsEndpoint = props.getProperty(TestProperties.STS_ENDPOINT);
iamEndpoint = props.getProperty(TestProperties.IAM_ENDPOINT);
s3AccessKey = TestConfig.getPropertyNotEmpty(props, TestProperties.S3_ACCESS_KEY);
s3SecretKey = TestConfig.getPropertyNotEmpty(props, TestProperties.S3_SECRET_KEY);

if (stsEndpoint != null && !stsEndpoint.isEmpty()
&& iamEndpoint != null && !iamEndpoint.isEmpty()) {
dynamicMode = true;

// disable SSL validation for lab systems with self-signed certificates
System.setProperty(SDKGlobalConfiguration.DISABLE_CERT_CHECKING_SYSTEM_PROPERTY, "true");

// create IAM client
iamClient = AmazonIdentityManagementClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(iamEndpoint, "us-east-1"))
.withCredentials(new AWSStaticCredentialsProvider(
new BasicAWSCredentials(s3AccessKey, s3SecretKey)))
.build();

// create or reuse IAM user
try {
iamUser = iamClient.createUser(new CreateUserRequest(IAM_USERNAME)).getUser();
log.info("Created IAM user: {}", iamUser.getArn());
} catch (EntityAlreadyExistsException e) {
iamUser = iamClient.getUser(new GetUserRequest().withUserName(IAM_USERNAME)).getUser();
log.info("Reusing existing IAM user: {}", iamUser.getArn());
}

// create or reuse IAM role with AssumeRole trust policy
String trustPolicy = "{ \"Version\": \"2012-10-17\",\n" +
" \"Statement\": [\n" +
" {\n" +
" \"Action\": \"sts:AssumeRole\"," +
" \"Resource\": \"*\",\n" +
" \"Principal\": { \"AWS\": \"" + iamUser.getArn().split(":user/")[0] + ":root\" },\n" +
" \"Effect\": \"Allow\"\n" +
" }\n" +
" ]\n }";
try {
iamRole = iamClient.createRole(new CreateRoleRequest()
.withRoleName(IAM_ROLE_NAME)
.withAssumeRolePolicyDocument(trustPolicy)).getRole();
log.info("Created IAM role: {}", iamRole.getArn());
} catch (EntityAlreadyExistsException e) {
iamRole = iamClient.getRole(new GetRoleRequest().withRoleName(IAM_ROLE_NAME)).getRole();
log.info("Reusing existing IAM role: {}", iamRole.getArn());
}

// create STS client
stsClient = AWSSecurityTokenServiceClientBuilder.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(stsEndpoint, "us-east-1"))
.withCredentials(new AWSStaticCredentialsProvider(
new BasicAWSCredentials(s3AccessKey, s3SecretKey)))
.build();

log.info("STS/IAM infrastructure ready for AssumeRole-based temp credential tests");
} else {
dynamicMode = false;
}
}

@AfterClass
public static void cleanupStsInfrastructure() {
if (iamClient != null) {
try {
if (iamUser != null) {
// delete access keys first
for (AccessKeyMetadata keyMeta : iamClient.listAccessKeys(
new ListAccessKeysRequest().withUserName(iamUser.getUserName())).getAccessKeyMetadata()) {
iamClient.deleteAccessKey(new DeleteAccessKeyRequest(iamUser.getUserName(), keyMeta.getAccessKeyId()));
}
iamClient.deleteUser(new DeleteUserRequest(iamUser.getUserName()));
log.info("Deleted IAM user: {}", iamUser.getUserName());
}
} catch (Exception e) {
log.warn("Failed to delete IAM user: {}", e.getMessage());
}
try {
if (iamRole != null) {
iamClient.deleteRole(new DeleteRoleRequest().withRoleName(iamRole.getRoleName()));
log.info("Deleted IAM role: {}", iamRole.getRoleName());
}
} catch (Exception e) {
log.warn("Failed to delete IAM role: {}", e.getMessage());
}
}
}

@Override
protected S3Config createS3Config() throws Exception {
S3Config s3Config = super.createS3Config();
s3Config.withIdentity(accessKey).withSecretKey(secretKey).withSessionToken(securityToken);

if (dynamicMode) {
// dynamic mode: AssumeRole via STS at runtime (like ECS Sync's EcsS3Test)
AssumeRoleResult assumeRoleResult = stsClient.assumeRole(new AssumeRoleRequest()
.withRoleSessionName("obj-client-temp-cred-test")
.withRoleArn(iamRole.getArn()));
Credentials stsCredentials = assumeRoleResult.getCredentials();

log.info("STS AssumeRole succeeded - using dynamic temporary credentials");
log.info("Temp accessKeyId={}...", stsCredentials.getAccessKeyId().substring(0,
Math.min(8, stsCredentials.getAccessKeyId().length())));

s3Config.withIdentity(stsCredentials.getAccessKeyId())
.withSecretKey(stsCredentials.getSecretAccessKey())
.withSessionToken(stsCredentials.getSessionToken());
} else {
// fallback: use static credentials from properties (legacy behavior)
Properties props = TestConfig.getProperties();
String accessKey = TestConfig.getPropertyNotEmpty(props, TestProperties.S3_TEMP_ACCESS_KEY);
String secretKey = TestConfig.getPropertyNotEmpty(props, TestProperties.S3_TEMP_SECRET_KEY);
String securityToken = TestConfig.getPropertyNotEmpty(props, TestProperties.S3_SECURITY_TOKEN);

s3Config.withIdentity(accessKey).withSecretKey(secretKey).withSessionToken(securityToken);
}

return s3Config;
}

@Override
protected void createBucket(String bucketName) throws Exception {
// bucket must be created with the original (non-temp) credentials because
// the AssumeRole temp credentials may not have permission to create buckets.
// Then we set a bucket policy granting the assumed role access.
S3Config ownerConfig = s3ConfigFromProperties();
ownerConfig.setSmartClient(false);
S3Client ownerClient = new S3JerseyClient(ownerConfig);
try {
ownerClient.createBucket(bucketName);
this.bucketOwner = ownerClient.getBucketAcl(bucketName).getOwner();

if (dynamicMode) {
// grant the assumed role full access to this bucket (like ECS Sync's EcsS3Test)
BucketPolicy bucketPolicy = new BucketPolicy()
.withVersion("2012-10-17")
.withId("temp-cred-test-policy")
.withStatements(Arrays.asList(
new BucketPolicyStatement()
.withSid("role-object-access")
.withPrincipal("{\"AWS\":\"" + iamRole.getArn() + "\"}")
.withEffect(BucketPolicyStatement.Effect.Allow)
.withActions(BucketPolicyAction.All)
.withResource("arn:aws:s3:::" + bucketName + "/*"),
new BucketPolicyStatement()
.withSid("role-bucket-access")
.withPrincipal("{\"AWS\":\"" + iamRole.getArn() + "\"}")
.withEffect(BucketPolicyStatement.Effect.Allow)
.withActions(BucketPolicyAction.All)
.withResource("arn:aws:s3:::" + bucketName)
));
ownerClient.setBucketPolicy(bucketName, bucketPolicy);
log.info("Set bucket policy for role {} on bucket {}", iamRole.getArn(), bucketName);
}
} finally {
ownerClient.destroy();
}
}

@Override
protected void cleanUpBucket(String bucketName) {
// clean up with owner credentials (temp creds may not have delete-bucket permission)
try {
S3Config ownerConfig = s3ConfigFromProperties();
S3Client ownerClient = new S3JerseyClient(ownerConfig);
try {
if (ownerClient.bucketExists(bucketName)) {
if (ownerClient.getBucketVersioning(bucketName).getStatus() != null) {
for (com.emc.object.s3.bean.AbstractVersion version :
ownerClient.listVersions(new com.emc.object.s3.request.ListVersionsRequest(bucketName)
.withEncodingType(com.emc.object.s3.bean.EncodingType.url)).getVersions()) {
ownerClient.deleteObject(new com.emc.object.s3.request.DeleteObjectRequest(bucketName, version.getKey())
.withVersionId(version.getVersionId()));
}
} else {
for (com.emc.object.s3.bean.S3Object object :
ownerClient.listObjects(new com.emc.object.s3.request.ListObjectsRequest(bucketName)
.withEncodingType(com.emc.object.s3.bean.EncodingType.url)).getObjects()) {
ownerClient.deleteObject(bucketName, object.getKey());
}
}
ownerClient.deleteBucket(bucketName);
}
} finally {
ownerClient.destroy();
}
} catch (Exception e) {
log.warn("Failed to clean up bucket {}: {}", bucketName, e.getMessage());
}
}

@Before
public void versionCheck() {
Assume.assumeTrue("ECS version must be at least 3.6.2", ecsVersion != null && ecsVersion.compareTo("3.6.2") >= 0);
Expand All @@ -55,7 +267,7 @@ public void testPreSignedUrl() throws Exception {

url = client.getPresignedUrl(getTestBucket(), key, new Date(System.currentTimeMillis() + 100000));

Response response = javax.ws.rs.client.ClientBuilder.newClient().target(url.toURI()).request().get();
javax.ws.rs.core.Response response = javax.ws.rs.client.ClientBuilder.newClient().target(url.toURI()).request().get();
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(content, response.readEntity(String.class));
}
Expand Down Expand Up @@ -193,6 +405,83 @@ public void testSetGetBucketAcl() {
public void testExtendObjectRetentionPeriod() {
}

// bucket-admin operations not allowed with AssumeRole temp credentials
// (the bucket policy only grants access to object operations on the test bucket)
@Ignore("temp credentials cannot list buckets at account level")
@Test
public void testListBuckets() {
}

@Ignore("temp credentials cannot list buckets at account level")
@Test
public void testListBucketsReq() {
}

@Ignore("temp credentials cannot get bucket info")
@Test
public void testGetBucketInfo() {
}

@Ignore("temp credentials cannot create new buckets")
@Test
public void testCreateBucketRequest() {
}

@Ignore("temp credentials cannot create encrypted buckets")
@Test
public void testCreateEncryptedBucket() {
}

@Ignore("temp credentials cannot create stale-read-allowed buckets")
@Test
public void testCreateStaleReadAllowedBucket() {
}

@Ignore("temp credentials cannot delete buckets")
@Test
public void testDeleteBucket() {
}

@Ignore("temp credentials cannot delete buckets with background tasks")
@Test
public void testDeleteBucketWithBackgroundTasks() {
}

@Ignore("temp credentials cannot delete buckets with MPU background tasks")
@Test
public void testDeleteBucketWithMPUWithBackgroundTasks() {
}

@Ignore("temp credentials cannot set bucket policy")
@Test
public void testBucketPolicy() {
}

@Ignore("temp credentials cannot create encrypted buckets")
@Test
public void testUploadPartChecksumOnEncryptedBucket() {
}

@Ignore("temp credentials cannot create buckets in other namespaces")
@Test
public void testStreamObjectBetweenBuckets() {
}

@Ignore("IAM user is not supported for Copy Range API on ECS")
@Test
public void testCopyRangeAPI() {
}

@Ignore("temp credentials cannot create Object Lock buckets")
@Test
public void testCreateObjectLockBucket() {
}

@Ignore("temp credentials cannot create Object Lock buckets")
@Test
public void testDeleteBucketInRetentionWithBackgroundTasks() {
}

private S3Client getPresignDummyClient() throws URISyntaxException {
return new S3JerseyClient(new S3Config(new URI("http://10.246.153.111:9020"))
.withIdentity("ASIAB51133607AA785B5").withSecretKey("rhkMxcjRq6iaW1KHAdy1QuO9Qi_LCDX9cuk3XUvsgkc")
Expand Down
3 changes: 3 additions & 0 deletions src/test/java/com/emc/object/util/TestProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public class TestProperties {
public static final String S3_TEMP_SECRET_KEY = "s3.temp_secret_key";
public static final String S3_SECURITY_TOKEN = "s3.security_token";

public static final String STS_ENDPOINT = "sts.endpoint";
public static final String IAM_ENDPOINT = "iam.endpoint";

public static final String PROXY_URI = "http.proxyUri";
public static final String NON_DEFAULT_VPOOL = "nonDefaultVpoolId";
}
Loading