Skip to content

Commit cf0be7a

Browse files
Retain reference to stdout for exceptional cases (#77460)
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>
1 parent 1fb1ad2 commit cf0be7a

4 files changed

Lines changed: 70 additions & 41 deletions

File tree

qa/os/src/test/java/org/elasticsearch/packaging/test/ArchiveGenerateInitialPasswordTests.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import org.apache.http.client.fluent.Request;
1212
import org.elasticsearch.packaging.util.Distribution;
13+
import org.elasticsearch.packaging.util.FileUtils;
1314
import org.elasticsearch.packaging.util.ServerUtils;
1415
import org.elasticsearch.packaging.util.Shell;
1516
import org.junit.BeforeClass;
@@ -21,9 +22,11 @@
2122

2223
import static org.elasticsearch.packaging.util.Archives.installArchive;
2324
import static org.elasticsearch.packaging.util.Archives.verifyArchiveInstallation;
25+
import static org.elasticsearch.packaging.util.FileExistenceMatchers.fileExists;
2426
import static org.hamcrest.CoreMatchers.containsString;
2527
import static org.hamcrest.Matchers.equalTo;
2628
import static org.hamcrest.Matchers.is;
29+
import static org.hamcrest.Matchers.not;
2730
import static org.junit.Assume.assumeTrue;
2831

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

68-
public void test40VerifyAutogeneratedCredentials() throws Exception {
71+
public void test40NoAutogenerationWhenDaemonized() throws Exception {
72+
/* Windows issue awaits fix: https://github.com/elastic/elasticsearch/issues/49340 */
73+
assumeTrue("expect command isn't on Windows", distribution.platform != Distribution.Platform.WINDOWS);
74+
stopElasticsearch();
75+
ServerUtils.enableSecurityAutoConfiguration(installation);
76+
awaitElasticsearchStartup(runElasticsearchStartCommand(null, true, true));
77+
assertThat(installation.logs.resolve("elasticsearch.log"), fileExists());
78+
String logfile = FileUtils.slurp(installation.logs.resolve("elasticsearch.log"));
79+
assertThat(logfile, not(containsString("Password for the elastic user is")));
80+
}
81+
82+
public void test50VerifyAutogeneratedCredentials() throws Exception {
6983
/* Windows issue awaits fix: https://github.com/elastic/elasticsearch/issues/49340 */
7084
assumeTrue("expect command isn't on Windows", distribution.platform != Distribution.Platform.WINDOWS);
7185
stopElasticsearch();
@@ -81,7 +95,7 @@ public void test40VerifyAutogeneratedCredentials() throws Exception {
8195
}
8296
}
8397

84-
public void test50PasswordAutogenerationOnlyOnce() throws Exception {
98+
public void test60PasswordAutogenerationOnlyOnce() throws Exception {
8599
/* Windows issue awaits fix: https://github.com/elastic/elasticsearch/issues/49340 */
86100
assumeTrue("expect command isn't on Windows", distribution.platform != Distribution.Platform.WINDOWS);
87101
stopElasticsearch();

server/src/main/java/org/elasticsearch/bootstrap/Bootstrap.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ static void init(
316316
final Environment initialEnv) throws BootstrapException, NodeValidationException, UserException {
317317
// force the class initializer for BootstrapInfo to run before
318318
// the security manager is installed
319-
BootstrapInfo.init();
319+
BootstrapInfo.init(getSysOutReference());
320320

321321
INSTANCE = new Bootstrap();
322322

@@ -424,6 +424,11 @@ static void init(
424424
}
425425
}
426426

427+
@SuppressForbidden(reason = "Retain reference for System.out")
428+
private static PrintStream getSysOutReference() {
429+
return System.out;
430+
}
431+
427432
@SuppressForbidden(reason = "System#out")
428433
private static Runnable getSysOutCloser() {
429434
return System.out::close;

server/src/main/java/org/elasticsearch/bootstrap/BootstrapInfo.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import org.elasticsearch.core.SuppressForbidden;
1212

13+
import java.io.PrintStream;
1314
import java.util.Dictionary;
1415
import java.util.Enumeration;
1516

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

23+
private static PrintStream originalStandardOut;
24+
2225
/** no instantiation */
2326
private BootstrapInfo() {}
2427

@@ -46,6 +49,11 @@ public static boolean isSystemCallFilterInstalled() {
4649
return Natives.isSystemCallFilterInstalled();
4750
}
4851

52+
/**
53+
* Returns a reference to the original System.out
54+
*/
55+
public static PrintStream getOriginalStandardOut() { return originalStandardOut; }
56+
4957
/**
5058
* codebase location for untrusted scripts (provide some additional safety)
5159
* <p>
@@ -110,7 +118,8 @@ public static Dictionary<Object,Object> getSystemProperties() {
110118
return SYSTEM_PROPERTIES;
111119
}
112120

113-
public static void init() {
121+
public static void init(PrintStream originalStandardOut) {
122+
BootstrapInfo.originalStandardOut = originalStandardOut;
114123
}
115124

116125
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/GenerateInitialBuiltinUsersPasswordListener.java

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,25 @@
1212
import org.elasticsearch.action.ActionListener;
1313
import org.elasticsearch.action.DocWriteRequest;
1414
import org.elasticsearch.action.support.WriteRequest;
15+
import org.elasticsearch.bootstrap.BootstrapInfo;
1516
import org.elasticsearch.common.settings.SecureString;
17+
import org.elasticsearch.core.Nullable;
1618
import org.elasticsearch.index.engine.VersionConflictEngineException;
1719
import org.elasticsearch.xpack.core.security.user.ElasticUser;
1820
import org.elasticsearch.xpack.core.security.user.KibanaSystemUser;
1921
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
2022
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
2123

24+
import java.io.PrintStream;
2225
import java.util.function.BiConsumer;
2326

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

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

2831
private static final Logger LOGGER = LogManager.getLogger(GenerateInitialBuiltinUsersPasswordListener.class);
29-
private NativeUsersStore nativeUsersStore;
30-
private SecurityIndexManager securityIndexManager;
32+
private final NativeUsersStore nativeUsersStore;
33+
private final SecurityIndexManager securityIndexManager;
3134

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

3740
@Override
3841
public void accept(SecurityIndexManager.State previousState, SecurityIndexManager.State currentState) {
42+
final PrintStream out = BootstrapInfo.getOriginalStandardOut();
43+
// Check if it has been closed, try to write something so that we trigger PrintStream#ensureOpen
44+
out.println();
45+
if (out.checkError()) {
46+
outputOnError(null);
47+
return;
48+
}
3949
if (previousState.equals(SecurityIndexManager.State.UNRECOVERED_STATE)
4050
&& currentState.equals(SecurityIndexManager.State.UNRECOVERED_STATE) == false
4151
&& securityIndexManager.indexExists() == false) {
@@ -57,7 +67,7 @@ public void accept(SecurityIndexManager.State previousState, SecurityIndexManage
5767
WriteRequest.RefreshPolicy.IMMEDIATE,
5868
ActionListener.wrap(
5969
r -> {
60-
outputOnSuccess(elasticPassword, kibanaSystemPassword);
70+
outputOnSuccess(elasticPassword, kibanaSystemPassword, out);
6171
}, this::outputOnError
6272
)
6373
);
@@ -66,55 +76,46 @@ public void accept(SecurityIndexManager.State previousState, SecurityIndexManage
6676
}
6777
}
6878

69-
private void outputOnSuccess(SecureString elasticPassword, SecureString kibanaSystemPassword) {
70-
LOGGER.info("");
71-
LOGGER.info("-----------------------------------------------------------------");
72-
LOGGER.info("");
73-
LOGGER.info("");
74-
LOGGER.info("");
75-
LOGGER.info("Password for the elastic user is: " + elasticPassword);
76-
LOGGER.info("");
77-
LOGGER.info("");
78-
LOGGER.info("");
79-
LOGGER.info("Password for the kibana_system user is: " + kibanaSystemPassword);
80-
LOGGER.info("");
81-
LOGGER.info("");
82-
LOGGER.info("Please note these down as they will not be shown again.");
83-
LOGGER.info("");
84-
LOGGER.info("You can use 'bin/elasticsearch-reset-elastic-password' at any time");
85-
LOGGER.info("in order to reset the password for the elastic user.");
86-
LOGGER.info("");
87-
LOGGER.info("");
88-
LOGGER.info("You can use 'bin/elasticsearch-reset-kibana-system-password' at any time");
89-
LOGGER.info("in order to reset the password for the kibana_system user.");
90-
LOGGER.info("");
91-
LOGGER.info("");
92-
LOGGER.info("");
93-
LOGGER.info("-----------------------------------------------------------------");
94-
LOGGER.info("");
79+
private void outputOnSuccess(SecureString elasticPassword, SecureString kibanaSystemPassword, PrintStream out) {
80+
out.println();
81+
out.println("-----------------------------------------------------------------");
82+
out.println();
83+
out.println("Password for the elastic user is: " + elasticPassword);
84+
out.println();
85+
out.println("Password for the kibana_system user is: " + kibanaSystemPassword);
86+
out.println();
87+
out.println("Please note these down as they will not be shown again.");
88+
out.println();
89+
out.println();
90+
out.println("You can use 'bin/elasticsearch-reset-elastic-password' at any time");
91+
out.println("in order to reset the password for the elastic user.");
92+
out.println();
93+
out.println("You can use 'bin/elasticsearch-reset-kibana-system-password' at any time");
94+
out.println("in order to reset the password for the kibana_system user.");
95+
out.println();
96+
out.println("-----------------------------------------------------------------");
97+
out.println();
9598
}
9699

97-
private void outputOnError(Exception e) {
100+
private void outputOnError(@Nullable Exception e) {
98101
if (e instanceof VersionConflictEngineException == false) {
99102
LOGGER.info("");
100103
LOGGER.info("-----------------------------------------------------------------");
101104
LOGGER.info("");
102-
LOGGER.info("");
103-
LOGGER.info("");
104-
LOGGER.info("Failed to set the password for the elastic and kibana-system users ");
105-
LOGGER.info("automatically");
105+
LOGGER.info("Unable set the password for the elastic and kibana_system users ");
106+
LOGGER.info("automatically.");
106107
LOGGER.info("");
107108
LOGGER.info("You can use 'bin/elasticsearch-reset-elastic-password'");
108109
LOGGER.info("in order to set the password for the elastic user.");
109110
LOGGER.info("");
110-
LOGGER.info("");
111111
LOGGER.info("You can use 'bin/elasticsearch-reset-kibana-system-password'");
112112
LOGGER.info("in order to set the password for the kibana_system user.");
113113
LOGGER.info("");
114-
LOGGER.info("");
115-
LOGGER.info("");
116114
LOGGER.info("-----------------------------------------------------------------");
117115
LOGGER.info("");
118116
}
117+
if (null != e) {
118+
LOGGER.warn("Error initializing passwords for elastic and kibana_system users", e);
119+
}
119120
}
120121
}

0 commit comments

Comments
 (0)