Skip to content

Commit d934a0c

Browse files
authored
Reinstate use of S3 protocol client setting (#127744)
The `s3.client.CLIENT_NAME.protocol` setting became unused in #126843 as it is inapplicable in the v2 SDK. However, the v2 SDK requires the `s3.client.CLIENT_NAME.endpoint` setting to be a URL that includes a scheme, so in #127489 we prepend a `https://` to the endpoint if needed. This commit generalizes this slightly so that we prepend `http://` if the endpoint has no scheme and the `.protocol` setting is set to `http`.
1 parent 9e3ae5b commit d934a0c

File tree

7 files changed

+162
-25
lines changed

7 files changed

+162
-25
lines changed

docs/changelog/126843.yaml

+4-5
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ breaking:
4242
`com.amazonaws.sdk.ec2MetadataServiceEndpointOverride` system property.
4343
4444
* AWS SDK v2 does not permit specifying a choice between HTTP and HTTPS so
45-
the `s3.client.${CLIENT_NAME}.protocol` setting is deprecated and no longer
46-
has any effect.
45+
the `s3.client.${CLIENT_NAME}.protocol` setting is deprecated.
4746
4847
* AWS SDK v2 does not permit control over throttling for retries, so the
4948
the `s3.client.${CLIENT_NAME}.use_throttle_retries` setting is deprecated
@@ -81,9 +80,9 @@ breaking:
8180
* If applicable, discontinue use of the
8281
`com.amazonaws.sdk.ec2MetadataServiceEndpointOverride` system property.
8382
84-
* If applicable, specify that you wish to use the insecure HTTP protocol to
85-
access the S3 API by setting `s3.client.${CLIENT_NAME}.endpoint` to a URL
86-
which starts with `http://`.
83+
* If applicable, specify the protocol to use to access the S3 API by
84+
setting `s3.client.${CLIENT_NAME}.endpoint` to a URL which starts with
85+
`http://` or `https://`.
8786
8887
* If applicable, discontinue use of the `log-delivery-write` canned ACL.
8988
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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.aws.DynamicRegionSupplier;
13+
import fixture.s3.S3HttpFixture;
14+
15+
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters;
16+
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
17+
18+
import org.elasticsearch.test.cluster.ElasticsearchCluster;
19+
import org.elasticsearch.test.fixtures.testcontainers.TestContainersThreadFilter;
20+
import org.junit.ClassRule;
21+
import org.junit.rules.RuleChain;
22+
import org.junit.rules.TestRule;
23+
24+
import java.util.function.Supplier;
25+
26+
import static fixture.aws.AwsCredentialsUtils.fixedAccessKey;
27+
import static org.hamcrest.Matchers.startsWith;
28+
29+
@ThreadLeakFilters(filters = { TestContainersThreadFilter.class })
30+
@ThreadLeakScope(ThreadLeakScope.Scope.NONE) // https://github.com/elastic/elasticsearch/issues/102482
31+
public class RepositoryS3ExplicitProtocolRestIT extends AbstractRepositoryS3RestTestCase {
32+
33+
private static final String PREFIX = getIdentifierPrefix("RepositoryS3ExplicitProtocolRestIT");
34+
private static final String BUCKET = PREFIX + "bucket";
35+
private static final String BASE_PATH = PREFIX + "base_path";
36+
private static final String ACCESS_KEY = PREFIX + "access-key";
37+
private static final String SECRET_KEY = PREFIX + "secret-key";
38+
private static final String CLIENT = "explicit_protocol_client";
39+
40+
private static final Supplier<String> regionSupplier = new DynamicRegionSupplier();
41+
private static final S3HttpFixture s3Fixture = new S3HttpFixture(
42+
true,
43+
BUCKET,
44+
BASE_PATH,
45+
fixedAccessKey(ACCESS_KEY, regionSupplier, "s3")
46+
);
47+
48+
private static String getEndpoint() {
49+
final var s3FixtureAddress = s3Fixture.getAddress();
50+
assertThat(s3FixtureAddress, startsWith("http://"));
51+
return s3FixtureAddress.substring("http://".length());
52+
}
53+
54+
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
55+
.module("repository-s3")
56+
.systemProperty("aws.region", regionSupplier)
57+
.keystore("s3.client." + CLIENT + ".access_key", ACCESS_KEY)
58+
.keystore("s3.client." + CLIENT + ".secret_key", SECRET_KEY)
59+
.setting("s3.client." + CLIENT + ".endpoint", RepositoryS3ExplicitProtocolRestIT::getEndpoint)
60+
.setting("s3.client." + CLIENT + ".protocol", () -> "http")
61+
.build();
62+
63+
@ClassRule
64+
public static TestRule ruleChain = RuleChain.outerRule(s3Fixture).around(cluster);
65+
66+
@Override
67+
protected String getTestRestCluster() {
68+
return cluster.getHttpAddresses();
69+
}
70+
71+
@Override
72+
protected String getBucketName() {
73+
return BUCKET;
74+
}
75+
76+
@Override
77+
protected String getBasePath() {
78+
return BASE_PATH;
79+
}
80+
81+
@Override
82+
protected String getClientName() {
83+
return CLIENT;
84+
}
85+
}

modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3ClientSettings.java

+15-3
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ final class S3ClientSettings {
7777
key -> new Setting<>(key, "", s -> s.toLowerCase(Locale.ROOT), Property.NodeScope)
7878
);
7979

80-
/** Formerly the protocol to use to connect to s3, now unused. V2 AWS SDK can infer the protocol from {@link #endpoint}. */
80+
/** The protocol to use to connect to s3, now only used if {@link #endpoint} is not a proper URI that starts with {@code http://} or
81+
* {@code https://}. */
8182
@UpdateForV10(owner = UpdateForV10.Owner.DISTRIBUTED_COORDINATION) // no longer used, should be removed in v10
82-
static final Setting.AffixSetting<HttpScheme> UNUSED_PROTOCOL_SETTING = Setting.affixKeySetting(
83+
static final Setting.AffixSetting<HttpScheme> PROTOCOL_SETTING = Setting.affixKeySetting(
8384
PREFIX,
8485
"protocol",
8586
key -> new Setting<>(key, "https", s -> HttpScheme.valueOf(s.toUpperCase(Locale.ROOT)), Property.NodeScope, Property.Deprecated)
@@ -181,6 +182,9 @@ final class S3ClientSettings {
181182
/** Credentials to authenticate with s3. */
182183
final AwsCredentials credentials;
183184

185+
/** The scheme (HTTP or HTTPS) for talking to the endpoint, for use only if the endpoint doesn't contain an explicit scheme */
186+
final HttpScheme protocol;
187+
184188
/** The s3 endpoint the client should talk to, or empty string to use the default. */
185189
final String endpoint;
186190

@@ -221,6 +225,7 @@ final class S3ClientSettings {
221225

222226
private S3ClientSettings(
223227
AwsCredentials credentials,
228+
HttpScheme protocol,
224229
String endpoint,
225230
String proxyHost,
226231
int proxyPort,
@@ -235,6 +240,7 @@ private S3ClientSettings(
235240
String region
236241
) {
237242
this.credentials = credentials;
243+
this.protocol = protocol;
238244
this.endpoint = endpoint;
239245
this.proxyHost = proxyHost;
240246
this.proxyPort = proxyPort;
@@ -261,6 +267,7 @@ S3ClientSettings refine(Settings repositorySettings) {
261267
.put(repositorySettings)
262268
.normalizePrefix(PREFIX + PLACEHOLDER_CLIENT + '.')
263269
.build();
270+
final HttpScheme newProtocol = getRepoSettingOrDefault(PROTOCOL_SETTING, normalizedSettings, protocol);
264271
final String newEndpoint = getRepoSettingOrDefault(ENDPOINT_SETTING, normalizedSettings, endpoint);
265272

266273
final String newProxyHost = getRepoSettingOrDefault(PROXY_HOST_SETTING, normalizedSettings, proxyHost);
@@ -284,7 +291,8 @@ S3ClientSettings refine(Settings repositorySettings) {
284291
newCredentials = credentials;
285292
}
286293
final String newRegion = getRepoSettingOrDefault(REGION, normalizedSettings, region);
287-
if (Objects.equals(endpoint, newEndpoint)
294+
if (Objects.equals(protocol, newProtocol)
295+
&& Objects.equals(endpoint, newEndpoint)
288296
&& Objects.equals(proxyHost, newProxyHost)
289297
&& proxyPort == newProxyPort
290298
&& proxyScheme == newProxyScheme
@@ -299,6 +307,7 @@ S3ClientSettings refine(Settings repositorySettings) {
299307
}
300308
return new S3ClientSettings(
301309
newCredentials,
310+
newProtocol,
302311
newEndpoint,
303312
newProxyHost,
304313
newProxyPort,
@@ -405,6 +414,7 @@ static S3ClientSettings getClientSettings(final Settings settings, final String
405414
) {
406415
return new S3ClientSettings(
407416
S3ClientSettings.loadCredentials(settings, clientName),
417+
getConfigValue(settings, clientName, PROTOCOL_SETTING),
408418
getConfigValue(settings, clientName, ENDPOINT_SETTING),
409419
getConfigValue(settings, clientName, PROXY_HOST_SETTING),
410420
getConfigValue(settings, clientName, PROXY_PORT_SETTING),
@@ -435,6 +445,7 @@ public boolean equals(final Object o) {
435445
&& maxConnections == that.maxConnections
436446
&& maxRetries == that.maxRetries
437447
&& Objects.equals(credentials, that.credentials)
448+
&& Objects.equals(protocol, that.protocol)
438449
&& Objects.equals(endpoint, that.endpoint)
439450
&& Objects.equals(proxyHost, that.proxyHost)
440451
&& proxyScheme == that.proxyScheme
@@ -448,6 +459,7 @@ public boolean equals(final Object o) {
448459
public int hashCode() {
449460
return Objects.hash(
450461
credentials,
462+
protocol,
451463
endpoint,
452464
proxyHost,
453465
proxyPort,

modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3RepositoryPlugin.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public List<Setting<?>> getSettings() {
131131
S3ClientSettings.SECRET_KEY_SETTING,
132132
S3ClientSettings.SESSION_TOKEN_SETTING,
133133
S3ClientSettings.ENDPOINT_SETTING,
134-
S3ClientSettings.UNUSED_PROTOCOL_SETTING,
134+
S3ClientSettings.PROTOCOL_SETTING,
135135
S3ClientSettings.PROXY_HOST_SETTING,
136136
S3ClientSettings.PROXY_PORT_SETTING,
137137
S3ClientSettings.PROXY_SCHEME_SETTING,

modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3Service.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -257,14 +257,18 @@ protected S3ClientBuilder buildClientBuilder(S3ClientSettings clientSettings, Sd
257257
String endpoint = clientSettings.endpoint;
258258
if ((endpoint.startsWith("http://") || endpoint.startsWith("https://")) == false) {
259259
// The SDK does not know how to interpret endpoints without a scheme prefix and will error. Therefore, when the scheme is
260-
// absent, we'll supply HTTPS as a default to avoid errors.
260+
// absent, we'll look at the deprecated .protocol setting
261261
// See https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/client-configuration.html#client-config-other-diffs
262-
endpoint = "https://" + endpoint;
262+
endpoint = switch (clientSettings.protocol) {
263+
case HTTP -> "http://" + endpoint;
264+
case HTTPS -> "https://" + endpoint;
265+
};
263266
LOGGER.warn(
264267
"""
265-
found S3 client with endpoint [{}] that is missing a scheme, guessing it should use 'https://'; \
268+
found S3 client with endpoint [{}] that is missing a scheme, guessing it should be [{}]; \
266269
to suppress this warning, add a scheme prefix to the [{}] setting on this node""",
267270
clientSettings.endpoint,
271+
endpoint,
268272
S3ClientSettings.ENDPOINT_SETTING.getConcreteSettingForNamespace("CLIENT_NAME").getKey()
269273
);
270274
}

modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3ClientSettingsTests.java

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public void testThereIsADefaultClientByDefault() {
3434

3535
final S3ClientSettings defaultSettings = settings.get("default");
3636
assertThat(defaultSettings.credentials, nullValue());
37+
assertThat(defaultSettings.protocol, is(HttpScheme.HTTPS));
3738
assertThat(defaultSettings.endpoint, is(emptyString()));
3839
assertThat(defaultSettings.proxyHost, is(emptyString()));
3940
assertThat(defaultSettings.proxyPort, is(80));

modules/repository-s3/src/test/java/org/elasticsearch/repositories/s3/S3ServiceTests.java

+49-13
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import software.amazon.awssdk.core.retry.conditions.RetryCondition;
1515
import software.amazon.awssdk.http.SdkHttpClient;
1616
import software.amazon.awssdk.regions.Region;
17-
import software.amazon.awssdk.services.s3.S3Client;
1817
import software.amazon.awssdk.services.s3.endpoints.S3EndpointParams;
1918
import software.amazon.awssdk.services.s3.endpoints.internal.DefaultS3EndpointProvider;
2019
import software.amazon.awssdk.services.s3.model.S3Exception;
@@ -223,20 +222,57 @@ public void testGetClientRegionFallbackToUsEast1() {
223222
}
224223

225224
public void testEndpointOverrideSchemeDefaultsToHttpsWhenNotSpecified() {
226-
final S3Service s3Service = new S3Service(
227-
mock(Environment.class),
228-
Settings.EMPTY,
229-
mock(ResourceWatcherService.class),
230-
() -> Region.of("es-test-region")
225+
final var endpointWithoutScheme = randomIdentifier() + ".ignore";
226+
final var clientName = randomIdentifier();
227+
assertThat(
228+
getEndpointUri(Settings.builder().put("s3.client." + clientName + ".endpoint", endpointWithoutScheme), clientName),
229+
equalTo(URI.create("https://" + endpointWithoutScheme))
230+
);
231+
}
232+
233+
public void testEndpointOverrideSchemeUsesHttpsIfHttpsProtocolSpecified() {
234+
final var endpointWithoutScheme = randomIdentifier() + ".ignore";
235+
final var clientName = randomIdentifier();
236+
assertThat(
237+
getEndpointUri(
238+
Settings.builder()
239+
.put("s3.client." + clientName + ".endpoint", endpointWithoutScheme)
240+
.put("s3.client." + clientName + ".protocol", "https"),
241+
clientName
242+
),
243+
equalTo(URI.create("https://" + endpointWithoutScheme))
231244
);
232-
final String endpointWithoutScheme = randomIdentifier() + ".ignore";
233-
S3Client s3Client = s3Service.buildClient(
234-
S3ClientSettings.getClientSettings(
235-
Settings.builder().put("s3.client.test-client.endpoint", endpointWithoutScheme).build(),
236-
"test-client"
245+
assertWarnings(Strings.format("""
246+
[s3.client.%s.protocol] setting was deprecated in Elasticsearch and will be removed in a future release. \
247+
See the breaking changes documentation for the next major version.""", clientName));
248+
}
249+
250+
public void testEndpointOverrideSchemeUsesHttpIfHttpProtocolSpecified() {
251+
final var endpointWithoutScheme = randomIdentifier() + ".ignore";
252+
final var clientName = randomIdentifier();
253+
assertThat(
254+
getEndpointUri(
255+
Settings.builder()
256+
.put("s3.client." + clientName + ".endpoint", endpointWithoutScheme)
257+
.put("s3.client." + clientName + ".protocol", "http"),
258+
clientName
237259
),
238-
mock(SdkHttpClient.class)
260+
equalTo(URI.create("http://" + endpointWithoutScheme))
239261
);
240-
assertThat(s3Client.serviceClientConfiguration().endpointOverride().get(), equalTo(URI.create("https://" + endpointWithoutScheme)));
262+
assertWarnings(Strings.format("""
263+
[s3.client.%s.protocol] setting was deprecated in Elasticsearch and will be removed in a future release. \
264+
See the breaking changes documentation for the next major version.""", clientName));
265+
}
266+
267+
private static URI getEndpointUri(Settings.Builder settings, String clientName) {
268+
return new S3Service(
269+
mock(Environment.class),
270+
Settings.EMPTY,
271+
mock(ResourceWatcherService.class),
272+
() -> Region.of(randomIdentifier())
273+
).buildClient(S3ClientSettings.getClientSettings(settings.build(), clientName), mock(SdkHttpClient.class))
274+
.serviceClientConfiguration()
275+
.endpointOverride()
276+
.get();
241277
}
242278
}

0 commit comments

Comments
 (0)