Skip to content

Commit

Permalink
[s3-repository] Lookup AWS Region for STS Client from STS endpoint (#…
Browse files Browse the repository at this point in the history
…84585) (#84946)

If we don't instruct to look up the region from the Endpoint URL, AWSSecurityTokenServiceClient tries to look it up implicitly in a custom way which requires reading the /.aws/config file for which we don't have a file permission.

The same approach is used for the general AmazonS3ClientBuilder

Resolves #83826, #52625
  • Loading branch information
arteam committed Mar 14, 2022
1 parent 1f6d32e commit ce0edd9
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 10 deletions.
6 changes: 6 additions & 0 deletions docs/changelog/84585.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 84585
summary: "[s3-repository] Lookup AWS Region for STS Client from STS endpoint"
area: Snapshot/Restore
type: bug
issues:
- 83826
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ class S3Service implements Closeable {
final CustomWebIdentityTokenCredentialsProvider webIdentityTokenCredentialsProvider;

S3Service(Environment environment) {
webIdentityTokenCredentialsProvider = new CustomWebIdentityTokenCredentialsProvider(environment);
webIdentityTokenCredentialsProvider = new CustomWebIdentityTokenCredentialsProvider(
environment,
System::getenv,
System::getProperty
);
}

/**
Expand Down Expand Up @@ -282,13 +286,19 @@ public void refresh() {
*/
static class CustomWebIdentityTokenCredentialsProvider implements AWSCredentialsProvider {

private static final String STS_HOSTNAME = "https://sts.amazonaws.com";

private STSAssumeRoleWithWebIdentitySessionCredentialsProvider credentialsProvider;
private AWSSecurityTokenService stsClient;

CustomWebIdentityTokenCredentialsProvider(Environment environment) {
CustomWebIdentityTokenCredentialsProvider(
Environment environment,
SystemEnvironment systemEnvironment,
JvmEnvironment jvmEnvironment
) {
// Check whether the original environment variable exists. If it doesn't,
// the system doesn't support AWS web identity tokens
if (System.getenv(AWS_WEB_IDENTITY_ENV_VAR) == null) {
if (systemEnvironment.getEnv(AWS_WEB_IDENTITY_ENV_VAR) == null) {
return;
}
// Make sure that a readable symlink to the token file exists in the plugin config directory
Expand All @@ -304,8 +314,8 @@ static class CustomWebIdentityTokenCredentialsProvider implements AWSCredentials
if (Files.isReadable(webIdentityTokenFileSymlink) == false) {
throw new IllegalStateException("Unable to read a Web Identity Token symlink in the config directory");
}
String roleArn = System.getenv(AWS_ROLE_ARN_ENV_VAR);
String roleSessionName = System.getenv(AWS_ROLE_SESSION_NAME_ENV_VAR);
String roleArn = systemEnvironment.getEnv(AWS_ROLE_ARN_ENV_VAR);
String roleSessionName = systemEnvironment.getEnv(AWS_ROLE_SESSION_NAME_ENV_VAR);
if (roleArn == null || roleSessionName == null) {
LOGGER.warn(
"Unable to use a web identity token for authentication. The AWS_WEB_IDENTITY_TOKEN_FILE environment "
Expand All @@ -315,11 +325,10 @@ static class CustomWebIdentityTokenCredentialsProvider implements AWSCredentials
}
AWSSecurityTokenServiceClientBuilder stsClientBuilder = AWSSecurityTokenServiceClient.builder();

// Just for testing
String customStsEndpoint = System.getProperty("com.amazonaws.sdk.stsMetadataServiceEndpointOverride");
if (customStsEndpoint != null) {
stsClientBuilder.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(customStsEndpoint, null));
}
// Custom system property used for specifying a mocked version of the STS for testing
String customStsEndpoint = jvmEnvironment.getProperty("com.amazonaws.sdk.stsMetadataServiceEndpointOverride", STS_HOSTNAME);
// Set the region explicitly via the endpoint URL, so the AWS SDK doesn't make any guesses internally.
stsClientBuilder.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(customStsEndpoint, null));
stsClientBuilder.withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()));
stsClient = SocketAccess.doPrivileged(stsClientBuilder::build);
try {
Expand Down Expand Up @@ -357,4 +366,14 @@ public void shutdown() throws IOException {
}
}
}

@FunctionalInterface
interface SystemEnvironment {
String getEnv(String name);
}

@FunctionalInterface
interface JvmEnvironment {
String getProperty(String key, String defaultValue);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

package org.elasticsearch.repositories.s3;

import com.amazonaws.auth.AWSCredentials;
import com.sun.net.httpserver.HttpServer;

import org.apache.logging.log4j.LogManager;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.env.Environment;
import org.elasticsearch.mocksocket.MockHttpServer;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.ESTestCase;
import org.junit.Assert;
import org.mockito.Mockito;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;

public class CustomWebIdentityTokenCredentialsProviderTests extends ESTestCase {

private static final String ROLE_ARN = "arn:aws:iam::123456789012:role/FederatedWebIdentityRole";
private static final String ROLE_NAME = "sts-fixture-test";

@SuppressForbidden(reason = "HTTP server is used for testing")
public void testCreateWebIdentityTokenCredentialsProvider() throws Exception {
HttpServer httpServer = MockHttpServer.createHttp(new InetSocketAddress(InetAddress.getLoopbackAddress().getHostAddress(), 0), 0);
httpServer.createContext("/", exchange -> {
try (exchange) {
exchange.getResponseHeaders().add("Content-Type", "text/xml; charset=UTF-8");
byte[] response = """
<AssumeRoleWithWebIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
<AssumeRoleWithWebIdentityResult>
<SubjectFromWebIdentityToken>amzn1.account.AF6RHO7KZU5XRVQJGXK6HB56KR2A</SubjectFromWebIdentityToken>
<Audience>client.5498841531868486423.1548@apps.example.com</Audience>
<AssumedRoleUser>
<Arn>%s</Arn>
<AssumedRoleId>AROACLKWSDQRAOEXAMPLE:%s</AssumedRoleId>
</AssumedRoleUser>
<Credentials>
<SessionToken>sts_session_token</SessionToken>
<SecretAccessKey>secret_access_key</SecretAccessKey>
<Expiration>%s</Expiration>
<AccessKeyId>sts_access_key</AccessKeyId>
</Credentials>
<SourceIdentity>SourceIdentityValue</SourceIdentity>
<Provider>www.amazon.com</Provider>
</AssumeRoleWithWebIdentityResult>
<ResponseMetadata>
<RequestId>ad4156e9-bce1-11e2-82e6-6b6efEXAMPLE</RequestId>
</ResponseMetadata>
</AssumeRoleWithWebIdentityResponse>
""".formatted(
ROLE_ARN,
ROLE_NAME,
ZonedDateTime.now().plusDays(1L).format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"))
).getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(RestStatus.OK.getStatus(), response.length);
exchange.getResponseBody().write(response);
}
});
httpServer.start();

Path configDirectory = Files.createTempDirectory("web-identity-token-test");
Files.createDirectory(configDirectory.resolve("repository-s3"));
Files.writeString(configDirectory.resolve("repository-s3/aws-web-identity-token-file"), "YXdzLXdlYi1pZGVudGl0eS10b2tlbi1maWxl");
Environment environment = Mockito.mock(Environment.class);
Mockito.when(environment.configFile()).thenReturn(configDirectory);

// No region is set, but the SDK shouldn't fail because of that
Map<String, String> environmentVariables = Map.of(
"AWS_WEB_IDENTITY_TOKEN_FILE",
"/var/run/secrets/eks.amazonaws.com/serviceaccount/token",
"AWS_ROLE_ARN",
ROLE_ARN,
"AWS_ROLE_SESSION_NAME",
ROLE_NAME
);
Map<String, String> systemProperties = Map.of(
"com.amazonaws.sdk.stsMetadataServiceEndpointOverride",
"http://" + httpServer.getAddress().getHostName() + ":" + httpServer.getAddress().getPort()
);
var webIdentityTokenCredentialsProvider = new S3Service.CustomWebIdentityTokenCredentialsProvider(
environment,
environmentVariables::get,
systemProperties::getOrDefault
);
try {
AWSCredentials credentials = S3Service.buildCredentials(
LogManager.getLogger(S3Service.class),
S3ClientSettings.getClientSettings(Settings.EMPTY, randomAlphaOfLength(8)),
webIdentityTokenCredentialsProvider
).getCredentials();

Assert.assertEquals("sts_access_key", credentials.getAWSAccessKeyId());
Assert.assertEquals("secret_access_key", credentials.getAWSSecretKey());
} finally {
webIdentityTokenCredentialsProvider.shutdown();
httpServer.stop(0);
}
}
}

0 comments on commit ce0edd9

Please sign in to comment.