diff --git a/x-pack/docs/en/security/authentication/realm-chains.asciidoc b/x-pack/docs/en/security/authentication/realm-chains.asciidoc index 6c609dfd9223f..08da78b14442a 100644 --- a/x-pack/docs/en/security/authentication/realm-chains.asciidoc +++ b/x-pack/docs/en/security/authentication/realm-chains.asciidoc @@ -23,14 +23,17 @@ after several successive failed login attempts. If the same username exists in multiple realms, unintentional account lockouts are possible. For more information, see <>. -The default realm chain contains the `native` and `file` realms. To explicitly +The default realm chain contains the `file` and `native` realms. To explicitly configure a realm chain, you specify the chain in the `elasticsearch.yml` file. -When you configure a realm chain, only the realms you specify are used for -authentication. To use the `native` and `file` realms, you must include them in -the chain. +If your realm chain does not contain `file` or `native` realm or does not disable +them explicitly, `file` and `native` realms will be added automatically to the +beginning of the realm chain in that order. To opt-out from the automatic behaviour, +you can explicitly configure the `file` and `native` realms with the `order` +and `enabled` settings. -The following snippet configures a realm chain that includes the `file` and -`native` realms, as well as two LDAP realms and an Active Directory realm. +The following snippet configures a realm chain that enables the `file` realm +as well as two LDAP realms and an Active Directory realm, but disables the +`native` realm. [source,yaml] ---------------------------------------- @@ -38,29 +41,29 @@ xpack.security.authc.realms: file.file1: order: 0 - native.native1: - order: 1 - ldap.ldap1: - order: 2 + order: 1 enabled: false url: 'url_to_ldap1' ... ldap.ldap2: - order: 3 + order: 2 url: 'url_to_ldap2' ... active_directory.ad1: - order: 4 + order: 3 url: 'url_to_ad' + + native.native1: + enabled: false ---------------------------------------- As can be seen above, each realm has a unique name that identifies it. Each type of realm dictates its own set of required and optional settings. That said, -there are -<>. +there are +<>. [[authorization_realms]] ==== Delegating authorization to another realm @@ -81,14 +84,14 @@ further explanation on which realms support this. For realms that support this feature, it can be enabled by configuring the `authorization_realms` setting on the authenticating realm. Check the list of <> for each realm -to see if they support the `authorization_realms` setting. - -If delegated authorization is enabled for a realm, it authenticates the user in -its standard manner (including relevant caching) then looks for that user in the -configured list of authorization realms. It tries each realm in the order they -are specified in the `authorization_realms` setting. The user is retrieved by -principal - the user must have identical usernames in the _authentication_ and -_authorization realms_. If the user cannot be found in any of the authorization +to see if they support the `authorization_realms` setting. + +If delegated authorization is enabled for a realm, it authenticates the user in +its standard manner (including relevant caching) then looks for that user in the +configured list of authorization realms. It tries each realm in the order they +are specified in the `authorization_realms` setting. The user is retrieved by +principal - the user must have identical usernames in the _authentication_ and +_authorization realms_. If the user cannot be found in any of the authorization realms, authentication fails. See <> for more details. diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/RealmConfig.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/RealmConfig.java index bbfa61aa2e80d..f9a985ef63f2c 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/RealmConfig.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/RealmConfig.java @@ -30,7 +30,7 @@ public RealmConfig(RealmIdentifier identifier, Settings settings, Environment en this.env = env; this.threadContext = threadContext; this.enabled = getSetting(RealmSettings.ENABLED_SETTING); - if (false == hasSetting(RealmSettings.ORDER_SETTING.apply(type()))) { + if (enabled && false == hasSetting(RealmSettings.ORDER_SETTING.apply(type()))) { throw new IllegalArgumentException("'order' is a mandatory parameter for realm config. " + "Found invalid config for realm: '" + identifier.name + "'\n" + "Please see the breaking changes documentation." diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/RealmConfigTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/RealmConfigTests.java index 4a786c0106fcb..16b5d225078d9 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/RealmConfigTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authc/RealmConfigTests.java @@ -15,6 +15,7 @@ import org.mockito.Mockito; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; public class RealmConfigTests extends ESTestCase { @@ -47,4 +48,11 @@ public void testWillFailWhenOrderSettingIsMissing() { var e = expectThrows(IllegalArgumentException.class, () -> new RealmConfig(realmIdentifier, settings, environment, threadContext)); assertThat(e.getMessage(), containsString("'order' is a mandatory parameter for realm config")); } + + public void testWillNotFailWhenOrderIsMissingAndDisabled() { + Settings settings = Settings.builder().put(globalSettings) + .put(RealmSettings.getFullSettingKey(realmIdentifier, RealmSettings.ENABLED_SETTING), false).build(); + final RealmConfig realmConfig = new RealmConfig(realmIdentifier, settings, environment, threadContext); + assertThat(realmConfig.enabled(), is(false)); + } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java index 1e501265fae04..7dc3849bd5fd0 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/Realms.java @@ -8,6 +8,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.elasticsearch.Assertions; import org.elasticsearch.action.ActionListener; import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.MapBuilder; @@ -70,11 +71,13 @@ public Realms(Settings settings, Environment env, Map fac this.threadContext = threadContext; this.reservedRealm = reservedRealm; assert factories.get(ReservedRealm.TYPE) == null; - this.realms = initRealms(); + final List realmConfigs = buildRealmConfigs(); + this.realms = initRealms(realmConfigs); + assert realms.get(0) == reservedRealm : "the first realm must be reserved realm"; // pre-computing a list of internal only realms allows us to have much cheaper iteration than a custom iterator // and is also simpler in terms of logic. These lists are small, so the duplication should not be a real issue here - List standardRealms = new ArrayList<>(); - List nativeRealms = new ArrayList<>(); + List standardRealms = new ArrayList<>(List.of(reservedRealm)); + List basicRealms = new ArrayList<>(List.of(reservedRealm)); for (Realm realm : realms) { // don't add the reserved realm here otherwise we end up with only this realm... if (InternalRealms.isStandardRealm(realm.type())) { @@ -82,22 +85,18 @@ public Realms(Settings settings, Environment env, Map fac } if (FileRealmSettings.TYPE.equals(realm.type()) || NativeRealmSettings.TYPE.equals(realm.type())) { - nativeRealms.add(realm); + basicRealms.add(realm); } } - for (List realmList : Arrays.asList(standardRealms, nativeRealms)) { - if (realmList.isEmpty()) { - addNativeRealms(realmList); + if (Assertions.ENABLED) { + for (List realmList : Arrays.asList(standardRealms, basicRealms)) { + assert realmList.get(0) == reservedRealm : "the first realm must be reserved realm"; } - - assert realmList.contains(reservedRealm) == false; - realmList.add(0, reservedRealm); - assert realmList.get(0) == reservedRealm; } this.standardRealmsOnly = Collections.unmodifiableList(standardRealms); - this.nativeRealmsOnly = Collections.unmodifiableList(nativeRealms); + this.nativeRealmsOnly = Collections.unmodifiableList(basicRealms); realms.forEach(r -> r.initialize(this, licenseState)); } @@ -164,42 +163,19 @@ public Realm.Factory realmFactory(String type) { return factories.get(type); } - protected List initRealms() throws Exception { - Map realmsSettings = RealmSettings.getRealmSettings(settings); - Set internalTypes = new HashSet<>(); + protected List initRealms(List realmConfigs) throws Exception { List realms = new ArrayList<>(); - List kerberosRealmNames = new ArrayList<>(); Map> nameToRealmIdentifier = new HashMap<>(); Map> orderToRealmName = new HashMap<>(); - for (RealmConfig.RealmIdentifier identifier: realmsSettings.keySet()) { - Realm.Factory factory = factories.get(identifier.getType()); - if (factory == null) { - throw new IllegalArgumentException("unknown realm type [" + identifier.getType() + "] for realm [" + identifier + "]"); - } - RealmConfig config = new RealmConfig(identifier, settings, env, threadContext); + for (RealmConfig config: realmConfigs) { + Realm.Factory factory = factories.get(config.identifier().getType()); + assert factory != null : "unknown realm type [" + config.identifier().getType() + "]"; if (config.enabled() == false) { if (logger.isDebugEnabled()) { - logger.debug("realm [{}] is disabled", identifier); + logger.debug("realm [{}] is disabled", config.identifier()); } continue; } - if (FileRealmSettings.TYPE.equals(identifier.getType()) || NativeRealmSettings.TYPE.equals(identifier.getType())) { - // this is an internal realm factory, let's make sure we didn't already registered one - // (there can only be one instance of an internal realm) - if (internalTypes.contains(identifier.getType())) { - throw new IllegalArgumentException("multiple [" + identifier.getType() + "] realms are configured. [" - + identifier.getType() + "] is an internal realm and therefore there can only be one such realm configured"); - } - internalTypes.add(identifier.getType()); - } - if (KerberosRealmSettings.TYPE.equals(identifier.getType())) { - kerberosRealmNames.add(identifier.getName()); - if (kerberosRealmNames.size() > 1) { - throw new IllegalArgumentException("multiple realms " + kerberosRealmNames.toString() + " configured of type [" - + identifier.getType() + "], [" + identifier.getType() + "] can only have one such realm " + - "configured"); - } - } Realm realm = factory.create(config); nameToRealmIdentifier.computeIfAbsent(realm.name(), k -> new HashSet<>()).add(RealmSettings.realmSettingPrefix(realm.type()) + realm.name()); @@ -209,13 +185,9 @@ protected List initRealms() throws Exception { } checkUniqueOrders(orderToRealmName); + Collections.sort(realms); - if (realms.isEmpty() == false) { - Collections.sort(realms); - } else { - // there is no "realms" configuration, add the defaults - addNativeRealms(realms); - } + maybeAddBasicRealms(realms, findDisabledBasicRealmTypes(realmConfigs)); // always add built in first! realms.add(0, reservedRealm); String duplicateRealms = nameToRealmIdentifier.entrySet().stream() @@ -290,21 +262,21 @@ public void usageStats(ActionListener> listener) { } } - private void addNativeRealms(List realms) throws Exception { - Realm.Factory fileRealm = factories.get(FileRealmSettings.TYPE); - if (fileRealm != null) { - var realmIdentifier = new RealmConfig.RealmIdentifier(FileRealmSettings.TYPE, FileRealmSettings.DEFAULT_NAME); - realms.add(fileRealm.create(new RealmConfig( - realmIdentifier, - ensureOrderSetting(settings, realmIdentifier, Integer.MIN_VALUE + 1), + private void maybeAddBasicRealms(List realms, Set disabledBasicRealmTypes) throws Exception { + final Set realmTypes = realms.stream().map(Realm::type).collect(Collectors.toUnmodifiableSet()); + // Add native realm first so that file realm will be in the beginning + if (false == disabledBasicRealmTypes.contains(NativeRealmSettings.TYPE) && false == realmTypes.contains(NativeRealmSettings.TYPE)) { + var nativeRealmId = new RealmConfig.RealmIdentifier(NativeRealmSettings.TYPE, NativeRealmSettings.DEFAULT_NAME); + realms.add(0, factories.get(NativeRealmSettings.TYPE).create(new RealmConfig( + nativeRealmId, + ensureOrderSetting(settings, nativeRealmId, Integer.MIN_VALUE), env, threadContext))); } - Realm.Factory indexRealmFactory = factories.get(NativeRealmSettings.TYPE); - if (indexRealmFactory != null) { - var realmIdentifier = new RealmConfig.RealmIdentifier(NativeRealmSettings.TYPE, NativeRealmSettings.DEFAULT_NAME); - realms.add(indexRealmFactory.create(new RealmConfig( - realmIdentifier, - ensureOrderSetting(settings, realmIdentifier, Integer.MIN_VALUE + 2), + if (false == disabledBasicRealmTypes.contains(FileRealmSettings.TYPE) && false == realmTypes.contains(FileRealmSettings.TYPE)) { + var fileRealmId = new RealmConfig.RealmIdentifier(FileRealmSettings.TYPE, FileRealmSettings.DEFAULT_NAME); + realms.add(0, factories.get(FileRealmSettings.TYPE).create(new RealmConfig( + fileRealmId, + ensureOrderSetting(settings, fileRealmId, Integer.MIN_VALUE), env, threadContext))); } } @@ -324,6 +296,47 @@ private void checkUniqueOrders(Map> orderToRealmName) { } } + private List buildRealmConfigs() { + final Map realmsSettings = RealmSettings.getRealmSettings(settings); + final Set internalTypes = new HashSet<>(); + final List kerberosRealmNames = new ArrayList<>(); + final List realmConfigs = new ArrayList<>(); + for (RealmConfig.RealmIdentifier identifier : realmsSettings.keySet()) { + Realm.Factory factory = factories.get(identifier.getType()); + if (factory == null) { + throw new IllegalArgumentException("unknown realm type [" + identifier.getType() + "] for realm [" + identifier + "]"); + } + RealmConfig config = new RealmConfig(identifier, settings, env, threadContext); + if (FileRealmSettings.TYPE.equals(identifier.getType()) || NativeRealmSettings.TYPE.equals(identifier.getType())) { + // this is an internal realm factory, let's make sure we didn't already registered one + // (there can only be one instance of an internal realm) + if (internalTypes.contains(identifier.getType())) { + throw new IllegalArgumentException("multiple [" + identifier.getType() + "] realms are configured. [" + + identifier.getType() + "] is an internal realm and therefore there can only be one such realm configured"); + } + internalTypes.add(identifier.getType()); + } + if (KerberosRealmSettings.TYPE.equals(identifier.getType())) { + kerberosRealmNames.add(identifier.getName()); + if (kerberosRealmNames.size() > 1) { + throw new IllegalArgumentException("multiple realms " + kerberosRealmNames.toString() + " configured of type [" + + identifier.getType() + "], [" + identifier.getType() + "] can only have one such realm " + + "configured"); + } + } + realmConfigs.add(config); + } + return realmConfigs; + } + + private Set findDisabledBasicRealmTypes(List realmConfigs) { + return realmConfigs.stream() + .filter(rc -> FileRealmSettings.TYPE.equals(rc.type()) || NativeRealmSettings.TYPE.equals(rc.type())) + .filter(rc -> false == rc.enabled()) + .map(RealmConfig::type) + .collect(Collectors.toUnmodifiableSet()); + } + private static void combineMaps(Map mapA, Map mapB) { for (Entry entry : mapB.entrySet()) { mapA.compute(entry.getKey(), (key, value) -> { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java index 202b64f9f3b78..6d30ff0265559 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/AuthenticationServiceTests.java @@ -75,6 +75,8 @@ import org.elasticsearch.xpack.core.security.authc.DefaultAuthenticationFailureHandler; import org.elasticsearch.xpack.core.security.authc.Realm; import org.elasticsearch.xpack.core.security.authc.Realm.Factory; +import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings; +import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings; import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; @@ -87,7 +89,9 @@ import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.audit.AuditUtil; import org.elasticsearch.xpack.security.authc.AuthenticationService.Authenticator; +import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; +import org.elasticsearch.xpack.security.authc.file.FileRealm; import org.elasticsearch.xpack.security.authc.service.ServiceAccountService; import org.elasticsearch.xpack.security.operator.OperatorPrivileges; import org.elasticsearch.xpack.security.support.CacheInvalidatorRegistry; @@ -214,9 +218,10 @@ public void init() throws Exception { ReservedRealm reservedRealm = mock(ReservedRealm.class); when(reservedRealm.type()).thenReturn("reserved"); when(reservedRealm.name()).thenReturn("reserved_realm"); - realms = spy(new TestRealms(Settings.EMPTY, TestEnvironment.newEnvironment(settings), Collections.emptyMap(), - licenseState, threadContext, reservedRealm, Arrays.asList(firstRealm, secondRealm), - Collections.singletonList(firstRealm))); + realms = spy(new TestRealms(Settings.EMPTY, TestEnvironment.newEnvironment(settings), + Map.of(FileRealmSettings.TYPE, config -> mock(FileRealm.class), NativeRealmSettings.TYPE, config -> mock(NativeRealm.class)), + licenseState, threadContext, reservedRealm, Arrays.asList(firstRealm, secondRealm), + Collections.singletonList(firstRealm))); auditTrail = mock(AuditTrail.class); auditTrailService = new AuditTrailService(Collections.singletonList(auditTrail), licenseState); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java index 3ddad06fa8d36..f98c881647f73 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/RealmsTests.java @@ -109,6 +109,9 @@ public void testWithSettings() throws Exception { builder.put("xpack.security.authc.realms.type_" + i + ".realm_" + i + ".order", orders.get(i)); orderToIndex.put(orders.get(i), i); } + final boolean fileRealmDisabled = randomDisableRealm(builder, FileRealmSettings.TYPE); + final boolean nativeRealmDisabled = randomDisableRealm(builder, NativeRealmSettings.TYPE); + Settings settings = builder.build(); Environment env = TestEnvironment.newEnvironment(settings); Realms realms = new Realms(settings, env, factories, licenseState, threadContext, reservedRealm); @@ -117,6 +120,7 @@ public void testWithSettings() throws Exception { assertThat(iterator.hasNext(), is(true)); Realm realm = iterator.next(); assertThat(realm, is(reservedRealm)); + assertImplicitlyAddedBasicRealms(iterator, fileRealmDisabled, nativeRealmDisabled); int i = 0; while (iterator.hasNext()) { @@ -126,6 +130,9 @@ public void testWithSettings() throws Exception { assertThat(realm.type(), equalTo("type_" + index)); assertThat(realm.name(), equalTo("realm_" + index)); i++; + if (i == randomRealmTypesCount) { + break; + } } assertThat(realms.getUnlicensedRealms(), empty()); @@ -220,6 +227,8 @@ public void testUnlicensedWithOnlyCustomRealms() throws Exception { builder.put("xpack.security.authc.realms.type_" + i + ".realm_" + i + ".order", orders.get(i)); orderToIndex.put(orders.get(i), i); } + final boolean fileRealmDisabled = randomDisableRealm(builder, FileRealmSettings.TYPE); + final boolean nativeRealmDisabled = randomDisableRealm(builder, NativeRealmSettings.TYPE); Settings settings = builder.build(); Environment env = TestEnvironment.newEnvironment(settings); Realms realms = new Realms(settings, env, factories, licenseState, threadContext, reservedRealm); @@ -229,6 +238,8 @@ public void testUnlicensedWithOnlyCustomRealms() throws Exception { assertThat(iter.hasNext(), is(true)); Realm realm = iter.next(); assertThat(realm, is(reservedRealm)); + assertImplicitlyAddedBasicRealms(iter, fileRealmDisabled, nativeRealmDisabled); + int i = 0; while (iter.hasNext()) { realm = iter.next(); @@ -237,6 +248,9 @@ public void testUnlicensedWithOnlyCustomRealms() throws Exception { assertThat(realm.type(), equalTo("type_" + index)); assertThat(realm.name(), equalTo("realm_" + index)); i++; + if (i == randomRealmTypesCount) { + break; + } } assertThat(realms.getUnlicensedRealms(), empty()); @@ -248,43 +262,7 @@ public void testUnlicensedWithOnlyCustomRealms() throws Exception { assertThat(iter.hasNext(), is(true)); realm = iter.next(); assertThat(realm, is(reservedRealm)); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm.type(), equalTo(FileRealmSettings.TYPE)); - assertThat(realm.name(), equalTo("default_" + FileRealmSettings.TYPE)); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm.type(), equalTo(NativeRealmSettings.TYPE)); - assertThat(realm.name(), equalTo("default_" + NativeRealmSettings.TYPE)); - assertThat(iter.hasNext(), is(false)); - - assertThat(realms.getUnlicensedRealms(), iterableWithSize(randomRealmTypesCount)); - iter = realms.getUnlicensedRealms().iterator(); - i = 0; - while (iter.hasNext()) { - realm = iter.next(); - assertThat(realm.order(), equalTo(i)); - int index = orderToIndex.get(i); - assertThat(realm.type(), equalTo("type_" + index)); - assertThat(realm.name(), equalTo("realm_" + index)); - i++; - } - - allowOnlyNativeRealms(); - - iter = realms.iterator(); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm, is(reservedRealm)); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm.type(), equalTo(FileRealmSettings.TYPE)); - assertThat(realm.name(), equalTo("default_" + FileRealmSettings.TYPE)); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm.type(), equalTo(NativeRealmSettings.TYPE)); - assertThat(realm.name(), equalTo("default_" + NativeRealmSettings.TYPE)); - assertThat(iter.hasNext(), is(false)); + assertImplicitlyAddedBasicRealms(iter, fileRealmDisabled, nativeRealmDisabled); assertThat(realms.getUnlicensedRealms(), iterableWithSize(randomRealmTypesCount)); iter = realms.getUnlicensedRealms().iterator(); @@ -306,6 +284,8 @@ public void testUnlicensedWithInternalRealms() throws Exception { .put("path.home", createTempDir()) .put("xpack.security.authc.realms.ldap.foo.order", "0") .put("xpack.security.authc.realms.type_0.custom.order", "1"); + final boolean fileRealmDisabled = randomDisableRealm(builder, FileRealmSettings.TYPE); + final boolean nativeRealmDisabled = randomDisableRealm(builder, NativeRealmSettings.TYPE); Settings settings = builder.build(); Environment env = TestEnvironment.newEnvironment(settings); Realms realms = new Realms(settings, env, factories, licenseState, threadContext, reservedRealm); @@ -313,16 +293,14 @@ public void testUnlicensedWithInternalRealms() throws Exception { assertThat(iter.hasNext(), is(true)); Realm realm = iter.next(); assertThat(realm, is(reservedRealm)); + assertImplicitlyAddedBasicRealms(iter, fileRealmDisabled, nativeRealmDisabled); + assertTrue(iter.hasNext()); + realm = iter.next(); + assertThat(realm.type(), is("ldap")); + assertTrue(iter.hasNext()); + realm = iter.next(); + assertThat(realm.type(), is("type_0")); - int i = 0; - // this is the iterator when licensed - List types = new ArrayList<>(); - while (iter.hasNext()) { - realm = iter.next(); - i++; - types.add(realm.type()); - } - assertThat(types, contains("ldap", "type_0")); assertThat(realms.getUnlicensedRealms(), empty()); assertThat(realms.getUnlicensedRealms(), sameInstance(realms.getUnlicensedRealms())); @@ -331,13 +309,10 @@ public void testUnlicensedWithInternalRealms() throws Exception { assertThat(iter.hasNext(), is(true)); realm = iter.next(); assertThat(realm, is(reservedRealm)); - i = 0; - while (iter.hasNext()) { - realm = iter.next(); - assertThat(realm.type(), is("ldap")); - i++; - } - assertThat(i, is(1)); + assertImplicitlyAddedBasicRealms(iter, fileRealmDisabled, nativeRealmDisabled); + assertTrue(iter.hasNext()); + realm = iter.next(); + assertThat(realm.type(), is("ldap")); assertThat(realms.getUnlicensedRealms(), iterableWithSize(1)); realm = realms.getUnlicensedRealms().get(0); @@ -349,15 +324,7 @@ public void testUnlicensedWithInternalRealms() throws Exception { assertThat(iter.hasNext(), is(true)); realm = iter.next(); assertThat(realm, is(reservedRealm)); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm.type(), equalTo(FileRealmSettings.TYPE)); - assertThat(realm.name(), equalTo("default_" + FileRealmSettings.TYPE)); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm.type(), equalTo(NativeRealmSettings.TYPE)); - assertThat(realm.name(), equalTo("default_" + NativeRealmSettings.TYPE)); - assertThat(iter.hasNext(), is(false)); + assertImplicitlyAddedBasicRealms(iter, fileRealmDisabled, nativeRealmDisabled); assertThat(realms.getUnlicensedRealms(), iterableWithSize(2)); realm = realms.getUnlicensedRealms().get(0); @@ -368,13 +335,15 @@ public void testUnlicensedWithInternalRealms() throws Exception { assertThat(realm.name(), equalTo("custom")); } - public void testUnlicensedWithNativeRealmSettings() throws Exception { + public void testUnlicensedWithBasicRealmSettings() throws Exception { factories.put(LdapRealmSettings.LDAP_TYPE, config -> new DummyRealm(LdapRealmSettings.LDAP_TYPE, config)); final String type = randomFrom(FileRealmSettings.TYPE, NativeRealmSettings.TYPE); + final String otherType = FileRealmSettings.TYPE.equals(type) ? NativeRealmSettings.TYPE : FileRealmSettings.TYPE; Settings.Builder builder = Settings.builder() .put("path.home", createTempDir()) .put("xpack.security.authc.realms.ldap.foo.order", "0") .put("xpack.security.authc.realms." + type + ".native.order", "1"); + final boolean otherTypeDisabled = randomDisableRealm(builder, otherType); Settings settings = builder.build(); Environment env = TestEnvironment.newEnvironment(settings); Realms realms = new Realms(settings, env, factories, licenseState, threadContext, reservedRealm); @@ -382,6 +351,11 @@ public void testUnlicensedWithNativeRealmSettings() throws Exception { assertThat(iter.hasNext(), is(true)); Realm realm = iter.next(); assertThat(realm, is(reservedRealm)); + if (false == otherTypeDisabled) { + assertThat(iter.hasNext(), is(true)); + realm = iter.next(); + assertThat(realm.type(), is(otherType)); + } assertThat(iter.hasNext(), is(true)); realm = iter.next(); assertThat(realm.type(), is("ldap")); @@ -396,6 +370,11 @@ public void testUnlicensedWithNativeRealmSettings() throws Exception { assertThat(iter.hasNext(), is(true)); realm = iter.next(); assertThat(realm, is(reservedRealm)); + if (false == otherTypeDisabled) { + assertThat(iter.hasNext(), is(true)); + realm = iter.next(); + assertThat(realm.type(), is(otherType)); + } assertThat(iter.hasNext(), is(true)); realm = iter.next(); assertThat(realm.type(), is(type)); @@ -413,6 +392,8 @@ public void testUnlicensedWithNonStandardRealms() throws Exception { Settings.Builder builder = Settings.builder() .put("path.home", createTempDir()) .put("xpack.security.authc.realms." + selectedRealmType + ".foo.order", "0"); + final boolean fileRealmDisabled = randomDisableRealm(builder, FileRealmSettings.TYPE); + final boolean nativeRealmDisabled = randomDisableRealm(builder, NativeRealmSettings.TYPE); Settings settings = builder.build(); Environment env = TestEnvironment.newEnvironment(settings); Realms realms = new Realms(settings, env, factories, licenseState, threadContext, reservedRealm); @@ -420,10 +401,10 @@ public void testUnlicensedWithNonStandardRealms() throws Exception { assertThat(iter.hasNext(), is(true)); Realm realm = iter.next(); assertThat(realm, is(reservedRealm)); + assertImplicitlyAddedBasicRealms(iter, fileRealmDisabled, nativeRealmDisabled); assertThat(iter.hasNext(), is(true)); realm = iter.next(); assertThat(realm.type(), is(selectedRealmType)); - assertThat(iter.hasNext(), is(false)); assertThat(realms.getUnlicensedRealms(), empty()); allowOnlyStandardRealms(); @@ -431,13 +412,7 @@ public void testUnlicensedWithNonStandardRealms() throws Exception { assertThat(iter.hasNext(), is(true)); realm = iter.next(); assertThat(realm, is(reservedRealm)); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm.type(), is(FileRealmSettings.TYPE)); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm.type(), is(NativeRealmSettings.TYPE)); - assertThat(iter.hasNext(), is(false)); + assertImplicitlyAddedBasicRealms(iter, fileRealmDisabled, nativeRealmDisabled); assertThat(realms.getUnlicensedRealms(), iterableWithSize(1)); realm = realms.getUnlicensedRealms().get(0); @@ -449,13 +424,7 @@ public void testUnlicensedWithNonStandardRealms() throws Exception { assertThat(iter.hasNext(), is(true)); realm = iter.next(); assertThat(realm, is(reservedRealm)); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm.type(), is(FileRealmSettings.TYPE)); - assertThat(iter.hasNext(), is(true)); - realm = iter.next(); - assertThat(realm.type(), is(NativeRealmSettings.TYPE)); - assertThat(iter.hasNext(), is(false)); + assertImplicitlyAddedBasicRealms(iter, fileRealmDisabled, nativeRealmDisabled); assertThat(realms.getUnlicensedRealms(), iterableWithSize(1)); realm = realms.getUnlicensedRealms().get(0); @@ -481,33 +450,24 @@ public void testDisabledRealmsAreNotAdded() throws Exception { logger.error("put [{}] -> [{}]", orders.get(i), i); } } + final boolean fileRealmDisabled = randomDisableRealm(builder, FileRealmSettings.TYPE); + final boolean nativeRealmDisabled = randomDisableRealm(builder, NativeRealmSettings.TYPE); Settings settings = builder.build(); Environment env = TestEnvironment.newEnvironment(settings); Realms realms = new Realms(settings, env, factories, licenseState, threadContext, reservedRealm); Iterator iterator = realms.iterator(); Realm realm = iterator.next(); assertThat(realm, is(reservedRealm)); - assertThat(iterator.hasNext(), is(true)); + assertImplicitlyAddedBasicRealms(iterator, fileRealmDisabled, nativeRealmDisabled); int count = 0; while (iterator.hasNext()) { realm = iterator.next(); Integer index = orderToIndex.get(realm.order()); - if (index == null) { - // Default realms are inserted when factories size is 1 and enabled is false - assertThat(realm.type(), equalTo(FileRealmSettings.TYPE)); - assertThat(realm.name(), equalTo("default_" + FileRealmSettings.TYPE)); - assertThat(iterator.hasNext(), is(true)); - realm = iterator.next(); - assertThat(realm.type(), equalTo(NativeRealmSettings.TYPE)); - assertThat(realm.name(), equalTo("default_" + NativeRealmSettings.TYPE)); - assertThat(iterator.hasNext(), is(false)); - } else { - assertThat(realm.type(), equalTo("type_" + index)); - assertThat(realm.name(), equalTo("realm_" + index)); - assertThat(settings.getAsBoolean("xpack.security.authc.realms.realm_" + index + ".enabled", true), equalTo(Boolean.TRUE)); - count++; - } + assertThat(realm.type(), equalTo("type_" + index)); + assertThat(realm.name(), equalTo("realm_" + index)); + assertThat(settings.getAsBoolean("xpack.security.authc.realms.realm_" + index + ".enabled", true), equalTo(Boolean.TRUE)); + count++; } assertThat(count, equalTo(orderToIndex.size())); @@ -538,6 +498,8 @@ public void testUsageStats() throws Exception { .put("path.home", createTempDir()) .put("xpack.security.authc.realms.type_0.foo.order", "0") .put("xpack.security.authc.realms.type_0.bar.order", "1"); + final boolean fileRealmDisabled = randomDisableRealm(builder, FileRealmSettings.TYPE); + final boolean nativeRealmDisabled = randomDisableRealm(builder, NativeRealmSettings.TYPE); Settings settings = builder.build(); Environment env = TestEnvironment.newEnvironment(settings); Realms realms = new Realms(settings, env, factories, licenseState, threadContext, reservedRealm); @@ -560,11 +522,22 @@ public void testUsageStats() throws Exception { if ("type_0".equals(type)) { continue; } - Map typeMap = (Map) entry.getValue(); - assertThat(typeMap, hasEntry("enabled", false)); + final boolean enabled; + final int size; + if (FileRealmSettings.TYPE.equals(type)) { + enabled = fileRealmDisabled == false; + size = enabled ? 4 : 2; + } else if (NativeRealmSettings.TYPE.equals(type)) { + enabled = nativeRealmDisabled == false; + size = enabled ? 4 : 2; + } else { + enabled = false; + size = 2; + } + assertThat(typeMap, hasEntry("enabled", enabled)); assertThat(typeMap, hasEntry("available", true)); - assertThat(typeMap.size(), is(2)); + assertThat(typeMap.size(), is(size)); } // check standard realms include native @@ -577,10 +550,19 @@ public void testUsageStats() throws Exception { for (Entry entry : usageStats.entrySet()) { final String type = entry.getKey(); Map typeMap = (Map) entry.getValue(); - if (FileRealmSettings.TYPE.equals(type) || NativeRealmSettings.TYPE.equals(type)) { - assertThat(typeMap, hasEntry("enabled", true)); + if (FileRealmSettings.TYPE.equals(type)) { assertThat(typeMap, hasEntry("available", true)); - assertThat((Iterable) typeMap.get("name"), contains("default_" + type)); + if (false == fileRealmDisabled) { + assertThat(typeMap, hasEntry("enabled", true)); + assertThat((Iterable) typeMap.get("name"), contains(FileRealmSettings.DEFAULT_NAME)); + } + } else if (NativeRealmSettings.TYPE.equals(type)) { + + assertThat(typeMap, hasEntry("available", true)); + if (false == nativeRealmDisabled) { + assertThat(typeMap, hasEntry("enabled", true)); + assertThat((Iterable) typeMap.get("name"), contains(NativeRealmSettings.DEFAULT_NAME)); + } } else { assertThat(typeMap, hasEntry("enabled", false)); assertThat(typeMap, hasEntry("available", false)); @@ -601,6 +583,38 @@ public void testInitRealmsFailsForMultipleKerberosRealms() throws IOException { "multiple realms [realm_1, realm_2] configured of type [kerberos], [kerberos] can only have one such realm configured"))); } + private boolean randomDisableRealm(Settings.Builder builder, String type) { + final boolean disabled = randomBoolean(); + if (disabled) { + builder.put("xpack.security.authc.realms." + type + ".native.enabled", false); + } + return disabled; + } + + private void assertImplicitlyAddedBasicRealms(Iterator iter, boolean fileRealmDisabled, boolean nativeRealmDisabled) { + Realm realm; + if (false == fileRealmDisabled && false == nativeRealmDisabled) { + assertTrue(iter.hasNext()); + realm = iter.next(); + assertThat(realm.type(), is(FileRealmSettings.TYPE)); + assertThat(realm.name(), is(FileRealmSettings.DEFAULT_NAME)); + assertTrue(iter.hasNext()); + realm = iter.next(); + assertThat(realm.type(), is(NativeRealmSettings.TYPE)); + assertThat(realm.name(), is(NativeRealmSettings.DEFAULT_NAME)); + } else if (false == fileRealmDisabled) { + assertTrue(iter.hasNext()); + realm = iter.next(); + assertThat(realm.type(), is(FileRealmSettings.TYPE)); + assertThat(realm.name(), is(FileRealmSettings.DEFAULT_NAME)); + } else if (false == nativeRealmDisabled) { + assertTrue(iter.hasNext()); + realm = iter.next(); + assertThat(realm.type(), is(NativeRealmSettings.TYPE)); + assertThat(realm.name(), is(NativeRealmSettings.DEFAULT_NAME)); + } + } + static class DummyRealm extends Realm { DummyRealm(String type, RealmConfig config) {