Skip to content

Commit c743f06

Browse files
wilkesdadoonet
authored andcommitted
Add Server Side Encryption support for S3 repository
For legal reasons, we need to store our ES backups in S3 using server side encryption. Closes elastic#78. (cherry picked from commit b3f9e12)
1 parent 6fd4945 commit c743f06

File tree

5 files changed

+102
-6
lines changed

5 files changed

+102
-6
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,9 @@ The following settings are supported:
9999
* `secret_key`: The secret key to use for authentication. Defaults to value of `cloud.aws.secret_key`.
100100
* `chunk_size`: Big files can be broken down into chunks during snapshotting if needed. The chunk size can be specified in bytes or by using size value notation, i.e. `1g`, `10m`, `5k`. Defaults to `100m`.
101101
* `compress`: When set to `true` metadata files are stored in compressed format. This setting doesn't affect index files that are already compressed by default. Defaults to `false`.
102+
* `server_side_encryption`: When set to `true` files are encrypted on server side using AES256 algorithm. Defaults to `false`.
102103

103-
The S3 repositories are using the same credentials as the rest of the AWSS3 Repo documentation #83 services provided by this plugin (`discovery` and `gateway`). They can be configured the following way:
104+
The S3 repositories are using the same credentials as the rest of the AWS services provided by this plugin (`discovery` and `gateway`). They can be configured the following way:
104105

105106
cloud:
106107
aws:

src/main/java/org/elasticsearch/cloud/aws/blobstore/S3BlobStore.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,15 @@ public class S3BlobStore extends AbstractComponent implements BlobStore {
5252

5353
private final int bufferSizeInBytes;
5454

55-
public S3BlobStore(Settings settings, AmazonS3 client, String bucket, @Nullable String region, ThreadPool threadPool) {
55+
private final boolean serverSideEncryption;
56+
57+
public S3BlobStore(Settings settings, AmazonS3 client, String bucket, @Nullable String region, ThreadPool threadPool, boolean serverSideEncryption) {
5658
super(settings);
5759
this.client = client;
5860
this.bucket = bucket;
5961
this.region = region;
6062
this.threadPool = threadPool;
63+
this.serverSideEncryption = serverSideEncryption;
6164

6265
this.bufferSizeInBytes = (int) settings.getAsBytesSize("buffer_size", new ByteSizeValue(100, ByteSizeUnit.KB)).bytes();
6366

@@ -87,6 +90,8 @@ public Executor executor() {
8790
return threadPool.executor(ThreadPool.Names.SNAPSHOT_DATA);
8891
}
8992

93+
public boolean serverSideEncryption() { return serverSideEncryption; }
94+
9095
public int bufferSizeInBytes() {
9196
return bufferSizeInBytes;
9297
}

src/main/java/org/elasticsearch/cloud/aws/blobstore/S3ImmutableBlobContainer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public void writeBlob(final String blobName, final InputStream is, final long si
4444
public void run() {
4545
try {
4646
ObjectMetadata md = new ObjectMetadata();
47+
if (blobStore.serverSideEncryption()) {
48+
md.setServerSideEncryption(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION);
49+
}
4750
md.setContentLength(sizeInBytes);
4851
PutObjectResult objectResult = blobStore.client().putObject(blobStore.bucket(), buildKey(blobName), is, md);
4952
listener.onCompleted();

src/main/java/org/elasticsearch/repositories/s3/S3Repository.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@
3636

3737
import java.io.IOException;
3838
import java.util.Locale;
39-
import java.util.concurrent.ExecutorService;
40-
import java.util.concurrent.TimeUnit;
4139

4240
/**
4341
* Shared file system implementation of the BlobStoreRepository
@@ -121,8 +119,9 @@ public S3Repository(RepositoryName name, RepositorySettings repositorySettings,
121119
}
122120
}
123121

124-
logger.debug("using bucket [{}], region [{}], chunk_size [{}]", bucket, region, chunkSize);
125-
blobStore = new S3BlobStore(settings, s3Service.client(region, repositorySettings.settings().get("access_key"), repositorySettings.settings().get("secret_key")), bucket, region, threadPool);
122+
boolean serverSideEncryption = repositorySettings.settings().getAsBoolean("server_side_encryption", componentSettings.getAsBoolean("server_side_encryption", false));
123+
logger.debug("using bucket [{}], region [{}], chunk_size [{}], server_side_encryption [{}]", bucket, region, chunkSize, serverSideEncryption);
124+
blobStore = new S3BlobStore(settings, s3Service.client(region, repositorySettings.settings().get("access_key"), repositorySettings.settings().get("secret_key")), bucket, region, threadPool, serverSideEncryption);
126125
this.chunkSize = repositorySettings.settings().getAsBytesSize("chunk_size", componentSettings.getAsBytesSize("chunk_size", new ByteSizeValue(100, ByteSizeUnit.MB)));
127126
this.compress = repositorySettings.settings().getAsBoolean("compress", componentSettings.getAsBoolean("compress", false));
128127
String basePath = repositorySettings.settings().get("base_path", null);

src/test/java/org/elasticsearch/repositories/s3/S3SnapshotRestoreTest.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import org.junit.Before;
4545
import org.junit.Test;
4646

47+
import java.util.List;
4748
import java.util.ArrayList;
4849

4950
import static org.hamcrest.Matchers.equalTo;
@@ -152,6 +153,93 @@ public void testSimpleWorkflow() {
152153
assertThat(clusterState.getMetaData().hasIndex("test-idx-1"), equalTo(true));
153154
assertThat(clusterState.getMetaData().hasIndex("test-idx-2"), equalTo(false));
154155
}
156+
157+
@Test
158+
public void testEncryption() {
159+
Client client = client();
160+
logger.info("--> creating s3 repository with bucket[{}] and path [{}]", cluster().getInstance(Settings.class).get("repositories.s3.bucket"), basePath);
161+
PutRepositoryResponse putRepositoryResponse = client.admin().cluster().preparePutRepository("test-repo")
162+
.setType("s3").setSettings(ImmutableSettings.settingsBuilder()
163+
.put("base_path", basePath)
164+
.put("chunk_size", randomIntBetween(1000, 10000))
165+
.put("server_side_encryption", true)
166+
).get();
167+
assertThat(putRepositoryResponse.isAcknowledged(), equalTo(true));
168+
169+
createIndex("test-idx-1", "test-idx-2", "test-idx-3");
170+
ensureGreen();
171+
172+
logger.info("--> indexing some data");
173+
for (int i = 0; i < 100; i++) {
174+
index("test-idx-1", "doc", Integer.toString(i), "foo", "bar" + i);
175+
index("test-idx-2", "doc", Integer.toString(i), "foo", "baz" + i);
176+
index("test-idx-3", "doc", Integer.toString(i), "foo", "baz" + i);
177+
}
178+
refresh();
179+
assertThat(client.prepareCount("test-idx-1").get().getCount(), equalTo(100L));
180+
assertThat(client.prepareCount("test-idx-2").get().getCount(), equalTo(100L));
181+
assertThat(client.prepareCount("test-idx-3").get().getCount(), equalTo(100L));
182+
183+
logger.info("--> snapshot");
184+
CreateSnapshotResponse createSnapshotResponse = client.admin().cluster().prepareCreateSnapshot("test-repo", "test-snap").setWaitForCompletion(true).setIndices("test-idx-*", "-test-idx-3").get();
185+
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0));
186+
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), equalTo(createSnapshotResponse.getSnapshotInfo().totalShards()));
187+
188+
assertThat(client.admin().cluster().prepareGetSnapshots("test-repo").setSnapshots("test-snap").get().getSnapshots().get(0).state(), equalTo(SnapshotState.SUCCESS));
189+
190+
Settings settings = cluster().getInstance(Settings.class);
191+
Settings bucket = settings.getByPrefix("repositories.s3.");
192+
AmazonS3 s3Client = cluster().getInstance(AwsS3Service.class).client(
193+
bucket.get("region", settings.get("repositories.s3.region")),
194+
bucket.get("access_key", settings.get("cloud.aws.access_key")),
195+
bucket.get("secret_key", settings.get("cloud.aws.secret_key")));
196+
197+
String bucketName = bucket.get("bucket");
198+
logger.info("--> verify encryption for bucket [{}], prefix [{}]", bucketName, basePath);
199+
List<S3ObjectSummary> summaries = s3Client.listObjects(bucketName, basePath).getObjectSummaries();
200+
for (S3ObjectSummary summary : summaries) {
201+
assertThat(s3Client.getObjectMetadata(bucketName, summary.getKey()).getServerSideEncryption(), equalTo("AES256"));
202+
}
203+
204+
logger.info("--> delete some data");
205+
for (int i = 0; i < 50; i++) {
206+
client.prepareDelete("test-idx-1", "doc", Integer.toString(i)).get();
207+
}
208+
for (int i = 50; i < 100; i++) {
209+
client.prepareDelete("test-idx-2", "doc", Integer.toString(i)).get();
210+
}
211+
for (int i = 0; i < 100; i += 2) {
212+
client.prepareDelete("test-idx-3", "doc", Integer.toString(i)).get();
213+
}
214+
refresh();
215+
assertThat(client.prepareCount("test-idx-1").get().getCount(), equalTo(50L));
216+
assertThat(client.prepareCount("test-idx-2").get().getCount(), equalTo(50L));
217+
assertThat(client.prepareCount("test-idx-3").get().getCount(), equalTo(50L));
218+
219+
logger.info("--> close indices");
220+
client.admin().indices().prepareClose("test-idx-1", "test-idx-2").get();
221+
222+
logger.info("--> restore all indices from the snapshot");
223+
RestoreSnapshotResponse restoreSnapshotResponse = client.admin().cluster().prepareRestoreSnapshot("test-repo", "test-snap").setWaitForCompletion(true).execute().actionGet();
224+
assertThat(restoreSnapshotResponse.getRestoreInfo().totalShards(), greaterThan(0));
225+
226+
ensureGreen();
227+
assertThat(client.prepareCount("test-idx-1").get().getCount(), equalTo(100L));
228+
assertThat(client.prepareCount("test-idx-2").get().getCount(), equalTo(100L));
229+
assertThat(client.prepareCount("test-idx-3").get().getCount(), equalTo(50L));
230+
231+
// Test restore after index deletion
232+
logger.info("--> delete indices");
233+
cluster().wipeIndices("test-idx-1", "test-idx-2");
234+
logger.info("--> restore one index after deletion");
235+
restoreSnapshotResponse = client.admin().cluster().prepareRestoreSnapshot("test-repo", "test-snap").setWaitForCompletion(true).setIndices("test-idx-*", "-test-idx-2").execute().actionGet();
236+
assertThat(restoreSnapshotResponse.getRestoreInfo().totalShards(), greaterThan(0));
237+
ensureGreen();
238+
assertThat(client.prepareCount("test-idx-1").get().getCount(), equalTo(100L));
239+
ClusterState clusterState = client.admin().cluster().prepareState().get().getState();
240+
assertThat(clusterState.getMetaData().hasIndex("test-idx-1"), equalTo(true));
241+
assertThat(clusterState.getMetaData().hasIndex("test-idx-2"), equalTo(false));
242+
}
155243

156244
/**
157245
* This test verifies that the test configuration is set up in a manner that

0 commit comments

Comments
 (0)