Skip to content

Commit 29c25b1

Browse files
Add SSE for pre signed URLs for AWS/GCP/ALI (#75)
1 parent de70eaf commit 29c25b1

File tree

10 files changed

+295
-23
lines changed

10 files changed

+295
-23
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
#
1+
#
22

3-
[![Build Status](https://github.com/salesforce/multicloudj/actions/workflows/build-test-codecov.yml/badge.svg)](https://github.com/salesforce/multicloudj/actions/workflows/build-test-codecov.yml)
3+
[![Java 11 Build](https://github.com/salesforce/multicloudj/actions/workflows/java11-build.yml/badge.svg)](https://github.com/salesforce/multicloudj/actions/workflows/java11-build.yml)
4+
[![Java 17 Build](https://github.com/salesforce/multicloudj/actions/workflows/java17-build.yml/badge.svg)](https://github.com/salesforce/multicloudj/actions/workflows/java17-build.yml)
5+
[![Java 21 Build](https://github.com/salesforce/multicloudj/actions/workflows/java21-build.yml/badge.svg)](https://github.com/salesforce/multicloudj/actions/workflows/java21-build.yml)
46
[![codecov](https://codecov.io/gh/salesforce/multicloudj/branch/main/graph/badge.svg)](https://codecov.io/gh/salesforce/multicloudj)
57
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
68
[![Maven Central](https://img.shields.io/maven-central/v/com.salesforce.multicloudj/multicloudj-parent.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/com.salesforce.multicloudj/multicloudj-parent)

blob/blob-ali/src/main/java/com/salesforce/multicloudj/blob/ali/AliTransformer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,13 @@ public GeneratePresignedUrlRequest toPresignedUrlUploadRequest(PresignedUrlReque
260260
if(encodedTagging instanceof String) {
261261
presignedUrlRequest.addHeader(OSSHeaders.OSS_TAGGING, (String)encodedTagging);
262262
}
263+
264+
// Add KMS encryption headers if KMS key is specified
265+
if(request.getKmsKeyId() != null && !request.getKmsKeyId().isEmpty()) {
266+
presignedUrlRequest.addHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION, ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION);
267+
presignedUrlRequest.addHeader(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION_KEY_ID, request.getKmsKeyId());
268+
}
269+
263270
return presignedUrlRequest;
264271
}
265272

blob/blob-ali/src/test/java/com/salesforce/multicloudj/blob/ali/AliTransformerTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,54 @@ void testToPresignedUrlUploadRequest() {
504504
assertNotNull(actual.getExpiration());
505505
}
506506

507+
@Test
508+
void testToPresignedUrlUploadRequestWithKmsKey() {
509+
Map<String, String> metadata = Map.of("key-1", "value-1");
510+
String kmsKeyId = "alias/my-kms-key";
511+
Duration duration = Duration.ofHours(12);
512+
PresignedUrlRequest presignedUploadRequest = PresignedUrlRequest.builder()
513+
.type(PresignedOperation.UPLOAD)
514+
.key("object-1")
515+
.metadata(metadata)
516+
.kmsKeyId(kmsKeyId)
517+
.duration(duration)
518+
.build();
519+
520+
var actual = transformer.toPresignedUrlUploadRequest(presignedUploadRequest);
521+
522+
assertEquals(HttpMethod.PUT, actual.getMethod());
523+
assertEquals(BUCKET, actual.getBucketName());
524+
assertEquals("object-1", actual.getKey());
525+
Map<String,String> headers = actual.getHeaders();
526+
assertEquals(ObjectMetadata.KMS_SERVER_SIDE_ENCRYPTION, headers.get(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION));
527+
assertEquals(kmsKeyId, headers.get(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION_KEY_ID));
528+
assertEquals("value-1", actual.getUserMetadata().get("key-1"));
529+
assertNotNull(actual.getExpiration());
530+
}
531+
532+
@Test
533+
void testToPresignedUrlUploadRequestWithoutKmsKey() {
534+
Map<String, String> metadata = Map.of("key-1", "value-1");
535+
Duration duration = Duration.ofHours(12);
536+
PresignedUrlRequest presignedUploadRequest = PresignedUrlRequest.builder()
537+
.type(PresignedOperation.UPLOAD)
538+
.key("object-1")
539+
.metadata(metadata)
540+
.duration(duration)
541+
.build();
542+
543+
var actual = transformer.toPresignedUrlUploadRequest(presignedUploadRequest);
544+
545+
assertEquals(HttpMethod.PUT, actual.getMethod());
546+
assertEquals(BUCKET, actual.getBucketName());
547+
assertEquals("object-1", actual.getKey());
548+
Map<String,String> headers = actual.getHeaders();
549+
assertNull(headers.get(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION));
550+
assertNull(headers.get(OSSHeaders.OSS_SERVER_SIDE_ENCRYPTION_KEY_ID));
551+
assertEquals("value-1", actual.getUserMetadata().get("key-1"));
552+
assertNotNull(actual.getExpiration());
553+
}
554+
507555
@Test
508556
void testToPresignedUrlDownloadRequest() {
509557
Duration duration = Duration.ofHours(12);

blob/blob-aws/src/main/java/com/salesforce/multicloudj/blob/aws/AwsTransformer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,9 @@ public PutObjectPresignRequest toPutObjectPresignRequest(PresignedUrlRequest req
355355
if(request.getTags() != null) {
356356
builder.withTags(request.getTags());
357357
}
358+
if(request.getKmsKeyId() != null) {
359+
builder.withKmsKeyId(request.getKmsKeyId());
360+
}
358361
UploadRequest uploadRequest = builder.build();
359362

360363
return PutObjectPresignRequest.builder()

blob/blob-aws/src/test/java/com/salesforce/multicloudj/blob/aws/AwsTransformerTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,46 @@ void testToPutObjectPresignRequest() {
494494
assertEquals(Duration.ofHours(4), actualRequest.signatureDuration());
495495
}
496496

497+
@Test
498+
void testToPutObjectPresignRequestWithKmsKey() {
499+
Map<String, String> metadata = Map.of("some-key", "some-value");
500+
Map<String, String> tags = Map.of("tag-key", "tag-value");
501+
String kmsKeyId = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012";
502+
PresignedUrlRequest presignedUrlRequest = PresignedUrlRequest.builder()
503+
.type(PresignedOperation.UPLOAD)
504+
.key("object-1")
505+
.duration(Duration.ofHours(4))
506+
.metadata(metadata)
507+
.tags(tags)
508+
.kmsKeyId(kmsKeyId)
509+
.build();
510+
PutObjectPresignRequest actualRequest = transformer.toPutObjectPresignRequest(presignedUrlRequest);
511+
assertEquals(BUCKET, actualRequest.putObjectRequest().bucket());
512+
assertEquals("object-1", actualRequest.putObjectRequest().key());
513+
assertEquals(metadata, actualRequest.putObjectRequest().metadata());
514+
assertEquals("tag-key=tag-value", actualRequest.putObjectRequest().tagging());
515+
assertEquals(Duration.ofHours(4), actualRequest.signatureDuration());
516+
assertEquals("aws:kms", actualRequest.putObjectRequest().serverSideEncryptionAsString());
517+
assertEquals(kmsKeyId, actualRequest.putObjectRequest().ssekmsKeyId());
518+
}
519+
520+
@Test
521+
void testToPutObjectPresignRequestWithoutKmsKey() {
522+
Map<String, String> metadata = Map.of("some-key", "some-value");
523+
PresignedUrlRequest presignedUrlRequest = PresignedUrlRequest.builder()
524+
.type(PresignedOperation.UPLOAD)
525+
.key("object-1")
526+
.duration(Duration.ofHours(4))
527+
.metadata(metadata)
528+
.build();
529+
PutObjectPresignRequest actualRequest = transformer.toPutObjectPresignRequest(presignedUrlRequest);
530+
assertEquals(BUCKET, actualRequest.putObjectRequest().bucket());
531+
assertEquals("object-1", actualRequest.putObjectRequest().key());
532+
assertEquals(metadata, actualRequest.putObjectRequest().metadata());
533+
assertNull(actualRequest.putObjectRequest().serverSideEncryptionAsString());
534+
assertNull(actualRequest.putObjectRequest().ssekmsKeyId());
535+
}
536+
497537
@Test
498538
void testToGetObjectPresignRequest() {
499539
PresignedUrlRequest presignedUrlRequest = PresignedUrlRequest.builder()

blob/blob-client/src/main/java/com/salesforce/multicloudj/blob/driver/PresignedUrlRequest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,9 @@ public class PresignedUrlRequest {
3737
* Optional: Specify the tags to be used in a presignedUrl upload
3838
*/
3939
private final Map<String, String> tags;
40+
41+
/**
42+
* Optional: Specify the KMS key ID to be used for encryption in a presignedUrl upload
43+
*/
44+
private final String kmsKeyId;
4045
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.salesforce.multicloudj.blob.driver;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.time.Duration;
6+
import java.util.Map;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertNull;
10+
11+
public class PresignedUrlRequestTest {
12+
13+
@Test
14+
void testBuilderWithAllFields() {
15+
String key = "test-key";
16+
Duration duration = Duration.ofHours(1);
17+
PresignedOperation type = PresignedOperation.UPLOAD;
18+
Map<String, String> metadata = Map.of("key1", "value1");
19+
Map<String, String> tags = Map.of("tag1", "tagValue1");
20+
String kmsKeyId = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012";
21+
22+
PresignedUrlRequest request = PresignedUrlRequest.builder()
23+
.key(key)
24+
.duration(duration)
25+
.type(type)
26+
.metadata(metadata)
27+
.tags(tags)
28+
.kmsKeyId(kmsKeyId)
29+
.build();
30+
31+
assertEquals(key, request.getKey());
32+
assertEquals(duration, request.getDuration());
33+
assertEquals(type, request.getType());
34+
assertEquals(metadata, request.getMetadata());
35+
assertEquals(tags, request.getTags());
36+
assertEquals(kmsKeyId, request.getKmsKeyId());
37+
}
38+
39+
@Test
40+
void testBuilderWithNullKmsKey() {
41+
String key = "test-key";
42+
Duration duration = Duration.ofHours(1);
43+
PresignedOperation type = PresignedOperation.DOWNLOAD;
44+
45+
PresignedUrlRequest request = PresignedUrlRequest.builder()
46+
.key(key)
47+
.duration(duration)
48+
.type(type)
49+
.kmsKeyId(null)
50+
.build();
51+
52+
assertEquals(key, request.getKey());
53+
assertEquals(duration, request.getDuration());
54+
assertEquals(type, request.getType());
55+
assertNull(request.getKmsKeyId());
56+
}
57+
58+
@Test
59+
void testBuilderWithEmptyKmsKey() {
60+
String key = "test-key";
61+
Duration duration = Duration.ofHours(1);
62+
PresignedOperation type = PresignedOperation.UPLOAD;
63+
64+
PresignedUrlRequest request = PresignedUrlRequest.builder()
65+
.key(key)
66+
.duration(duration)
67+
.type(type)
68+
.kmsKeyId("")
69+
.build();
70+
71+
assertEquals(key, request.getKey());
72+
assertEquals(duration, request.getDuration());
73+
assertEquals(type, request.getType());
74+
assertEquals("", request.getKmsKeyId());
75+
}
76+
77+
@Test
78+
void testBuilderWithoutKmsKey() {
79+
String key = "test-key";
80+
Duration duration = Duration.ofHours(1);
81+
PresignedOperation type = PresignedOperation.UPLOAD;
82+
Map<String, String> metadata = Map.of("key1", "value1");
83+
84+
PresignedUrlRequest request = PresignedUrlRequest.builder()
85+
.key(key)
86+
.duration(duration)
87+
.type(type)
88+
.metadata(metadata)
89+
.build();
90+
91+
assertEquals(key, request.getKey());
92+
assertEquals(duration, request.getDuration());
93+
assertEquals(type, request.getType());
94+
assertEquals(metadata, request.getMetadata());
95+
assertNull(request.getKmsKeyId());
96+
}
97+
}

blob/blob-gcp/src/main/java/com/salesforce/multicloudj/blob/gcp/GcpBlobStore.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import java.nio.file.Path;
6363
import java.util.ArrayList;
6464
import java.util.Collection;
65+
import java.util.HashMap;
6566
import java.util.Iterator;
6667
import java.util.List;
6768
import java.util.Map;
@@ -373,11 +374,21 @@ protected URL doGeneratePresignedUrl(PresignedUrlRequest request) {
373374
httpMethod = HttpMethod.GET;
374375
break;
375376
}
377+
List<Storage.SignUrlOption> options = new ArrayList<>();
378+
options.add(Storage.SignUrlOption.httpMethod(httpMethod));
379+
options.add(Storage.SignUrlOption.withV4Signature());
380+
381+
// Add KMS encryption header if specified
382+
if (request.getKmsKeyId() != null && !request.getKmsKeyId().isEmpty()) {
383+
Map<String, String> extHeaders = new HashMap<>();
384+
extHeaders.put("x-goog-encryption-kms-key-name", request.getKmsKeyId());
385+
options.add(Storage.SignUrlOption.withExtHeaders(extHeaders));
386+
}
387+
376388
return storage.signUrl(blobInfo,
377389
request.getDuration().toMillis(),
378390
TimeUnit.MILLISECONDS,
379-
Storage.SignUrlOption.httpMethod(httpMethod),
380-
Storage.SignUrlOption.withV4Signature());
391+
options.toArray(new Storage.SignUrlOption[0]));
381392
}
382393

383394
@Override

0 commit comments

Comments
 (0)