Upgrade SDK and test discovery-ec2 credential providers (#41732)

Upgrades the AWS SDK to the same version that we're using for the repository-s3 plugin, providing
testing capabilities to override certain SDK endpoints in order to point them to localhost for testing.
Adds tests for the various credential providers.
This commit is contained in:
Yannick Welsch 2019-05-08 09:26:37 +02:00
parent 4c909e93bb
commit 5b71baa100
9 changed files with 1134 additions and 343 deletions

View File

@ -23,7 +23,7 @@ esplugin {
} }
versions << [ versions << [
'aws': '1.11.187' 'aws': '1.11.505'
] ]
dependencies { dependencies {

View File

@ -1 +0,0 @@
6f47fcd3c2917bef69dc36aba203c5ea4af9bf24

View File

@ -0,0 +1 @@
d19328c227b2b5ad81d137361ebc9cbcd0396465

View File

@ -1 +0,0 @@
f3e5a8601f3105624674b1a12ca34f453a4b5895

View File

@ -0,0 +1 @@
b669b3c90ea9bf73734ab26f0cb30c5c66addf55

View File

@ -20,6 +20,7 @@
import org.elasticsearch.gradle.MavenFilteringHack import org.elasticsearch.gradle.MavenFilteringHack
import org.elasticsearch.gradle.test.AntFixture import org.elasticsearch.gradle.test.AntFixture
import org.elasticsearch.gradle.test.RestIntegTestTask
apply plugin: 'elasticsearch.standalone-rest-test' apply plugin: 'elasticsearch.standalone-rest-test'
apply plugin: 'elasticsearch.rest-test' apply plugin: 'elasticsearch.rest-test'
@ -30,14 +31,6 @@ dependencies {
final int ec2NumberOfNodes = 3 final int ec2NumberOfNodes = 3
/** A task to start the AmazonEC2Fixture which emulates an EC2 service **/
task ec2Fixture(type: AntFixture) {
dependsOn compileTestJava
env 'CLASSPATH', "${ -> project.sourceSets.test.runtimeClasspath.asPath }"
executable = new File(project.runtimeJavaHome, 'bin/java')
args 'org.elasticsearch.discovery.ec2.AmazonEC2Fixture', baseDir, "${buildDir}/testclusters/integTest-1/config/unicast_hosts.txt"
}
Map<String, Object> expansions = [ Map<String, Object> expansions = [
'expected_nodes': ec2NumberOfNodes 'expected_nodes': ec2NumberOfNodes
] ]
@ -47,20 +40,71 @@ processTestResources {
MavenFilteringHack.filter(it, expansions) MavenFilteringHack.filter(it, expansions)
} }
integTest { // disable default test task, use spezialized ones below
dependsOn ec2Fixture, project(':plugins:discovery-ec2').bundlePlugin integTest.enabled = false
/*
* Test using various credential providers (see also https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/credentials.html):
* - Elasticsearch Keystore (secure settings discovery.ec2.access_key and discovery.ec2.secret_key)
* - Java system properties (aws.accessKeyId and aws.secretAccessKey)
* - Environment variables (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY)
* - ECS container credentials (loaded from ECS if the environment variable AWS_CONTAINER_CREDENTIALS_RELATIVE_URI is set)
* - Instance profile credentials (delivered through the EC2 metadata service)
*
* Notably missing is a test for the default credential profiles file, which is located at ~/.aws/credentials and would at least require a
* custom Java security policy to work.
*/
['KeyStore', 'EnvVariables', 'SystemProperties', 'ContainerCredentials', 'InstanceProfile'].forEach { action ->
AntFixture fixture = tasks.create(name: "ec2Fixture${action}", type: AntFixture) {
dependsOn compileTestJava
env 'CLASSPATH', "${ -> project.sourceSets.test.runtimeClasspath.asPath }"
executable = new File(project.runtimeJavaHome, 'bin/java')
args 'org.elasticsearch.discovery.ec2.AmazonEC2Fixture', baseDir, "${buildDir}/testclusters/integTest${action}-1/config/unicast_hosts.txt"
}
tasks.create(name: "integTest${action}", type: RestIntegTestTask) {
dependsOn fixture, project(':plugins:discovery-ec2').bundlePlugin
}
check.dependsOn("integTest${action}")
testClusters."integTest${action}" {
numberOfNodes = ec2NumberOfNodes
plugin file(project(':plugins:discovery-ec2').bundlePlugin.archiveFile)
setting 'discovery.seed_providers', 'ec2'
setting 'network.host', '_ec2_'
setting 'discovery.ec2.endpoint', { "http://${-> fixture.addressAndPort}" }
systemProperty "com.amazonaws.sdk.ec2MetadataServiceEndpointOverride", { "http://${-> fixture.addressAndPort}" }
}
} }
testClusters.integTest { // Extra config for KeyStore
numberOfNodes = ec2NumberOfNodes testClusters.integTestKeyStore {
plugin file(project(':plugins:discovery-ec2').bundlePlugin.archiveFile)
keystore 'discovery.ec2.access_key', 'ec2_integration_test_access_key' keystore 'discovery.ec2.access_key', 'ec2_integration_test_access_key'
keystore 'discovery.ec2.secret_key', 'ec2_integration_test_secret_key' keystore 'discovery.ec2.secret_key', 'ec2_integration_test_secret_key'
setting 'discovery.seed_providers', 'ec2'
setting 'network.host', '_ec2_'
setting 'discovery.ec2.endpoint', { "http://${ec2Fixture.addressAndPort}" }
systemProperty "com.amazonaws.sdk.ec2MetadataServiceEndpointOverride", { "http://${ec2Fixture.addressAndPort}" }
} }
// Extra config for EnvVariables
testClusters.integTestEnvVariables {
environment 'AWS_ACCESS_KEY_ID', 'ec2_integration_test_access_key'
environment 'AWS_SECRET_ACCESS_KEY', 'ec2_integration_test_secret_key'
}
// Extra config for SystemProperties
testClusters.integTestSystemProperties {
systemProperty 'aws.accessKeyId', 'ec2_integration_test_access_key'
systemProperty 'aws.secretKey', 'ec2_integration_test_secret_key'
}
// Extra config for ContainerCredentials
ec2FixtureContainerCredentials.env 'ACTIVATE_CONTAINER_CREDENTIALS', true
testClusters.integTestContainerCredentials {
environment 'AWS_CONTAINER_CREDENTIALS_FULL_URI',
{ "http://${-> tasks.findByName("ec2FixtureContainerCredentials").addressAndPort}/ecs_credentials_endpoint" }
}
// Extra config for InstanceProfile
ec2FixtureInstanceProfile.env 'ACTIVATE_INSTANCE_PROFILE', true

View File

@ -18,10 +18,12 @@
*/ */
package org.elasticsearch.discovery.ec2; package org.elasticsearch.discovery.ec2;
import com.amazonaws.util.DateUtils;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.client.utils.URLEncodedUtils;
import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.fixture.AbstractHttpFixture; import org.elasticsearch.test.fixture.AbstractHttpFixture;
@ -34,8 +36,12 @@ import java.io.StringWriter;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
@ -45,10 +51,14 @@ import static java.nio.charset.StandardCharsets.UTF_8;
public class AmazonEC2Fixture extends AbstractHttpFixture { public class AmazonEC2Fixture extends AbstractHttpFixture {
private final Path nodes; private final Path nodes;
private final boolean instanceProfile;
private final boolean containerCredentials;
private AmazonEC2Fixture(final String workingDir, final String nodesUriPath) { private AmazonEC2Fixture(final String workingDir, final String nodesUriPath, boolean instanceProfile, boolean containerCredentials) {
super(workingDir); super(workingDir);
this.nodes = toPath(Objects.requireNonNull(nodesUriPath)); this.nodes = toPath(Objects.requireNonNull(nodesUriPath));
this.instanceProfile = instanceProfile;
this.containerCredentials = containerCredentials;
} }
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
@ -56,7 +66,10 @@ public class AmazonEC2Fixture extends AbstractHttpFixture {
throw new IllegalArgumentException("AmazonEC2Fixture <working directory> <nodes transport uri file>"); throw new IllegalArgumentException("AmazonEC2Fixture <working directory> <nodes transport uri file>");
} }
final AmazonEC2Fixture fixture = new AmazonEC2Fixture(args[0], args[1]); boolean instanceProfile = Booleans.parseBoolean(System.getenv("ACTIVATE_INSTANCE_PROFILE"), false);
boolean containerCredentials = Booleans.parseBoolean(System.getenv("ACTIVATE_CONTAINER_CREDENTIALS"), false);
final AmazonEC2Fixture fixture = new AmazonEC2Fixture(args[0], args[1], instanceProfile, containerCredentials);
fixture.listen(); fixture.listen();
} }
@ -65,6 +78,12 @@ public class AmazonEC2Fixture extends AbstractHttpFixture {
if ("/".equals(request.getPath()) && (HttpPost.METHOD_NAME.equals(request.getMethod()))) { if ("/".equals(request.getPath()) && (HttpPost.METHOD_NAME.equals(request.getMethod()))) {
final String userAgent = request.getHeader("User-Agent"); final String userAgent = request.getHeader("User-Agent");
if (userAgent != null && userAgent.startsWith("aws-sdk-java")) { if (userAgent != null && userAgent.startsWith("aws-sdk-java")) {
final String auth = request.getHeader("Authorization");
if (auth == null || auth.contains("ec2_integration_test_access_key") == false) {
throw new IllegalArgumentException("wrong access key: " + auth);
}
// Simulate an EC2 DescribeInstancesResponse // Simulate an EC2 DescribeInstancesResponse
byte[] responseBody = EMPTY_BYTE; byte[] responseBody = EMPTY_BYTE;
for (NameValuePair parse : URLEncodedUtils.parse(new String(request.getBody(), UTF_8), UTF_8)) { for (NameValuePair parse : URLEncodedUtils.parse(new String(request.getBody(), UTF_8), UTF_8)) {
@ -79,6 +98,32 @@ public class AmazonEC2Fixture extends AbstractHttpFixture {
if ("/latest/meta-data/local-ipv4".equals(request.getPath()) && (HttpGet.METHOD_NAME.equals(request.getMethod()))) { if ("/latest/meta-data/local-ipv4".equals(request.getPath()) && (HttpGet.METHOD_NAME.equals(request.getMethod()))) {
return new Response(RestStatus.OK.getStatus(), TEXT_PLAIN_CONTENT_TYPE, "127.0.0.1".getBytes(UTF_8)); return new Response(RestStatus.OK.getStatus(), TEXT_PLAIN_CONTENT_TYPE, "127.0.0.1".getBytes(UTF_8));
} }
if (instanceProfile &&
"/latest/meta-data/iam/security-credentials/".equals(request.getPath()) &&
HttpGet.METHOD_NAME.equals(request.getMethod())) {
final Map<String, String> headers = new HashMap<>(contentType("text/plain"));
return new Response(RestStatus.OK.getStatus(), headers, "my_iam_profile".getBytes(UTF_8));
}
if ((containerCredentials &&
"/ecs_credentials_endpoint".equals(request.getPath()) &&
HttpGet.METHOD_NAME.equals(request.getMethod())) ||
("/latest/meta-data/iam/security-credentials/my_iam_profile".equals(request.getPath()) &&
HttpGet.METHOD_NAME.equals(request.getMethod()))) {
final Date expiration = new Date(new Date().getTime() + TimeUnit.DAYS.toMillis(1));
final String response = "{"
+ "\"AccessKeyId\": \"" + "ec2_integration_test_access_key" + "\","
+ "\"Expiration\": \"" + DateUtils.formatISO8601Date(expiration) + "\","
+ "\"RoleArn\": \"" + "test" + "\","
+ "\"SecretAccessKey\": \"" + "test" + "\","
+ "\"Token\": \"" + "test" + "\""
+ "}";
final Map<String, String> headers = new HashMap<>(contentType("application/json"));
return new Response(RestStatus.OK.getStatus(), headers, response.getBytes(UTF_8));
}
return null; return null;
} }

View File

@ -22,13 +22,12 @@ package org.elasticsearch.discovery.ec2;
import com.amazonaws.ClientConfiguration; import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.http.IdleConnectionReaper; import com.amazonaws.http.IdleConnectionReaper;
import com.amazonaws.internal.StaticCredentialsProvider;
import com.amazonaws.retry.RetryPolicy; import com.amazonaws.retry.RetryPolicy;
import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.AmazonEC2Client;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
@ -97,11 +96,11 @@ class AwsEc2ServiceImpl implements AwsEc2Service {
static AWSCredentialsProvider buildCredentials(Logger logger, Ec2ClientSettings clientSettings) { static AWSCredentialsProvider buildCredentials(Logger logger, Ec2ClientSettings clientSettings) {
final AWSCredentials credentials = clientSettings.credentials; final AWSCredentials credentials = clientSettings.credentials;
if (credentials == null) { if (credentials == null) {
logger.debug("Using either environment variables, system properties or instance profile credentials"); logger.debug("Using default provider chain");
return new DefaultAWSCredentialsProviderChain(); return DefaultAWSCredentialsProviderChain.getInstance();
} else { } else {
logger.debug("Using basic key/secret credentials"); logger.debug("Using basic key/secret credentials");
return new StaticCredentialsProvider(credentials); return new AWSStaticCredentialsProvider(credentials);
} }
} }