diff --git a/plugins/discovery-ec2/build.gradle b/plugins/discovery-ec2/build.gradle index e32ba6948d62d..b08561ffde0ed 100644 --- a/plugins/discovery-ec2/build.gradle +++ b/plugins/discovery-ec2/build.gradle @@ -48,9 +48,30 @@ bundlePlugin { } } +task writeTestJavaPolicy { + doLast { + final File tmp = file("${buildDir}/tmp") + if (tmp.exists() == false && tmp.mkdirs() == false) { + throw new GradleException("failed to create temporary directory [${tmp}]") + } + final File javaPolicy = file("${tmp}/java.policy") + javaPolicy.write( + [ + "grant {", + " permission java.util.PropertyPermission \"com.amazonaws.sdk.ec2MetadataServiceEndpointOverride\", \"write\";", + "};" + ].join("\n")) + } +} + test { + dependsOn writeTestJavaPolicy // this is needed for insecure plugins, remove if possible! systemProperty 'tests.artifact', project.name + + // this is needed to manipulate com.amazonaws.sdk.ec2MetadataServiceEndpointOverride system property + // it is better rather disable security manager at all with `systemProperty 'tests.security.manager', 'false'` + systemProperty 'java.security.policy', "file://${buildDir}/tmp/java.policy" } check { diff --git a/plugins/discovery-ec2/qa/amazon-ec2/build.gradle b/plugins/discovery-ec2/qa/amazon-ec2/build.gradle index 90fac9e80cd78..898a31192ffb0 100644 --- a/plugins/discovery-ec2/qa/amazon-ec2/build.gradle +++ b/plugins/discovery-ec2/qa/amazon-ec2/build.gradle @@ -55,7 +55,10 @@ integTestCluster { keystoreSetting 'discovery.ec2.access_key', 'ec2_integration_test_access_key' keystoreSetting 'discovery.ec2.secret_key', 'ec2_integration_test_secret_key' setting 'discovery.zen.hosts_provider', 'ec2' + setting 'network.host', '_ec2_' setting 'discovery.ec2.endpoint', "http://${-> ec2Fixture.addressAndPort}" + systemProperty "com.amazonaws.sdk.ec2MetadataServiceEndpointOverride", "http://${-> ec2Fixture.addressAndPort}" + unicastTransportUri = { seedNode, node, ant -> return null } waitCondition = { node, ant -> diff --git a/plugins/discovery-ec2/qa/amazon-ec2/src/test/java/org/elasticsearch/discovery/ec2/AmazonEC2Fixture.java b/plugins/discovery-ec2/qa/amazon-ec2/src/test/java/org/elasticsearch/discovery/ec2/AmazonEC2Fixture.java index 0cf4cbdeadb34..6027bd861590e 100644 --- a/plugins/discovery-ec2/qa/amazon-ec2/src/test/java/org/elasticsearch/discovery/ec2/AmazonEC2Fixture.java +++ b/plugins/discovery-ec2/qa/amazon-ec2/src/test/java/org/elasticsearch/discovery/ec2/AmazonEC2Fixture.java @@ -19,6 +19,8 @@ package org.elasticsearch.discovery.ec2; import org.apache.http.NameValuePair; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URLEncodedUtils; import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.rest.RestStatus; @@ -60,7 +62,7 @@ public static void main(String[] args) throws Exception { @Override protected Response handle(final Request request) throws IOException { - if ("/".equals(request.getPath()) && ("POST".equals(request.getMethod()))) { + if ("/".equals(request.getPath()) && (HttpPost.METHOD_NAME.equals(request.getMethod()))) { final String userAgent = request.getHeader("User-Agent"); if (userAgent != null && userAgent.startsWith("aws-sdk-java")) { // Simulate an EC2 DescribeInstancesResponse @@ -74,6 +76,9 @@ protected Response handle(final Request request) throws IOException { return new Response(RestStatus.OK.getStatus(), contentType("text/xml; charset=UTF-8"), responseBody); } } + 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 null; } diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java index 35c2b5288bfeb..6d677d03c3909 100644 --- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java +++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java @@ -40,8 +40,6 @@ class AwsEc2ServiceImpl extends AbstractComponent implements AwsEc2Service { - public static final String EC2_METADATA_URL = "http://169.254.169.254/latest/meta-data/"; - private final AtomicReference> lazyClientReference = new AtomicReference<>(); diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java index 6b26808f74c91..6400d10dff021 100644 --- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java +++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java @@ -19,6 +19,7 @@ package org.elasticsearch.discovery.ec2; +import com.amazonaws.util.EC2MetadataUtils; import com.amazonaws.util.json.Jackson; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -129,7 +130,8 @@ public Settings additionalSettings() { final Settings.Builder builder = Settings.builder(); // Adds a node attribute for the ec2 availability zone - final String azMetadataUrl = AwsEc2ServiceImpl.EC2_METADATA_URL + "placement/availability-zone"; + final String azMetadataUrl = EC2MetadataUtils.getHostAddressForEC2MetadataService() + + "/latest/meta-data/placement/availability-zone"; builder.put(getAvailabilityZoneNodeAttributes(settings, azMetadataUrl)); return builder.build(); } diff --git a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2NameResolver.java b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2NameResolver.java index 4cfaba23ed433..e9dd3a10e4cc3 100644 --- a/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2NameResolver.java +++ b/plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2NameResolver.java @@ -19,6 +19,7 @@ package org.elasticsearch.discovery.ec2; +import com.amazonaws.util.EC2MetadataUtils; import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.component.AbstractComponent; @@ -86,7 +87,7 @@ private enum Ec2HostnameType { @SuppressForbidden(reason = "We call getInputStream in doPrivileged and provide SocketPermission") public InetAddress[] resolve(Ec2HostnameType type) throws IOException { InputStream in = null; - String metadataUrl = AwsEc2ServiceImpl.EC2_METADATA_URL + type.ec2Name; + String metadataUrl = EC2MetadataUtils.getHostAddressForEC2MetadataService() + "/latest/meta-data/" + type.ec2Name; try { URL url = new URL(metadataUrl); logger.debug("obtaining ec2 hostname from ec2 meta-data url {}", url); diff --git a/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2NetworkTests.java b/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2NetworkTests.java index 9e904c47f17d3..dedf56b836eb3 100644 --- a/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2NetworkTests.java +++ b/plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2NetworkTests.java @@ -19,16 +19,32 @@ package org.elasticsearch.discovery.ec2; +import com.sun.net.httpserver.HttpServer; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.SuppressForbidden; import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.mocksocket.MockHttpServer; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.ESTestCase; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; import java.io.IOException; +import java.io.OutputStream; import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; import java.util.Collections; +import java.util.function.BiConsumer; +import static com.amazonaws.SDKGlobalConfiguration.EC2_METADATA_SERVICE_OVERRIDE_SYSTEM_PROPERTY; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.Matchers.arrayContaining; -import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; /** * Test for EC2 network.host settings. @@ -36,22 +52,65 @@ * Warning: This test doesn't assert that the exceptions are thrown. * They aren't. */ +@SuppressForbidden(reason = "use http server") public class Ec2NetworkTests extends ESTestCase { + + private static HttpServer httpServer; + + @BeforeClass + public static void startHttp() throws Exception { + httpServer = MockHttpServer.createHttp(new InetSocketAddress(InetAddress.getLoopbackAddress().getHostAddress(), 0), 0); + + BiConsumer registerContext = (path, v) ->{ + final byte[] message = v.getBytes(UTF_8); + httpServer.createContext(path, (s) -> { + s.sendResponseHeaders(RestStatus.OK.getStatus(), message.length); + OutputStream responseBody = s.getResponseBody(); + responseBody.write(message); + responseBody.close(); + }); + }; + registerContext.accept("/latest/meta-data/local-ipv4","127.0.0.1"); + registerContext.accept("/latest/meta-data/public-ipv4","165.168.10.2"); + registerContext.accept("/latest/meta-data/public-hostname","165.168.10.3"); + registerContext.accept("/latest/meta-data/local-hostname","10.10.10.5"); + + httpServer.start(); + } + + @Before + public void setup() { + // redirect EC2 metadata service to httpServer + AccessController.doPrivileged((PrivilegedAction) () -> System.setProperty(EC2_METADATA_SERVICE_OVERRIDE_SYSTEM_PROPERTY, + "http://" + httpServer.getAddress().getHostName() + ":" + httpServer.getAddress().getPort())); + } + + @AfterClass + public static void stopHttp() { + httpServer.stop(0); + httpServer = null; + } + /** * Test for network.host: _ec2_ */ public void testNetworkHostEc2() throws IOException { - Settings nodeSettings = Settings.builder() - .put("network.host", "_ec2_") - .build(); + resolveEc2("_ec2_", InetAddress.getByName("127.0.0.1")); + } + + /** + * Test for network.host: _ec2_ + */ + public void testNetworkHostUnableToResolveEc2() { + // redirect EC2 metadata service to unknown location + AccessController.doPrivileged((PrivilegedAction) () -> System.setProperty(EC2_METADATA_SERVICE_OVERRIDE_SYSTEM_PROPERTY, + "http://127.0.0.1/")); - NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver())); - // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. try { - networkService.resolveBindHostAddresses(null); - // note: this can succeed and the test can pass + resolveEc2("_ec2_", (InetAddress[]) null); } catch (IOException e) { - assertThat(e.getMessage(), containsString("local-ipv4")); + assertThat(e.getMessage(), + equalTo("IOException caught when fetching InetAddress from [http://127.0.0.1//latest/meta-data/local-ipv4]")); } } @@ -59,108 +118,58 @@ public void testNetworkHostEc2() throws IOException { * Test for network.host: _ec2:publicIp_ */ public void testNetworkHostEc2PublicIp() throws IOException { - Settings nodeSettings = Settings.builder() - .put("network.host", "_ec2:publicIp_") - .build(); - - NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver())); - // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. - try { - networkService.resolveBindHostAddresses(null); - // note: this can succeed and the test can pass - } catch (IOException e) { - assertThat(e.getMessage(), containsString("public-ipv4")); - } + resolveEc2("_ec2:publicIp_", InetAddress.getByName("165.168.10.2")); } /** * Test for network.host: _ec2:privateIp_ */ public void testNetworkHostEc2PrivateIp() throws IOException { - Settings nodeSettings = Settings.builder() - .put("network.host", "_ec2:privateIp_") - .build(); - - NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver())); - // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. - try { - networkService.resolveBindHostAddresses(null); - // note: this can succeed and the test can pass - } catch (IOException e) { - assertThat(e.getMessage(), containsString("local-ipv4")); - } + resolveEc2("_ec2:privateIp_", InetAddress.getByName("127.0.0.1")); } /** * Test for network.host: _ec2:privateIpv4_ */ public void testNetworkHostEc2PrivateIpv4() throws IOException { - Settings nodeSettings = Settings.builder() - .put("network.host", "_ec2:privateIpv4_") - .build(); - - NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver())); - // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. - try { - networkService.resolveBindHostAddresses(null); - // note: this can succeed and the test can pass - } catch (IOException e) { - assertThat(e.getMessage(), containsString("local-ipv4")); - } + resolveEc2("_ec2:privateIpv4_", InetAddress.getByName("127.0.0.1")); } /** * Test for network.host: _ec2:privateDns_ */ public void testNetworkHostEc2PrivateDns() throws IOException { - Settings nodeSettings = Settings.builder() - .put("network.host", "_ec2:privateDns_") - .build(); - - NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver())); - // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. - try { - networkService.resolveBindHostAddresses(null); - // note: this can succeed and the test can pass - } catch (IOException e) { - assertThat(e.getMessage(), containsString("local-hostname")); - } + resolveEc2("_ec2:privateDns_", InetAddress.getByName("10.10.10.5")); } /** * Test for network.host: _ec2:publicIpv4_ */ public void testNetworkHostEc2PublicIpv4() throws IOException { - Settings nodeSettings = Settings.builder() - .put("network.host", "_ec2:publicIpv4_") - .build(); - - NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver())); - // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. - try { - networkService.resolveBindHostAddresses(null); - // note: this can succeed and the test can pass - } catch (IOException e) { - assertThat(e.getMessage(), containsString("public-ipv4")); - } + resolveEc2("_ec2:publicIpv4_", InetAddress.getByName("165.168.10.2")); } /** * Test for network.host: _ec2:publicDns_ */ public void testNetworkHostEc2PublicDns() throws IOException { + resolveEc2("_ec2:publicDns_", InetAddress.getByName("165.168.10.3")); + } + + private InetAddress[] resolveEc2(String host, InetAddress ... expected) throws IOException { Settings nodeSettings = Settings.builder() - .put("network.host", "_ec2:publicDns_") - .build(); + .put("network.host", host) + .build(); NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver())); - // TODO we need to replace that with a mock. For now we check the URL we are supposed to reach. - try { - networkService.resolveBindHostAddresses(null); - // note: this can succeed and the test can pass - } catch (IOException e) { - assertThat(e.getMessage(), containsString("public-hostname")); + + InetAddress[] addresses = networkService.resolveBindHostAddresses( + NetworkService.GLOBAL_NETWORK_BINDHOST_SETTING.get(nodeSettings).toArray(Strings.EMPTY_ARRAY)); + if (expected == null) { + fail("We should get an IOException, resolved addressed:" + Arrays.toString(addresses)); } + assertThat(addresses, arrayContaining(expected)); + return addresses; } /**