Skip to content

Commit

Permalink
Retain reference to stdout for exceptional cases (#77460)
Browse files Browse the repository at this point in the history
In exceptional cases, there is a need for the ES process to print to
the user's "console" without the output appearing in log files.
An example is sensitive information such as the initial password for
an administrative user.

In these cases we would like to print to System.out instead of using
log4j.

However, we intentionally redirect stdout to go a log4j logger,
because that is the preferred place to capture the sorts of messages
that are typically printed to System.out

This change introduces a stashed reference to the original stdout
PrintWriter before we redirect to log4g in BootstrapInfo, that
can be used in said cases.
It also updates the relevant code that was printing the sensitive
information to the log, to use this newly introduced reference.

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
  • Loading branch information
jkakavas and elasticmachine committed Sep 9, 2021
1 parent 1fb1ad2 commit cf0be7a
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import org.apache.http.client.fluent.Request;
import org.elasticsearch.packaging.util.Distribution;
import org.elasticsearch.packaging.util.FileUtils;
import org.elasticsearch.packaging.util.ServerUtils;
import org.elasticsearch.packaging.util.Shell;
import org.junit.BeforeClass;
Expand All @@ -21,9 +22,11 @@

import static org.elasticsearch.packaging.util.Archives.installArchive;
import static org.elasticsearch.packaging.util.Archives.verifyArchiveInstallation;
import static org.elasticsearch.packaging.util.FileExistenceMatchers.fileExists;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assume.assumeTrue;

public class ArchiveGenerateInitialPasswordTests extends PackagingTestCase {
Expand Down Expand Up @@ -65,7 +68,18 @@ public void test30NoAutoGenerationWhenAutoConfigurationDisabled() throws Excepti
assertThat(usersAndPasswords.isEmpty(), is(true));
}

public void test40VerifyAutogeneratedCredentials() throws Exception {
public void test40NoAutogenerationWhenDaemonized() throws Exception {
/* Windows issue awaits fix: https://github.com/elastic/elasticsearch/issues/49340 */
assumeTrue("expect command isn't on Windows", distribution.platform != Distribution.Platform.WINDOWS);
stopElasticsearch();
ServerUtils.enableSecurityAutoConfiguration(installation);
awaitElasticsearchStartup(runElasticsearchStartCommand(null, true, true));
assertThat(installation.logs.resolve("elasticsearch.log"), fileExists());
String logfile = FileUtils.slurp(installation.logs.resolve("elasticsearch.log"));
assertThat(logfile, not(containsString("Password for the elastic user is")));
}

public void test50VerifyAutogeneratedCredentials() throws Exception {
/* Windows issue awaits fix: https://github.com/elastic/elasticsearch/issues/49340 */
assumeTrue("expect command isn't on Windows", distribution.platform != Distribution.Platform.WINDOWS);
stopElasticsearch();
Expand All @@ -81,7 +95,7 @@ public void test40VerifyAutogeneratedCredentials() throws Exception {
}
}

public void test50PasswordAutogenerationOnlyOnce() throws Exception {
public void test60PasswordAutogenerationOnlyOnce() throws Exception {
/* Windows issue awaits fix: https://github.com/elastic/elasticsearch/issues/49340 */
assumeTrue("expect command isn't on Windows", distribution.platform != Distribution.Platform.WINDOWS);
stopElasticsearch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ static void init(
final Environment initialEnv) throws BootstrapException, NodeValidationException, UserException {
// force the class initializer for BootstrapInfo to run before
// the security manager is installed
BootstrapInfo.init();
BootstrapInfo.init(getSysOutReference());

INSTANCE = new Bootstrap();

Expand Down Expand Up @@ -424,6 +424,11 @@ static void init(
}
}

@SuppressForbidden(reason = "Retain reference for System.out")
private static PrintStream getSysOutReference() {
return System.out;
}

@SuppressForbidden(reason = "System#out")
private static Runnable getSysOutCloser() {
return System.out::close;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import org.elasticsearch.core.SuppressForbidden;

import java.io.PrintStream;
import java.util.Dictionary;
import java.util.Enumeration;

Expand All @@ -19,6 +20,8 @@
@SuppressForbidden(reason = "exposes read-only view of system properties")
public final class BootstrapInfo {

private static PrintStream originalStandardOut;

/** no instantiation */
private BootstrapInfo() {}

Expand Down Expand Up @@ -46,6 +49,11 @@ public static boolean isSystemCallFilterInstalled() {
return Natives.isSystemCallFilterInstalled();
}

/**
* Returns a reference to the original System.out
*/
public static PrintStream getOriginalStandardOut() { return originalStandardOut; }

/**
* codebase location for untrusted scripts (provide some additional safety)
* <p>
Expand Down Expand Up @@ -110,7 +118,8 @@ public static Dictionary<Object,Object> getSystemProperties() {
return SYSTEM_PROPERTIES;
}

public static void init() {
public static void init(PrintStream originalStandardOut) {
BootstrapInfo.originalStandardOut = originalStandardOut;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,25 @@
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.bootstrap.BootstrapInfo;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.engine.VersionConflictEngineException;
import org.elasticsearch.xpack.core.security.user.ElasticUser;
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
import org.elasticsearch.xpack.security.support.SecurityIndexManager;

import java.io.PrintStream;
import java.util.function.BiConsumer;

import static org.elasticsearch.xpack.security.tool.CommandUtils.generatePassword;

public class GenerateInitialBuiltinUsersPasswordListener implements BiConsumer<SecurityIndexManager.State, SecurityIndexManager.State> {

private static final Logger LOGGER = LogManager.getLogger(GenerateInitialBuiltinUsersPasswordListener.class);
private NativeUsersStore nativeUsersStore;
private SecurityIndexManager securityIndexManager;
private final NativeUsersStore nativeUsersStore;
private final SecurityIndexManager securityIndexManager;

public GenerateInitialBuiltinUsersPasswordListener(NativeUsersStore nativeUsersStore, SecurityIndexManager securityIndexManager) {
this.nativeUsersStore = nativeUsersStore;
Expand All @@ -36,6 +39,13 @@ public GenerateInitialBuiltinUsersPasswordListener(NativeUsersStore nativeUsersS

@Override
public void accept(SecurityIndexManager.State previousState, SecurityIndexManager.State currentState) {
final PrintStream out = BootstrapInfo.getOriginalStandardOut();
// Check if it has been closed, try to write something so that we trigger PrintStream#ensureOpen
out.println();
if (out.checkError()) {
outputOnError(null);
return;
}
if (previousState.equals(SecurityIndexManager.State.UNRECOVERED_STATE)
&& currentState.equals(SecurityIndexManager.State.UNRECOVERED_STATE) == false
&& securityIndexManager.indexExists() == false) {
Expand All @@ -57,7 +67,7 @@ public void accept(SecurityIndexManager.State previousState, SecurityIndexManage
WriteRequest.RefreshPolicy.IMMEDIATE,
ActionListener.wrap(
r -> {
outputOnSuccess(elasticPassword, kibanaSystemPassword);
outputOnSuccess(elasticPassword, kibanaSystemPassword, out);
}, this::outputOnError
)
);
Expand All @@ -66,55 +76,46 @@ public void accept(SecurityIndexManager.State previousState, SecurityIndexManage
}
}

private void outputOnSuccess(SecureString elasticPassword, SecureString kibanaSystemPassword) {
LOGGER.info("");
LOGGER.info("-----------------------------------------------------------------");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("Password for the elastic user is: " + elasticPassword);
LOGGER.info("");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("Password for the kibana_system user is: " + kibanaSystemPassword);
LOGGER.info("");
LOGGER.info("");
LOGGER.info("Please note these down as they will not be shown again.");
LOGGER.info("");
LOGGER.info("You can use 'bin/elasticsearch-reset-elastic-password' at any time");
LOGGER.info("in order to reset the password for the elastic user.");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("You can use 'bin/elasticsearch-reset-kibana-system-password' at any time");
LOGGER.info("in order to reset the password for the kibana_system user.");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("-----------------------------------------------------------------");
LOGGER.info("");
private void outputOnSuccess(SecureString elasticPassword, SecureString kibanaSystemPassword, PrintStream out) {
out.println();
out.println("-----------------------------------------------------------------");
out.println();
out.println("Password for the elastic user is: " + elasticPassword);
out.println();
out.println("Password for the kibana_system user is: " + kibanaSystemPassword);
out.println();
out.println("Please note these down as they will not be shown again.");
out.println();
out.println();
out.println("You can use 'bin/elasticsearch-reset-elastic-password' at any time");
out.println("in order to reset the password for the elastic user.");
out.println();
out.println("You can use 'bin/elasticsearch-reset-kibana-system-password' at any time");
out.println("in order to reset the password for the kibana_system user.");
out.println();
out.println("-----------------------------------------------------------------");
out.println();
}

private void outputOnError(Exception e) {
private void outputOnError(@Nullable Exception e) {
if (e instanceof VersionConflictEngineException == false) {
LOGGER.info("");
LOGGER.info("-----------------------------------------------------------------");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("Failed to set the password for the elastic and kibana-system users ");
LOGGER.info("automatically");
LOGGER.info("Unable set the password for the elastic and kibana_system users ");
LOGGER.info("automatically.");
LOGGER.info("");
LOGGER.info("You can use 'bin/elasticsearch-reset-elastic-password'");
LOGGER.info("in order to set the password for the elastic user.");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("You can use 'bin/elasticsearch-reset-kibana-system-password'");
LOGGER.info("in order to set the password for the kibana_system user.");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("");
LOGGER.info("-----------------------------------------------------------------");
LOGGER.info("");
}
if (null != e) {
LOGGER.warn("Error initializing passwords for elastic and kibana_system users", e);
}
}
}

0 comments on commit cf0be7a

Please sign in to comment.