Skip to content

Commit 771bea1

Browse files
authored
Add end-to-end test for reloading S3 credentials (#116994)
We don't seem to have a test that completely verifies that a S3 repository can reload credentials from an updated keystore. This commit adds such a test. Backport of #116762 to 8.16.
1 parent 0a18b0e commit 771bea1

File tree

3 files changed

+108
-2
lines changed

3 files changed

+108
-2
lines changed

modules/repository-s3/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.elasticsearch.gradle.internal.test.InternalClusterTestPlugin
1212
*/
1313
apply plugin: 'elasticsearch.internal-yaml-rest-test'
1414
apply plugin: 'elasticsearch.internal-cluster-test'
15+
apply plugin: 'elasticsearch.internal-java-rest-test'
1516

1617
esplugin {
1718
description 'The S3 repository plugin adds S3 repositories'
@@ -48,6 +49,10 @@ dependencies {
4849
yamlRestTestImplementation project(':test:fixtures:minio-fixture')
4950
internalClusterTestImplementation project(':test:fixtures:minio-fixture')
5051

52+
javaRestTestImplementation project(":test:framework")
53+
javaRestTestImplementation project(':test:fixtures:s3-fixture')
54+
javaRestTestImplementation project(':modules:repository-s3')
55+
5156
yamlRestTestRuntimeOnly "org.slf4j:slf4j-simple:${versions.slf4j}"
5257
internalClusterTestRuntimeOnly "org.slf4j:slf4j-simple:${versions.slf4j}"
5358
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.repositories.s3;
11+
12+
import fixture.s3.S3HttpFixture;
13+
14+
import org.elasticsearch.client.Request;
15+
import org.elasticsearch.client.ResponseException;
16+
import org.elasticsearch.common.settings.Settings;
17+
import org.elasticsearch.test.ESTestCase;
18+
import org.elasticsearch.test.cluster.ElasticsearchCluster;
19+
import org.elasticsearch.test.cluster.MutableSettingsProvider;
20+
import org.elasticsearch.test.rest.ESRestTestCase;
21+
import org.junit.ClassRule;
22+
import org.junit.rules.RuleChain;
23+
import org.junit.rules.TestRule;
24+
25+
import java.io.IOException;
26+
27+
import static org.hamcrest.CoreMatchers.containsString;
28+
import static org.hamcrest.Matchers.allOf;
29+
import static org.hamcrest.Matchers.equalTo;
30+
31+
public class RepositoryS3RestIT extends ESRestTestCase {
32+
33+
private static final String BUCKET = "RepositoryS3JavaRestTest-bucket";
34+
private static final String BASE_PATH = "RepositoryS3JavaRestTest-base-path";
35+
36+
public static final S3HttpFixture s3Fixture = new S3HttpFixture(true, BUCKET, BASE_PATH, "ignored");
37+
38+
private static final MutableSettingsProvider keystoreSettings = new MutableSettingsProvider();
39+
40+
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
41+
.module("repository-s3")
42+
.keystore(keystoreSettings)
43+
.setting("s3.client.default.endpoint", s3Fixture::getAddress)
44+
.build();
45+
46+
@ClassRule
47+
public static TestRule ruleChain = RuleChain.outerRule(s3Fixture).around(cluster);
48+
49+
@Override
50+
protected String getTestRestCluster() {
51+
return cluster.getHttpAddresses();
52+
}
53+
54+
public void testReloadCredentialsFromKeystore() throws IOException {
55+
// Register repository (?verify=false because we don't have access to the blob store yet)
56+
final var repositoryName = randomIdentifier();
57+
registerRepository(
58+
repositoryName,
59+
S3Repository.TYPE,
60+
false,
61+
Settings.builder().put("bucket", BUCKET).put("base_path", BASE_PATH).build()
62+
);
63+
final var verifyRequest = new Request("POST", "/_snapshot/" + repositoryName + "/_verify");
64+
65+
// Set up initial credentials
66+
final var accessKey1 = randomIdentifier();
67+
s3Fixture.setAccessKey(accessKey1);
68+
keystoreSettings.put("s3.client.default.access_key", accessKey1);
69+
keystoreSettings.put("s3.client.default.secret_key", randomIdentifier());
70+
cluster.updateStoredSecureSettings();
71+
assertOK(client().performRequest(new Request("POST", "/_nodes/reload_secure_settings")));
72+
73+
// Check access using initial credentials
74+
assertOK(client().performRequest(verifyRequest));
75+
76+
// Rotate credentials in blob store
77+
final var accessKey2 = randomValueOtherThan(accessKey1, ESTestCase::randomIdentifier);
78+
s3Fixture.setAccessKey(accessKey2);
79+
80+
// Ensure that initial credentials now invalid
81+
final var accessDeniedException2 = expectThrows(ResponseException.class, () -> client().performRequest(verifyRequest));
82+
assertThat(accessDeniedException2.getResponse().getStatusLine().getStatusCode(), equalTo(500));
83+
assertThat(
84+
accessDeniedException2.getMessage(),
85+
allOf(containsString("Bad access key"), containsString("Status Code: 403"), containsString("Error Code: AccessDenied"))
86+
);
87+
88+
// Set up refreshed credentials
89+
keystoreSettings.put("s3.client.default.access_key", accessKey2);
90+
cluster.updateStoredSecureSettings();
91+
assertOK(client().performRequest(new Request("POST", "/_nodes/reload_secure_settings")));
92+
93+
// Check access using refreshed credentials
94+
assertOK(client().performRequest(verifyRequest));
95+
}
96+
97+
}

test/fixtures/s3-fixture/src/main/java/fixture/s3/S3HttpFixture.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ public class S3HttpFixture extends ExternalResource {
2626

2727
private HttpServer server;
2828

29-
private boolean enabled;
29+
private final boolean enabled;
3030
private final String bucket;
3131
private final String basePath;
32-
protected final String accessKey;
32+
protected volatile String accessKey;
3333

3434
public S3HttpFixture() {
3535
this(true);
@@ -98,4 +98,8 @@ private static InetSocketAddress resolveAddress(String address, int port) {
9898
throw new RuntimeException(e);
9999
}
100100
}
101+
102+
public void setAccessKey(String accessKey) {
103+
this.accessKey = accessKey;
104+
}
101105
}

0 commit comments

Comments
 (0)