Fallback to `keystore.seed` as a bootstrap password if actual password is not present (elastic/x-pack-elasticsearch#2295)

Today we require the `bootstrap.password` to be present in the keystore in order to
bootstrap xpack. With the addition of `keystore.seed` we have a randomly generated password
per node to do the bootstrapping. This will improve the initial user experience significantly
since the user doesn't need to create a keystore and add a password, they keystore is created
automatically unless already present and is always created with this random seed.

Relates to elastic/elasticsearch#26253

Original commit: elastic/x-pack-elasticsearch@5a984b4fd8
This commit is contained in:
Simon Willnauer 2017-08-17 16:42:32 +02:00 committed by GitHub
parent 2ae5634dc9
commit 724325f161
4 changed files with 31 additions and 13 deletions

View File

@ -9,6 +9,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.settings.KeyStoreWrapper;
import org.elasticsearch.common.settings.SecureSetting;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Setting;
@ -53,7 +54,8 @@ public class ReservedRealm extends CachingUsernamePasswordRealm {
public static final Setting<Boolean> ACCEPT_DEFAULT_PASSWORD_SETTING = Setting.boolSetting(
Security.setting("authc.accept_default_password"), true, Setting.Property.NodeScope, Setting.Property.Filtered,
Setting.Property.Deprecated);
public static final Setting<SecureString> BOOTSTRAP_ELASTIC_PASSWORD = SecureSetting.secureString("bootstrap.password", null);
public static final Setting<SecureString> BOOTSTRAP_ELASTIC_PASSWORD = SecureSetting.secureString("bootstrap.password",
KeyStoreWrapper.SEED_SETTING);
private final NativeUsersStore nativeUsersStore;
private final AnonymousUser anonymousUser;

View File

@ -17,6 +17,7 @@ import org.elasticsearch.common.Booleans;
import org.elasticsearch.common.CheckedFunction;
import org.elasticsearch.common.settings.KeyStoreWrapper;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.env.Environment;
@ -191,15 +192,16 @@ public class SetupPasswordTool extends MultiCommand {
void setupOptions(OptionSet options, Environment env) throws Exception {
client = clientFunction.apply(env);
KeyStoreWrapper keyStore = keyStoreFunction.apply(env);
String providedUrl = urlOption.value(options);
url = providedUrl == null ? client.getDefaultURL() : providedUrl;
setShouldPrompt(options);
try (KeyStoreWrapper keyStore = keyStoreFunction.apply(env)) {
String providedUrl = urlOption.value(options);
url = providedUrl == null ? client.getDefaultURL() : providedUrl;
setShouldPrompt(options);
// TODO: We currently do not support keystore passwords
keyStore.decrypt(new char[0]);
elasticUserPassword = keyStore.getString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey());
// TODO: We currently do not support keystore passwords
keyStore.decrypt(new char[0]);
Settings build = Settings.builder().setSecureSettings(keyStore).build();
elasticUserPassword = ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.get(build);
}
}
private void setParser() {
@ -232,7 +234,7 @@ public class SetupPasswordTool extends MultiCommand {
try {
String route = url + "/_xpack/security/user/" + user + "/_password";
String response = client.postURL("PUT", route, elasticUser, elasticUserPassword, buildPayload(password));
client.postURL("PUT", route, elasticUser, elasticUserPassword, buildPayload(password));
callback.accept(user, password);
if (isSuperUser) {
elasticUserPassword = password;

View File

@ -28,6 +28,9 @@ import org.mockito.Mockito;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import static org.mockito.Matchers.anyString;
@ -41,7 +44,7 @@ import static org.mockito.Mockito.when;
public class SetupPasswordToolTests extends CommandTestCase {
private final String pathHomeParameter = "-Epath.home=" + createTempDir();
private SecureString bootstrapPassword = new SecureString("bootstrap-password".toCharArray());
private SecureString bootstrapPassword;
private final String ep = "elastic-password";
private final String kp = "kibana-password";
private final String lp = "logstash-password";
@ -50,9 +53,21 @@ public class SetupPasswordToolTests extends CommandTestCase {
@Before
public void setSecretsAndKeyStore() throws GeneralSecurityException {
// sometimes we fall back to the keystore seed as this is the default when a new node starts
boolean useFallback = randomBoolean();
bootstrapPassword = useFallback ? new SecureString("0xCAFEBABE".toCharArray()) :
new SecureString("bootstrap-password".toCharArray());
this.keyStore = mock(KeyStoreWrapper.class);
this.httpClient = mock(CommandLineHttpClient.class);
when(keyStore.getString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey())).thenReturn(bootstrapPassword);
when(keyStore.isLoaded()).thenReturn(true);
if (useFallback) {
when(keyStore.getSettingNames()).thenReturn(new HashSet<>(Arrays.asList(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey(),
KeyStoreWrapper.SEED_SETTING.getKey())));
when(keyStore.getString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey())).thenReturn(bootstrapPassword);
} else {
when(keyStore.getSettingNames()).thenReturn(Collections.singleton(KeyStoreWrapper.SEED_SETTING.getKey()));
when(keyStore.getString(KeyStoreWrapper.SEED_SETTING.getKey())).thenReturn(bootstrapPassword);
}
when(httpClient.getDefaultURL()).thenReturn("http://localhost:9200");
terminal.addSecretInput(ep);

View File

@ -12,7 +12,6 @@ integTestRunner {
integTestCluster {
plugin ':x-pack-elasticsearch:plugin'
keystoreSetting 'bootstrap.password', 'x-pack-test-password'
setupCommand 'setupTestAdmin',
'bin/x-pack/users', 'useradd', "test_admin", '-p', 'x-pack-test-password', '-r', "superuser"
waitCondition = { node, ant ->