Skip to content

Commit a784c1b

Browse files
authored
Cross account support (#8)
* Add support for Canned access control lists * Moved SSEAwsKeyManagementParams and CannedAccessControlList as properties in S3Dao
1 parent 07587a7 commit a784c1b

File tree

8 files changed

+163
-84
lines changed

8 files changed

+163
-84
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ You can download release builds through the [releases section of this](https://g
2323
<dependency>
2424
<groupId>software.amazon.payloadoffloading</groupId>
2525
<artifactId>payloadoffloading-common</artifactId>
26-
<version>1.0.0</version>
26+
<version>1.1.0</version>
2727
<type>jar</type>
2828
</dependency>
2929
```

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>software.amazon.payloadoffloading</groupId>
88
<artifactId>payloadoffloading-common</artifactId>
9-
<version>1.0.0</version>
9+
<version>1.1.0</version>
1010
<packaging>jar</packaging>
1111
<name>Payload offloading common library for AWS</name>
1212
<description>Common library between extended Amazon AWS clients to save payloads up to 2GB on Amazon S3.</description>

src/main/java/software/amazon/payloadoffloading/PayloadStorageConfiguration.java

+42
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.amazonaws.AmazonClientException;
44
import com.amazonaws.annotation.NotThreadSafe;
55
import com.amazonaws.services.s3.AmazonS3;
6+
import com.amazonaws.services.s3.model.CannedAccessControlList;
67
import com.amazonaws.services.s3.model.SSEAwsKeyManagementParams;
78
import org.apache.commons.logging.Log;
89
import org.apache.commons.logging.LogFactory;
@@ -20,6 +21,10 @@ public class PayloadStorageConfiguration {
2021
private int payloadSizeThreshold = 0;
2122
private boolean alwaysThroughS3 = false;
2223
private boolean payloadSupport = false;
24+
/**
25+
* This field is optional, it is set only when we want to add access control list to Amazon S3 buckets and objects
26+
*/
27+
private CannedAccessControlList cannedAccessControlList;
2328
/**
2429
* This field is optional, it is set only when we want to configure S3 Server Side Encryption with KMS.
2530
*/
@@ -29,6 +34,7 @@ public PayloadStorageConfiguration() {
2934
s3 = null;
3035
s3BucketName = null;
3136
sseAwsKeyManagementParams = null;
37+
cannedAccessControlList = null;
3238
}
3339

3440
public PayloadStorageConfiguration(PayloadStorageConfiguration other) {
@@ -38,6 +44,7 @@ public PayloadStorageConfiguration(PayloadStorageConfiguration other) {
3844
this.payloadSupport = other.isPayloadSupportEnabled();
3945
this.alwaysThroughS3 = other.isAlwaysThroughS3();
4046
this.payloadSizeThreshold = other.getPayloadSizeThreshold();
47+
this.cannedAccessControlList = other.cannedAccessControlList;
4148
}
4249

4350
/**
@@ -212,4 +219,39 @@ public boolean isAlwaysThroughS3() {
212219
public void setAlwaysThroughS3(boolean alwaysThroughS3) {
213220
this.alwaysThroughS3 = alwaysThroughS3;
214221
}
222+
223+
/**
224+
* Configures the ACL to apply to the Amazon S3 putObject request.
225+
* @param cannedAccessControlList
226+
* The ACL to be used when storing objects in Amazon S3
227+
*/
228+
public void setCannedAccessControlList(CannedAccessControlList cannedAccessControlList) {
229+
this.cannedAccessControlList = cannedAccessControlList;
230+
}
231+
232+
/**
233+
* Configures the ACL to apply to the Amazon S3 putObject request.
234+
* @param cannedAccessControlList
235+
* The ACL to be used when storing objects in Amazon S3
236+
*/
237+
public PayloadStorageConfiguration withCannedAccessControlList(CannedAccessControlList cannedAccessControlList) {
238+
setCannedAccessControlList(cannedAccessControlList);
239+
return this;
240+
}
241+
242+
/**
243+
* Checks whether an ACL have been configured for storing objects in Amazon S3.
244+
* @return True if ACL is defined
245+
*/
246+
public boolean isCannedAccessControlListDefined() {
247+
return null != cannedAccessControlList;
248+
}
249+
250+
/**
251+
* Gets the AWS ACL to apply to the Amazon S3 putObject request.
252+
* @return Amazon S3 object ACL
253+
*/
254+
public CannedAccessControlList getCannedAccessControlList() {
255+
return cannedAccessControlList;
256+
}
215257
}

src/main/java/software/amazon/payloadoffloading/S3BackedPayloadStore.java

+1-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package software.amazon.payloadoffloading;
22

3-
import com.amazonaws.services.s3.model.SSEAwsKeyManagementParams;
43
import org.apache.commons.logging.Log;
54
import org.apache.commons.logging.LogFactory;
65

@@ -14,25 +13,18 @@ public class S3BackedPayloadStore implements PayloadStore {
1413

1514
private final String s3BucketName;
1615
private final S3Dao s3Dao;
17-
private final SSEAwsKeyManagementParams sseAwsKeyManagementParams;
1816

1917
public S3BackedPayloadStore(S3Dao s3Dao, String s3BucketName) {
20-
this(s3Dao, s3BucketName, null);
21-
}
22-
23-
public S3BackedPayloadStore(S3Dao s3Dao, String s3BucketName,
24-
SSEAwsKeyManagementParams sseAwsKeyManagementParams) {
2518
this.s3BucketName = s3BucketName;
2619
this.s3Dao = s3Dao;
27-
this.sseAwsKeyManagementParams = sseAwsKeyManagementParams;
2820
}
2921

3022
@Override
3123
public String storeOriginalPayload(String payload, Long payloadContentSize) {
3224
String s3Key = UUID.randomUUID().toString();
3325

3426
// Store the payload content in S3.
35-
s3Dao.storeTextInS3(s3BucketName, s3Key, sseAwsKeyManagementParams, payload, payloadContentSize);
27+
s3Dao.storeTextInS3(s3BucketName, s3Key, payload, payloadContentSize);
3628
LOG.info("S3 object created, Bucket name: " + s3BucketName + ", Object key: " + s3Key + ".");
3729

3830
// Convert S3 pointer (bucket name, key, etc) to JSON string

src/main/java/software/amazon/payloadoffloading/S3Dao.java

+13-6
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,17 @@
1919
public class S3Dao {
2020
private static final Log LOG = LogFactory.getLog(S3Dao.class);
2121
private final AmazonS3 s3Client;
22+
private final SSEAwsKeyManagementParams sseAwsKeyManagementParams;
23+
private final CannedAccessControlList cannedAccessControlList;
2224

2325
public S3Dao(AmazonS3 s3Client) {
26+
this(s3Client, null, null);
27+
}
28+
29+
public S3Dao(AmazonS3 s3Client, SSEAwsKeyManagementParams sseAwsKeyManagementParams, CannedAccessControlList cannedAccessControlList) {
2430
this.s3Client = s3Client;
31+
this.sseAwsKeyManagementParams = sseAwsKeyManagementParams;
32+
this.cannedAccessControlList = cannedAccessControlList;
2533
}
2634

2735
public String getTextFromS3(String s3BucketName, String s3Key) {
@@ -60,14 +68,17 @@ public String getTextFromS3(String s3BucketName, String s3Key) {
6068
return embeddedText;
6169
}
6270

63-
public void storeTextInS3(String s3BucketName, String s3Key, SSEAwsKeyManagementParams sseAwsKeyManagementParams,
64-
String payloadContentStr, Long payloadContentSize) {
71+
public void storeTextInS3(String s3BucketName, String s3Key, String payloadContentStr, Long payloadContentSize) {
6572
InputStream payloadContentStream = new ByteArrayInputStream(payloadContentStr.getBytes(StandardCharsets.UTF_8));
6673
ObjectMetadata payloadContentStreamMetadata = new ObjectMetadata();
6774
payloadContentStreamMetadata.setContentLength(payloadContentSize);
6875
PutObjectRequest putObjectRequest = new PutObjectRequest(s3BucketName, s3Key,
6976
payloadContentStream, payloadContentStreamMetadata);
7077

78+
if (cannedAccessControlList != null) {
79+
putObjectRequest.withCannedAcl(cannedAccessControlList);
80+
}
81+
7182
// https://docs.aws.amazon.com/AmazonS3/latest/dev/kms-using-sdks.html
7283
if (sseAwsKeyManagementParams != null) {
7384
LOG.debug("Using SSE-KMS in put object request.");
@@ -89,10 +100,6 @@ public void storeTextInS3(String s3BucketName, String s3Key, SSEAwsKeyManagement
89100
}
90101
}
91102

92-
public void storeTextInS3(String s3BucketName, String s3Key, String payloadContentStr, Long payloadContentSize) {
93-
storeTextInS3(s3BucketName, s3Key, null, payloadContentStr, payloadContentSize);
94-
}
95-
96103
public void deletePayloadFromS3(String s3BucketName, String s3Key) {
97104
try {
98105
s3Client.deleteObject(s3BucketName, s3Key);

src/test/java/software/amazon/payloadoffloading/PayloadStorageConfigurationTest.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package software.amazon.payloadoffloading;
22

33
import com.amazonaws.services.s3.AmazonS3;
4+
import com.amazonaws.services.s3.model.CannedAccessControlList;
45
import com.amazonaws.services.s3.model.SSEAwsKeyManagementParams;
56
import org.junit.Before;
67
import org.junit.Test;
@@ -16,10 +17,12 @@ public class PayloadStorageConfigurationTest {
1617
private static String s3BucketName = "test-bucket-name";
1718
private static String s3ServerSideEncryptionKMSKeyId = "test-customer-managed-kms-key-id";
1819
private SSEAwsKeyManagementParams sseAwsKeyManagementParams;
20+
private CannedAccessControlList cannedAccessControlList;
1921

2022
@Before
2123
public void setup() {
2224
sseAwsKeyManagementParams = new SSEAwsKeyManagementParams(s3ServerSideEncryptionKMSKeyId);
25+
cannedAccessControlList = CannedAccessControlList.BucketOwnerFullControl;
2326
}
2427

2528
@Test
@@ -33,14 +36,16 @@ public void testCopyConstructor() {
3336

3437
payloadStorageConfiguration.withPayloadSupportEnabled(s3, s3BucketName)
3538
.withAlwaysThroughS3(alwaysThroughS3).withPayloadSizeThreshold(payloadSizeThreshold)
36-
.withSSEAwsKeyManagementParams(sseAwsKeyManagementParams);
39+
.withSSEAwsKeyManagementParams(sseAwsKeyManagementParams)
40+
.withCannedAccessControlList(cannedAccessControlList);
3741

3842
PayloadStorageConfiguration newPayloadStorageConfiguration = new PayloadStorageConfiguration(payloadStorageConfiguration);
3943

4044
assertEquals(s3, newPayloadStorageConfiguration.getAmazonS3Client());
4145
assertEquals(s3BucketName, newPayloadStorageConfiguration.getS3BucketName());
4246
assertEquals(sseAwsKeyManagementParams, newPayloadStorageConfiguration.getSSEAwsKeyManagementParams());
4347
assertEquals(s3ServerSideEncryptionKMSKeyId, newPayloadStorageConfiguration.getSSEAwsKeyManagementParams().getAwsKmsKeyId());
48+
assertEquals(cannedAccessControlList, newPayloadStorageConfiguration.getCannedAccessControlList());
4449
assertTrue(newPayloadStorageConfiguration.isPayloadSupportEnabled());
4550
assertEquals(alwaysThroughS3, newPayloadStorageConfiguration.isAlwaysThroughS3());
4651
assertEquals(payloadSizeThreshold, newPayloadStorageConfiguration.getPayloadSizeThreshold());
@@ -88,4 +93,16 @@ public void testSseAwsKeyManagementParams() {
8893
assertEquals(s3ServerSideEncryptionKMSKeyId, payloadStorageConfiguration.getSSEAwsKeyManagementParams()
8994
.getAwsKmsKeyId());
9095
}
96+
97+
@Test
98+
public void testCannedAccessControlList() {
99+
100+
PayloadStorageConfiguration payloadStorageConfiguration = new PayloadStorageConfiguration();
101+
102+
assertFalse(payloadStorageConfiguration.isCannedAccessControlListDefined());
103+
104+
payloadStorageConfiguration.withCannedAccessControlList(cannedAccessControlList);
105+
assertTrue(payloadStorageConfiguration.isCannedAccessControlListDefined());
106+
assertEquals(cannedAccessControlList, payloadStorageConfiguration.getCannedAccessControlList());
107+
}
91108
}

src/test/java/software/amazon/payloadoffloading/S3BackedPayloadStoreTest.java

+10-66
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package software.amazon.payloadoffloading;
22

33
import com.amazonaws.AmazonClientException;
4+
import com.amazonaws.services.s3.model.CannedAccessControlList;
45
import com.amazonaws.services.s3.model.SSEAwsKeyManagementParams;
56
import junitparams.JUnitParamsRunner;
6-
import junitparams.Parameters;
77
import org.hamcrest.Matchers;
88
import org.junit.Before;
99
import org.junit.Rule;
@@ -35,65 +35,23 @@ public void setup() {
3535
payloadStore = new S3BackedPayloadStore(s3Dao, S3_BUCKET_NAME);
3636
}
3737

38-
private Object[] testData() {
39-
// Here, we create separate mock of S3Dao because JUnitParamsRunner collects parameters
40-
// for tests well before invocation of @Before or @BeforeClass methods.
41-
// That means our default s3Dao mock isn't instantiated until then. For parameterized tests,
42-
// we instantiate our local S3Dao mock per combination, pass it to S3BackedPayloadStore and also pass it
43-
// as test parameter to allow verifying calls to the mockS3Dao.
44-
S3Dao noEncryptionS3Dao = mock(S3Dao.class);
45-
S3Dao defaultEncryptionS3Dao = mock(S3Dao.class);
46-
S3Dao customerKMSKeyEncryptionS3Dao = mock(S3Dao.class);
47-
return new Object[][]{
48-
// No S3 SSE-KMS encryption
49-
{
50-
new S3BackedPayloadStore(noEncryptionS3Dao, S3_BUCKET_NAME),
51-
null,
52-
noEncryptionS3Dao
53-
},
54-
// S3 SSE-KMS encryption with AWS managed KMS keys
55-
{
56-
new S3BackedPayloadStore(defaultEncryptionS3Dao, S3_BUCKET_NAME, new SSEAwsKeyManagementParams()),
57-
new SSEAwsKeyManagementParams(),
58-
defaultEncryptionS3Dao
59-
},
60-
// S3 SSE-KMS encryption with customer managed KMS key
61-
{
62-
new S3BackedPayloadStore(customerKMSKeyEncryptionS3Dao, S3_BUCKET_NAME,
63-
new SSEAwsKeyManagementParams(S3_SERVER_SIDE_ENCRYPTION_KMS_KEY_ID)),
64-
new SSEAwsKeyManagementParams(S3_SERVER_SIDE_ENCRYPTION_KMS_KEY_ID),
65-
customerKMSKeyEncryptionS3Dao
66-
}
67-
};
68-
}
69-
7038
@Test
71-
@Parameters(method = "testData")
72-
public void testStoreOriginalPayloadOnSuccess(PayloadStore payloadStore,
73-
SSEAwsKeyManagementParams expectedParams, S3Dao mockS3Dao) {
39+
public void testStoreOriginalPayloadOnSuccess() {
7440
String actualPayloadPointer = payloadStore.storeOriginalPayload(ANY_PAYLOAD, ANY_PAYLOAD_LENGTH);
7541

7642
ArgumentCaptor<String> keyCaptor = ArgumentCaptor.forClass(String.class);
7743
ArgumentCaptor<SSEAwsKeyManagementParams> sseArgsCaptor = ArgumentCaptor.forClass(SSEAwsKeyManagementParams.class);
44+
ArgumentCaptor<CannedAccessControlList> cannedArgsCaptor = ArgumentCaptor.forClass(CannedAccessControlList.class);
7845

79-
verify(mockS3Dao, times(1)).storeTextInS3(eq(S3_BUCKET_NAME), keyCaptor.capture(),
80-
sseArgsCaptor.capture(), eq(ANY_PAYLOAD), eq(ANY_PAYLOAD_LENGTH));
46+
verify(s3Dao, times(1)).storeTextInS3(eq(S3_BUCKET_NAME), keyCaptor.capture(),
47+
eq(ANY_PAYLOAD), eq(ANY_PAYLOAD_LENGTH));
8148

8249
PayloadS3Pointer expectedPayloadPointer = new PayloadS3Pointer(S3_BUCKET_NAME, keyCaptor.getValue());
8350
assertEquals(expectedPayloadPointer.toJson(), actualPayloadPointer);
84-
85-
if (expectedParams == null) {
86-
assertTrue(sseArgsCaptor.getValue() == null);
87-
} else {
88-
assertEquals(expectedParams.getAwsKmsKeyId(), sseArgsCaptor.getValue().getAwsKmsKeyId());
89-
}
9051
}
9152

9253
@Test
93-
@Parameters(method = "testData")
94-
public void testStoreOriginalPayloadDoesAlwaysCreateNewObjects(PayloadStore payloadStore,
95-
SSEAwsKeyManagementParams expectedParams,
96-
S3Dao mockS3Dao) {
54+
public void testStoreOriginalPayloadDoesAlwaysCreateNewObjects() {
9755
//Store any payload
9856
String anyActualPayloadPointer = payloadStore
9957
.storeOriginalPayload(ANY_PAYLOAD, ANY_PAYLOAD_LENGTH);
@@ -104,11 +62,8 @@ public void testStoreOriginalPayloadDoesAlwaysCreateNewObjects(PayloadStore payl
10462

10563
ArgumentCaptor<String> anyOtherKeyCaptor = ArgumentCaptor.forClass(String.class);
10664

107-
ArgumentCaptor<SSEAwsKeyManagementParams> sseArgsCaptor = ArgumentCaptor
108-
.forClass(SSEAwsKeyManagementParams.class);
109-
110-
verify(mockS3Dao, times(2)).storeTextInS3(eq(S3_BUCKET_NAME), anyOtherKeyCaptor.capture(),
111-
sseArgsCaptor.capture(), eq(ANY_PAYLOAD), eq(ANY_PAYLOAD_LENGTH));
65+
verify(s3Dao, times(2)).storeTextInS3(eq(S3_BUCKET_NAME), anyOtherKeyCaptor.capture(),
66+
eq(ANY_PAYLOAD), eq(ANY_PAYLOAD_LENGTH));
11267

11368
String anyS3Key = anyOtherKeyCaptor.getAllValues().get(0);
11469
String anyOtherS3Key = anyOtherKeyCaptor.getAllValues().get(1);
@@ -121,26 +76,15 @@ public void testStoreOriginalPayloadDoesAlwaysCreateNewObjects(PayloadStore payl
12176

12277
assertThat(anyS3Key, Matchers.not(anyOtherS3Key));
12378
assertThat(anyActualPayloadPointer, Matchers.not(anyOtherActualPayloadPointer));
124-
125-
if (expectedParams == null) {
126-
assertTrue(sseArgsCaptor.getAllValues().stream().allMatch(actualParams -> actualParams == null));
127-
} else {
128-
assertTrue(sseArgsCaptor.getAllValues().stream().allMatch(actualParams ->
129-
(actualParams.getAwsKmsKeyId() == null && expectedParams.getAwsKmsKeyId() == null)
130-
|| (actualParams.getAwsKmsKeyId().equals(expectedParams.getAwsKmsKeyId()))));
131-
}
13279
}
13380

13481
@Test
135-
@Parameters(method = "testData")
136-
public void testStoreOriginalPayloadOnS3Failure(PayloadStore payloadStore,
137-
SSEAwsKeyManagementParams expectedParams, S3Dao mockS3Dao) {
82+
public void testStoreOriginalPayloadOnS3Failure() {
13883
doThrow(new AmazonClientException("S3 Exception"))
139-
.when(mockS3Dao)
84+
.when(s3Dao)
14085
.storeTextInS3(
14186
any(String.class),
14287
any(String.class),
143-
expectedParams == null ? isNull() : any(SSEAwsKeyManagementParams.class),
14488
any(String.class),
14589
any(Long.class));
14690

0 commit comments

Comments
 (0)