From fc1a1fb89888038768ced7f307ed132295aabf89 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Tue, 31 Dec 2019 11:39:10 +1100 Subject: [PATCH 1/6] Support Client and RoleMapping in custom Realms Previously custom realms were limited in what services and components they had easy access to. It was possible to work around this because a security extension is packaged within a Plugin, so there were ways to store this components in static/SetOnce variables and access them from the realm, but those techniques were fragile, undocumented and difficult to discover. This change includes key services as an argument to most of the methods on SecurityExtension so that custom realm / role provider authors can have easy access to them. Resolves: #48369 --- .../core/security/SecurityExtension.java | 32 ++++++- .../security/authc/support/CachingRealm.java | 2 +- .../authc/support/UserRoleMapper.java | 2 +- .../xpack/security/Security.java | 14 ++- .../realm/TransportClearRealmCacheAction.java | 2 +- .../authc/kerberos/KerberosRealm.java | 4 +- .../xpack/security/authc/ldap/LdapRealm.java | 4 +- .../authc/oidc/OpenIdConnectRealm.java | 2 +- .../xpack/security/authc/pki/PkiRealm.java | 4 +- .../xpack/security/authc/saml/SamlRealm.java | 2 +- .../support/CachingUsernamePasswordRealm.java | 1 + .../security/authc/support/DnRoleMapper.java | 2 + .../support/mapper/CompositeRoleMapper.java | 4 +- .../mapper/NativeRoleMappingStore.java | 4 +- .../security/support/ExtensionComponents.java | 59 ++++++++++++ .../xpack/security/SecurityTests.java | 2 +- ...ansportOpenIdConnectLogoutActionTests.java | 2 +- .../saml/TransportSamlLogoutActionTests.java | 2 +- .../kerberos/KerberosRealmCacheTests.java | 2 +- .../authc/kerberos/KerberosRealmTestCase.java | 2 +- .../authc/kerberos/KerberosRealmTests.java | 2 +- .../authc/oidc/OpenIdConnectRealmTests.java | 2 +- .../security/authc/pki/PkiRealmTests.java | 2 +- .../authc/saml/SamlRealmTestHelper.java | 2 +- .../security/authc/saml/SamlRealmTests.java | 2 +- .../DistinguishedNamePredicateTests.java | 3 +- .../mapper/ExpressionRoleMappingTests.java | 2 +- .../mapper/NativeRoleMappingStoreTests.java | 2 +- .../build.gradle | 1 + .../example/ExampleSecurityExtension.java | 17 ++-- .../example/SpiExtensionPlugin.java | 2 + .../example/realm/CustomRoleMappingRealm.java | 96 +++++++++++++++++++ .../realm/CustomRoleMappingRealmIT.java | 69 +++++++++++++ .../realm/CustomRoleMappingRealmTests.java | 69 +++++++++++++ 34 files changed, 374 insertions(+), 45 deletions(-) rename x-pack/plugin/{security/src/main/java/org/elasticsearch/xpack => core/src/main/java/org/elasticsearch/xpack/core}/security/authc/support/CachingRealm.java (93%) rename x-pack/plugin/{security/src/main/java/org/elasticsearch/xpack => core/src/main/java/org/elasticsearch/xpack/core}/security/authc/support/UserRoleMapper.java (99%) create mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/ExtensionComponents.java create mode 100644 x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/realm/CustomRoleMappingRealm.java create mode 100644 x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmIT.java create mode 100644 x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmTests.java diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java index 9f0eb474a59c8..1e85a10e4347d 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java @@ -7,10 +7,14 @@ import org.apache.lucene.util.SPIClassIterator; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler; import org.elasticsearch.xpack.core.security.authc.Realm; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult; @@ -28,6 +32,22 @@ */ public interface SecurityExtension { + /** + * This interface provides access to components (clients & services) that may be used + * within custom realms and role providers. + */ + interface SecurityComponents { + /** An internal client for retrieving information/data from this cluster */ + Client client(); + /** The Elasticsearch thread pools */ + ThreadPool threadPool(); + /** Provides the ability to monitor files for changes */ + ResourceWatcherService resourceWatcherService(); + /** Access to listen to changes in cluster state and settings */ + ClusterService clusterService(); + /** Provides support for mapping users' roles from groups and metadata */ + UserRoleMapper roleMapper(); + } /** * Returns authentication realm implementations added by this extension. * @@ -35,9 +55,9 @@ public interface SecurityExtension { * is a {@link Realm.Factory} which will construct * that realm for use in authentication when that realm type is configured. * - * @param resourceWatcherService Use to watch configuration files for changes + * @param components Access to components that may be used to build realms */ - default Map getRealms(ResourceWatcherService resourceWatcherService) { + default Map getRealms(SecurityComponents components) { return Collections.emptyMap(); } @@ -46,8 +66,10 @@ default Map getRealms(ResourceWatcherService resourceWatc * * Only one installed extension may have an authentication failure handler. If more than * one extension returns a non-null handler, an error is raised. + * + * @param components Access to components that may be used to build the handler */ - default AuthenticationFailureHandler getAuthenticationFailureHandler() { + default AuthenticationFailureHandler getAuthenticationFailureHandler(SecurityComponents components) { return null; } @@ -73,10 +95,10 @@ default AuthenticationFailureHandler getAuthenticationFailureHandler() { * By default, an empty list is returned. * * @param settings The configured settings for the node - * @param resourceWatcherService Use to watch configuration files for changes + * @param components Access to components that may be used to build roles */ default List, ActionListener>> - getRolesProviders(Settings settings, ResourceWatcherService resourceWatcherService) { + getRolesProviders(Settings settings, SecurityComponents components) { return Collections.emptyList(); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingRealm.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/CachingRealm.java similarity index 93% rename from x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingRealm.java rename to x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/CachingRealm.java index 6089c8f9a70fb..9f561956b9106 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingRealm.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/CachingRealm.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.authc.support; +package org.elasticsearch.xpack.core.security.authc.support; import org.elasticsearch.xpack.core.security.authc.Realm; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/UserRoleMapper.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/UserRoleMapper.java similarity index 99% rename from x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/UserRoleMapper.java rename to x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/UserRoleMapper.java index 6ceb9629d6b4f..8a1b14cef483d 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/UserRoleMapper.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/support/UserRoleMapper.java @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -package org.elasticsearch.xpack.security.authc.support; +package org.elasticsearch.xpack.core.security.authc.support; import com.unboundid.ldap.sdk.DN; import com.unboundid.ldap.sdk.LDAPException; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 3b24042f47efb..e0d662b34e6fb 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -232,6 +232,7 @@ import org.elasticsearch.xpack.security.rest.action.user.RestHasPrivilegesAction; import org.elasticsearch.xpack.security.rest.action.user.RestPutUserAction; import org.elasticsearch.xpack.security.rest.action.user.RestSetEnabledAction; +import org.elasticsearch.xpack.security.support.ExtensionComponents; import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.elasticsearch.xpack.security.support.SecurityStatusChangeListener; import org.elasticsearch.xpack.security.transport.SecurityHttpSettings; @@ -391,10 +392,12 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste final AnonymousUser anonymousUser = new AnonymousUser(settings); final ReservedRealm reservedRealm = new ReservedRealm(env, settings, nativeUsersStore, anonymousUser, securityIndex.get(), threadPool); + final SecurityExtension.SecurityComponents extensionComponents = new ExtensionComponents(client, threadPool, clusterService, + resourceWatcherService, nativeRoleMappingStore); Map realmFactories = new HashMap<>(InternalRealms.getFactories(threadPool, resourceWatcherService, getSslService(), nativeUsersStore, nativeRoleMappingStore, securityIndex.get())); for (SecurityExtension extension : securityExtensions) { - Map newRealms = extension.getRealms(resourceWatcherService); + Map newRealms = extension.getRealms(extensionComponents); for (Map.Entry entry : newRealms.entrySet()) { if (realmFactories.put(entry.getKey(), entry.getValue()) != null) { throw new IllegalArgumentException("Realm type [" + entry.getKey() + "] is already registered"); @@ -420,7 +423,7 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste final ReservedRolesStore reservedRolesStore = new ReservedRolesStore(); List, ActionListener>> rolesProviders = new ArrayList<>(); for (SecurityExtension extension : securityExtensions) { - rolesProviders.addAll(extension.getRolesProviders(settings, resourceWatcherService)); + rolesProviders.addAll(extension.getRolesProviders(settings, extensionComponents)); } final ApiKeyService apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, getLicenseState(), securityIndex.get(), @@ -436,7 +439,7 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste getLicenseState().addListener(allRolesStore::invalidateAll); getLicenseState().addListener(new SecurityStatusChangeListener(getLicenseState())); - final AuthenticationFailureHandler failureHandler = createAuthenticationFailureHandler(realms); + final AuthenticationFailureHandler failureHandler = createAuthenticationFailureHandler(realms, extensionComponents); authcService.set(new AuthenticationService(settings, realms, auditTrailService, failureHandler, threadPool, anonymousUser, tokenService, apiKeyService)); components.add(authcService.get()); @@ -496,11 +499,12 @@ private AuthorizationEngine getAuthorizationEngine() { return authorizationEngine; } - private AuthenticationFailureHandler createAuthenticationFailureHandler(final Realms realms) { + private AuthenticationFailureHandler createAuthenticationFailureHandler(final Realms realms, + final SecurityExtension.SecurityComponents components) { AuthenticationFailureHandler failureHandler = null; String extensionName = null; for (SecurityExtension extension : securityExtensions) { - AuthenticationFailureHandler extensionFailureHandler = extension.getAuthenticationFailureHandler(); + AuthenticationFailureHandler extensionFailureHandler = extension.getAuthenticationFailureHandler(components); if (extensionFailureHandler != null && failureHandler != null) { throw new IllegalStateException("Extensions [" + extensionName + "] and [" + extension.toString() + "] " + "both set an authentication failure handler"); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/realm/TransportClearRealmCacheAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/realm/TransportClearRealmCacheAction.java index 8acc6631920fc..74ce788322293 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/realm/TransportClearRealmCacheAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/realm/TransportClearRealmCacheAction.java @@ -21,7 +21,7 @@ import org.elasticsearch.xpack.core.security.authc.Realm; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authc.Realms; -import org.elasticsearch.xpack.security.authc.support.CachingRealm; +import org.elasticsearch.xpack.core.security.authc.support.CachingRealm; import java.io.IOException; import java.util.List; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealm.java index 2ca8efd4cd5e5..297cb9f500f3a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealm.java @@ -21,9 +21,9 @@ import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings; import org.elasticsearch.xpack.core.security.user.User; -import org.elasticsearch.xpack.security.authc.support.CachingRealm; +import org.elasticsearch.xpack.core.security.authc.support.CachingRealm; import org.elasticsearch.xpack.security.authc.support.DelegatedAuthorizationSupport; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore; import org.ietf.jgss.GSSException; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java index 2f104c98cbdc6..388c12076c76f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ldap/LdapRealm.java @@ -35,8 +35,8 @@ import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; import org.elasticsearch.xpack.security.authc.support.DelegatedAuthorizationSupport; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper.UserData; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper.UserData; import org.elasticsearch.xpack.security.authc.support.mapper.CompositeRoleMapper; import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealm.java index a3bc026e330f2..f4adbde785f2b 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealm.java @@ -44,7 +44,7 @@ import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.security.authc.TokenService; import org.elasticsearch.xpack.security.authc.support.DelegatedAuthorizationSupport; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import java.net.URI; import java.net.URISyntaxException; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java index ca6e4e09c2d28..525c79379f3d5 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/pki/PkiRealm.java @@ -32,9 +32,9 @@ import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings; import org.elasticsearch.xpack.security.authc.BytesKey; import org.elasticsearch.xpack.security.authc.TokenService; -import org.elasticsearch.xpack.security.authc.support.CachingRealm; +import org.elasticsearch.xpack.core.security.authc.support.CachingRealm; import org.elasticsearch.xpack.security.authc.support.DelegatedAuthorizationSupport; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.security.authc.support.mapper.CompositeRoleMapper; import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java index b9508ecd97815..4f8677364dcf7 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/saml/SamlRealm.java @@ -50,7 +50,7 @@ import org.elasticsearch.xpack.security.authc.Realms; import org.elasticsearch.xpack.security.authc.TokenService; import org.elasticsearch.xpack.security.authc.support.DelegatedAuthorizationSupport; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.criterion.EntityRoleCriterion; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java index fed4e1fb13ee6..1ac41e1c411c8 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/CachingUsernamePasswordRealm.java @@ -17,6 +17,7 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; import org.elasticsearch.xpack.core.security.authc.RealmConfig; +import org.elasticsearch.xpack.core.security.authc.support.CachingRealm; import org.elasticsearch.xpack.core.security.authc.support.CachingUsernamePasswordRealmSettings; import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java index 1018c591617a9..6873533850914 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapper.java @@ -20,7 +20,9 @@ import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.XPackPlugin; import org.elasticsearch.xpack.core.security.authc.RealmConfig; +import org.elasticsearch.xpack.core.security.authc.support.CachingRealm; import org.elasticsearch.xpack.core.security.authc.support.DnRoleMapperSettings; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import java.io.IOException; import java.nio.file.Files; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/CompositeRoleMapper.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/CompositeRoleMapper.java index 21f6a1e2a8cda..d6aaf838374dd 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/CompositeRoleMapper.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/CompositeRoleMapper.java @@ -15,9 +15,9 @@ import org.elasticsearch.action.support.GroupedActionListener; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.security.authc.RealmConfig; -import org.elasticsearch.xpack.security.authc.support.CachingRealm; +import org.elasticsearch.xpack.core.security.authc.support.CachingRealm; import org.elasticsearch.xpack.security.authc.support.DnRoleMapper; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; /** * A {@link UserRoleMapper} that composes one or more delegate role-mappers. diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java index f00c48b9d8288..2bd02e6decb1f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStore.java @@ -36,8 +36,8 @@ import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping; import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExpressionModel; import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; -import org.elasticsearch.xpack.security.authc.support.CachingRealm; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.CachingRealm; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.security.support.SecurityIndexManager; import java.io.IOException; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/ExtensionComponents.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/ExtensionComponents.java new file mode 100644 index 0000000000000..f6d845a079754 --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/ExtensionComponents.java @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +package org.elasticsearch.xpack.security.support; + +import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.core.security.SecurityExtension; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; + +/** + * Immutable implementation of {@link SecurityExtension.SecurityComponents}. + */ +public class ExtensionComponents implements SecurityExtension.SecurityComponents { + private final Client client; + private final ThreadPool threadPool; + private final ClusterService clusterService; + private final ResourceWatcherService resourceWatcherService; + private final UserRoleMapper roleMapper; + + public ExtensionComponents(Client client, ThreadPool threadPool, ClusterService clusterService, + ResourceWatcherService resourceWatcherService, UserRoleMapper roleMapper) { + this.client = client; + this.threadPool = threadPool; + this.clusterService = clusterService; + this.resourceWatcherService = resourceWatcherService; + this.roleMapper = roleMapper; + } + + @Override + public Client client() { + return client; + } + + @Override + public ThreadPool threadPool() { + return threadPool; + } + + @Override + public ResourceWatcherService resourceWatcherService() { + return resourceWatcherService; + } + + @Override + public ClusterService clusterService() { + return clusterService; + } + + @Override + public UserRoleMapper roleMapper() { + return roleMapper; + } +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 2d663ea619f9a..49961dfd8b203 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -89,7 +89,7 @@ public static class DummyExtension implements SecurityExtension { } @Override - public Map getRealms(ResourceWatcherService resourceWatcherService) { + public Map getRealms(SecurityComponents components) { return Collections.singletonMap(realmType, config -> null); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java index 387470090735b..5f024cf7a92a8 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/oidc/TransportOpenIdConnectLogoutActionTests.java @@ -51,7 +51,7 @@ import org.elasticsearch.xpack.security.authc.TokenService; import org.elasticsearch.xpack.security.authc.oidc.OpenIdConnectRealm; import org.elasticsearch.xpack.security.authc.oidc.OpenIdConnectTestCase; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.junit.After; import org.junit.Before; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java index f86f01ec4acc5..e24f38824adc8 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/action/saml/TransportSamlLogoutActionTests.java @@ -61,7 +61,7 @@ import org.elasticsearch.xpack.security.authc.saml.SamlRealm; import org.elasticsearch.xpack.security.authc.saml.SamlRealmTests; import org.elasticsearch.xpack.security.authc.saml.SamlTestCase; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.junit.After; import org.junit.Before; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmCacheTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmCacheTests.java index c3d6c5ae07e0e..88c9fd69d7639 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmCacheTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmCacheTests.java @@ -12,7 +12,7 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings; import org.elasticsearch.xpack.core.security.user.User; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper.UserData; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper.UserData; import org.ietf.jgss.GSSException; import javax.security.auth.login.LoginException; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTestCase.java index fe8220dad4e6e..aa09db07d20ad 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTestCase.java @@ -27,7 +27,7 @@ import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings; import org.elasticsearch.xpack.core.security.support.Exceptions; import org.elasticsearch.xpack.core.security.user.User; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore; import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.junit.After; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTests.java index 8d6869404bbaf..3e4d69a2f7f3f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmTests.java @@ -22,7 +22,7 @@ import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.support.MockLookupRealm; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper.UserData; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper.UserData; import org.ietf.jgss.GSSException; import javax.security.auth.login.LoginException; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealmTests.java index 58e3a69da5be4..9d68443556c4a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectRealmTests.java @@ -26,7 +26,7 @@ import org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.support.MockLookupRealm; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.hamcrest.Matchers; import org.junit.Before; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiRealmTests.java index 5b5d45b1d41fc..f9123c231cd9a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/pki/PkiRealmTests.java @@ -29,7 +29,7 @@ import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.BytesKey; import org.elasticsearch.xpack.security.authc.support.MockLookupRealm; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.junit.Before; import org.mockito.Mockito; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTestHelper.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTestHelper.java index 9e1414b438d34..e737e189e9395 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTestHelper.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTestHelper.java @@ -13,7 +13,7 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.xpack.core.security.authc.RealmConfig; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java index b4800c798a7f5..5ce16b42538a9 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRealmTests.java @@ -35,7 +35,7 @@ import org.elasticsearch.xpack.core.ssl.TestsSSLService; import org.elasticsearch.xpack.security.authc.Realms; import org.elasticsearch.xpack.security.authc.support.MockLookupRealm; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.hamcrest.Matchers; import org.junit.Before; import org.mockito.Mockito; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DistinguishedNamePredicateTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DistinguishedNamePredicateTests.java index 51ea82fc0e431..07d75079a2588 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DistinguishedNamePredicateTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DistinguishedNamePredicateTests.java @@ -7,6 +7,7 @@ import com.unboundid.ldap.sdk.DN; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression.FieldValue; import java.util.Locale; @@ -73,4 +74,4 @@ public void testParsingMalformedInput() { private void assertPredicate(Predicate predicate, Object value, boolean expected) { assertThat("Predicate [" + predicate + "] match [" + value + "]", predicate.test(new FieldValue(value)), equalTo(expected)); } -} \ No newline at end of file +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/ExpressionRoleMappingTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/ExpressionRoleMappingTests.java index 6f78435a47c14..3b67fb1954c2e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/ExpressionRoleMappingTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/ExpressionRoleMappingTests.java @@ -33,7 +33,7 @@ import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.AllExpression; import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.AnyExpression; import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.hamcrest.Matchers; import org.junit.Before; import org.mockito.Mockito; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java index 787d4069c1e1e..a7af38a05379a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/mapper/NativeRoleMappingStoreTests.java @@ -34,7 +34,7 @@ import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm; -import org.elasticsearch.xpack.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; import org.elasticsearch.xpack.security.support.SecurityIndexManager; import org.hamcrest.Matchers; diff --git a/x-pack/qa/security-example-spi-extension/build.gradle b/x-pack/qa/security-example-spi-extension/build.gradle index 96f189ffc3ac0..26fd474da9020 100644 --- a/x-pack/qa/security-example-spi-extension/build.gradle +++ b/x-pack/qa/security-example-spi-extension/build.gradle @@ -28,6 +28,7 @@ testClusters.integTest { setting 'xpack.security.authc.realms.custom.custom.filtered_setting', 'should be filtered' setting 'xpack.security.authc.realms.file.esusers.order', '1' setting 'xpack.security.authc.realms.native.native.order', '2' + setting 'xpack.security.authc.realms.custom_role_mapping.role_map.order', '3' setting 'xpack.security.enabled', 'true' setting 'xpack.ilm.enabled', 'false' setting 'xpack.ml.enabled', 'false' diff --git a/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java b/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java index 3f85d8086d678..b20e4274f9901 100644 --- a/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java +++ b/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java @@ -9,11 +9,11 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.example.realm.CustomAuthenticationFailureHandler; import org.elasticsearch.example.realm.CustomRealm; +import org.elasticsearch.example.realm.CustomRoleMappingRealm; import org.elasticsearch.example.role.CustomInMemoryRolesProvider; -import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.core.security.SecurityExtension; import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler; import org.elasticsearch.xpack.core.security.authc.Realm; -import org.elasticsearch.xpack.core.security.SecurityExtension; import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult; import java.security.AccessController; @@ -43,19 +43,22 @@ public class ExampleSecurityExtension implements SecurityExtension { } @Override - public Map getRealms(ResourceWatcherService resourceWatcherService) { - return Collections.singletonMap(CustomRealm.TYPE, CustomRealm::new); + public Map getRealms(SecurityComponents components) { + return Map.ofEntries( + Map.entry(CustomRealm.TYPE, CustomRealm::new), + Map.entry(CustomRoleMappingRealm.TYPE, + config -> new CustomRoleMappingRealm(config, components.roleMapper())) + ); } @Override - public AuthenticationFailureHandler getAuthenticationFailureHandler() { + public AuthenticationFailureHandler getAuthenticationFailureHandler(SecurityComponents components) { return new CustomAuthenticationFailureHandler(); } - @Override public List, ActionListener>> - getRolesProviders(Settings settings, ResourceWatcherService resourceWatcherService) { + getRolesProviders(Settings settings, SecurityComponents components) { CustomInMemoryRolesProvider rp1 = new CustomInMemoryRolesProvider(Collections.singletonMap(ROLE_A, "read")); Map roles = new HashMap<>(); roles.put(ROLE_A, "all"); diff --git a/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/SpiExtensionPlugin.java b/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/SpiExtensionPlugin.java index eedb06f2c1bad..ee47fa0bf7b5b 100644 --- a/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/SpiExtensionPlugin.java +++ b/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/SpiExtensionPlugin.java @@ -7,6 +7,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.example.realm.CustomRealm; +import org.elasticsearch.example.realm.CustomRoleMappingRealm; import org.elasticsearch.plugins.ActionPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.rest.RestHeaderDefinition; @@ -33,6 +34,7 @@ public Collection getRestHeaders() { public List> getSettings() { List> list = new ArrayList<>(RealmSettings.getStandardSettings(CustomRealm.TYPE)); list.add(RealmSettings.simpleString(CustomRealm.TYPE, "filtered_setting", Setting.Property.NodeScope, Setting.Property.Filtered)); + list.addAll(RealmSettings.getStandardSettings(CustomRoleMappingRealm.TYPE)); return list; } } diff --git a/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/realm/CustomRoleMappingRealm.java b/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/realm/CustomRoleMappingRealm.java new file mode 100644 index 0000000000000..2b277d39b1364 --- /dev/null +++ b/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/realm/CustomRoleMappingRealm.java @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.example.realm; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.cache.Cache; +import org.elasticsearch.common.cache.CacheBuilder; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; +import org.elasticsearch.xpack.core.security.authc.AuthenticationToken; +import org.elasticsearch.xpack.core.security.authc.Realm; +import org.elasticsearch.xpack.core.security.authc.RealmConfig; +import org.elasticsearch.xpack.core.security.authc.support.CachingRealm; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; +import org.elasticsearch.xpack.core.security.user.User; + +import java.util.List; +import java.util.Map; + +/** + * An example realm with specific behaviours: + * (1) It only supports lookup (that is, "run-as" and "authorization_realms") but not authentication + * (2) It performs role mapping to determine the roles for the looked-up user + * (3) It caches the looked-up User objects + */ +public class CustomRoleMappingRealm extends Realm implements CachingRealm { + + public static final String TYPE = "custom_role_mapping"; + + static final String USERNAME = "role_mapped_user"; + static final String USER_GROUP = "user_group"; + + private final Cache cache; + private final UserRoleMapper roleMapper; + + public CustomRoleMappingRealm(RealmConfig config, UserRoleMapper roleMapper) { + super(config); + this.cache = CacheBuilder.builder().build(); + this.roleMapper = roleMapper; + this.roleMapper.refreshRealmOnChange(this); + } + + @Override + public boolean supports(AuthenticationToken token) { + return false; + } + + @Override + public UsernamePasswordToken token(ThreadContext threadContext) { + return null; + } + + @Override + public void authenticate(AuthenticationToken authToken, ActionListener listener) { + listener.onResponse(AuthenticationResult.notHandled()); + } + + @Override + public void lookupUser(String username, ActionListener listener) { + final User user = cache.get(username); + if (user != null) { + listener.onResponse(user); + return; + } + if (USERNAME.equals(username)) { + buildUser(username, ActionListener.wrap( + u -> listener.onResponse(cache.computeIfAbsent(username, k -> u)), + listener::onFailure + )); + } else { + listener.onResponse(null); + } + } + + private void buildUser(String username, ActionListener listener) { + final UserRoleMapper.UserData data = new UserRoleMapper.UserData(username, null, List.of(USER_GROUP), Map.of(), super.config); + roleMapper.resolveRoles(data, ActionListener.wrap( + roles -> listener.onResponse(new User(username, roles.toArray(String[]::new))), + listener::onFailure + )); + } + + @Override + public void expire(String username) { + this.cache.invalidate(username); + } + + @Override + public void expireAll() { + this.cache.invalidateAll(); + } +} diff --git a/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmIT.java b/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmIT.java new file mode 100644 index 0000000000000..4eee89d0d6ce5 --- /dev/null +++ b/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmIT.java @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.example.realm; + +import org.elasticsearch.client.Request; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.Response; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.ESRestTestCase; +import org.junit.Before; + +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + +/** + * Integration test to test authentication with the custom role-mapping realm + */ +public class CustomRoleMappingRealmIT extends ESRestTestCase { + + private String expectedRole; + + @Override + protected Settings restClientSettings() { + return Settings.builder() + .put(ThreadContext.PREFIX + "." + CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER) + .put(ThreadContext.PREFIX + "." + CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW.toString()) + .build(); + } + + @Before + public void setupRoleMapping() throws Exception { + expectedRole = randomAlphaOfLengthBetween(4, 16); + Request request = new Request("PUT", "/_security/role_mapping/test"); + request.setJsonEntity("{" + + "\"enabled\": true," + + "\"roles\":[\"" + + expectedRole + + "\"]," + + "\"rules\":{\"field\":{\"groups\":\"" + CustomRoleMappingRealm.USER_GROUP + "\"} }" + + "}"); + adminClient().performRequest(request); + } + + public void testUserWithRoleMapping() throws Exception { + Request request = new Request("GET", "/_security/_authenticate"); + RequestOptions.Builder options = request.getOptions().toBuilder(); + // Authenticate as the custom realm superuser + options.addHeader(CustomRealm.USER_HEADER, CustomRealm.KNOWN_USER); + options.addHeader(CustomRealm.PW_HEADER, CustomRealm.KNOWN_PW.toString()); + // But "run-as" the role mapped user + options.addHeader("es-security-runas-user", CustomRoleMappingRealm.USERNAME); + request.setOptions(options); + + final Response response = client().performRequest(request); + final Map authenticate = entityAsMap(response); + assertThat(authenticate.get("username"), is(CustomRoleMappingRealm.USERNAME)); + assertThat(authenticate.get("roles"), instanceOf(List.class)); + assertThat(authenticate.get("roles"), equalTo(List.of(expectedRole))); + } + +} diff --git a/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmTests.java b/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmTests.java new file mode 100644 index 0000000000000..45d5674639237 --- /dev/null +++ b/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmTests.java @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.example.realm; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.env.Environment; +import org.elasticsearch.env.TestEnvironment; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; +import org.elasticsearch.xpack.core.security.authc.RealmConfig; +import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; +import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; +import org.elasticsearch.xpack.core.security.user.User; +import org.hamcrest.Matchers; +import org.mockito.Mockito; + +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.hamcrest.Matchers.arrayContainingInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.sameInstance; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; + +public class CustomRoleMappingRealmTests extends ESTestCase { + + public void testCachingOfUserLookup() throws Exception { + final Environment env = super.newEnvironment(); + final UserRoleMapper roleMapper = mock(UserRoleMapper.class); + final RealmConfig realmConfig = new RealmConfig( + new RealmConfig.RealmIdentifier(CustomRoleMappingRealm.TYPE, "test"), + env.settings(), env, new ThreadContext(env.settings()) + ); + CustomRoleMappingRealm realm = new CustomRoleMappingRealm(realmConfig, roleMapper); + + final AtomicInteger roleMappingCounter = new AtomicInteger(0); + doAnswer(inv -> { + ActionListener> listener = (ActionListener>) inv.getArguments()[1]; + roleMappingCounter.incrementAndGet(); + listener.onResponse(Set.of("role1", "role2")); + return null; + }).when(roleMapper).resolveRoles(any(UserRoleMapper.UserData.class), any(ActionListener.class)); + + PlainActionFuture future = new PlainActionFuture<>(); + realm.lookupUser(CustomRoleMappingRealm.USERNAME, future); + final User user1 = future.get(); + assertThat(user1.principal(), is(CustomRoleMappingRealm.USERNAME)); + assertThat(user1.roles(), arrayContainingInAnyOrder("role1", "role2")); + assertThat(roleMappingCounter.get(), is(1)); + + future = new PlainActionFuture<>(); + realm.lookupUser(CustomRoleMappingRealm.USERNAME, future); + final User user2 = future.get(); + assertThat(user2, sameInstance(user1)); + assertThat(roleMappingCounter.get(), is(1)); + } + +} From 83dc7b0fc72a32207c54b739cd4c90ef3f39279f Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Tue, 31 Dec 2019 12:39:22 +1100 Subject: [PATCH 2/6] Fix javadoc --- .../elasticsearch/xpack/core/security/SecurityExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java index 1e85a10e4347d..eef0a544fca9d 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java @@ -33,7 +33,7 @@ public interface SecurityExtension { /** - * This interface provides access to components (clients & services) that may be used + * This interface provides access to components (clients and services) that may be used * within custom realms and role providers. */ interface SecurityComponents { From e132cccd523f174be7b0f6718d9e36f0756fd455 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Tue, 31 Dec 2019 15:34:15 +1100 Subject: [PATCH 3/6] Fix unchecked warnings --- .../realm/CustomRoleMappingRealmTests.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmTests.java b/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmTests.java index 45d5674639237..4057f2636d08f 100644 --- a/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmTests.java +++ b/x-pack/qa/security-example-spi-extension/src/test/java/org/elasticsearch/example/realm/CustomRoleMappingRealmTests.java @@ -7,26 +7,18 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.PlainActionFuture; -import org.elasticsearch.common.settings.SecureString; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.env.Environment; -import org.elasticsearch.env.TestEnvironment; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.support.UserRoleMapper; -import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken; import org.elasticsearch.xpack.core.security.user.User; -import org.hamcrest.Matchers; -import org.mockito.Mockito; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; import static org.hamcrest.Matchers.arrayContainingInAnyOrder; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.sameInstance; import static org.mockito.Matchers.any; @@ -45,12 +37,10 @@ public void testCachingOfUserLookup() throws Exception { CustomRoleMappingRealm realm = new CustomRoleMappingRealm(realmConfig, roleMapper); final AtomicInteger roleMappingCounter = new AtomicInteger(0); - doAnswer(inv -> { - ActionListener> listener = (ActionListener>) inv.getArguments()[1]; + mockRoleMapping(roleMapper, () -> { roleMappingCounter.incrementAndGet(); - listener.onResponse(Set.of("role1", "role2")); - return null; - }).when(roleMapper).resolveRoles(any(UserRoleMapper.UserData.class), any(ActionListener.class)); + return Set.of("role1", "role2"); + }); PlainActionFuture future = new PlainActionFuture<>(); realm.lookupUser(CustomRoleMappingRealm.USERNAME, future); @@ -66,4 +56,13 @@ public void testCachingOfUserLookup() throws Exception { assertThat(roleMappingCounter.get(), is(1)); } + @SuppressWarnings("unchecked") + private void mockRoleMapping(UserRoleMapper roleMapper, Supplier> supplier) { + doAnswer(inv -> { + ActionListener> listener = (ActionListener>) inv.getArguments()[1]; + listener.onResponse(supplier.get()); + return null; + }).when(roleMapper).resolveRoles(any(UserRoleMapper.UserData.class), any(ActionListener.class)); + } + } From e320fd585037ac6a707e052d0e2be7b8d0515417 Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Mon, 6 Jan 2020 15:44:17 +1100 Subject: [PATCH 4/6] Add settings and environment --- .../core/security/SecurityExtension.java | 5 +++++ .../xpack/security/Security.java | 2 +- .../security/support/ExtensionComponents.java | 22 ++++++++++++++----- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java index eef0a544fca9d..5ef11f053b174 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java @@ -10,6 +10,7 @@ import org.elasticsearch.client.Client; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler; @@ -37,6 +38,10 @@ public interface SecurityExtension { * within custom realms and role providers. */ interface SecurityComponents { + /** Global settings for the current node */ + Settings settings(); + /** Provides access to key filesystem paths */ + Environment environment(); /** An internal client for retrieving information/data from this cluster */ Client client(); /** The Elasticsearch thread pools */ diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index e0d662b34e6fb..4d111110db00f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -392,7 +392,7 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste final AnonymousUser anonymousUser = new AnonymousUser(settings); final ReservedRealm reservedRealm = new ReservedRealm(env, settings, nativeUsersStore, anonymousUser, securityIndex.get(), threadPool); - final SecurityExtension.SecurityComponents extensionComponents = new ExtensionComponents(client, threadPool, clusterService, + final SecurityExtension.SecurityComponents extensionComponents = new ExtensionComponents(env, client, clusterService, resourceWatcherService, nativeRoleMappingStore); Map realmFactories = new HashMap<>(InternalRealms.getFactories(threadPool, resourceWatcherService, getSslService(), nativeUsersStore, nativeRoleMappingStore, securityIndex.get())); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/ExtensionComponents.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/ExtensionComponents.java index f6d845a079754..4a074540e6190 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/ExtensionComponents.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/support/ExtensionComponents.java @@ -8,6 +8,8 @@ import org.elasticsearch.client.Client; import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.env.Environment; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.security.SecurityExtension; @@ -16,22 +18,32 @@ /** * Immutable implementation of {@link SecurityExtension.SecurityComponents}. */ -public class ExtensionComponents implements SecurityExtension.SecurityComponents { +public final class ExtensionComponents implements SecurityExtension.SecurityComponents { + private final Environment environment; private final Client client; - private final ThreadPool threadPool; private final ClusterService clusterService; private final ResourceWatcherService resourceWatcherService; private final UserRoleMapper roleMapper; - public ExtensionComponents(Client client, ThreadPool threadPool, ClusterService clusterService, + public ExtensionComponents(Environment environment, Client client, ClusterService clusterService, ResourceWatcherService resourceWatcherService, UserRoleMapper roleMapper) { + this.environment = environment; this.client = client; - this.threadPool = threadPool; this.clusterService = clusterService; this.resourceWatcherService = resourceWatcherService; this.roleMapper = roleMapper; } + @Override + public Settings settings() { + return environment.settings(); + } + + @Override + public Environment environment() { + return environment; + } + @Override public Client client() { return client; @@ -39,7 +51,7 @@ public Client client() { @Override public ThreadPool threadPool() { - return threadPool; + return client.threadPool(); } @Override From ee713a7acefb00083a87b566894eb9f63d68af1e Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Mon, 13 Jan 2020 23:31:43 +1100 Subject: [PATCH 5/6] Remove Settings argument from getRolesProviders --- .../elasticsearch/xpack/core/security/SecurityExtension.java | 3 +-- .../main/java/org/elasticsearch/xpack/security/Security.java | 2 +- .../org/elasticsearch/example/ExampleSecurityExtension.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java index 5ef11f053b174..b45d4ad99ccbd 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityExtension.java @@ -99,11 +99,10 @@ default AuthenticationFailureHandler getAuthenticationFailureHandler(SecurityCom * * By default, an empty list is returned. * - * @param settings The configured settings for the node * @param components Access to components that may be used to build roles */ default List, ActionListener>> - getRolesProviders(Settings settings, SecurityComponents components) { + getRolesProviders(SecurityComponents components) { return Collections.emptyList(); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 8e7368aab3c1b..e295dfd696bc3 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -423,7 +423,7 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste final ReservedRolesStore reservedRolesStore = new ReservedRolesStore(); List, ActionListener>> rolesProviders = new ArrayList<>(); for (SecurityExtension extension : securityExtensions) { - rolesProviders.addAll(extension.getRolesProviders(settings, extensionComponents)); + rolesProviders.addAll(extension.getRolesProviders(extensionComponents)); } final ApiKeyService apiKeyService = new ApiKeyService(settings, Clock.systemUTC(), client, getLicenseState(), securityIndex.get(), diff --git a/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java b/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java index b20e4274f9901..d1739040086b7 100644 --- a/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java +++ b/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java @@ -58,7 +58,7 @@ public AuthenticationFailureHandler getAuthenticationFailureHandler(SecurityComp @Override public List, ActionListener>> - getRolesProviders(Settings settings, SecurityComponents components) { + getRolesProviders(SecurityComponents components) { CustomInMemoryRolesProvider rp1 = new CustomInMemoryRolesProvider(Collections.singletonMap(ROLE_A, "read")); Map roles = new HashMap<>(); roles.put(ROLE_A, "all"); From 52e051e280b00007049c9f94b5d5fd10bae1dabd Mon Sep 17 00:00:00 2001 From: Tim Vernum Date: Tue, 14 Jan 2020 00:06:28 +1100 Subject: [PATCH 6/6] Remove unused import --- .../java/org/elasticsearch/example/ExampleSecurityExtension.java | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java b/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java index d1739040086b7..764f044a01154 100644 --- a/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java +++ b/x-pack/qa/security-example-spi-extension/src/main/java/org/elasticsearch/example/ExampleSecurityExtension.java @@ -6,7 +6,6 @@ package org.elasticsearch.example; import org.elasticsearch.action.ActionListener; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.example.realm.CustomAuthenticationFailureHandler; import org.elasticsearch.example.realm.CustomRealm; import org.elasticsearch.example.realm.CustomRoleMappingRealm;