From ff73d15e025696a48daac4a82c0e33784dadabbe Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 18 Jan 2022 14:05:04 +0200 Subject: [PATCH 01/13] WIP --- .../io/ansi/AnsiConsoleLoader.java | 5 + .../InitialNodeSecurityAutoConfiguration.java | 118 ++++++++++++------ 2 files changed, 85 insertions(+), 38 deletions(-) diff --git a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java index 4356ce57e1ebc..fefe214d9cd71 100644 --- a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java +++ b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java @@ -7,6 +7,7 @@ */ package org.elasticsearch.io.ansi; +import org.fusesource.jansi.Ansi; import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiPrintStream; import org.fusesource.jansi.AnsiType; @@ -14,6 +15,8 @@ import java.io.PrintStream; import java.util.function.Supplier; +import static org.fusesource.jansi.Ansi.ansi; + /** * Loads the({@link PrintStream} print stream) from {@link AnsiConsole} and checks whether it meets our requirements for a "Console". * @see org.elasticsearch.bootstrap.ConsoleLoader @@ -23,6 +26,8 @@ public class AnsiConsoleLoader implements Supplier { public PrintStream get() { final AnsiPrintStream out = AnsiConsole.out(); if (isValidConsole(out)) { + Ansi a = ansi().bold().a("Salut").boldOff(); + out.print(a); return out; } else { return null; 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 903ab07aa13b4..6955a6ebb4cb3 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 @@ -187,70 +187,112 @@ private static void outputInformationToConsole( String caCertFingerprint, PrintStream out ) { + final String infoBullet = "\u2139"; + final String bullet = "\u2022"; + final String hyphenBullet = "\u2043"; + final String errorBullet = "\u274C"; + final String successBullet = "\u2705"; + final String horizontalLine = "\u2501"; + final String boldOnANSI = "\u001B[1m"; + final String boldOffANSI = "\u001B[22m"; + final String cmdOn = "`"; + final String cmdOff = "`"; + final int horizontalLineLength = 140; StringBuilder builder = new StringBuilder(); builder.append(System.lineSeparator()); + builder.append(horizontalLine.repeat(horizontalLineLength)); builder.append(System.lineSeparator()); - builder.append("--------------------------------------------------------------------------------------------------------------"); + builder.append(infoBullet + " Elasticsearch security features have been automatically configured!"); + builder.append(System.lineSeparator()); + builder.append(infoBullet + " Authentication is enabled and cluster connections are encrypted."); builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); if (elasticPassword == null) { - builder.append("Unable to auto-generate the password for the elastic built-in superuser."); - } else if (Strings.isEmpty(elasticPassword)) { - builder.append("The generated password for the elastic built-in superuser has not been changed."); - } else { - builder.append("The generated password for the elastic built-in superuser is:"); + builder.append(errorBullet + " Unable to auto-generate the password for the " + boldOnANSI + "elastic" + boldOffANSI + + " built-in superuser."); + } else if (false == Strings.isEmpty(elasticPassword)) { + builder.append(successBullet + " Password for the " + boldOnANSI + "elastic" + boldOffANSI + + " user (reset with " + cmdOn + "bin/elasticsearch-reset-password -u elastic" + cmdOff + "):"); builder.append(System.lineSeparator()); - builder.append(elasticPassword); + builder.append(" " + boldOnANSI + elasticPassword + boldOffANSI); + } + builder.append(System.lineSeparator()); + builder.append(System.lineSeparator()); + if (null != caCertFingerprint) { + builder.append(successBullet + " 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("The enrollment token for Kibana instances, valid for the next "); - builder.append(BaseEnrollmentTokenGenerator.ENROLL_API_KEY_EXPIRATION_MINUTES); - builder.append(" minutes:"); + 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()); - builder.append(kibanaEnrollmentToken); + builder.append(bullet + " Copy the following enrollment token and paste it into Kibana in your browser "); + builder.append("(valid for the next 30 minutes):"); + builder.append(System.lineSeparator()); + builder.append(" " + boldOnANSI + kibanaEnrollmentToken + boldOffANSI); } else { - builder.append("Unable to generate an enrollment token for Kibana instances."); + // TODO + builder.append(errorBullet + " Unable to generate an enrollment token for Kibana instances."); } builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); - if (nodeEnrollmentToken == null) { - builder.append("Unable to generate an enrollment token for Elasticsearch nodes."); + + // Node enrollment token + if (null == nodeEnrollmentToken) { + builder.append(errorBullet + " An enrollment token to enroll new nodes wasn't generated."); + builder.append(" To add nodes and enroll them into this cluster:"); builder.append(System.lineSeparator()); + builder.append(bullet + " On this node:"); builder.append(System.lineSeparator()); - } else if (false == Strings.isEmpty(nodeEnrollmentToken)) { - builder.append("The enrollment token for Elasticsearch instances, valid for the next "); - builder.append(BaseEnrollmentTokenGenerator.ENROLL_API_KEY_EXPIRATION_MINUTES); - builder.append(" minutes:"); + builder.append(" " + hyphenBullet + " Create an enrollment token with " + cmdOn + + "bin/elasticsearch-create-enrollment-token -s node" + cmdOff + "."); builder.append(System.lineSeparator()); - builder.append(nodeEnrollmentToken); + builder.append(" " + hyphenBullet + " Restart Elasticsearch."); builder.append(System.lineSeparator()); + builder.append(bullet + " On the other node:"); builder.append(System.lineSeparator()); - } - if (null != caCertFingerprint) { - builder.append("The hex-encoded SHA-256 fingerprint of the generated HTTPS CA DER-encoded certificate:"); + builder.append(" " + hyphenBullet + " Start Elasticsearch on other nodes with " + cmdOn + + "bin/elasticsearch --enrollment-token " + cmdOff + + ", using the enrollment token that you generated."); + builder.append(System.lineSeparator()); + } else if (Strings.isEmpty(nodeEnrollmentToken)) { + builder.append(infoBullet + " Configure other nodes to join this cluster:"); + builder.append(System.lineSeparator()); + builder.append(bullet + " On this node:"); + builder.append(System.lineSeparator()); + builder.append(" " + hyphenBullet + " Create an enrollment token with " + cmdOn + + "bin/elasticsearch-create-enrollment-token -s node" + cmdOff + "."); + builder.append(System.lineSeparator()); + builder.append(" " + hyphenBullet + " Uncomment the " + boldOnANSI + + "transport.host" + boldOffANSI + " setting at the end of " + boldOnANSI + + "config/elasticsearch.yml" + boldOffANSI + "."); builder.append(System.lineSeparator()); - builder.append(caCertFingerprint); + builder.append(" " + hyphenBullet + " Restart Elasticsearch."); builder.append(System.lineSeparator()); + builder.append(bullet + " On the other node:"); + builder.append(System.lineSeparator()); + builder.append(" " + hyphenBullet + " Start Elasticsearch on other nodes with " + cmdOn + + "bin/elasticsearch --enrollment-token " + cmdOff + + ", using the enrollment token that you generated."); + builder.append(System.lineSeparator()); + } else { + builder.append(infoBullet + " Configure other nodes to join this cluster:"); + builder.append(System.lineSeparator()); + builder.append(bullet + " Copy the following enrollment token and start new Elasticsearch nodes with " + + cmdOn + "bin/elasticsearch --enrollment-token " + cmdOff + " (valid for the next 30 minutes):"); + builder.append(System.lineSeparator()); + builder.append(" " + boldOnANSI + nodeEnrollmentToken + boldOffANSI); } + + builder.append(horizontalLine.repeat(horizontalLineLength)); builder.append(System.lineSeparator()); - builder.append(System.lineSeparator()); - builder.append("You can complete the following actions at any time:"); - builder.append(System.lineSeparator()); - builder.append("Reset the password of the elastic built-in superuser with 'bin/elasticsearch-reset-password -u elastic'."); - builder.append(System.lineSeparator()); - builder.append(System.lineSeparator()); - builder.append("Generate an enrollment token for Kibana instances with 'bin/elasticsearch-create-enrollment-token -s kibana'."); - builder.append(System.lineSeparator()); - builder.append(System.lineSeparator()); - builder.append("Generate an enrollment token for Elasticsearch nodes with 'bin/elasticsearch-create-enrollment-token -s node'."); - builder.append(System.lineSeparator()); - builder.append(System.lineSeparator()); - builder.append("--------------------------------------------------------------------------------------------------------------"); - builder.append(System.lineSeparator()); - builder.append(System.lineSeparator()); + out.println(builder); } } From 8d5681929a50e0c631e6b899609a64fb56e65f5a Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 18 Jan 2022 15:00:40 +0200 Subject: [PATCH 02/13] terminal length still not detected --- .../elasticsearch/io/ansi/AnsiConsoleLoader.java | 4 ---- .../InitialNodeSecurityAutoConfiguration.java | 15 +++++++++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java index fefe214d9cd71..3eff4a58a9384 100644 --- a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java +++ b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java @@ -15,8 +15,6 @@ import java.io.PrintStream; import java.util.function.Supplier; -import static org.fusesource.jansi.Ansi.ansi; - /** * Loads the({@link PrintStream} print stream) from {@link AnsiConsole} and checks whether it meets our requirements for a "Console". * @see org.elasticsearch.bootstrap.ConsoleLoader @@ -26,8 +24,6 @@ public class AnsiConsoleLoader implements Supplier { public PrintStream get() { final AnsiPrintStream out = AnsiConsole.out(); if (isValidConsole(out)) { - Ansi a = ansi().bold().a("Salut").boldOff(); - out.print(a); return out; } else { return null; 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 8b942b2346327..01c154073d8d3 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 @@ -9,6 +9,7 @@ import org.apache.log4j.LogManager; import org.apache.log4j.Logger; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.bulk.BackoffPolicy; import org.elasticsearch.action.support.GroupedActionListener; @@ -261,8 +262,8 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); builder.append(" " + boldOnANSI + kibanaEnrollmentToken + boldOffANSI); } else { - // TODO - builder.append(errorBullet + " Unable to generate an enrollment token for Kibana instances."); + builder.append(errorBullet + " Unable to generate an enrollment token for Kibana instances, "); + builder.append("try invoking " + cmdOn + "bin/elasticsearch-create-enrollment-token -s kibana" + cmdOff + "."); } builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); @@ -284,7 +285,6 @@ private static void outputInformationToConsole( builder.append(" " + hyphenBullet + " Start Elasticsearch on other nodes with " + cmdOn + "bin/elasticsearch --enrollment-token " + cmdOff + ", using the enrollment token that you generated."); - builder.append(System.lineSeparator()); } else if (Strings.isEmpty(nodeEnrollmentToken)) { builder.append(infoBullet + " Configure other nodes to join this cluster:"); builder.append(System.lineSeparator()); @@ -304,7 +304,6 @@ private static void outputInformationToConsole( builder.append(" " + hyphenBullet + " Start Elasticsearch on other nodes with " + cmdOn + "bin/elasticsearch --enrollment-token " + cmdOff + ", using the enrollment token that you generated."); - builder.append(System.lineSeparator()); } else { builder.append(infoBullet + " Configure other nodes to join this cluster:"); builder.append(System.lineSeparator()); @@ -312,8 +311,16 @@ private static void outputInformationToConsole( cmdOn + "bin/elasticsearch --enrollment-token " + cmdOff + " (valid for the next 30 minutes):"); builder.append(System.lineSeparator()); builder.append(" " + boldOnANSI + nodeEnrollmentToken + boldOffANSI); + builder.append(System.lineSeparator()); + builder.append(System.lineSeparator()); + builder.append("If you're running in Docker, copy the enrollment token and run:"); + builder.append(System.lineSeparator()); + builder.append(cmdOn + "docker run --name -p :9200 --net elastic -it"); + builder.append("docker.elastic.co/elasticsearch/elasticsearch:" + Version.CURRENT + " /bin/bash -c"); + builder.append("\"/usr/share/elasticsearch/bin/elasticsearch --enrollment-token \""); } + builder.append(System.lineSeparator()); builder.append(horizontalLine.repeat(horizontalLineLength)); builder.append(System.lineSeparator()); From 1a674d47eb91bf2cb39a670b1e8d54bfba119ddc Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 18 Jan 2022 15:10:37 +0200 Subject: [PATCH 03/13] Spotless --- .../io/ansi/AnsiConsoleLoader.java | 1 - .../InitialNodeSecurityAutoConfiguration.java | 97 +++++++++++++++---- 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java index 3eff4a58a9384..4356ce57e1ebc 100644 --- a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java +++ b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java @@ -7,7 +7,6 @@ */ package org.elasticsearch.io.ansi; -import org.fusesource.jansi.Ansi; import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiPrintStream; import org.fusesource.jansi.AnsiType; 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 01c154073d8d3..6c15154433f01 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 @@ -22,7 +22,6 @@ import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore; -import org.elasticsearch.xpack.security.enrollment.BaseEnrollmentTokenGenerator; import org.elasticsearch.xpack.security.enrollment.InternalEnrollmentTokenGenerator; import org.elasticsearch.xpack.security.support.SecurityIndexManager; @@ -233,11 +232,27 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); if (elasticPassword == null) { - builder.append(errorBullet + " Unable to auto-generate the password for the " + boldOnANSI + "elastic" + boldOffANSI + - " built-in superuser."); + builder.append( + errorBullet + + " Unable to auto-generate the password for the " + + boldOnANSI + + "elastic" + + boldOffANSI + + " built-in superuser." + ); } else if (false == Strings.isEmpty(elasticPassword)) { - builder.append(successBullet + " Password for the " + boldOnANSI + "elastic" + boldOffANSI + - " user (reset with " + cmdOn + "bin/elasticsearch-reset-password -u elastic" + cmdOff + "):"); + builder.append( + successBullet + + " Password for the " + + boldOnANSI + + "elastic" + + boldOffANSI + + " user (reset with " + + cmdOn + + "bin/elasticsearch-reset-password -u elastic" + + cmdOff + + "):" + ); builder.append(System.lineSeparator()); builder.append(" " + boldOnANSI + elasticPassword + boldOffANSI); } @@ -275,40 +290,82 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); builder.append(bullet + " On this node:"); builder.append(System.lineSeparator()); - builder.append(" " + hyphenBullet + " Create an enrollment token with " + cmdOn + - "bin/elasticsearch-create-enrollment-token -s node" + cmdOff + "."); + builder.append( + " " + + hyphenBullet + + " Create an enrollment token with " + + cmdOn + + "bin/elasticsearch-create-enrollment-token -s node" + + cmdOff + + "." + ); builder.append(System.lineSeparator()); builder.append(" " + hyphenBullet + " Restart Elasticsearch."); builder.append(System.lineSeparator()); builder.append(bullet + " On the other node:"); builder.append(System.lineSeparator()); - builder.append(" " + hyphenBullet + " Start Elasticsearch on other nodes with " + cmdOn + - "bin/elasticsearch --enrollment-token " + cmdOff + - ", using the enrollment token that you generated."); + builder.append( + " " + + hyphenBullet + + " Start Elasticsearch on other nodes with " + + cmdOn + + "bin/elasticsearch --enrollment-token " + + cmdOff + + ", using the enrollment token that you generated." + ); } else if (Strings.isEmpty(nodeEnrollmentToken)) { builder.append(infoBullet + " Configure other nodes to join this cluster:"); builder.append(System.lineSeparator()); builder.append(bullet + " On this node:"); builder.append(System.lineSeparator()); - builder.append(" " + hyphenBullet + " Create an enrollment token with " + cmdOn + - "bin/elasticsearch-create-enrollment-token -s node" + cmdOff + "."); + builder.append( + " " + + hyphenBullet + + " Create an enrollment token with " + + cmdOn + + "bin/elasticsearch-create-enrollment-token -s node" + + cmdOff + + "." + ); builder.append(System.lineSeparator()); - builder.append(" " + hyphenBullet + " Uncomment the " + boldOnANSI + - "transport.host" + boldOffANSI + " setting at the end of " + boldOnANSI + - "config/elasticsearch.yml" + boldOffANSI + "."); + builder.append( + " " + + hyphenBullet + + " Uncomment the " + + boldOnANSI + + "transport.host" + + boldOffANSI + + " setting at the end of " + + boldOnANSI + + "config/elasticsearch.yml" + + boldOffANSI + + "." + ); builder.append(System.lineSeparator()); builder.append(" " + hyphenBullet + " Restart Elasticsearch."); builder.append(System.lineSeparator()); builder.append(bullet + " On the other node:"); builder.append(System.lineSeparator()); - builder.append(" " + hyphenBullet + " Start Elasticsearch on other nodes with " + cmdOn + - "bin/elasticsearch --enrollment-token " + cmdOff + - ", using the enrollment token that you generated."); + builder.append( + " " + + hyphenBullet + + " Start Elasticsearch on other nodes with " + + cmdOn + + "bin/elasticsearch --enrollment-token " + + cmdOff + + ", using the enrollment token that you generated." + ); } else { builder.append(infoBullet + " Configure other nodes to join this cluster:"); builder.append(System.lineSeparator()); - builder.append(bullet + " Copy the following enrollment token and start new Elasticsearch nodes with " + - cmdOn + "bin/elasticsearch --enrollment-token " + cmdOff + " (valid for the next 30 minutes):"); + builder.append( + bullet + + " Copy the following enrollment token and start new Elasticsearch nodes with " + + cmdOn + + "bin/elasticsearch --enrollment-token " + + cmdOff + + " (valid for the next 30 minutes):" + ); builder.append(System.lineSeparator()); builder.append(" " + boldOnANSI + nodeEnrollmentToken + boldOffANSI); builder.append(System.lineSeparator()); From f796b57e8c5d97b3a6621144f5a6e56ea112070f Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Tue, 18 Jan 2022 17:40:50 +0200 Subject: [PATCH 04/13] Nit --- .../xpack/security/InitialNodeSecurityAutoConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6c15154433f01..d9a4f2aff54bc 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 @@ -372,7 +372,7 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); builder.append("If you're running in Docker, copy the enrollment token and run:"); builder.append(System.lineSeparator()); - builder.append(cmdOn + "docker run --name -p :9200 --net elastic -it"); + builder.append(cmdOn + "docker run --name -p :9200 --net elastic -it "); builder.append("docker.elastic.co/elasticsearch/elasticsearch:" + Version.CURRENT + " /bin/bash -c"); builder.append("\"/usr/share/elasticsearch/bin/elasticsearch --enrollment-token \""); } From 49516026c372315086e4eaf1032f21c6f7e1906d Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Thu, 20 Jan 2022 09:26:33 +0200 Subject: [PATCH 05/13] Border length the width of the terminal --- .../io/ansi/AnsiConsoleLoader.java | 11 +++--- .../bootstrap/ConsoleLoaderTests.java | 3 +- .../elasticsearch/bootstrap/Bootstrap.java | 4 +- .../bootstrap/BootstrapInfo.java | 11 +++--- .../bootstrap/ConsoleLoader.java | 16 ++++---- .../InitialNodeSecurityAutoConfiguration.java | 37 +++++++++++-------- 6 files changed, 43 insertions(+), 39 deletions(-) diff --git a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java index 4356ce57e1ebc..c5c5c9e7a7a66 100644 --- a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java +++ b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java @@ -7,23 +7,24 @@ */ package org.elasticsearch.io.ansi; +import org.elasticsearch.bootstrap.ConsoleLoader; import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiPrintStream; import org.fusesource.jansi.AnsiType; -import java.io.PrintStream; import java.util.function.Supplier; /** - * Loads the({@link PrintStream} print stream) from {@link AnsiConsole} and checks whether it meets our requirements for a "Console". + * Loads the {@link AnsiConsole} and checks whether it meets our requirements for a "Console". * @see org.elasticsearch.bootstrap.ConsoleLoader */ -public class AnsiConsoleLoader implements Supplier { +public class AnsiConsoleLoader implements Supplier { - public PrintStream get() { + public ConsoleLoader.Console get() { final AnsiPrintStream out = AnsiConsole.out(); if (isValidConsole(out)) { - return out; + // TODO use reflection to surface the Charset, in order to verify that the unicode code points can be printed + return new ConsoleLoader.Console(out, () -> out.getTerminalWidth()); } else { return null; } diff --git a/distribution/tools/ansi-console/src/test/java/org/elasticsearch/bootstrap/ConsoleLoaderTests.java b/distribution/tools/ansi-console/src/test/java/org/elasticsearch/bootstrap/ConsoleLoaderTests.java index e0bc5b04ec4e4..f34ddae8a2ffb 100644 --- a/distribution/tools/ansi-console/src/test/java/org/elasticsearch/bootstrap/ConsoleLoaderTests.java +++ b/distribution/tools/ansi-console/src/test/java/org/elasticsearch/bootstrap/ConsoleLoaderTests.java @@ -11,7 +11,6 @@ import org.elasticsearch.io.ansi.AnsiConsoleLoader; import org.elasticsearch.test.ESTestCase; -import java.io.PrintStream; import java.util.function.Supplier; import static org.hamcrest.Matchers.instanceOf; @@ -20,7 +19,7 @@ public class ConsoleLoaderTests extends ESTestCase { public void testBuildSupplier() { - final Supplier supplier = ConsoleLoader.buildConsoleLoader(AnsiConsoleLoader.class.getClassLoader()); + final Supplier supplier = ConsoleLoader.buildConsoleLoader(AnsiConsoleLoader.class.getClassLoader()); assertThat(supplier, notNullValue()); assertThat(supplier, instanceOf(AnsiConsoleLoader.class)); } diff --git a/server/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java b/server/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java index c57f6e1f86c21..ba94dcb965f41 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java @@ -301,7 +301,7 @@ static void init(final boolean foreground, final Path pidFile, final boolean qui final SecureSettings keystore = BootstrapUtil.loadSecureSettings(initialEnv); final Environment environment = createEnvironment(pidFile, keystore, initialEnv.settings(), initialEnv.configFile()); - BootstrapInfo.setConsoleOutput(getConsole(environment)); + BootstrapInfo.setConsole(getConsole(environment)); // the LogConfigurator will replace System.out and System.err with redirects to our logfile, so we need to capture // the stream objects before calling LogConfigurator to be able to close them when appropriate @@ -417,7 +417,7 @@ static void init(final boolean foreground, final Path pidFile, final boolean qui } } - private static PrintStream getConsole(Environment environment) { + private static ConsoleLoader.Console getConsole(Environment environment) { return ConsoleLoader.loadConsole(environment); } diff --git a/server/src/main/java/org/elasticsearch/bootstrap/BootstrapInfo.java b/server/src/main/java/org/elasticsearch/bootstrap/BootstrapInfo.java index 295cc00fa67e0..0b63211f2851a 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/BootstrapInfo.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/BootstrapInfo.java @@ -11,7 +11,6 @@ import org.apache.lucene.util.SetOnce; import org.elasticsearch.core.SuppressForbidden; -import java.io.PrintStream; import java.util.Dictionary; import java.util.Enumeration; @@ -21,7 +20,7 @@ @SuppressForbidden(reason = "exposes read-only view of system properties") public final class BootstrapInfo { - private static final SetOnce consoleOutput = new SetOnce<>(); + private static final SetOnce console = new SetOnce<>(); /** no instantiation */ private BootstrapInfo() {} @@ -53,8 +52,8 @@ public static boolean isSystemCallFilterInstalled() { /** * Returns a reference to a stream attached to Standard Output, iff we have determined that stdout is a console (tty) */ - public static PrintStream getConsoleOutput() { - return consoleOutput.get(); + public static ConsoleLoader.Console getConsole() { + return console.get(); } /** @@ -123,8 +122,8 @@ public static Dictionary getSystemProperties() { public static void init() {} - static void setConsoleOutput(PrintStream output) { - consoleOutput.set(output); + static void setConsole(ConsoleLoader.Console console) { + BootstrapInfo.console.set(console); } } diff --git a/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java b/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java index 703739ceff355..20c1991556771 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java @@ -28,20 +28,20 @@ public class ConsoleLoader { private static final String CONSOLE_LOADER_CLASS = "org.elasticsearch.io.ansi.AnsiConsoleLoader"; - public static PrintStream loadConsole(Environment env) { + public static Console loadConsole(Environment env) { final ClassLoader classLoader = buildClassLoader(env); - final Supplier supplier = buildConsoleLoader(classLoader); + final Supplier supplier = buildConsoleLoader(classLoader); return supplier.get(); } + public record Console(PrintStream printStream, Supplier width) {} + @SuppressWarnings("unchecked") - static Supplier buildConsoleLoader(ClassLoader classLoader) { + static Supplier buildConsoleLoader(ClassLoader classLoader) { try { - final Class> cls = (Class>) classLoader.loadClass( - CONSOLE_LOADER_CLASS - ); - final Constructor> constructor = cls.getConstructor(); - final Supplier supplier = constructor.newInstance(); + final Class> cls = (Class>) classLoader.loadClass(CONSOLE_LOADER_CLASS); + final Constructor> constructor = cls.getConstructor(); + final Supplier supplier = constructor.newInstance(); return supplier; } catch (ReflectiveOperationException e) { throw new RuntimeException("Failed to load ANSI console", e); 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 d9a4f2aff54bc..a09193e32af70 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 @@ -14,6 +14,7 @@ import org.elasticsearch.action.bulk.BackoffPolicy; import org.elasticsearch.action.support.GroupedActionListener; import org.elasticsearch.bootstrap.BootstrapInfo; +import org.elasticsearch.bootstrap.ConsoleLoader; import org.elasticsearch.client.internal.Client; import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.concurrent.AbstractRunnable; @@ -25,7 +26,6 @@ import org.elasticsearch.xpack.security.enrollment.InternalEnrollmentTokenGenerator; import org.elasticsearch.xpack.security.support.SecurityIndexManager; -import java.io.PrintStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -73,8 +73,8 @@ public static void maybeGenerateEnrollmentTokensAndElasticCredentialsOnNodeStart client ); - final PrintStream out = getConsoleOutput(); - if (out == null) { + final ConsoleLoader.Console console = getConsole(); + if (console == null) { LOGGER.info( "Auto-configuration will not generate a password for the elastic built-in superuser, as we cannot " + " determine if there is a terminal attached to the elasticsearch process. You can use the" @@ -132,7 +132,7 @@ protected void doRun() { kibanaEnrollmentToken, nodeEnrollmentToken, httpsCaFingerprint, - out + console ); }, e -> LOGGER.error("Unexpected exception during security auto-configuration", e)), 3 @@ -191,17 +191,17 @@ protected void doRun() { }); } - private static PrintStream getConsoleOutput() { - final PrintStream output = BootstrapInfo.getConsoleOutput(); - if (output == null) { + private static ConsoleLoader.Console getConsole() { + final ConsoleLoader.Console console = BootstrapInfo.getConsole(); + if (console == null) { return null; } // Check if it has been closed, try to write something so that we trigger PrintStream#ensureOpen - output.println(); - if (output.checkError()) { + console.printStream().println(); + if (console.printStream().checkError()) { return null; } - return output; + return console; } private static void outputInformationToConsole( @@ -209,7 +209,7 @@ private static void outputInformationToConsole( String kibanaEnrollmentToken, String nodeEnrollmentToken, String caCertFingerprint, - PrintStream out + ConsoleLoader.Console console ) { final String infoBullet = "\u2139"; final String bullet = "\u2022"; @@ -221,9 +221,12 @@ private static void outputInformationToConsole( final String boldOffANSI = "\u001B[22m"; final String cmdOn = "`"; final String cmdOff = "`"; - final int horizontalLineLength = 140; + final int horizontalLineLength = console.width().get(); StringBuilder builder = new StringBuilder(); builder.append(System.lineSeparator()); + builder.append(System.lineSeparator()); + builder.append(System.lineSeparator()); + builder.append(System.lineSeparator()); builder.append(horizontalLine.repeat(horizontalLineLength)); builder.append(System.lineSeparator()); builder.append(infoBullet + " Elasticsearch security features have been automatically configured!"); @@ -372,16 +375,18 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); builder.append("If you're running in Docker, copy the enrollment token and run:"); builder.append(System.lineSeparator()); - builder.append(cmdOn + "docker run --name -p :9200 --net elastic -it "); - builder.append("docker.elastic.co/elasticsearch/elasticsearch:" + Version.CURRENT + " /bin/bash -c"); - builder.append("\"/usr/share/elasticsearch/bin/elasticsearch --enrollment-token \""); + builder.append(cmdOn + "docker run --name -p :9200 --net elastic -e \"ENROLLMENT_TOKEN=\""); + builder.append(" docker.elastic.co/elasticsearch/elasticsearch:" + Version.CURRENT + cmdOff); } builder.append(System.lineSeparator()); builder.append(horizontalLine.repeat(horizontalLineLength)); builder.append(System.lineSeparator()); + builder.append(System.lineSeparator()); + builder.append(System.lineSeparator()); + builder.append(System.lineSeparator()); - out.println(builder); + console.printStream().println(builder); } interface OnNodeStartedListener { From d98599c69c794d7f8808779f38941dd2e2e69edb Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sat, 22 Jan 2022 18:36:14 +0200 Subject: [PATCH 06/13] Detect console charset and fallback --- .../io/ansi/AnsiConsoleLoader.java | 33 ++++++++++++- .../bootstrap/ConsoleLoader.java | 4 +- .../InitialNodeSecurityAutoConfiguration.java | 46 +++++++++++-------- 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java index c5c5c9e7a7a66..6b59e3f6c5b64 100644 --- a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java +++ b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java @@ -7,24 +7,33 @@ */ package org.elasticsearch.io.ansi; +import org.apache.logging.log4j.Logger; import org.elasticsearch.bootstrap.ConsoleLoader; +import org.elasticsearch.core.Nullable; import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiPrintStream; import org.fusesource.jansi.AnsiType; +import org.fusesource.jansi.io.AnsiOutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.charset.Charset; import java.util.function.Supplier; +import static org.apache.logging.log4j.LogManager.getLogger; + /** * Loads the {@link AnsiConsole} and checks whether it meets our requirements for a "Console". * @see org.elasticsearch.bootstrap.ConsoleLoader */ public class AnsiConsoleLoader implements Supplier { + private static final Logger logger = getLogger(AnsiConsoleLoader.class); + public ConsoleLoader.Console get() { final AnsiPrintStream out = AnsiConsole.out(); if (isValidConsole(out)) { - // TODO use reflection to surface the Charset, in order to verify that the unicode code points can be printed - return new ConsoleLoader.Console(out, () -> out.getTerminalWidth()); + return new ConsoleLoader.Console(out, () -> out.getTerminalWidth(), tryExtractPrintCharset(out)); } else { return null; } @@ -37,4 +46,24 @@ static boolean isValidConsole(AnsiPrintStream out) { && out.getTerminalWidth() > 0 // docker, non-terminal logs ; } + + /** + * Uses reflection on the jansi lib in order to expose the {@code Charset} used to encode the console's print stream. + * The {@code Charset} is not otherwise exposed, and this avoids replicating the charset selection logic. + */ + @Nullable + static Charset tryExtractPrintCharset(AnsiPrintStream ansiPrintStream) { + try { + Method getOutMethod = ansiPrintStream.getClass().getDeclaredMethod("getOut"); + getOutMethod.setAccessible(true); + AnsiOutputStream ansiOutputStream = (AnsiOutputStream) getOutMethod.invoke(ansiPrintStream); + Field charsetField = ansiOutputStream.getClass().getDeclaredField("cs"); + charsetField.setAccessible(true); + return (Charset) charsetField.get(ansiOutputStream); + } catch (Exception e) { + // has the library been upgraded and it now doesn't expose the same fields with the same names? + logger.info("Failed to detect JANSI's print stream encoding", e); + return null; + } + } } diff --git a/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java b/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java index 20c1991556771..d3668ef7102a6 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java @@ -8,6 +8,7 @@ package org.elasticsearch.bootstrap; +import org.elasticsearch.core.Nullable; import org.elasticsearch.env.Environment; import java.io.IOException; @@ -16,6 +17,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.function.Supplier; @@ -34,7 +36,7 @@ public static Console loadConsole(Environment env) { return supplier.get(); } - public record Console(PrintStream printStream, Supplier width) {} + public record Console(PrintStream printStream, Supplier width, @Nullable Charset charset) {} @SuppressWarnings("unchecked") static Supplier buildConsoleLoader(ClassLoader classLoader) { 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 a09193e32af70..f6c9569d5cfae 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 @@ -26,6 +26,7 @@ import org.elasticsearch.xpack.security.enrollment.InternalEnrollmentTokenGenerator; import org.elasticsearch.xpack.security.support.SecurityIndexManager; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -211,27 +212,34 @@ private static void outputInformationToConsole( String caCertFingerprint, ConsoleLoader.Console console ) { - final String infoBullet = "\u2139"; - final String bullet = "\u2022"; - final String hyphenBullet = "\u2043"; - final String errorBullet = "\u274C"; - final String successBullet = "\u2705"; - final String horizontalLine = "\u2501"; + // Use eye-catching pictograms to output the configuration information, but only if the + // console charset utilizes some known variation of UTF, otherwise we risk that the encoder + // cannot handle the special unicode code points and will display funky question marks instead + boolean useUnicode = StandardCharsets.UTF_8.equals(console.charset()) + || StandardCharsets.UTF_16.equals(console.charset()) + || StandardCharsets.UTF_16LE.equals(console.charset()) + || StandardCharsets.UTF_16BE.equals(console.charset()); + final String infoBullet = useUnicode ? "\u2139\uFE0F" : "->"; + final String bullet = useUnicode ? "\u2022" : "*"; + final String hyphenBullet = useUnicode ? "\u2043" : "-"; + final String errorBullet = useUnicode ? "\u274C" : "X"; + final String successBullet = useUnicode ? "\u2705" : "->"; + final String horizontalBorderLine = useUnicode ? "\u2501" : "-"; final String boldOnANSI = "\u001B[1m"; final String boldOffANSI = "\u001B[22m"; final String cmdOn = "`"; final String cmdOff = "`"; - final int horizontalLineLength = console.width().get(); + final int horizontalBorderLength = console.width().get(); StringBuilder builder = new StringBuilder(); builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); - builder.append(horizontalLine.repeat(horizontalLineLength)); + builder.append(horizontalBorderLine.repeat(horizontalBorderLength)); builder.append(System.lineSeparator()); - builder.append(infoBullet + " Elasticsearch security features have been automatically configured!"); + builder.append(successBullet + " Elasticsearch security features have been automatically configured!"); builder.append(System.lineSeparator()); - builder.append(infoBullet + " Authentication is enabled and cluster connections are encrypted."); + builder.append(successBullet + " Authentication is enabled and cluster connections are encrypted."); builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); if (elasticPassword == null) { @@ -245,7 +253,7 @@ private static void outputInformationToConsole( ); } else if (false == Strings.isEmpty(elasticPassword)) { builder.append( - successBullet + infoBullet + " Password for the " + boldOnANSI + "elastic" @@ -257,15 +265,15 @@ private static void outputInformationToConsole( + "):" ); builder.append(System.lineSeparator()); - builder.append(" " + boldOnANSI + elasticPassword + boldOffANSI); + builder.append(" " + boldOnANSI + elasticPassword + boldOffANSI); } builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); if (null != caCertFingerprint) { - builder.append(successBullet + " 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(" " + boldOnANSI + caCertFingerprint + boldOffANSI); } builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); @@ -305,12 +313,12 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); builder.append(" " + hyphenBullet + " Restart Elasticsearch."); builder.append(System.lineSeparator()); - builder.append(bullet + " On the other node:"); + builder.append(bullet + " On other nodes:"); builder.append(System.lineSeparator()); builder.append( " " + hyphenBullet - + " Start Elasticsearch on other nodes with " + + " Start Elasticsearch with " + cmdOn + "bin/elasticsearch --enrollment-token " + cmdOff @@ -347,12 +355,12 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); builder.append(" " + hyphenBullet + " Restart Elasticsearch."); builder.append(System.lineSeparator()); - builder.append(bullet + " On the other node:"); + builder.append(bullet + " On other nodes:"); builder.append(System.lineSeparator()); builder.append( " " + hyphenBullet - + " Start Elasticsearch on other nodes with " + + " Start Elasticsearch with " + cmdOn + "bin/elasticsearch --enrollment-token " + cmdOff @@ -380,7 +388,7 @@ private static void outputInformationToConsole( } builder.append(System.lineSeparator()); - builder.append(horizontalLine.repeat(horizontalLineLength)); + builder.append(horizontalBorderLine.repeat(horizontalBorderLength)); builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); From 5ab6a29303f48a20649c3e83d72f789b7b62b736 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sat, 22 Jan 2022 18:44:29 +0200 Subject: [PATCH 07/13] suppress forbidden --- .../main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java index 6b59e3f6c5b64..34605694ba9eb 100644 --- a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java +++ b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java @@ -10,6 +10,7 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.bootstrap.ConsoleLoader; import org.elasticsearch.core.Nullable; +import org.elasticsearch.core.SuppressForbidden; import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiPrintStream; import org.fusesource.jansi.AnsiType; @@ -51,6 +52,7 @@ static boolean isValidConsole(AnsiPrintStream out) { * Uses reflection on the jansi lib in order to expose the {@code Charset} used to encode the console's print stream. * The {@code Charset} is not otherwise exposed, and this avoids replicating the charset selection logic. */ + @SuppressForbidden(reason = "Best effort exposing print stream's charset with reflection") @Nullable static Charset tryExtractPrintCharset(AnsiPrintStream ansiPrintStream) { try { From 7148258d6d15a6d4f5d14b9aa8743dfe77bd5843 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sat, 22 Jan 2022 18:59:17 +0200 Subject: [PATCH 08/13] optionally disable ansi escape codes --- .../java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java | 3 ++- .../main/java/org/elasticsearch/bootstrap/ConsoleLoader.java | 2 +- .../xpack/security/InitialNodeSecurityAutoConfiguration.java | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java index 34605694ba9eb..339705d241bac 100644 --- a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java +++ b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java @@ -11,6 +11,7 @@ import org.elasticsearch.bootstrap.ConsoleLoader; import org.elasticsearch.core.Nullable; import org.elasticsearch.core.SuppressForbidden; +import org.fusesource.jansi.Ansi; import org.fusesource.jansi.AnsiConsole; import org.fusesource.jansi.AnsiPrintStream; import org.fusesource.jansi.AnsiType; @@ -34,7 +35,7 @@ public class AnsiConsoleLoader implements Supplier { public ConsoleLoader.Console get() { final AnsiPrintStream out = AnsiConsole.out(); if (isValidConsole(out)) { - return new ConsoleLoader.Console(out, () -> out.getTerminalWidth(), tryExtractPrintCharset(out)); + return new ConsoleLoader.Console(out, () -> out.getTerminalWidth(), Ansi.isEnabled(), tryExtractPrintCharset(out)); } else { return null; } diff --git a/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java b/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java index d3668ef7102a6..8b0d914e2da3d 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/ConsoleLoader.java @@ -36,7 +36,7 @@ public static Console loadConsole(Environment env) { return supplier.get(); } - public record Console(PrintStream printStream, Supplier width, @Nullable Charset charset) {} + public record Console(PrintStream printStream, Supplier width, Boolean ansiEnabled, @Nullable Charset charset) {} @SuppressWarnings("unchecked") static Supplier buildConsoleLoader(ClassLoader classLoader) { 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 f6c9569d5cfae..b64a28db92229 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 @@ -225,8 +225,8 @@ private static void outputInformationToConsole( final String errorBullet = useUnicode ? "\u274C" : "X"; final String successBullet = useUnicode ? "\u2705" : "->"; final String horizontalBorderLine = useUnicode ? "\u2501" : "-"; - final String boldOnANSI = "\u001B[1m"; - final String boldOffANSI = "\u001B[22m"; + final String boldOnANSI = console.ansiEnabled() ? "\u001B[1m" : ""; + final String boldOffANSI = console.ansiEnabled() ? "\u001B[22m" : ""; final String cmdOn = "`"; final String cmdOff = "`"; final int horizontalBorderLength = console.width().get(); From 04853a3577e08c1c6812afa474cb7413ab195f10 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sun, 23 Jan 2022 12:56:23 +0200 Subject: [PATCH 09/13] Nit --- .../java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java index 339705d241bac..218c592b42a1c 100644 --- a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java +++ b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java @@ -50,8 +50,8 @@ static boolean isValidConsole(AnsiPrintStream out) { } /** - * Uses reflection on the jansi lib in order to expose the {@code Charset} used to encode the console's print stream. - * The {@code Charset} is not otherwise exposed, and this avoids replicating the charset selection logic. + * Uses reflection on the JANSI lib in order to expose the {@code Charset} used to encode the console's print stream. + * The {@code Charset} is not otherwise exposed by the library, and this avoids replicating the charset selection logic in out code. */ @SuppressForbidden(reason = "Best effort exposing print stream's charset with reflection") @Nullable From e61226a4f97bf0f2a352fdf48378be8d6eb4ceb3 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 24 Jan 2022 11:31:27 +0200 Subject: [PATCH 10/13] Some tests --- distribution/tools/ansi-console/build.gradle | 6 +++ .../io/ansi/AnsiConsoleLoader.java | 14 ++++-- .../io/ansi/AnsiConsoleLoaderTests.java | 50 ++++++++++++++++--- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/distribution/tools/ansi-console/build.gradle b/distribution/tools/ansi-console/build.gradle index f0bd8a79d26f2..f386add3f238f 100644 --- a/distribution/tools/ansi-console/build.gradle +++ b/distribution/tools/ansi-console/build.gradle @@ -18,3 +18,9 @@ dependencies { api "org.fusesource.jansi:jansi:2.3.4" } +// the code and tests in this project cover console initialization +// which happens before the SecurityManager is installed +tasks.named("test").configure { + systemProperty 'tests.security.manager', 'false' +} + diff --git a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java index 218c592b42a1c..81914e344e609 100644 --- a/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java +++ b/distribution/tools/ansi-console/src/main/java/org/elasticsearch/io/ansi/AnsiConsoleLoader.java @@ -34,6 +34,11 @@ public class AnsiConsoleLoader implements Supplier { public ConsoleLoader.Console get() { final AnsiPrintStream out = AnsiConsole.out(); + return newConsole(out); + } + + // package-private for tests + static @Nullable ConsoleLoader.Console newConsole(AnsiPrintStream out) { if (isValidConsole(out)) { return new ConsoleLoader.Console(out, () -> out.getTerminalWidth(), Ansi.isEnabled(), tryExtractPrintCharset(out)); } else { @@ -41,7 +46,7 @@ public ConsoleLoader.Console get() { } } - static boolean isValidConsole(AnsiPrintStream out) { + private static boolean isValidConsole(AnsiPrintStream out) { return out != null // cannot load stdout && out.getType() != AnsiType.Redirected // output is a pipe (etc) && out.getType() != AnsiType.Unsupported // could not determine terminal type @@ -55,7 +60,7 @@ static boolean isValidConsole(AnsiPrintStream out) { */ @SuppressForbidden(reason = "Best effort exposing print stream's charset with reflection") @Nullable - static Charset tryExtractPrintCharset(AnsiPrintStream ansiPrintStream) { + private static Charset tryExtractPrintCharset(AnsiPrintStream ansiPrintStream) { try { Method getOutMethod = ansiPrintStream.getClass().getDeclaredMethod("getOut"); getOutMethod.setAccessible(true); @@ -63,9 +68,10 @@ static Charset tryExtractPrintCharset(AnsiPrintStream ansiPrintStream) { Field charsetField = ansiOutputStream.getClass().getDeclaredField("cs"); charsetField.setAccessible(true); return (Charset) charsetField.get(ansiOutputStream); - } catch (Exception e) { + } catch (Throwable t) { // has the library been upgraded and it now doesn't expose the same fields with the same names? - logger.info("Failed to detect JANSI's print stream encoding", e); + // is the Security Manager installed preventing the access + logger.info("Failed to detect JANSI's print stream encoding", t); return null; } } diff --git a/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java b/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java index 4e2da7dd54480..72a0ada13fd76 100644 --- a/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java +++ b/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java @@ -8,6 +8,7 @@ package org.elasticsearch.io.ansi; +import org.elasticsearch.bootstrap.ConsoleLoader; import org.elasticsearch.test.ESTestCase; import org.fusesource.jansi.AnsiColors; import org.fusesource.jansi.AnsiMode; @@ -17,9 +18,12 @@ import org.fusesource.jansi.io.AnsiProcessor; import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; public class AnsiConsoleLoaderTests extends ESTestCase { @@ -30,35 +34,68 @@ public class AnsiConsoleLoaderTests extends ESTestCase { private static final AnsiOutputStream.IoRunnable NO_OP_RUNNABLE = () -> {}; + private static final Charset[] charsets = new Charset[] { + StandardCharsets.US_ASCII, + StandardCharsets.ISO_8859_1, + StandardCharsets.UTF_8, + StandardCharsets.UTF_16, + StandardCharsets.UTF_16LE, + StandardCharsets.UTF_16BE }; + public void testNullOutputIsNotConsole() { - assertThat(AnsiConsoleLoader.isValidConsole(null), is(false)); + assertThat(AnsiConsoleLoader.newConsole(null), nullValue()); } public void testRedirectedOutputIsNotConsole() { try (AnsiPrintStream ansiPrintStream = buildStream(AnsiType.Redirected, randomIntBetween(80, 120))) { - assertThat(AnsiConsoleLoader.isValidConsole(ansiPrintStream), is(false)); + assertThat(AnsiConsoleLoader.newConsole(ansiPrintStream), nullValue()); } } public void testUnsupportedTerminalIsNotConsole() { try (AnsiPrintStream ansiPrintStream = buildStream(AnsiType.Unsupported, randomIntBetween(80, 120))) { - assertThat(AnsiConsoleLoader.isValidConsole(ansiPrintStream), is(false)); + assertThat(AnsiConsoleLoader.newConsole(ansiPrintStream), nullValue()); } } public void testZeroWidthTerminalIsNotConsole() { try (AnsiPrintStream ansiPrintStream = buildStream(randomFrom(SUPPORTED_TERMINAL_TYPES), 0)) { - assertThat(AnsiConsoleLoader.isValidConsole(ansiPrintStream), is(false)); + assertThat(AnsiConsoleLoader.newConsole(ansiPrintStream), nullValue()); } } public void testStandardTerminalIsConsole() { + int width = randomIntBetween(40, 260); + try (AnsiPrintStream ansiPrintStream = buildStream(randomFrom(SUPPORTED_TERMINAL_TYPES), width)) { + ConsoleLoader.Console console = AnsiConsoleLoader.newConsole(ansiPrintStream); + assertThat(console, notNullValue()); + assertThat(console.width().get(), is(width)); + } + } + + public void testConsoleCharset() { + Charset charset = randomFrom(charsets); + try (AnsiPrintStream ansiPrintStream = buildStream(randomFrom(SUPPORTED_TERMINAL_TYPES), randomIntBetween(40, 260), charset)) { + ConsoleLoader.Console console = AnsiConsoleLoader.newConsole(ansiPrintStream); + assertThat(console, notNullValue()); + assertThat(console.charset(), is(charset)); + } + } + + public void testDisableANSISystemProperties() { + System.setProperty("org.fusesource.jansi.Ansi.disable", "true"); try (AnsiPrintStream ansiPrintStream = buildStream(randomFrom(SUPPORTED_TERMINAL_TYPES), randomIntBetween(40, 260))) { - assertThat(AnsiConsoleLoader.isValidConsole(ansiPrintStream), is(true)); + ConsoleLoader.Console console = AnsiConsoleLoader.newConsole(ansiPrintStream); + assertThat(console, notNullValue()); + assertThat(console.ansiEnabled(), is(false)); } } private AnsiPrintStream buildStream(AnsiType type, int width) { + return buildStream(type, width, randomFrom(charsets)); + } + + private AnsiPrintStream buildStream(AnsiType type, int width, Charset cs) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final AnsiOutputStream ansiOutputStream = new AnsiOutputStream( baos, @@ -67,12 +104,11 @@ private AnsiPrintStream buildStream(AnsiType type, int width) { new AnsiProcessor(baos), type, randomFrom(AnsiColors.values()), - randomFrom(StandardCharsets.UTF_8, StandardCharsets.US_ASCII, StandardCharsets.UTF_16, StandardCharsets.ISO_8859_1), + cs, NO_OP_RUNNABLE, NO_OP_RUNNABLE, randomBoolean() ); return new AnsiPrintStream(ansiOutputStream, randomBoolean()); } - } From 4686054ddc563526bdad3b75567675ca5c69eece Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 24 Jan 2022 16:16:56 +0200 Subject: [PATCH 11/13] Nits --- .../org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java | 2 ++ .../xpack/security/InitialNodeSecurityAutoConfiguration.java | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java b/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java index 72a0ada13fd76..0a0817ee4ef6f 100644 --- a/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java +++ b/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java @@ -9,6 +9,7 @@ package org.elasticsearch.io.ansi; import org.elasticsearch.bootstrap.ConsoleLoader; +import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.test.ESTestCase; import org.fusesource.jansi.AnsiColors; import org.fusesource.jansi.AnsiMode; @@ -82,6 +83,7 @@ public void testConsoleCharset() { } } + @SuppressForbidden(reason = "set property for JANSI disabled tests") public void testDisableANSISystemProperties() { System.setProperty("org.fusesource.jansi.Ansi.disable", "true"); try (AnsiPrintStream ansiPrintStream = buildStream(randomFrom(SUPPORTED_TERMINAL_TYPES), randomIntBetween(40, 260))) { 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 b64a28db92229..e41d68c983376 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 @@ -381,9 +381,9 @@ private static void outputInformationToConsole( builder.append(" " + boldOnANSI + nodeEnrollmentToken + boldOffANSI); builder.append(System.lineSeparator()); builder.append(System.lineSeparator()); - builder.append("If you're running in Docker, copy the enrollment token and run:"); + builder.append(" If you're running in Docker, copy the enrollment token and run:"); builder.append(System.lineSeparator()); - builder.append(cmdOn + "docker run --name -p :9200 --net elastic -e \"ENROLLMENT_TOKEN=\""); + builder.append(cmdOn + "docker run -e \"ENROLLMENT_TOKEN=\""); builder.append(" docker.elastic.co/elasticsearch/elasticsearch:" + Version.CURRENT + cmdOff); } From aa7310650ba086291ae9f736e6007060b8ac7062 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 24 Jan 2022 17:16:05 +0200 Subject: [PATCH 12/13] Nits --- .../org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java | 7 +++---- .../security/InitialNodeSecurityAutoConfiguration.java | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java b/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java index 0a0817ee4ef6f..4b40f8fa568de 100644 --- a/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java +++ b/distribution/tools/ansi-console/src/test/java/org/elasticsearch/io/ansi/AnsiConsoleLoaderTests.java @@ -9,8 +9,8 @@ package org.elasticsearch.io.ansi; import org.elasticsearch.bootstrap.ConsoleLoader; -import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.test.ESTestCase; +import org.fusesource.jansi.Ansi; import org.fusesource.jansi.AnsiColors; import org.fusesource.jansi.AnsiMode; import org.fusesource.jansi.AnsiPrintStream; @@ -83,9 +83,8 @@ public void testConsoleCharset() { } } - @SuppressForbidden(reason = "set property for JANSI disabled tests") - public void testDisableANSISystemProperties() { - System.setProperty("org.fusesource.jansi.Ansi.disable", "true"); + public void testDisableANSI() { + Ansi.setEnabled(false); try (AnsiPrintStream ansiPrintStream = buildStream(randomFrom(SUPPORTED_TERMINAL_TYPES), randomIntBetween(40, 260))) { ConsoleLoader.Console console = AnsiConsoleLoader.newConsole(ansiPrintStream); assertThat(console, notNullValue()); 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 e41d68c983376..5ab41e7400df4 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 @@ -383,8 +383,8 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); builder.append(" If you're running in Docker, copy the enrollment token and run:"); builder.append(System.lineSeparator()); - builder.append(cmdOn + "docker run -e \"ENROLLMENT_TOKEN=\""); - builder.append(" docker.elastic.co/elasticsearch/elasticsearch:" + Version.CURRENT + cmdOff); + builder.append(" " + cmdOn + "docker run -e \"ENROLLMENT_TOKEN=\" docker.elastic.co/elasticsearch/elasticsearch:" + + Version.CURRENT + cmdOff); } builder.append(System.lineSeparator()); From d0aeb74430932b07058ed00902f0e1c9cdbcef50 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Mon, 24 Jan 2022 18:09:54 +0200 Subject: [PATCH 13/13] Spotless --- .../security/InitialNodeSecurityAutoConfiguration.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 5ab41e7400df4..cdc93a9c48a6d 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 @@ -383,8 +383,13 @@ private static void outputInformationToConsole( builder.append(System.lineSeparator()); builder.append(" If you're running in Docker, copy the enrollment token and run:"); builder.append(System.lineSeparator()); - builder.append(" " + cmdOn + "docker run -e \"ENROLLMENT_TOKEN=\" docker.elastic.co/elasticsearch/elasticsearch:" + - Version.CURRENT + cmdOff); + builder.append( + " " + + cmdOn + + "docker run -e \"ENROLLMENT_TOKEN=\" docker.elastic.co/elasticsearch/elasticsearch:" + + Version.CURRENT + + cmdOff + ); } builder.append(System.lineSeparator());