From 590777150f317ded5cee305cf6ae325cf1513cfd Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Fri, 2 Aug 2019 12:28:59 +1000 Subject: [PATCH] Explicitly fail if a realm only exists in keystore (#45091) There are no realms that can be configured exclusively with secure settings. Every realm that supports secure settings also requires one or more non-secure settings. However, sometimes a node will be configured with entries in the keystore for which there is nothing in elasticsearch.yml - this may be because the realm we removed from the yml, but not deleted from the keystore, or it could be because there was a typo in the realm name which has accidentially orphaned the keystore entry. In these cases the realm building would fail, but the error would not always be clear or point to the root cause (orphaned keystore entries). RealmSettings would act as though the realm existed, but then fail because an incorrect combination of settings was provided. This change causes realm building to fail early, with an explicit message about incorrect keystore entries. Backport of: #44471 --- .../core/security/authc/RealmSettings.java | 20 ++++++++++++++ .../security/authc/RealmSettingsTests.java | 27 ++++++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/RealmSettings.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/RealmSettings.java index 0c35525f1de..6ab511df90e 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/RealmSettings.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/RealmSettings.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.settings.SecureSetting; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.settings.SettingsException; import java.util.Arrays; import java.util.List; @@ -91,12 +92,31 @@ public class RealmSettings { return settingsByName.names().stream().map(name -> { final RealmConfig.RealmIdentifier id = new RealmConfig.RealmIdentifier(type, name); final Settings realmSettings = settingsByName.getAsSettings(name); + verifyRealmSettings(id, realmSettings); return new Tuple<>(id, realmSettings); }); }) .collect(Collectors.toMap(Tuple::v1, Tuple::v2)); } + /** + * Performs any necessary verifications on a realms settings that are not automatically applied by Settings validation infrastructure. + */ + private static void verifyRealmSettings(RealmConfig.RealmIdentifier identifier, Settings realmSettings) { + final Settings nonSecureSettings = Settings.builder().put(realmSettings, false).build(); + if (nonSecureSettings.isEmpty()) { + final String prefix = realmSettingPrefix(identifier); + throw new SettingsException( + "found settings for the realm [{}] (with type [{}]) in the secure settings (elasticsearch.keystore)," + + " but this realm does not have any settings in elasticsearch.yml." + + " Please remove these settings from the keystore, or update their names to match one of the realms that are" + + " defined in elasticsearch.yml - [{}]", + identifier.getName(), identifier.getType(), + realmSettings.keySet().stream().map(k -> prefix + k).collect(Collectors.joining(",")) + ); + } + } + public static String getFullSettingKey(String realmName, Setting.AffixSetting setting) { return setting.getConcreteSettingForNamespace(realmName).getKey(); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java index eb33408f338..6061469d700 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmSettingsTests.java @@ -10,12 +10,16 @@ import org.elasticsearch.common.settings.MockSecureSettings; import org.elasticsearch.common.settings.SecureSettings; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.settings.SettingsException; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.authc.InternalRealmsSettings; +import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmSettings; +import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings; import org.elasticsearch.xpack.core.security.authc.support.Hasher; +import org.hamcrest.Matchers; import java.util.Collections; import java.util.HashSet; @@ -23,7 +27,6 @@ import java.util.List; import java.util.Set; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.notNullValue; public class RealmSettingsTests extends ESTestCase { private static final List CACHE_HASHING_ALGOS = Hasher.getAvailableAlgoCacheHash(); @@ -87,6 +90,21 @@ public class RealmSettingsTests extends ESTestCase { assertSuccess(settings); } + public void testSettingsWithKeystoreOnlyRealmDoesNotValidate() throws Exception { + final String securePasswordKey = RealmSettings.getFullSettingKey( + new RealmConfig.RealmIdentifier("ldap", "ldap_1"), PoolingSessionFactorySettings.SECURE_BIND_PASSWORD); + final Settings.Builder builder = Settings.builder() + .put(ldapRealm("ldap-1", randomBoolean(), randomBoolean()).build()); + SecuritySettingsSource.addSecureSettings(builder, secureSettings -> { + secureSettings.setString(securePasswordKey, "secret-password"); + }); + final Settings settings = builder.build(); + final SettingsException exception = expectThrows(SettingsException.class, () -> RealmSettings.getRealmSettings(settings)); + assertThat(exception.getMessage(), containsString("elasticsearch.keystore")); + assertThat(exception.getMessage(), containsString("elasticsearch.yml")); + assertThat(exception.getMessage(), containsString(securePasswordKey)); + } + private Settings.Builder nativeRealm(String name) { return realm("native", name, nativeSettings()); } @@ -277,12 +295,7 @@ public class RealmSettingsTests extends ESTestCase { } catch (RuntimeException e) { fail("Settings do not validate: " + e); } - } - - private void assertErrorWithCause(String realmType, String realmName, String message, Settings settings) { - final IllegalArgumentException exception = assertError(realmType, realmName, settings); - assertThat(exception.getCause(), notNullValue()); - assertThat(exception.getCause().getMessage(), containsString(message)); + assertThat(RealmSettings.getRealmSettings(settings), Matchers.not(Matchers.anEmptyMap())); } private void assertErrorWithMessage(String realmType, String realmName, String message, Settings settings) {