Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions x-pack/docs/en/security/authentication/realm-chains.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ after several successive failed login attempts. If the same username exists in
multiple realms, unintentional account lockouts are possible. For more
information, see <<trouble-shoot-active-directory>>.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like us to include an example here (or somewhere else in the docs), like:

This sample enables the file realm and an LDAP realm, but disables the native realm:

xpack.security.authc.realms:
  file.file_realm:
    order: 0

  ldap.corporate_ldap
    order: 1
    url: 'url_to_ldap_directory'
    ...

  native.native_realm:
    enabled: false

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the existing sample to disable the native realm as an example. Thanks!


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.
Expand Down Expand Up @@ -59,8 +61,8 @@ xpack.security.authc.realms:

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
<<ref-realm-settings,settings that are common to all realms>>.
there are
<<ref-realm-settings,settings that are common to all realms>>.

[[authorization_realms]]
==== Delegating authorization to another realm
Expand All @@ -81,14 +83,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
<<realm-settings,supported settings>> 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 <<configuring-authorization-delegation>> for more details.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,34 +71,32 @@ public Realms(Settings settings, Environment env, Map<String, Realm.Factory> fac
this.threadContext = threadContext;
this.reservedRealm = reservedRealm;
assert factories.get(ReservedRealm.TYPE) == null;
this.realms = initRealms();
final List<RealmConfig> 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<Realm> standardRealms = new ArrayList<>();
List<Realm> nativeRealms = new ArrayList<>();
List<Realm> standardRealms = new ArrayList<>(List.of(reservedRealm));
List<Realm> 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())) {
standardRealms.add(realm);
}

if (FileRealmSettings.TYPE.equals(realm.type()) || NativeRealmSettings.TYPE.equals(realm.type())) {
nativeRealms.add(realm);
basicRealms.add(realm);
}
}

for (List<Realm> realmList : Arrays.asList(standardRealms, nativeRealms)) {
if (realmList.isEmpty()) {
addNativeRealms(realmList);
if (Assertions.ENABLED) {
for (List<Realm> 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));
}

Expand Down Expand Up @@ -164,42 +163,19 @@ public Realm.Factory realmFactory(String type) {
return factories.get(type);
}

protected List<Realm> initRealms() throws Exception {
Map<RealmConfig.RealmIdentifier, Settings> realmsSettings = RealmSettings.getRealmSettings(settings);
Set<String> internalTypes = new HashSet<>();
protected List<Realm> initRealms(List<RealmConfig> realmConfigs) throws Exception {
List<Realm> realms = new ArrayList<>();
List<String> kerberosRealmNames = new ArrayList<>();
Map<String, Set<String>> nameToRealmIdentifier = new HashMap<>();
Map<Integer, Set<String>> 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());
Expand All @@ -209,13 +185,9 @@ protected List<Realm> 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()
Expand Down Expand Up @@ -290,21 +262,21 @@ public void usageStats(ActionListener<Map<String, Object>> listener) {
}
}

private void addNativeRealms(List<Realm> 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<Realm> realms, Set<String> disabledBasicRealmTypes) throws Exception {
final Set<String> 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)));
}
}
Expand All @@ -324,6 +296,47 @@ private void checkUniqueOrders(Map<Integer, Set<String>> orderToRealmName) {
}
}

private List<RealmConfig> buildRealmConfigs() {
final Map<RealmConfig.RealmIdentifier, Settings> realmsSettings = RealmSettings.getRealmSettings(settings);
final Set<String> internalTypes = new HashSet<>();
final List<String> kerberosRealmNames = new ArrayList<>();
final List<RealmConfig> 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<String> findDisabledBasicRealmTypes(List<RealmConfig> 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<String, Object> mapA, Map<String, Object> mapB) {
for (Entry<String, Object> entry : mapB.entrySet()) {
mapA.compute(entry.getKey(), (key, value) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.authc.service.ServiceAccountsTokenStore.CompositeServiceAccountsTokenStore;
import org.elasticsearch.xpack.security.operator.OperatorPrivileges;
Expand Down Expand Up @@ -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.<String, Realm.Factory>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);
Expand Down
Loading