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:
parent
2ae5634dc9
commit
724325f161
|
@ -9,6 +9,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||||
import org.apache.logging.log4j.util.Supplier;
|
import org.apache.logging.log4j.util.Supplier;
|
||||||
import org.elasticsearch.Version;
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
|
import org.elasticsearch.common.settings.KeyStoreWrapper;
|
||||||
import org.elasticsearch.common.settings.SecureSetting;
|
import org.elasticsearch.common.settings.SecureSetting;
|
||||||
import org.elasticsearch.common.settings.SecureString;
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
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(
|
public static final Setting<Boolean> ACCEPT_DEFAULT_PASSWORD_SETTING = Setting.boolSetting(
|
||||||
Security.setting("authc.accept_default_password"), true, Setting.Property.NodeScope, Setting.Property.Filtered,
|
Security.setting("authc.accept_default_password"), true, Setting.Property.NodeScope, Setting.Property.Filtered,
|
||||||
Setting.Property.Deprecated);
|
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 NativeUsersStore nativeUsersStore;
|
||||||
private final AnonymousUser anonymousUser;
|
private final AnonymousUser anonymousUser;
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.elasticsearch.common.Booleans;
|
||||||
import org.elasticsearch.common.CheckedFunction;
|
import org.elasticsearch.common.CheckedFunction;
|
||||||
import org.elasticsearch.common.settings.KeyStoreWrapper;
|
import org.elasticsearch.common.settings.KeyStoreWrapper;
|
||||||
import org.elasticsearch.common.settings.SecureString;
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
|
@ -191,15 +192,16 @@ public class SetupPasswordTool extends MultiCommand {
|
||||||
|
|
||||||
void setupOptions(OptionSet options, Environment env) throws Exception {
|
void setupOptions(OptionSet options, Environment env) throws Exception {
|
||||||
client = clientFunction.apply(env);
|
client = clientFunction.apply(env);
|
||||||
KeyStoreWrapper keyStore = keyStoreFunction.apply(env);
|
try (KeyStoreWrapper keyStore = keyStoreFunction.apply(env)) {
|
||||||
String providedUrl = urlOption.value(options);
|
String providedUrl = urlOption.value(options);
|
||||||
url = providedUrl == null ? client.getDefaultURL() : providedUrl;
|
url = providedUrl == null ? client.getDefaultURL() : providedUrl;
|
||||||
setShouldPrompt(options);
|
setShouldPrompt(options);
|
||||||
|
|
||||||
// TODO: We currently do not support keystore passwords
|
// TODO: We currently do not support keystore passwords
|
||||||
keyStore.decrypt(new char[0]);
|
keyStore.decrypt(new char[0]);
|
||||||
|
Settings build = Settings.builder().setSecureSettings(keyStore).build();
|
||||||
elasticUserPassword = keyStore.getString(ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.getKey());
|
elasticUserPassword = ReservedRealm.BOOTSTRAP_ELASTIC_PASSWORD.get(build);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setParser() {
|
private void setParser() {
|
||||||
|
@ -232,7 +234,7 @@ public class SetupPasswordTool extends MultiCommand {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String route = url + "/_xpack/security/user/" + user + "/_password";
|
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);
|
callback.accept(user, password);
|
||||||
if (isSuperUser) {
|
if (isSuperUser) {
|
||||||
elasticUserPassword = password;
|
elasticUserPassword = password;
|
||||||
|
|
|
@ -28,6 +28,9 @@ import org.mockito.Mockito;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.mockito.Matchers.anyString;
|
import static org.mockito.Matchers.anyString;
|
||||||
|
@ -41,7 +44,7 @@ import static org.mockito.Mockito.when;
|
||||||
public class SetupPasswordToolTests extends CommandTestCase {
|
public class SetupPasswordToolTests extends CommandTestCase {
|
||||||
|
|
||||||
private final String pathHomeParameter = "-Epath.home=" + createTempDir();
|
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 ep = "elastic-password";
|
||||||
private final String kp = "kibana-password";
|
private final String kp = "kibana-password";
|
||||||
private final String lp = "logstash-password";
|
private final String lp = "logstash-password";
|
||||||
|
@ -50,9 +53,21 @@ public class SetupPasswordToolTests extends CommandTestCase {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setSecretsAndKeyStore() throws GeneralSecurityException {
|
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.keyStore = mock(KeyStoreWrapper.class);
|
||||||
this.httpClient = mock(CommandLineHttpClient.class);
|
this.httpClient = mock(CommandLineHttpClient.class);
|
||||||
|
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);
|
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");
|
when(httpClient.getDefaultURL()).thenReturn("http://localhost:9200");
|
||||||
|
|
||||||
terminal.addSecretInput(ep);
|
terminal.addSecretInput(ep);
|
||||||
|
|
|
@ -12,7 +12,6 @@ integTestRunner {
|
||||||
|
|
||||||
integTestCluster {
|
integTestCluster {
|
||||||
plugin ':x-pack-elasticsearch:plugin'
|
plugin ':x-pack-elasticsearch:plugin'
|
||||||
keystoreSetting 'bootstrap.password', 'x-pack-test-password'
|
|
||||||
setupCommand 'setupTestAdmin',
|
setupCommand 'setupTestAdmin',
|
||||||
'bin/x-pack/users', 'useradd', "test_admin", '-p', 'x-pack-test-password', '-r', "superuser"
|
'bin/x-pack/users', 'useradd', "test_admin", '-p', 'x-pack-test-password', '-r', "superuser"
|
||||||
waitCondition = { node, ant ->
|
waitCondition = { node, ant ->
|
||||||
|
|
Loading…
Reference in New Issue