org.elasticsearch.xpack.security.authc.Realms Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of x-pack-security Show documentation
Show all versions of x-pack-security Show documentation
Elasticsearch Expanded Pack Plugin - Security
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
package org.elasticsearch.xpack.security.authc;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.RefCountingRunnable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.logging.DeprecationCategory;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.Maps;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.core.IOUtils;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.LicensedFeature;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.node.Node;
import org.elasticsearch.xpack.core.XPackSettings;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.DomainConfig;
import org.elasticsearch.xpack.core.security.authc.Realm;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmDomain;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
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.kerberos.KerberosRealmSettings;
import org.elasticsearch.xpack.security.Security;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.security.support.ReloadableSecurityComponent;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Serves as a realms registry (also responsible for ordering the realms appropriately)
*/
public class Realms extends AbstractLifecycleComponent implements Iterable, ReloadableSecurityComponent {
private static final Logger logger = LogManager.getLogger(Realms.class);
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(logger.getName());
private final Settings settings;
private final Environment env;
private final Map factories;
private final XPackLicenseState licenseState;
private final ThreadContext threadContext;
private final ReservedRealm reservedRealm;
// All realms that were configured from the node settings, some of these may not be enabled due to licensing
private final List allConfiguredRealms;
private final Map domainNameToConfig;
// The realmRefs include all realms explicitly or implicitly configured regardless whether they are disabled or not
private final Map realmRefs;
// the realms in current use. This list will change dynamically as the license changes
private volatile List activeRealms;
@SuppressWarnings("this-escape")
public Realms(
Settings settings,
Environment env,
Map factories,
XPackLicenseState licenseState,
ThreadContext threadContext,
ReservedRealm reservedRealm
) throws Exception {
this.settings = settings;
this.env = env;
this.factories = factories;
this.licenseState = licenseState;
this.threadContext = threadContext;
this.reservedRealm = reservedRealm;
assert XPackSettings.SECURITY_ENABLED.get(settings) : "security must be enabled";
assert factories.get(ReservedRealm.TYPE) == null;
final Map realmToDomainConfig = RealmSettings.computeRealmNameToDomainConfigAssociation(settings);
domainNameToConfig = realmToDomainConfig.values()
.stream()
.distinct()
.collect(Collectors.toMap(DomainConfig::name, Function.identity()));
final List realmConfigs = buildRealmConfigs();
// initRealms will add default file and native realm config if they are not explicitly configured
final List initialRealms = initRealms(realmConfigs);
realmRefs = calculateRealmRefs(realmConfigs, realmToDomainConfig);
for (Realm realm : initialRealms) {
Authentication.RealmRef realmRef = Objects.requireNonNull(
realmRefs.get(new RealmConfig.RealmIdentifier(realm.type(), realm.name())),
"realmRef can not be null"
);
realm.setRealmRef(realmRef);
}
this.allConfiguredRealms = initialRealms;
this.allConfiguredRealms.forEach(r -> r.initialize(this.allConfiguredRealms, licenseState));
assert this.allConfiguredRealms.get(0) == reservedRealm : "the first realm must be reserved realm";
this.activeRealms = calculateLicensedRealms(licenseState.copyCurrentLicenseState());
licenseState.addListener(this::recomputeActiveRealms);
}
private Map calculateRealmRefs(
Collection realmConfigs,
Map domainForRealm
) {
final String nodeName = Node.NODE_NAME_SETTING.get(settings);
final Map realmRefs = new HashMap<>();
assert realmConfigs.stream().noneMatch(rc -> rc.name().equals(reservedRealm.name()) && rc.type().equals(reservedRealm.type()))
: "reserved realm cannot be configured";
realmRefs.put(
new RealmConfig.RealmIdentifier(reservedRealm.type(), reservedRealm.name()),
new Authentication.RealmRef(reservedRealm.name(), reservedRealm.type(), nodeName, null)
);
for (RealmConfig realmConfig : realmConfigs) {
final RealmConfig.RealmIdentifier realmIdentifier = new RealmConfig.RealmIdentifier(realmConfig.type(), realmConfig.name());
final DomainConfig domainConfig = domainForRealm.get(realmConfig.name());
final RealmDomain realmDomain;
if (domainConfig != null) {
final String domainName = domainConfig.name();
Set domainIdentifiers = new HashSet<>();
for (RealmConfig otherRealmConfig : realmConfigs) {
final DomainConfig otherDomainConfig = domainForRealm.get(otherRealmConfig.name());
if (otherDomainConfig != null && domainName.equals(otherDomainConfig.name())) {
domainIdentifiers.add(otherRealmConfig.identifier());
}
}
realmDomain = new RealmDomain(domainName, domainIdentifiers);
} else {
realmDomain = null;
}
realmRefs.put(
realmIdentifier,
new Authentication.RealmRef(realmIdentifier.getName(), realmIdentifier.getType(), nodeName, realmDomain)
);
}
assert realmRefs.values().stream().filter(realmRef -> ReservedRealm.TYPE.equals(realmRef.getType())).toList().size() == 1
: "there must be exactly one reserved realm configured";
assert realmRefs.values().stream().filter(realmRef -> NativeRealmSettings.TYPE.equals(realmRef.getType())).toList().size() == 1
: "there must be exactly one native realm configured";
assert realmRefs.values().stream().filter(realmRef -> FileRealmSettings.TYPE.equals(realmRef.getType())).toList().size() == 1
: "there must be exactly one file realm configured";
return Map.copyOf(realmRefs);
}
protected void recomputeActiveRealms() {
final XPackLicenseState licenseStateSnapshot = licenseState.copyCurrentLicenseState();
final List licensedRealms = calculateLicensedRealms(licenseStateSnapshot);
logger.info(
"license mode is [{}], currently licensed security realms are [{}]",
licenseStateSnapshot.getOperationMode().description(),
Strings.collectionToCommaDelimitedString(licensedRealms)
);
// Stop license-tracking for any previously-active realms that are no longer allowed
if (activeRealms != null) {
activeRealms.stream().filter(r -> licensedRealms.contains(r) == false).forEach(realm -> {
handleDisabledRealmDueToLicenseChange(realm, licenseStateSnapshot);
});
}
activeRealms = licensedRealms;
}
// Can be overridden in testing
protected void handleDisabledRealmDueToLicenseChange(Realm realm, XPackLicenseState licenseStateSnapshot) {
final LicensedFeature.Persistent feature = getLicensedFeatureForRealm(realm.type());
assert feature != null
: "Realm ["
+ realm
+ "] with no licensed feature became inactive due to change to license mode ["
+ licenseStateSnapshot.getOperationMode()
+ "]";
feature.stopTracking(licenseStateSnapshot, realm.name());
logger.warn(
"The [{}.{}] realm has been automatically disabled due to a change in license [{}]",
realm.type(),
realm.name(),
licenseStateSnapshot.statusDescription()
);
}
@Override
public Iterator iterator() {
return getActiveRealms().iterator();
}
/**
* Returns a list of realms that are configured, but are not permitted under the current license.
*/
public List getUnlicensedRealms() {
final List activeSnapshot = activeRealms;
if (activeSnapshot.equals(allConfiguredRealms)) {
return Collections.emptyList();
}
// Otherwise, we return anything in "all realms" that is not in the allowed realm list
return allConfiguredRealms.stream().filter(r -> activeSnapshot.contains(r) == false).toList();
}
public Stream stream() {
return StreamSupport.stream(this.spliterator(), false);
}
public List getActiveRealms() {
assert activeRealms != null : "Active realms not configured";
return activeRealms;
}
public DomainConfig getDomainConfig(String domainName) {
return domainNameToConfig.get(domainName);
}
// Protected for testing
protected List calculateLicensedRealms(XPackLicenseState licenseStateSnapshot) {
return allConfiguredRealms.stream().filter(r -> checkLicense(r, licenseStateSnapshot)).toList();
}
private static boolean checkLicense(Realm realm, XPackLicenseState licenseState) {
final LicensedFeature.Persistent feature = getLicensedFeatureForRealm(realm.type());
if (feature == null) {
return true;
}
return feature.checkAndStartTracking(licenseState, realm.name());
}
public static boolean isRealmTypeAvailable(XPackLicenseState licenseState, String type) {
final LicensedFeature.Persistent feature = getLicensedFeatureForRealm(type);
if (feature == null) {
return true;
}
return feature.checkWithoutTracking(licenseState);
}
@Nullable
private static LicensedFeature.Persistent getLicensedFeatureForRealm(String realmType) {
assert Strings.hasText(realmType) : "Realm type must be provided (received [" + realmType + "])";
if (InternalRealms.isInternalRealm(realmType)) {
return InternalRealms.getLicensedFeature(realmType);
} else {
return Security.CUSTOM_REALMS_FEATURE;
}
}
public Realm realm(String name) {
for (Realm realm : activeRealms) {
if (name.equals(realm.name())) {
return realm;
}
}
return null;
}
public Realm.Factory realmFactory(String type) {
return factories.get(type);
}
protected List initRealms(List realmConfigs) throws Exception {
List realms = new ArrayList<>();
Map> nameToRealmIdentifier = new HashMap<>();
Map> orderToRealmName = new HashMap<>();
List reservedPrefixedRealmIdentifiers = new ArrayList<>();
for (RealmConfig config : realmConfigs) {
Realm.Factory factory = factories.get(config.identifier().getType());
assert factory != null : "unknown realm type [" + config.identifier().getType() + "]";
if (config.identifier().getName().startsWith(RealmSettings.RESERVED_REALM_AND_DOMAIN_NAME_PREFIX)) {
reservedPrefixedRealmIdentifiers.add(config.identifier());
}
if (config.enabled() == false) {
if (logger.isDebugEnabled()) {
logger.debug("realm [{}] is disabled", config.identifier());
}
continue;
}
Realm realm = factory.create(config);
nameToRealmIdentifier.computeIfAbsent(realm.name(), k -> new HashSet<>())
.add(RealmSettings.realmSettingPrefix(realm.type()) + realm.name());
orderToRealmName.computeIfAbsent(realm.order(), k -> new HashSet<>()).add(realm.name());
realms.add(realm);
}
checkUniqueOrders(orderToRealmName);
Collections.sort(realms);
ensureUniqueExplicitlyConfiguredRealmNames(nameToRealmIdentifier);
maybeAddBasicRealms(realms, realmConfigs);
// always add built in first!
addReservedRealm(realms);
logDeprecationForReservedPrefixedRealmNames(reservedPrefixedRealmIdentifiers);
return Collections.unmodifiableList(realms);
}
@SuppressWarnings("unchecked")
public void usageStats(ActionListener