From fbcc9d5f22f40ad2fb3a831dd5770cc5ebe62ee8 Mon Sep 17 00:00:00 2001 From: Ioannis Kakavas Date: Tue, 25 Jan 2022 21:27:01 +0200 Subject: [PATCH] Bind to non-localhost for transport in some cases (#82973) When enrolling a new node to an existing cluster, we sometimes need to bind transport to non-localhost addresse. If the other nodes of the cluster are on different hosts than this node, then the default configuration of binding transport layer to localhost will prevent this node to join the cluster even after "successful" enrollment. We check the non-localhost transport addresses that we receive during enrollment and if any of these are not in the list of non-localhost IP addresses that we gather from all interfaces of the current host, we assume that at least some other node in the cluster runs on another host. --- docs/changelog/82973.yaml | 5 +++ .../xpack/security/cli/AutoConfigureNode.java | 36 ++++++++++++++++- .../security/cli/AutoConfigureNodeTests.java | 40 +++++++++++++++++++ .../InitialNodeSecurityAutoConfiguration.java | 8 ++-- 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 docs/changelog/82973.yaml diff --git a/docs/changelog/82973.yaml b/docs/changelog/82973.yaml new file mode 100644 index 0000000000000..d815d7e8968ae --- /dev/null +++ b/docs/changelog/82973.yaml @@ -0,0 +1,5 @@ +pr: 82973 +summary: Bind to non-localhost for transport in some cases +area: Security +type: bug +issues: [] diff --git a/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/AutoConfigureNode.java b/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/AutoConfigureNode.java index ef3901cf5b9b5..f0a004af88f65 100644 --- a/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/AutoConfigureNode.java +++ b/x-pack/plugin/security/cli/src/main/java/org/elasticsearch/xpack/security/cli/AutoConfigureNode.java @@ -54,7 +54,10 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; +import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.LinkOption; @@ -799,7 +802,11 @@ protected void execute(Terminal terminal, OptionSet options, Environment env) th bw.newLine(); bw.write("# Connections are encrypted and mutually authenticated"); bw.newLine(); - bw.write("#" + TransportSettings.HOST.getKey() + ": " + hostSettingValue(NetworkUtils.getAllAddresses())); + if (false == inEnrollmentMode + || false == anyRemoteHostNodeAddress(transportAddresses, NetworkUtils.getAllAddresses())) { + bw.write("#"); + } + bw.write(TransportSettings.HOST.getKey() + ": " + hostSettingValue(NetworkUtils.getAllAddresses())); bw.newLine(); } bw.newLine(); @@ -846,6 +853,33 @@ private String initialMasterNodesSettingValue(Environment environment) { return "[\"${HOSTNAME}\"]"; } + /** + * Determines if a node that is enrolling to an existing cluster is on a different host than the other nodes of the + * cluster. If this is the case, then the default configuration of + * binding transport layer to localhost will prevent this node to join the cluster even after "successful" enrollment. + * We check the non-localhost transport addresses that we receive during enrollment and if any of these are not in the + * list of non-localhost IP addresses that we gather from all interfaces of the current host, we assume that at least + * some other node in the cluster runs on another host. + * If the transport layer addresses we found out in enrollment are all localhost, we cannot be sure where we are still + * on the same host, but we assume that as it is safer to do so and do not bind to non localhost for this node either. + */ + protected static boolean anyRemoteHostNodeAddress(List allNodesTransportPublishAddresses, InetAddress[] allHostAddresses) { + final List allAddressesList = Arrays.asList(allHostAddresses); + for (String nodeStringAddress : allNodesTransportPublishAddresses) { + try { + final URI uri = new URI("http://" + nodeStringAddress); + final InetAddress nodeAddress = InetAddress.getByName(uri.getHost()); + if (false == nodeAddress.isLoopbackAddress() && false == allAddressesList.contains(nodeAddress)) { + // this node's address is on a remote host + return true; + } + } catch (URISyntaxException | UnknownHostException e) { + // we could fail here but if any of the transport addresses are usable, we can join the cluster + } + } + return false; + } + protected String hostSettingValue(InetAddress[] allAddresses) { if (Arrays.stream(allAddresses).anyMatch(InetAddress::isSiteLocalAddress)) { return "[_local_, _site_]"; diff --git a/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/AutoConfigureNodeTests.java b/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/AutoConfigureNodeTests.java index eea4a9ddaf33c..8f3c1c7f6bce8 100644 --- a/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/AutoConfigureNodeTests.java +++ b/x-pack/plugin/security/cli/src/test/java/org/elasticsearch/xpack/security/cli/AutoConfigureNodeTests.java @@ -24,6 +24,7 @@ import org.elasticsearch.test.ESTestCase; import java.io.IOException; +import java.net.InetAddress; import java.nio.file.Files; import java.nio.file.Path; import java.security.KeyStore; @@ -31,7 +32,9 @@ import java.util.List; import static java.nio.file.StandardOpenOption.CREATE_NEW; +import static org.elasticsearch.xpack.security.cli.AutoConfigureNode.anyRemoteHostNodeAddress; import static org.elasticsearch.xpack.security.cli.AutoConfigureNode.removePreviousAutoconfiguration; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; public class AutoConfigureNodeTests extends ESTestCase { @@ -210,6 +213,43 @@ public void testGeneratedHTTPCertificateSANs() throws Exception { } } + public void testAnyRemoteHostNodeAddress() throws Exception { + List remoteAddresses = List.of("192.168.0.1:9300", "127.0.0.1:9300"); + InetAddress[] localAddresses = new InetAddress[] { InetAddress.getByName("192.168.0.1"), InetAddress.getByName("127.0.0.1") }; + assertThat(anyRemoteHostNodeAddress(remoteAddresses, localAddresses), equalTo(false)); + + remoteAddresses = List.of("192.168.0.1:9300", "127.0.0.1:9300", "[::1]:9300"); + localAddresses = new InetAddress[] { InetAddress.getByName("192.168.0.1"), InetAddress.getByName("127.0.0.1") }; + assertThat(anyRemoteHostNodeAddress(remoteAddresses, localAddresses), equalTo(false)); + + remoteAddresses = List.of("192.168.0.1:9300", "127.0.0.1:9300", "[::1]:9300"); + localAddresses = new InetAddress[] { + InetAddress.getByName("192.168.0.1"), + InetAddress.getByName("127.0.0.1"), + InetAddress.getByName("10.0.0.1") }; + assertThat(anyRemoteHostNodeAddress(remoteAddresses, localAddresses), equalTo(false)); + + remoteAddresses = List.of("192.168.0.1:9300", "127.0.0.1:9300", "[::1]:9300", "10.0.0.1:9301"); + localAddresses = new InetAddress[] { InetAddress.getByName("192.168.0.1"), InetAddress.getByName("127.0.0.1") }; + assertThat(anyRemoteHostNodeAddress(remoteAddresses, localAddresses), equalTo(true)); + + remoteAddresses = List.of("127.0.0.1:9300", "[::1]:9300"); + localAddresses = new InetAddress[] { InetAddress.getByName("[::1]"), InetAddress.getByName("127.0.0.1") }; + assertThat(anyRemoteHostNodeAddress(remoteAddresses, localAddresses), equalTo(false)); + + remoteAddresses = List.of("127.0.0.1:9300", "[::1]:9300"); + localAddresses = new InetAddress[] { InetAddress.getByName("192.168.2.3") }; + assertThat(anyRemoteHostNodeAddress(remoteAddresses, localAddresses), equalTo(false)); + + remoteAddresses = List.of("1.2.3.4:9300"); + localAddresses = new InetAddress[] { InetAddress.getByName("[::1]"), InetAddress.getByName("127.0.0.1") }; + assertThat(anyRemoteHostNodeAddress(remoteAddresses, localAddresses), equalTo(true)); + + remoteAddresses = List.of(); + localAddresses = new InetAddress[] { InetAddress.getByName("192.168.0.1"), InetAddress.getByName("127.0.0.1") }; + assertThat(anyRemoteHostNodeAddress(remoteAddresses, localAddresses), equalTo(false)); + } + private boolean checkGeneralNameSan(X509Certificate certificate, String generalName, int generalNameTag) throws Exception { for (List san : certificate.getSubjectAlternativeNames()) { if (san.get(0).equals(generalNameTag) && san.get(1).equals(generalName)) { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/InitialNodeSecurityAutoConfiguration.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/InitialNodeSecurityAutoConfiguration.java index cdc93a9c48a6d..fb1c58edf4cf7 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/InitialNodeSecurityAutoConfiguration.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/InitialNodeSecurityAutoConfiguration.java @@ -254,7 +254,7 @@ private static void outputInformationToConsole( } else if (false == Strings.isEmpty(elasticPassword)) { builder.append( infoBullet - + " Password for the " + + " Password for the " + boldOnANSI + "elastic" + boldOffANSI @@ -271,7 +271,7 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); if (null != caCertFingerprint) { - builder.append(infoBullet + " HTTP CA certificate SHA-256 fingerprint:"); + builder.append(infoBullet + " HTTP CA certificate SHA-256 fingerprint:"); builder.append(System.lineSeparator()); builder.append(" " + boldOnANSI + caCertFingerprint + boldOffANSI); } @@ -279,7 +279,7 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); if (null != kibanaEnrollmentToken) { - builder.append(infoBullet + " Configure Kibana to use this cluster:"); + builder.append(infoBullet + " Configure Kibana to use this cluster:"); builder.append(System.lineSeparator()); builder.append(bullet + " Run Kibana and click the configuration link in the terminal when Kibana starts."); builder.append(System.lineSeparator()); @@ -325,7 +325,7 @@ private static void outputInformationToConsole( + ", using the enrollment token that you generated." ); } else if (Strings.isEmpty(nodeEnrollmentToken)) { - builder.append(infoBullet + " Configure other nodes to join this cluster:"); + builder.append(infoBullet + " Configure other nodes to join this cluster:"); builder.append(System.lineSeparator()); builder.append(bullet + " On this node:"); builder.append(System.lineSeparator());