Skip to content

Commit 7c0fc20

Browse files
ECS Task IAM profile credentials ignored in repository-s3 plugin (#31864)
ECS Task IAM profile credentials ignored in repository-s3 plugin (#31864) Closes #26913
1 parent f6d7854 commit 7c0fc20

File tree

7 files changed

+315
-13
lines changed

7 files changed

+315
-13
lines changed

buildSrc/src/main/groovy/org/elasticsearch/gradle/test/ClusterConfiguration.groovy

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ class ClusterConfiguration {
142142
// there are cases when value depends on task that is not executed yet on configuration stage
143143
Map<String, Object> systemProperties = new HashMap<>()
144144

145+
Map<String, Object> environmentVariables = new HashMap<>()
146+
145147
Map<String, Object> settings = new HashMap<>()
146148

147149
Map<String, String> keystoreSettings = new HashMap<>()
@@ -164,6 +166,11 @@ class ClusterConfiguration {
164166
systemProperties.put(property, value)
165167
}
166168

169+
@Input
170+
void environment(String variable, Object value) {
171+
environmentVariables.put(variable, value)
172+
}
173+
167174
@Input
168175
void setting(String name, Object value) {
169176
settings.put(name, value)

buildSrc/src/main/groovy/org/elasticsearch/gradle/test/NodeInfo.groovy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ class NodeInfo {
181181

182182
args.addAll("-E", "node.portsfile=true")
183183
env = [:]
184+
env.putAll(config.environmentVariables)
184185
for (Map.Entry<String, String> property : System.properties.entrySet()) {
185186
if (property.key.startsWith('tests.es.')) {
186187
args.add("-E")

docs/plugins/repository-s3.asciidoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ include::install_remove.asciidoc[]
1313
==== Getting started with AWS
1414

1515
The plugin provides a repository type named `s3` which may be used when creating a repository.
16-
The repository defaults to using
17-
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html[IAM Role]
16+
The repository defaults to using https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html[ECS IAM Role] or
17+
http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html[EC2 IAM Role]
1818
credentials for authentication. The only mandatory setting is the bucket name:
1919

2020
[source,js]

plugins/repository-s3/build.gradle

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,15 @@ String s3TemporaryBasePath = System.getenv("amazon_s3_base_path_temporary")
9292
String s3EC2Bucket = System.getenv("amazon_s3_bucket_ec2")
9393
String s3EC2BasePath = System.getenv("amazon_s3_base_path_ec2")
9494

95+
String s3ECSBucket = System.getenv("amazon_s3_bucket_ecs")
96+
String s3ECSBasePath = System.getenv("amazon_s3_base_path_ecs")
97+
9598
// If all these variables are missing then we are testing against the internal fixture instead, which has the following
9699
// credentials hard-coded in.
97100

98101
if (!s3PermanentAccessKey && !s3PermanentSecretKey && !s3PermanentBucket && !s3PermanentBasePath
99-
&& !s3EC2Bucket && !s3EC2BasePath) {
102+
&& !s3EC2Bucket && !s3EC2BasePath
103+
&& !s3ECSBucket && !s3ECSBasePath) {
100104
s3PermanentAccessKey = 's3_integration_test_permanent_access_key'
101105
s3PermanentSecretKey = 's3_integration_test_permanent_secret_key'
102106
s3PermanentBucket = 'permanent-bucket-test'
@@ -105,10 +109,14 @@ if (!s3PermanentAccessKey && !s3PermanentSecretKey && !s3PermanentBucket && !s3P
105109
s3EC2Bucket = 'ec2-bucket-test'
106110
s3EC2BasePath = 'integration_test'
107111

112+
s3ECSBucket = 'ecs-bucket-test'
113+
s3ECSBasePath = 'integration_test'
114+
108115
useFixture = true
109116

110117
} else if (!s3PermanentAccessKey || !s3PermanentSecretKey || !s3PermanentBucket || !s3PermanentBasePath
111-
|| !s3EC2Bucket || !s3EC2BasePath) {
118+
|| !s3EC2Bucket || !s3EC2BasePath
119+
|| !s3ECSBucket || !s3ECSBasePath) {
112120
throw new IllegalArgumentException("not all options specified to run against external S3 service")
113121
}
114122

@@ -284,7 +292,8 @@ if (useFixture && minioDistribution) {
284292
// Minio only supports a single access key, see https://github.com/minio/minio/pull/5968
285293
integTestMinioRunner.systemProperty 'tests.rest.blacklist', [
286294
'repository_s3/30_repository_temporary_credentials/*',
287-
'repository_s3/40_repository_ec2_credentials/*'
295+
'repository_s3/40_repository_ec2_credentials/*',
296+
'repository_s3/50_repository_ecs_credentials/*'
288297
].join(",")
289298

290299
project.check.dependsOn(integTestMinio)
@@ -302,7 +311,8 @@ task s3FixtureProperties {
302311
"s3Fixture.temporary_bucket_name" : s3TemporaryBucket,
303312
"s3Fixture.temporary_key" : s3TemporaryAccessKey,
304313
"s3Fixture.temporary_session_token": s3TemporarySessionToken,
305-
"s3Fixture.ec2_bucket_name" : s3EC2Bucket
314+
"s3Fixture.ec2_bucket_name" : s3EC2Bucket,
315+
"s3Fixture.ecs_bucket_name" : s3ECSBucket
306316
]
307317

308318
doLast {
@@ -327,7 +337,9 @@ Map<String, Object> expansions = [
327337
'temporary_bucket': s3TemporaryBucket,
328338
'temporary_base_path': s3TemporaryBasePath,
329339
'ec2_bucket': s3EC2Bucket,
330-
'ec2_base_path': s3EC2BasePath
340+
'ec2_base_path': s3EC2BasePath,
341+
'ecs_bucket': s3ECSBucket,
342+
'ecs_base_path': s3ECSBasePath
331343
]
332344

333345
processTestResources {
@@ -364,6 +376,34 @@ integTestCluster {
364376
}
365377
}
366378

379+
integTestRunner.systemProperty 'tests.rest.blacklist', 'repository_s3/50_repository_ecs_credentials/*'
380+
381+
///
382+
RestIntegTestTask integTestECS = project.tasks.create('integTestECS', RestIntegTestTask.class) {
383+
description = "Runs tests using the ECS repository."
384+
}
385+
386+
// The following closure must execute before the afterEvaluate block in the constructor of the following integrationTest tasks:
387+
project.afterEvaluate {
388+
ClusterConfiguration cluster = project.extensions.getByName('integTestECSCluster') as ClusterConfiguration
389+
cluster.dependsOn(project.s3Fixture)
390+
391+
cluster.setting 's3.client.integration_test_ecs.endpoint', "http://${-> s3Fixture.addressAndPort}"
392+
393+
Task integTestECSTask = project.tasks.getByName('integTestECS')
394+
integTestECSTask.clusterConfig.plugin(project.path)
395+
integTestECSTask.clusterConfig.environment 'AWS_CONTAINER_CREDENTIALS_FULL_URI',
396+
"http://${-> s3Fixture.addressAndPort}/ecs_credentials_endpoint"
397+
integTestECSRunner.systemProperty 'tests.rest.blacklist', [
398+
'repository_s3/10_basic/*',
399+
'repository_s3/20_repository_permanent_credentials/*',
400+
'repository_s3/30_repository_temporary_credentials/*',
401+
'repository_s3/40_repository_ec2_credentials/*'
402+
].join(",")
403+
}
404+
project.check.dependsOn(integTestECS)
405+
///
406+
367407
thirdPartyAudit.excludes = [
368408
// classes are missing
369409
'javax.servlet.ServletContextEvent',

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import com.amazonaws.ClientConfiguration;
2323
import com.amazonaws.auth.AWSCredentials;
2424
import com.amazonaws.auth.AWSCredentialsProvider;
25-
import com.amazonaws.auth.InstanceProfileCredentialsProvider;
25+
import com.amazonaws.auth.EC2ContainerCredentialsProviderWrapper;
2626
import com.amazonaws.http.IdleConnectionReaper;
2727
import com.amazonaws.internal.StaticCredentialsProvider;
2828
import com.amazonaws.services.s3.AmazonS3;
@@ -156,10 +156,11 @@ protected synchronized void releaseCachedClients() {
156156
}
157157

158158
static class PrivilegedInstanceProfileCredentialsProvider implements AWSCredentialsProvider {
159-
private final InstanceProfileCredentialsProvider credentials;
159+
private final AWSCredentialsProvider credentials;
160160

161161
private PrivilegedInstanceProfileCredentialsProvider() {
162-
this.credentials = new InstanceProfileCredentialsProvider();
162+
// InstanceProfileCredentialsProvider as last item of chain
163+
this.credentials = new EC2ContainerCredentialsProviderWrapper();
163164
}
164165

165166
@Override

plugins/repository-s3/src/test/java/org/elasticsearch/repositories/s3/AmazonS3Fixture.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ private AmazonS3Fixture(final String workingDir, Properties properties) {
8888
final Bucket ec2Bucket = new Bucket("s3Fixture.ec2",
8989
randomAsciiAlphanumOfLength(random, 10), randomAsciiAlphanumOfLength(random, 10));
9090

91-
this.handlers = defaultHandlers(buckets, ec2Bucket);
91+
final Bucket ecsBucket = new Bucket("s3Fixture.ecs",
92+
randomAsciiAlphanumOfLength(random, 10), randomAsciiAlphanumOfLength(random, 10));
93+
94+
this.handlers = defaultHandlers(buckets, ec2Bucket, ecsBucket);
9295
}
9396

9497
private static String nonAuthPath(Request request) {
@@ -174,7 +177,7 @@ public static void main(final String[] args) throws Exception {
174177
}
175178

176179
/** Builds the default request handlers **/
177-
private PathTrie<RequestHandler> defaultHandlers(final Map<String, Bucket> buckets, final Bucket ec2Bucket) {
180+
private PathTrie<RequestHandler> defaultHandlers(final Map<String, Bucket> buckets, final Bucket ec2Bucket, final Bucket ecsBucket) {
178181
final PathTrie<RequestHandler> handlers = new PathTrie<>(RestUtils.REST_DECODER);
179182

180183
// HEAD Object
@@ -400,11 +403,18 @@ private PathTrie<RequestHandler> defaultHandlers(final Map<String, Bucket> bucke
400403
handlers.insert(nonAuthPath(HttpGet.METHOD_NAME, "/latest/meta-data/iam/security-credentials/{profileName}"), (request) -> {
401404
final String profileName = request.getParam("profileName");
402405
if (EC2_PROFILE.equals(profileName) == false) {
403-
return new Response(RestStatus.NOT_FOUND.getStatus(), new HashMap<>(), "unknown credentials".getBytes(UTF_8));
406+
return new Response(RestStatus.NOT_FOUND.getStatus(), new HashMap<>(), "unknown profile".getBytes(UTF_8));
404407
}
405408
return credentialResponseFunction.apply(profileName, ec2Bucket.key, ec2Bucket.token);
406409
});
407410

411+
// GET
412+
//
413+
// https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
414+
handlers.insert(nonAuthPath(HttpGet.METHOD_NAME, "/ecs_credentials_endpoint"),
415+
(request) -> credentialResponseFunction.apply("CPV_ECS", ecsBucket.key, ecsBucket.token));
416+
417+
408418
return handlers;
409419
}
410420

0 commit comments

Comments
 (0)