Skip to content

Commit 9b836db

Browse files
jed326tandonks
authored andcommitted
Add support for SSE-KMS, remove old server_side_encryption setting, add support for bucket owner verification (opensearch-project#18312)
Signed-off-by: Jay Deng <jayd0104@gmail.com>
1 parent df62baa commit 9b836db

14 files changed

+395
-58
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2828
- Use QueryCoordinatorContext for the rewrite in validate API. ([#18272](https://github.com/opensearch-project/OpenSearch/pull/18272))
2929
- Upgrade crypto kms plugin dependencies for AWS SDK v2.x. ([#18268](https://github.com/opensearch-project/OpenSearch/pull/18268))
3030
- Add support for `matched_fields` with the unified highlighter ([#18164](https://github.com/opensearch-project/OpenSearch/issues/18164))
31+
- [repository-s3] Add support for SSE-KMS and S3 bucket owner verification ([#18312](https://github.com/opensearch-project/OpenSearch/pull/18312))
3132

3233
### Changed
3334
- Create generic DocRequest to better categorize ActionRequests ([#18269](https://github.com/opensearch-project/OpenSearch/pull/18269)))
@@ -53,6 +54,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5354
### Deprecated
5455

5556
### Removed
57+
- [repository-s3] Removed existing ineffective `server_side_encryption` setting ([#18312](https://github.com/opensearch-project/OpenSearch/pull/18312))
5658

5759
### Fixed
5860
- Fix simultaneously creating a snapshot and updating the repository can potentially trigger an infinite loop ([#17532](https://github.com/opensearch-project/OpenSearch/pull/17532))

plugins/repository-s3/src/main/java/org/opensearch/repositories/s3/S3BlobContainer.java

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import software.amazon.awssdk.services.s3.model.NoSuchKeyException;
5454
import software.amazon.awssdk.services.s3.model.ObjectAttributes;
5555
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
56-
import software.amazon.awssdk.services.s3.model.ServerSideEncryption;
5756
import software.amazon.awssdk.services.s3.model.UploadPartRequest;
5857
import software.amazon.awssdk.services.s3.model.UploadPartResponse;
5958
import software.amazon.awssdk.services.s3.paginators.ListObjectsV2Iterable;
@@ -110,6 +109,7 @@
110109
import static org.opensearch.repositories.s3.S3Repository.MAX_FILE_SIZE;
111110
import static org.opensearch.repositories.s3.S3Repository.MAX_FILE_SIZE_USING_MULTIPART;
112111
import static org.opensearch.repositories.s3.S3Repository.MIN_PART_SIZE_USING_MULTIPART;
112+
import static org.opensearch.repositories.s3.utils.SseKmsUtil.configureEncryptionSettings;
113113

114114
class S3BlobContainer extends AbstractBlobContainer implements AsyncMultiStreamBlobContainer {
115115

@@ -129,7 +129,13 @@ public boolean blobExists(String blobName) {
129129
try (AmazonS3Reference clientReference = blobStore.clientReference()) {
130130
SocketAccess.doPrivileged(
131131
() -> clientReference.get()
132-
.headObject(HeadObjectRequest.builder().bucket(blobStore.bucket()).key(buildKey(blobName)).build())
132+
.headObject(
133+
HeadObjectRequest.builder()
134+
.bucket(blobStore.bucket())
135+
.key(buildKey(blobName))
136+
.expectedBucketOwner(blobStore.expectedBucketOwner())
137+
.build()
138+
)
133139
);
134140
return true;
135141
} catch (NoSuchKeyException e) {
@@ -214,7 +220,12 @@ public void asyncBlobUpload(WriteContext writeContext, ActionListener<Void> comp
214220
writeContext.doRemoteDataIntegrityCheck(),
215221
writeContext.getExpectedChecksum(),
216222
blobStore.isUploadRetryEnabled(),
217-
writeContext.getMetadata()
223+
writeContext.getMetadata(),
224+
blobStore.serverSideEncryptionType(),
225+
blobStore.serverSideEncryptionKmsKey(),
226+
blobStore.serverSideEncryptionBucketKey(),
227+
blobStore.serverSideEncryptionEncryptionContext(),
228+
blobStore.expectedBucketOwner()
218229
);
219230
try {
220231
// If file size is greater than the queue capacity than SizeBasedBlockingQ will always reject the upload.
@@ -498,6 +509,7 @@ private ListObjectsV2Request listObjectsRequest(String keyPath) {
498509
.prefix(keyPath)
499510
.delimiter("/")
500511
.overrideConfiguration(o -> o.addMetricPublisher(blobStore.getStatsMetricPublisher().listObjectsMetricPublisher))
512+
.expectedBucketOwner(blobStore.expectedBucketOwner())
501513
.build();
502514
}
503515

@@ -534,14 +546,13 @@ void executeSingleUpload(
534546
.contentLength(blobSize)
535547
.storageClass(blobStore.getStorageClass())
536548
.acl(blobStore.getCannedACL())
537-
.overrideConfiguration(o -> o.addMetricPublisher(blobStore.getStatsMetricPublisher().putObjectMetricPublisher));
549+
.overrideConfiguration(o -> o.addMetricPublisher(blobStore.getStatsMetricPublisher().putObjectMetricPublisher))
550+
.expectedBucketOwner(blobStore.expectedBucketOwner());
538551

539552
if (CollectionUtils.isNotEmpty(metadata)) {
540553
putObjectRequestBuilder = putObjectRequestBuilder.metadata(metadata);
541554
}
542-
if (blobStore.serverSideEncryption()) {
543-
putObjectRequestBuilder.serverSideEncryption(ServerSideEncryption.AES256);
544-
}
555+
configureEncryptionSettings(putObjectRequestBuilder, blobStore);
545556

546557
PutObjectRequest putObjectRequest = putObjectRequestBuilder.build();
547558
try (AmazonS3Reference clientReference = blobStore.clientReference()) {
@@ -591,15 +602,14 @@ void executeMultipartUpload(
591602
.key(blobName)
592603
.storageClass(blobStore.getStorageClass())
593604
.acl(blobStore.getCannedACL())
594-
.overrideConfiguration(o -> o.addMetricPublisher(blobStore.getStatsMetricPublisher().multipartUploadMetricCollector));
605+
.overrideConfiguration(o -> o.addMetricPublisher(blobStore.getStatsMetricPublisher().multipartUploadMetricCollector))
606+
.expectedBucketOwner(blobStore.expectedBucketOwner());
595607

596608
if (CollectionUtils.isNotEmpty(metadata)) {
597609
createMultipartUploadRequestBuilder.metadata(metadata);
598610
}
599611

600-
if (blobStore.serverSideEncryption()) {
601-
createMultipartUploadRequestBuilder.serverSideEncryption(ServerSideEncryption.AES256);
602-
}
612+
configureEncryptionSettings(createMultipartUploadRequestBuilder, blobStore);
603613

604614
final InputStream requestInputStream;
605615
if (blobStore.isUploadRetryEnabled()) {
@@ -628,6 +638,7 @@ void executeMultipartUpload(
628638
.partNumber(i)
629639
.contentLength((i < nbParts) ? partSize : lastPartSize)
630640
.overrideConfiguration(o -> o.addMetricPublisher(blobStore.getStatsMetricPublisher().multipartUploadMetricCollector))
641+
.expectedBucketOwner(blobStore.expectedBucketOwner())
631642
.build();
632643

633644
bytesCount += uploadPartRequest.contentLength();
@@ -650,6 +661,7 @@ void executeMultipartUpload(
650661
.uploadId(uploadId.get())
651662
.multipartUpload(CompletedMultipartUpload.builder().parts(parts).build())
652663
.overrideConfiguration(o -> o.addMetricPublisher(blobStore.getStatsMetricPublisher().multipartUploadMetricCollector))
664+
.expectedBucketOwner(blobStore.expectedBucketOwner())
653665
.build();
654666

655667
SocketAccess.doPrivilegedVoid(() -> clientReference.get().completeMultipartUpload(completeMultipartUploadRequest));
@@ -663,6 +675,7 @@ void executeMultipartUpload(
663675
.bucket(bucketName)
664676
.key(blobName)
665677
.uploadId(uploadId.get())
678+
.expectedBucketOwner(blobStore.expectedBucketOwner())
666679
.build();
667680
try (AmazonS3Reference clientReference = blobStore.clientReference()) {
668681
SocketAccess.doPrivilegedVoid(() -> clientReference.get().abortMultipartUpload(abortRequest));
@@ -729,12 +742,14 @@ CompletableFuture<InputStreamContainer> getBlobPartInputStreamContainer(
729742
@Nullable Integer partNumber
730743
) {
731744
final boolean isMultipartObject = partNumber != null;
732-
final GetObjectRequest.Builder getObjectRequestBuilder = GetObjectRequest.builder().bucket(bucketName).key(blobKey);
745+
final GetObjectRequest.Builder getObjectRequestBuilder = GetObjectRequest.builder()
746+
.bucket(bucketName)
747+
.key(blobKey)
748+
.expectedBucketOwner(blobStore.expectedBucketOwner());
733749

734750
if (isMultipartObject) {
735751
getObjectRequestBuilder.partNumber(partNumber);
736752
}
737-
738753
return SocketAccess.doPrivileged(
739754
() -> s3AsyncClient.getObject(getObjectRequestBuilder.build(), AsyncResponseTransformer.toBlockingInputStream())
740755
.thenApply(response -> transformResponseToInputStreamContainer(response, isMultipartObject))
@@ -775,6 +790,7 @@ CompletableFuture<GetObjectAttributesResponse> getBlobMetadata(S3AsyncClient s3A
775790
.bucket(bucketName)
776791
.key(blobName)
777792
.objectAttributes(ObjectAttributes.CHECKSUM, ObjectAttributes.OBJECT_SIZE, ObjectAttributes.OBJECT_PARTS)
793+
.expectedBucketOwner(blobStore.expectedBucketOwner())
778794
.build();
779795

780796
return SocketAccess.doPrivileged(() -> s3AsyncClient.getObjectAttributes(getObjectAttributesRequest));

plugins/repository-s3/src/main/java/org/opensearch/repositories/s3/S3BlobStore.java

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
import org.opensearch.repositories.s3.async.SizeBasedBlockingQ;
4949

5050
import java.io.IOException;
51+
import java.nio.charset.StandardCharsets;
52+
import java.util.Base64;
5153
import java.util.Collections;
5254
import java.util.HashMap;
5355
import java.util.Locale;
@@ -57,9 +59,13 @@
5759
import static org.opensearch.repositories.s3.S3Repository.BUFFER_SIZE_SETTING;
5860
import static org.opensearch.repositories.s3.S3Repository.BULK_DELETE_SIZE;
5961
import static org.opensearch.repositories.s3.S3Repository.CANNED_ACL_SETTING;
62+
import static org.opensearch.repositories.s3.S3Repository.EXPECTED_BUCKET_OWNER_SETTING;
6063
import static org.opensearch.repositories.s3.S3Repository.PERMIT_BACKED_TRANSFER_ENABLED;
6164
import static org.opensearch.repositories.s3.S3Repository.REDIRECT_LARGE_S3_UPLOAD;
62-
import static org.opensearch.repositories.s3.S3Repository.SERVER_SIDE_ENCRYPTION_SETTING;
65+
import static org.opensearch.repositories.s3.S3Repository.SERVER_SIDE_ENCRYPTION_BUCKET_KEY_SETTING;
66+
import static org.opensearch.repositories.s3.S3Repository.SERVER_SIDE_ENCRYPTION_ENCRYPTION_CONTEXT_SETTING;
67+
import static org.opensearch.repositories.s3.S3Repository.SERVER_SIDE_ENCRYPTION_KMS_KEY_SETTING;
68+
import static org.opensearch.repositories.s3.S3Repository.SERVER_SIDE_ENCRYPTION_TYPE_SETTING;
6369
import static org.opensearch.repositories.s3.S3Repository.STORAGE_CLASS_SETTING;
6470
import static org.opensearch.repositories.s3.S3Repository.UPLOAD_RETRY_ENABLED;
6571

@@ -81,7 +87,11 @@ public class S3BlobStore implements BlobStore {
8187

8288
private volatile boolean permitBackedTransferEnabled;
8389

84-
private volatile boolean serverSideEncryption;
90+
private volatile String serverSideEncryptionType;
91+
private volatile String serverSideEncryptionKmsKey;
92+
private volatile boolean serverSideEncryptionBucketKey;
93+
private volatile String serverSideEncryptionEncryptionContext;
94+
private volatile String expectedBucketOwner;
8595

8696
private volatile ObjectCannedACL cannedACL;
8797

@@ -107,7 +117,6 @@ public class S3BlobStore implements BlobStore {
107117
S3AsyncService s3AsyncService,
108118
boolean multipartUploadEnabled,
109119
String bucket,
110-
boolean serverSideEncryption,
111120
ByteSizeValue bufferSize,
112121
String cannedACL,
113122
String storageClass,
@@ -119,13 +128,17 @@ public class S3BlobStore implements BlobStore {
119128
AsyncExecutorContainer normalExecutorBuilder,
120129
SizeBasedBlockingQ normalPrioritySizeBasedBlockingQ,
121130
SizeBasedBlockingQ lowPrioritySizeBasedBlockingQ,
122-
GenericStatsMetricPublisher genericStatsMetricPublisher
131+
GenericStatsMetricPublisher genericStatsMetricPublisher,
132+
String serverSideEncryptionType,
133+
String serverSideEncryptionKmsKey,
134+
boolean serverSideEncryptionBucketKey,
135+
String serverSideEncryptionEncryptionContext,
136+
String expectedBucketOwner
123137
) {
124138
this.service = service;
125139
this.s3AsyncService = s3AsyncService;
126140
this.multipartUploadEnabled = multipartUploadEnabled;
127141
this.bucket = bucket;
128-
this.serverSideEncryption = serverSideEncryption;
129142
this.bufferSize = bufferSize;
130143
this.cannedACL = initCannedACL(cannedACL);
131144
this.storageClass = initStorageClass(storageClass);
@@ -142,20 +155,29 @@ public class S3BlobStore implements BlobStore {
142155
this.lowPrioritySizeBasedBlockingQ = lowPrioritySizeBasedBlockingQ;
143156
this.genericStatsMetricPublisher = genericStatsMetricPublisher;
144157
this.permitBackedTransferEnabled = PERMIT_BACKED_TRANSFER_ENABLED.get(repositoryMetadata.settings());
158+
this.serverSideEncryptionType = serverSideEncryptionType;
159+
this.serverSideEncryptionKmsKey = serverSideEncryptionKmsKey;
160+
this.serverSideEncryptionBucketKey = serverSideEncryptionBucketKey;
161+
this.serverSideEncryptionEncryptionContext = serverSideEncryptionEncryptionContext;
162+
this.expectedBucketOwner = expectedBucketOwner;
145163
}
146164

147165
@Override
148166
public void reload(RepositoryMetadata repositoryMetadata) {
149167
this.repositoryMetadata = repositoryMetadata;
150168
this.bucket = BUCKET_SETTING.get(repositoryMetadata.settings());
151-
this.serverSideEncryption = SERVER_SIDE_ENCRYPTION_SETTING.get(repositoryMetadata.settings());
152169
this.bufferSize = BUFFER_SIZE_SETTING.get(repositoryMetadata.settings());
153170
this.cannedACL = initCannedACL(CANNED_ACL_SETTING.get(repositoryMetadata.settings()));
154171
this.storageClass = initStorageClass(STORAGE_CLASS_SETTING.get(repositoryMetadata.settings()));
155172
this.bulkDeletesSize = BULK_DELETE_SIZE.get(repositoryMetadata.settings());
156173
this.redirectLargeUploads = REDIRECT_LARGE_S3_UPLOAD.get(repositoryMetadata.settings());
157174
this.uploadRetryEnabled = UPLOAD_RETRY_ENABLED.get(repositoryMetadata.settings());
158175
this.permitBackedTransferEnabled = PERMIT_BACKED_TRANSFER_ENABLED.get(repositoryMetadata.settings());
176+
this.serverSideEncryptionType = SERVER_SIDE_ENCRYPTION_TYPE_SETTING.get(repositoryMetadata.settings());
177+
this.serverSideEncryptionKmsKey = SERVER_SIDE_ENCRYPTION_KMS_KEY_SETTING.get(repositoryMetadata.settings());
178+
this.serverSideEncryptionBucketKey = SERVER_SIDE_ENCRYPTION_BUCKET_KEY_SETTING.get(repositoryMetadata.settings());
179+
this.serverSideEncryptionEncryptionContext = SERVER_SIDE_ENCRYPTION_ENCRYPTION_CONTEXT_SETTING.get(repositoryMetadata.settings());
180+
this.expectedBucketOwner = EXPECTED_BUCKET_OWNER_SETTING.get(repositoryMetadata.settings());
159181
}
160182

161183
@Override
@@ -191,8 +213,33 @@ public String bucket() {
191213
return bucket;
192214
}
193215

194-
public boolean serverSideEncryption() {
195-
return serverSideEncryption;
216+
public String serverSideEncryptionType() {
217+
return serverSideEncryptionType;
218+
}
219+
220+
public String serverSideEncryptionKmsKey() {
221+
return serverSideEncryptionKmsKey;
222+
}
223+
224+
public boolean serverSideEncryptionBucketKey() {
225+
return serverSideEncryptionBucketKey;
226+
}
227+
228+
/**
229+
* Returns the SSE encryption context base64 UTF8 encoded, as required by S3 SDK. If the encryption context is empty return
230+
* null as the S3 client ignores null header values
231+
*/
232+
public String serverSideEncryptionEncryptionContext() {
233+
return serverSideEncryptionEncryptionContext.isEmpty()
234+
? null
235+
: Base64.getEncoder().encodeToString(serverSideEncryptionEncryptionContext.getBytes(StandardCharsets.UTF_8));
236+
}
237+
238+
/**
239+
* Returns the expected bucket owner if set, else null as the S3 client ignores null header values
240+
*/
241+
public String expectedBucketOwner() {
242+
return expectedBucketOwner.isEmpty() ? null : expectedBucketOwner;
196243
}
197244

198245
public long bufferSizeInBytes() {

0 commit comments

Comments
 (0)