Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bind to non-localhost for transport in some cases #82973

Merged
Merged
5 changes: 5 additions & 0 deletions docs/changelog/82973.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 82973
summary: Bind to non-localhost for transport in some cases
area: Security
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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<String> allNodesTransportPublishAddresses, InetAddress[] allHostAddresses) {
final List<InetAddress> 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_]";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@
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;
import java.security.cert.X509Certificate;
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 {
Expand Down Expand Up @@ -210,6 +213,43 @@ public void testGeneratedHTTPCertificateSANs() throws Exception {
}
}

public void testAnyRemoteHostNodeAddress() throws Exception {
List<String> 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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -271,15 +271,15 @@ 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);
}
builder.append(System.lineSeparator());
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());
Expand Down Expand Up @@ -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:");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand you're tackling #82740 (comment) .
If it is important to you, take care to align the leading spacing of subsequent rows.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is important to you, take care to align the leading spacing of subsequent rows.

I don't think that matters much ( in my opinion at least ). I just wanted to make sure that the words do not collide with the unicode symbols ( not sure if we can fix that universally either but I thought I'd try and we can always iterate based on feedback )

builder.append(System.lineSeparator());
builder.append(bullet + " On this node:");
builder.append(System.lineSeparator());
Expand Down