Skip to content

Commit 1eff103

Browse files
authored
Reinstate use of S3 protocol client setting (#127809)
* Reinstate use of S3 `protocol` client setting 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`. Backport of #127744 to `8.19` * Fix up warning assertions
1 parent 12ec1f7 commit 1eff103

File tree

7 files changed

+160
-25
lines changed

7 files changed

+160
-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,8 +77,9 @@ 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}. */
81-
static final Setting.AffixSetting<HttpScheme> UNUSED_PROTOCOL_SETTING = Setting.affixKeySetting(
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://}. */
82+
static final Setting.AffixSetting<HttpScheme> PROTOCOL_SETTING = Setting.affixKeySetting(
8283
PREFIX,
8384
"protocol",
8485
key -> new Setting<>(key, "https", s -> HttpScheme.valueOf(s.toUpperCase(Locale.ROOT)), Property.NodeScope, Property.Deprecated)
@@ -178,6 +179,9 @@ final class S3ClientSettings {
178179
/** Credentials to authenticate with s3. */
179180
final AwsCredentials credentials;
180181

182+
/** The scheme (HTTP or HTTPS) for talking to the endpoint, for use only if the endpoint doesn't contain an explicit scheme */
183+
final HttpScheme protocol;
184+
181185
/** The s3 endpoint the client should talk to, or empty string to use the default. */
182186
final String endpoint;
183187

@@ -218,6 +222,7 @@ final class S3ClientSettings {
218222

219223
private S3ClientSettings(
220224
AwsCredentials credentials,
225+
HttpScheme protocol,
221226
String endpoint,
222227
String proxyHost,
223228
int proxyPort,
@@ -232,6 +237,7 @@ private S3ClientSettings(
232237
String region
233238
) {
234239
this.credentials = credentials;
240+
this.protocol = protocol;
235241
this.endpoint = endpoint;
236242
this.proxyHost = proxyHost;
237243
this.proxyPort = proxyPort;
@@ -258,6 +264,7 @@ S3ClientSettings refine(Settings repositorySettings) {
258264
.put(repositorySettings)
259265
.normalizePrefix(PREFIX + PLACEHOLDER_CLIENT + '.')
260266
.build();
267+
final HttpScheme newProtocol = getRepoSettingOrDefault(PROTOCOL_SETTING, normalizedSettings, protocol);
261268
final String newEndpoint = getRepoSettingOrDefault(ENDPOINT_SETTING, normalizedSettings, endpoint);
262269

263270
final String newProxyHost = getRepoSettingOrDefault(PROXY_HOST_SETTING, normalizedSettings, proxyHost);
@@ -281,7 +288,8 @@ S3ClientSettings refine(Settings repositorySettings) {
281288
newCredentials = credentials;
282289
}
283290
final String newRegion = getRepoSettingOrDefault(REGION, normalizedSettings, region);
284-
if (Objects.equals(endpoint, newEndpoint)
291+
if (Objects.equals(protocol, newProtocol)
292+
&& Objects.equals(endpoint, newEndpoint)
285293
&& Objects.equals(proxyHost, newProxyHost)
286294
&& proxyPort == newProxyPort
287295
&& proxyScheme == newProxyScheme
@@ -296,6 +304,7 @@ S3ClientSettings refine(Settings repositorySettings) {
296304
}
297305
return new S3ClientSettings(
298306
newCredentials,
307+
newProtocol,
299308
newEndpoint,
300309
newProxyHost,
301310
newProxyPort,
@@ -402,6 +411,7 @@ static S3ClientSettings getClientSettings(final Settings settings, final String
402411
) {
403412
return new S3ClientSettings(
404413
S3ClientSettings.loadCredentials(settings, clientName),
414+
getConfigValue(settings, clientName, PROTOCOL_SETTING),
405415
getConfigValue(settings, clientName, ENDPOINT_SETTING),
406416
getConfigValue(settings, clientName, PROXY_HOST_SETTING),
407417
getConfigValue(settings, clientName, PROXY_PORT_SETTING),
@@ -432,6 +442,7 @@ public boolean equals(final Object o) {
432442
&& maxConnections == that.maxConnections
433443
&& maxRetries == that.maxRetries
434444
&& Objects.equals(credentials, that.credentials)
445+
&& Objects.equals(protocol, that.protocol)
435446
&& Objects.equals(endpoint, that.endpoint)
436447
&& Objects.equals(proxyHost, that.proxyHost)
437448
&& proxyScheme == that.proxyScheme
@@ -445,6 +456,7 @@ public boolean equals(final Object o) {
445456
public int hashCode() {
446457
return Objects.hash(
447458
credentials,
459+
protocol,
448460
endpoint,
449461
proxyHost,
450462
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

+47-13
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
import software.amazon.awssdk.http.SdkHttpClient;
1212
import software.amazon.awssdk.regions.Region;
13-
import software.amazon.awssdk.services.s3.S3Client;
1413
import software.amazon.awssdk.services.s3.endpoints.S3EndpointParams;
1514
import software.amazon.awssdk.services.s3.endpoints.internal.DefaultS3EndpointProvider;
1615

@@ -190,20 +189,55 @@ public void testGetClientRegionFallbackToUsEast1() {
190189
}
191190

192191
public void testEndpointOverrideSchemeDefaultsToHttpsWhenNotSpecified() {
193-
final S3Service s3Service = new S3Service(
194-
mock(Environment.class),
195-
Settings.EMPTY,
196-
mock(ResourceWatcherService.class),
197-
() -> Region.of("es-test-region")
192+
final var endpointWithoutScheme = randomIdentifier() + ".ignore";
193+
final var clientName = randomIdentifier();
194+
assertThat(
195+
getEndpointUri(Settings.builder().put("s3.client." + clientName + ".endpoint", endpointWithoutScheme), clientName),
196+
equalTo(URI.create("https://" + endpointWithoutScheme))
197+
);
198+
}
199+
200+
public void testEndpointOverrideSchemeUsesHttpsIfHttpsProtocolSpecified() {
201+
final var endpointWithoutScheme = randomIdentifier() + ".ignore";
202+
final var clientName = randomIdentifier();
203+
assertThat(
204+
getEndpointUri(
205+
Settings.builder()
206+
.put("s3.client." + clientName + ".endpoint", endpointWithoutScheme)
207+
.put("s3.client." + clientName + ".protocol", "https"),
208+
clientName
209+
),
210+
equalTo(URI.create("https://" + endpointWithoutScheme))
198211
);
199-
final String endpointWithoutScheme = randomIdentifier() + ".ignore";
200-
S3Client s3Client = s3Service.buildClient(
201-
S3ClientSettings.getClientSettings(
202-
Settings.builder().put("s3.client.test-client.endpoint", endpointWithoutScheme).build(),
203-
"test-client"
212+
assertCriticalWarnings(Strings.format("""
213+
[s3.client.%s.protocol] setting was deprecated in Elasticsearch and will be removed in a future release.""", clientName));
214+
}
215+
216+
public void testEndpointOverrideSchemeUsesHttpIfHttpProtocolSpecified() {
217+
final var endpointWithoutScheme = randomIdentifier() + ".ignore";
218+
final var clientName = randomIdentifier();
219+
assertThat(
220+
getEndpointUri(
221+
Settings.builder()
222+
.put("s3.client." + clientName + ".endpoint", endpointWithoutScheme)
223+
.put("s3.client." + clientName + ".protocol", "http"),
224+
clientName
204225
),
205-
mock(SdkHttpClient.class)
226+
equalTo(URI.create("http://" + endpointWithoutScheme))
206227
);
207-
assertThat(s3Client.serviceClientConfiguration().endpointOverride().get(), equalTo(URI.create("https://" + endpointWithoutScheme)));
228+
assertCriticalWarnings(Strings.format("""
229+
[s3.client.%s.protocol] setting was deprecated in Elasticsearch and will be removed in a future release.""", clientName));
230+
}
231+
232+
private static URI getEndpointUri(Settings.Builder settings, String clientName) {
233+
return new S3Service(
234+
mock(Environment.class),
235+
Settings.EMPTY,
236+
mock(ResourceWatcherService.class),
237+
() -> Region.of(randomIdentifier())
238+
).buildClient(S3ClientSettings.getClientSettings(settings.build(), clientName), mock(SdkHttpClient.class))
239+
.serviceClientConfiguration()
240+
.endpointOverride()
241+
.get();
208242
}
209243
}

0 commit comments

Comments
 (0)