Skip to content

Commit 9d28a10

Browse files
Discovery EC2: Utilize Amazon SDK to resolve EC2 metadata server (#35246)
* Discovery EC2: Utilize Amazon SDK to resolve EC2 metadata server Closes #35141
1 parent 02043a2 commit 9d28a10

File tree

7 files changed

+121
-82
lines changed

7 files changed

+121
-82
lines changed

plugins/discovery-ec2/build.gradle

+21
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,30 @@ bundlePlugin {
4848
}
4949
}
5050

51+
task writeTestJavaPolicy {
52+
doLast {
53+
final File tmp = file("${buildDir}/tmp")
54+
if (tmp.exists() == false && tmp.mkdirs() == false) {
55+
throw new GradleException("failed to create temporary directory [${tmp}]")
56+
}
57+
final File javaPolicy = file("${tmp}/java.policy")
58+
javaPolicy.write(
59+
[
60+
"grant {",
61+
" permission java.util.PropertyPermission \"com.amazonaws.sdk.ec2MetadataServiceEndpointOverride\", \"write\";",
62+
"};"
63+
].join("\n"))
64+
}
65+
}
66+
5167
test {
68+
dependsOn writeTestJavaPolicy
5269
// this is needed for insecure plugins, remove if possible!
5370
systemProperty 'tests.artifact', project.name
71+
72+
// this is needed to manipulate com.amazonaws.sdk.ec2MetadataServiceEndpointOverride system property
73+
// it is better rather disable security manager at all with `systemProperty 'tests.security.manager', 'false'`
74+
systemProperty 'java.security.policy', "file://${buildDir}/tmp/java.policy"
5475
}
5576

5677
check {

plugins/discovery-ec2/qa/amazon-ec2/build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ integTestCluster {
5555
keystoreSetting 'discovery.ec2.access_key', 'ec2_integration_test_access_key'
5656
keystoreSetting 'discovery.ec2.secret_key', 'ec2_integration_test_secret_key'
5757
setting 'discovery.zen.hosts_provider', 'ec2'
58+
setting 'network.host', '_ec2_'
5859
setting 'discovery.ec2.endpoint', "http://${-> ec2Fixture.addressAndPort}"
60+
systemProperty "com.amazonaws.sdk.ec2MetadataServiceEndpointOverride", "http://${-> ec2Fixture.addressAndPort}"
61+
5962
unicastTransportUri = { seedNode, node, ant -> return null }
6063

6164
waitCondition = { node, ant ->

plugins/discovery-ec2/qa/amazon-ec2/src/test/java/org/elasticsearch/discovery/ec2/AmazonEC2Fixture.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
package org.elasticsearch.discovery.ec2;
2020

2121
import org.apache.http.NameValuePair;
22+
import org.apache.http.client.methods.HttpGet;
23+
import org.apache.http.client.methods.HttpPost;
2224
import org.apache.http.client.utils.URLEncodedUtils;
2325
import org.elasticsearch.common.SuppressForbidden;
2426
import org.elasticsearch.rest.RestStatus;
@@ -60,7 +62,7 @@ public static void main(String[] args) throws Exception {
6062

6163
@Override
6264
protected Response handle(final Request request) throws IOException {
63-
if ("/".equals(request.getPath()) && ("POST".equals(request.getMethod()))) {
65+
if ("/".equals(request.getPath()) && (HttpPost.METHOD_NAME.equals(request.getMethod()))) {
6466
final String userAgent = request.getHeader("User-Agent");
6567
if (userAgent != null && userAgent.startsWith("aws-sdk-java")) {
6668
// Simulate an EC2 DescribeInstancesResponse
@@ -74,6 +76,9 @@ protected Response handle(final Request request) throws IOException {
7476
return new Response(RestStatus.OK.getStatus(), contentType("text/xml; charset=UTF-8"), responseBody);
7577
}
7678
}
79+
if ("/latest/meta-data/local-ipv4".equals(request.getPath()) && (HttpGet.METHOD_NAME.equals(request.getMethod()))) {
80+
return new Response(RestStatus.OK.getStatus(), TEXT_PLAIN_CONTENT_TYPE, "127.0.0.1".getBytes(UTF_8));
81+
}
7782
return null;
7883
}
7984

plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/AwsEc2ServiceImpl.java

-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@
4040

4141
class AwsEc2ServiceImpl extends AbstractComponent implements AwsEc2Service {
4242

43-
public static final String EC2_METADATA_URL = "http://169.254.169.254/latest/meta-data/";
44-
4543
private final AtomicReference<LazyInitializable<AmazonEc2Reference, ElasticsearchException>> lazyClientReference =
4644
new AtomicReference<>();
4745

plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2DiscoveryPlugin.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.discovery.ec2;
2121

22+
import com.amazonaws.util.EC2MetadataUtils;
2223
import com.amazonaws.util.json.Jackson;
2324
import org.apache.logging.log4j.LogManager;
2425
import org.apache.logging.log4j.Logger;
@@ -129,7 +130,8 @@ public Settings additionalSettings() {
129130
final Settings.Builder builder = Settings.builder();
130131

131132
// Adds a node attribute for the ec2 availability zone
132-
final String azMetadataUrl = AwsEc2ServiceImpl.EC2_METADATA_URL + "placement/availability-zone";
133+
final String azMetadataUrl = EC2MetadataUtils.getHostAddressForEC2MetadataService()
134+
+ "/latest/meta-data/placement/availability-zone";
133135
builder.put(getAvailabilityZoneNodeAttributes(settings, azMetadataUrl));
134136
return builder.build();
135137
}

plugins/discovery-ec2/src/main/java/org/elasticsearch/discovery/ec2/Ec2NameResolver.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.discovery.ec2;
2121

22+
import com.amazonaws.util.EC2MetadataUtils;
2223
import org.elasticsearch.core.internal.io.IOUtils;
2324
import org.elasticsearch.common.SuppressForbidden;
2425
import org.elasticsearch.common.component.AbstractComponent;
@@ -86,7 +87,7 @@ private enum Ec2HostnameType {
8687
@SuppressForbidden(reason = "We call getInputStream in doPrivileged and provide SocketPermission")
8788
public InetAddress[] resolve(Ec2HostnameType type) throws IOException {
8889
InputStream in = null;
89-
String metadataUrl = AwsEc2ServiceImpl.EC2_METADATA_URL + type.ec2Name;
90+
String metadataUrl = EC2MetadataUtils.getHostAddressForEC2MetadataService() + "/latest/meta-data/" + type.ec2Name;
9091
try {
9192
URL url = new URL(metadataUrl);
9293
logger.debug("obtaining ec2 hostname from ec2 meta-data url {}", url);

plugins/discovery-ec2/src/test/java/org/elasticsearch/discovery/ec2/Ec2NetworkTests.java

+86-77
Original file line numberDiff line numberDiff line change
@@ -19,148 +19,157 @@
1919

2020
package org.elasticsearch.discovery.ec2;
2121

22+
import com.sun.net.httpserver.HttpServer;
23+
import org.elasticsearch.common.Strings;
24+
import org.elasticsearch.common.SuppressForbidden;
2225
import org.elasticsearch.common.network.NetworkService;
2326
import org.elasticsearch.common.settings.Settings;
27+
import org.elasticsearch.mocksocket.MockHttpServer;
28+
import org.elasticsearch.rest.RestStatus;
2429
import org.elasticsearch.test.ESTestCase;
30+
import org.junit.AfterClass;
31+
import org.junit.Before;
32+
import org.junit.BeforeClass;
2533

2634
import java.io.IOException;
35+
import java.io.OutputStream;
2736
import java.net.InetAddress;
37+
import java.net.InetSocketAddress;
38+
import java.security.AccessController;
39+
import java.security.PrivilegedAction;
40+
import java.util.Arrays;
2841
import java.util.Collections;
42+
import java.util.function.BiConsumer;
2943

44+
import static com.amazonaws.SDKGlobalConfiguration.EC2_METADATA_SERVICE_OVERRIDE_SYSTEM_PROPERTY;
45+
import static java.nio.charset.StandardCharsets.UTF_8;
3046
import static org.hamcrest.Matchers.arrayContaining;
31-
import static org.hamcrest.Matchers.containsString;
47+
import static org.hamcrest.Matchers.equalTo;
3248

3349
/**
3450
* Test for EC2 network.host settings.
3551
* <p>
3652
* Warning: This test doesn't assert that the exceptions are thrown.
3753
* They aren't.
3854
*/
55+
@SuppressForbidden(reason = "use http server")
3956
public class Ec2NetworkTests extends ESTestCase {
57+
58+
private static HttpServer httpServer;
59+
60+
@BeforeClass
61+
public static void startHttp() throws Exception {
62+
httpServer = MockHttpServer.createHttp(new InetSocketAddress(InetAddress.getLoopbackAddress().getHostAddress(), 0), 0);
63+
64+
BiConsumer<String, String> registerContext = (path, v) ->{
65+
final byte[] message = v.getBytes(UTF_8);
66+
httpServer.createContext(path, (s) -> {
67+
s.sendResponseHeaders(RestStatus.OK.getStatus(), message.length);
68+
OutputStream responseBody = s.getResponseBody();
69+
responseBody.write(message);
70+
responseBody.close();
71+
});
72+
};
73+
registerContext.accept("/latest/meta-data/local-ipv4","127.0.0.1");
74+
registerContext.accept("/latest/meta-data/public-ipv4","165.168.10.2");
75+
registerContext.accept("/latest/meta-data/public-hostname","165.168.10.3");
76+
registerContext.accept("/latest/meta-data/local-hostname","10.10.10.5");
77+
78+
httpServer.start();
79+
}
80+
81+
@Before
82+
public void setup() {
83+
// redirect EC2 metadata service to httpServer
84+
AccessController.doPrivileged((PrivilegedAction<String>) () -> System.setProperty(EC2_METADATA_SERVICE_OVERRIDE_SYSTEM_PROPERTY,
85+
"http://" + httpServer.getAddress().getHostName() + ":" + httpServer.getAddress().getPort()));
86+
}
87+
88+
@AfterClass
89+
public static void stopHttp() {
90+
httpServer.stop(0);
91+
httpServer = null;
92+
}
93+
4094
/**
4195
* Test for network.host: _ec2_
4296
*/
4397
public void testNetworkHostEc2() throws IOException {
44-
Settings nodeSettings = Settings.builder()
45-
.put("network.host", "_ec2_")
46-
.build();
98+
resolveEc2("_ec2_", InetAddress.getByName("127.0.0.1"));
99+
}
100+
101+
/**
102+
* Test for network.host: _ec2_
103+
*/
104+
public void testNetworkHostUnableToResolveEc2() {
105+
// redirect EC2 metadata service to unknown location
106+
AccessController.doPrivileged((PrivilegedAction<String>) () -> System.setProperty(EC2_METADATA_SERVICE_OVERRIDE_SYSTEM_PROPERTY,
107+
"http://127.0.0.1/"));
47108

48-
NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver()));
49-
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
50109
try {
51-
networkService.resolveBindHostAddresses(null);
52-
// note: this can succeed and the test can pass
110+
resolveEc2("_ec2_", (InetAddress[]) null);
53111
} catch (IOException e) {
54-
assertThat(e.getMessage(), containsString("local-ipv4"));
112+
assertThat(e.getMessage(),
113+
equalTo("IOException caught when fetching InetAddress from [http://127.0.0.1//latest/meta-data/local-ipv4]"));
55114
}
56115
}
57116

58117
/**
59118
* Test for network.host: _ec2:publicIp_
60119
*/
61120
public void testNetworkHostEc2PublicIp() throws IOException {
62-
Settings nodeSettings = Settings.builder()
63-
.put("network.host", "_ec2:publicIp_")
64-
.build();
65-
66-
NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver()));
67-
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
68-
try {
69-
networkService.resolveBindHostAddresses(null);
70-
// note: this can succeed and the test can pass
71-
} catch (IOException e) {
72-
assertThat(e.getMessage(), containsString("public-ipv4"));
73-
}
121+
resolveEc2("_ec2:publicIp_", InetAddress.getByName("165.168.10.2"));
74122
}
75123

76124
/**
77125
* Test for network.host: _ec2:privateIp_
78126
*/
79127
public void testNetworkHostEc2PrivateIp() throws IOException {
80-
Settings nodeSettings = Settings.builder()
81-
.put("network.host", "_ec2:privateIp_")
82-
.build();
83-
84-
NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver()));
85-
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
86-
try {
87-
networkService.resolveBindHostAddresses(null);
88-
// note: this can succeed and the test can pass
89-
} catch (IOException e) {
90-
assertThat(e.getMessage(), containsString("local-ipv4"));
91-
}
128+
resolveEc2("_ec2:privateIp_", InetAddress.getByName("127.0.0.1"));
92129
}
93130

94131
/**
95132
* Test for network.host: _ec2:privateIpv4_
96133
*/
97134
public void testNetworkHostEc2PrivateIpv4() throws IOException {
98-
Settings nodeSettings = Settings.builder()
99-
.put("network.host", "_ec2:privateIpv4_")
100-
.build();
101-
102-
NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver()));
103-
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
104-
try {
105-
networkService.resolveBindHostAddresses(null);
106-
// note: this can succeed and the test can pass
107-
} catch (IOException e) {
108-
assertThat(e.getMessage(), containsString("local-ipv4"));
109-
}
135+
resolveEc2("_ec2:privateIpv4_", InetAddress.getByName("127.0.0.1"));
110136
}
111137

112138
/**
113139
* Test for network.host: _ec2:privateDns_
114140
*/
115141
public void testNetworkHostEc2PrivateDns() throws IOException {
116-
Settings nodeSettings = Settings.builder()
117-
.put("network.host", "_ec2:privateDns_")
118-
.build();
119-
120-
NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver()));
121-
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
122-
try {
123-
networkService.resolveBindHostAddresses(null);
124-
// note: this can succeed and the test can pass
125-
} catch (IOException e) {
126-
assertThat(e.getMessage(), containsString("local-hostname"));
127-
}
142+
resolveEc2("_ec2:privateDns_", InetAddress.getByName("10.10.10.5"));
128143
}
129144

130145
/**
131146
* Test for network.host: _ec2:publicIpv4_
132147
*/
133148
public void testNetworkHostEc2PublicIpv4() throws IOException {
134-
Settings nodeSettings = Settings.builder()
135-
.put("network.host", "_ec2:publicIpv4_")
136-
.build();
137-
138-
NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver()));
139-
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
140-
try {
141-
networkService.resolveBindHostAddresses(null);
142-
// note: this can succeed and the test can pass
143-
} catch (IOException e) {
144-
assertThat(e.getMessage(), containsString("public-ipv4"));
145-
}
149+
resolveEc2("_ec2:publicIpv4_", InetAddress.getByName("165.168.10.2"));
146150
}
147151

148152
/**
149153
* Test for network.host: _ec2:publicDns_
150154
*/
151155
public void testNetworkHostEc2PublicDns() throws IOException {
156+
resolveEc2("_ec2:publicDns_", InetAddress.getByName("165.168.10.3"));
157+
}
158+
159+
private InetAddress[] resolveEc2(String host, InetAddress ... expected) throws IOException {
152160
Settings nodeSettings = Settings.builder()
153-
.put("network.host", "_ec2:publicDns_")
154-
.build();
161+
.put("network.host", host)
162+
.build();
155163

156164
NetworkService networkService = new NetworkService(Collections.singletonList(new Ec2NameResolver()));
157-
// TODO we need to replace that with a mock. For now we check the URL we are supposed to reach.
158-
try {
159-
networkService.resolveBindHostAddresses(null);
160-
// note: this can succeed and the test can pass
161-
} catch (IOException e) {
162-
assertThat(e.getMessage(), containsString("public-hostname"));
165+
166+
InetAddress[] addresses = networkService.resolveBindHostAddresses(
167+
NetworkService.GLOBAL_NETWORK_BINDHOST_SETTING.get(nodeSettings).toArray(Strings.EMPTY_ARRAY));
168+
if (expected == null) {
169+
fail("We should get an IOException, resolved addressed:" + Arrays.toString(addresses));
163170
}
171+
assertThat(addresses, arrayContaining(expected));
172+
return addresses;
164173
}
165174

166175
/**

0 commit comments

Comments
 (0)