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 3f567abb5701e..4b4c33c6bea5f 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 @@ -465,6 +465,7 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste final NativePrivilegeStore privilegeStore = new NativePrivilegeStore(settings, client, securityIndex.get()); components.add(privilegeStore); + final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(settings); final FileRolesStore fileRolesStore = new FileRolesStore(settings, env, resourceWatcherService, getLicenseState()); final NativeRolesStore nativeRolesStore = new NativeRolesStore(settings, client, getLicenseState(), securityIndex.get()); final ReservedRolesStore reservedRolesStore = new ReservedRolesStore(); @@ -473,13 +474,13 @@ Collection createComponents(Client client, ThreadPool threadPool, Cluste rolesProviders.addAll(extension.getRolesProviders(settings, resourceWatcherService)); } final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, - reservedRolesStore, privilegeStore, rolesProviders, threadPool.getThreadContext(), getLicenseState()); + reservedRolesStore, privilegeStore, rolesProviders, threadPool.getThreadContext(), getLicenseState(), fieldPermissionsCache); securityIndex.get().addIndexStateListener(allRolesStore::onSecurityIndexStateChange); // to keep things simple, just invalidate all cached entries on license change. this happens so rarely that the impact should be // minimal getLicenseState().addListener(allRolesStore::invalidateAll); final AuthorizationService authzService = new AuthorizationService(settings, allRolesStore, clusterService, - auditTrailService, failureHandler, threadPool, anonymousUser, apiKeyService); + auditTrailService, failureHandler, threadPool, anonymousUser, apiKeyService, fieldPermissionsCache); components.add(nativeRolesStore); // used by roles actions components.add(reservedRolesStore); // used by roles actions components.add(allRolesStore); // for SecurityFeatureSet and clear roles cache diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java index 808817c4bc3eb..1c860766b4751 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java @@ -38,7 +38,6 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache; import org.elasticsearch.xpack.core.security.authz.permission.Role; import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; @@ -96,8 +95,7 @@ public class ApiKeyService { private final Hasher hasher; private final boolean enabled; - public ApiKeyService(Settings settings, Clock clock, Client client, - SecurityIndexManager securityIndex, ClusterService clusterService) { + public ApiKeyService(Settings settings, Clock clock, Client client, SecurityIndexManager securityIndex, ClusterService clusterService) { this.clock = clock; this.client = client; this.securityIndex = securityIndex; @@ -221,25 +219,13 @@ void authenticateWithApiKeyIfPresent(ThreadContext ctx, ActionListener listener) { + public void getRoleForApiKey(Authentication authentication, CompositeRolesStore rolesStore, ActionListener listener) { if (authentication.getAuthenticationType() != Authentication.AuthenticationType.API_KEY) { throw new IllegalStateException("authentication type must be api key but is " + authentication.getAuthenticationType()); } final Map metadata = authentication.getMetadata(); final String apiKeyId = (String) metadata.get(API_KEY_ID_KEY); - final String contextKeyId = threadContext.getTransient(API_KEY_ID_KEY); - if (apiKeyId.equals(contextKeyId)) { - final Role preBuiltRole = threadContext.getTransient(API_KEY_ROLE_KEY); - if (preBuiltRole != null) { - listener.onResponse(preBuiltRole); - return; - } - } else if (contextKeyId != null) { - throw new IllegalStateException("authentication api key id [" + apiKeyId + "] does not match context value [" + - contextKeyId + "]"); - } final Map roleDescriptors = (Map) metadata.get(API_KEY_ROLE_DESCRIPTORS_KEY); final List roleDescriptorList = roleDescriptors.entrySet().stream() @@ -258,11 +244,7 @@ public void getRoleForApiKey(Authentication authentication, ThreadContext thread } }).collect(Collectors.toList()); - rolesStore.buildRoleFromDescriptors(roleDescriptorList, fieldPermissionsCache, ActionListener.wrap(role -> { - threadContext.putTransient(API_KEY_ID_KEY, apiKeyId); - threadContext.putTransient(API_KEY_ROLE_KEY, role); - listener.onResponse(role); - }, listener::onFailure)); + rolesStore.buildAndCacheRoleFromDescriptors(roleDescriptorList, apiKeyId, listener); } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index ae5dd7e36cf59..99a8e9c7c48b3 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -112,7 +112,8 @@ public class AuthorizationService { public AuthorizationService(Settings settings, CompositeRolesStore rolesStore, ClusterService clusterService, AuditTrailService auditTrail, AuthenticationFailureHandler authcFailureHandler, - ThreadPool threadPool, AnonymousUser anonymousUser, ApiKeyService apiKeyService) { + ThreadPool threadPool, AnonymousUser anonymousUser, ApiKeyService apiKeyService, + FieldPermissionsCache fieldPermissionsCache) { this.rolesStore = rolesStore; this.clusterService = clusterService; this.auditTrail = auditTrail; @@ -122,7 +123,7 @@ public AuthorizationService(Settings settings, CompositeRolesStore rolesStore, C this.anonymousUser = anonymousUser; this.isAnonymousEnabled = AnonymousUser.isAnonymousEnabled(settings); this.anonymousAuthzExceptionEnabled = ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.get(settings); - this.fieldPermissionsCache = new FieldPermissionsCache(settings); + this.fieldPermissionsCache = fieldPermissionsCache; this.apiKeyService = apiKeyService; } @@ -480,7 +481,7 @@ public void roles(User user, Authentication authentication, ActionListener final Authentication.AuthenticationType authType = authentication.getAuthenticationType(); if (authType == Authentication.AuthenticationType.API_KEY) { - apiKeyService.getRoleForApiKey(authentication, threadContext, rolesStore, fieldPermissionsCache, roleActionListener); + apiKeyService.getRoleForApiKey(authentication, rolesStore, roleActionListener); } else { Set roleNames = new HashSet<>(); Collections.addAll(roleNames, user.roles()); @@ -496,7 +497,7 @@ public void roles(User user, Authentication authentication, ActionListener } else if (roleNames.contains(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName())) { roleActionListener.onResponse(ReservedRolesStore.SUPERUSER_ROLE); } else { - rolesStore.roles(roleNames, fieldPermissionsCache, roleActionListener); + rolesStore.roles(roleNames, roleActionListener); } } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java index b4c2a34fc747c..378760604811d 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java @@ -68,7 +68,7 @@ */ public class CompositeRolesStore { - + private static final String ROLES_STORE_SOURCE = "roles_stores"; private static final Setting CACHE_SIZE_SETTING = Setting.intSetting("xpack.security.authz.store.roles.cache.max_size", 10000, Property.NodeScope); private static final Setting NEGATIVE_LOOKUP_CACHE_SIZE_SETTING = @@ -92,7 +92,8 @@ public class CompositeRolesStore { private final NativeRolesStore nativeRolesStore; private final NativePrivilegeStore privilegeStore; private final XPackLicenseState licenseState; - private final Cache, Role> roleCache; + private final FieldPermissionsCache fieldPermissionsCache; + private final Cache roleCache; private final Cache negativeLookupCache; private final ThreadContext threadContext; private final AtomicLong numInvalidation = new AtomicLong(); @@ -102,13 +103,14 @@ public class CompositeRolesStore { public CompositeRolesStore(Settings settings, FileRolesStore fileRolesStore, NativeRolesStore nativeRolesStore, ReservedRolesStore reservedRolesStore, NativePrivilegeStore privilegeStore, List, ActionListener>> rolesProviders, - ThreadContext threadContext, XPackLicenseState licenseState) { + ThreadContext threadContext, XPackLicenseState licenseState, FieldPermissionsCache fieldPermissionsCache) { this.fileRolesStore = fileRolesStore; fileRolesStore.addListener(this::invalidate); this.nativeRolesStore = nativeRolesStore; this.privilegeStore = privilegeStore; this.licenseState = licenseState; - CacheBuilder, Role> builder = CacheBuilder.builder(); + this.fieldPermissionsCache = fieldPermissionsCache; + CacheBuilder builder = CacheBuilder.builder(); final int cacheSize = CACHE_SIZE_SETTING.get(settings); if (cacheSize >= 0) { builder.setMaximumWeight(cacheSize); @@ -133,8 +135,9 @@ public CompositeRolesStore(Settings settings, FileRolesStore fileRolesStore, Nat } } - public void roles(Set roleNames, FieldPermissionsCache fieldPermissionsCache, ActionListener roleActionListener) { - Role existing = roleCache.get(roleNames); + public void roles(Set roleNames, ActionListener roleActionListener) { + final RoleKey roleKey = new RoleKey(roleNames, ROLES_STORE_SOURCE); + Role existing = roleCache.get(roleKey); if (existing != null) { roleActionListener.onResponse(existing); } else { @@ -154,33 +157,54 @@ public void roles(Set roleNames, FieldPermissionsCache fieldPermissionsC .filter((rd) -> rd.isUsingDocumentOrFieldLevelSecurity() == false) .collect(Collectors.toSet()); } - logger.trace("Building role from descriptors [{}] for names [{}]", effectiveDescriptors, roleNames); - buildRoleFromDescriptors(effectiveDescriptors, fieldPermissionsCache, privilegeStore, ActionListener.wrap(role -> { - if (role != null && rolesRetrievalResult.isSuccess()) { - try (ReleasableLock ignored = readLock.acquire()) { - /* this is kinda spooky. We use a read/write lock to ensure we don't modify the cache if we hold - * the write lock (fetching stats for instance - which is kinda overkill?) but since we fetching - * stuff in an async fashion we need to make sure that if the cache got invalidated since we - * started the request we don't put a potential stale result in the cache, hence the - * numInvalidation.get() comparison to the number of invalidation when we started. we just try to - * be on the safe side and don't cache potentially stale results - */ - if (invalidationCounter == numInvalidation.get()) { - roleCache.computeIfAbsent(roleNames, (s) -> role); - } - } - - for (String missingRole : rolesRetrievalResult.getMissingRoles()) { - negativeLookupCache.computeIfAbsent(missingRole, s -> Boolean.TRUE); - } - } - roleActionListener.onResponse(role); - }, roleActionListener::onFailure)); + buildThenMaybeCacheRole(roleKey, effectiveDescriptors, rolesRetrievalResult.getMissingRoles(), + rolesRetrievalResult.isSuccess(), invalidationCounter, roleActionListener); }, roleActionListener::onFailure)); } } + public void buildAndCacheRoleFromDescriptors(Collection roleDescriptors, String source, + ActionListener listener) { + if (ROLES_STORE_SOURCE.equals(source)) { + throw new IllegalArgumentException("source [" + ROLES_STORE_SOURCE + "] is reserved for internal use"); + } + RoleKey roleKey = new RoleKey(roleDescriptors.stream().map(RoleDescriptor::getName).collect(Collectors.toSet()), source); + Role existing = roleCache.get(roleKey); + if (existing != null) { + listener.onResponse(existing); + } else { + final long invalidationCounter = numInvalidation.get(); + buildThenMaybeCacheRole(roleKey, roleDescriptors, Collections.emptySet(), true, invalidationCounter, listener); + } + } + + private void buildThenMaybeCacheRole(RoleKey roleKey, Collection roleDescriptors, Set missing, + boolean tryCache, long invalidationCounter, ActionListener listener) { + logger.trace("Building role from descriptors [{}] for names [{}] from source [{}]", roleDescriptors, roleKey.names, roleKey.source); + buildRoleFromDescriptors(roleDescriptors, fieldPermissionsCache, privilegeStore, ActionListener.wrap(role -> { + if (role != null && tryCache) { + try (ReleasableLock ignored = readLock.acquire()) { + /* this is kinda spooky. We use a read/write lock to ensure we don't modify the cache if we hold + * the write lock (fetching stats for instance - which is kinda overkill?) but since we fetching + * stuff in an async fashion we need to make sure that if the cache got invalidated since we + * started the request we don't put a potential stale result in the cache, hence the + * numInvalidation.get() comparison to the number of invalidation when we started. we just try to + * be on the safe side and don't cache potentially stale results + */ + if (invalidationCounter == numInvalidation.get()) { + roleCache.computeIfAbsent(roleKey, (s) -> role); + } + } + + for (String missingRole : missing) { + negativeLookupCache.computeIfAbsent(missingRole, s -> Boolean.TRUE); + } + } + listener.onResponse(role); + }, listener::onFailure)); + } + public void getRoleDescriptors(Set roleNames, ActionListener> listener) { roleDescriptors(roleNames, ActionListener.wrap(rolesRetrievalResult -> { if (rolesRetrievalResult.isSuccess()) { @@ -241,11 +265,6 @@ private String names(Collection descriptors) { return descriptors.stream().map(RoleDescriptor::getName).collect(Collectors.joining(",")); } - public void buildRoleFromDescriptors(Collection roleDescriptors, FieldPermissionsCache fieldPermissionsCache, - ActionListener listener) { - buildRoleFromDescriptors(roleDescriptors, fieldPermissionsCache, privilegeStore, listener); - } - public static void buildRoleFromDescriptors(Collection roleDescriptors, FieldPermissionsCache fieldPermissionsCache, NativePrivilegeStore privilegeStore, ActionListener listener) { if (roleDescriptors.isEmpty()) { @@ -346,10 +365,10 @@ public void invalidate(String role) { // the cache cannot be modified while doing this operation per the terms of the cache iterator try (ReleasableLock ignored = writeLock.acquire()) { - Iterator> keyIter = roleCache.keys().iterator(); + Iterator keyIter = roleCache.keys().iterator(); while (keyIter.hasNext()) { - Set key = keyIter.next(); - if (key.contains(role)) { + RoleKey key = keyIter.next(); + if (key.names.contains(role)) { keyIter.remove(); } } @@ -362,10 +381,10 @@ public void invalidate(Set roles) { // the cache cannot be modified while doing this operation per the terms of the cache iterator try (ReleasableLock ignored = writeLock.acquire()) { - Iterator> keyIter = roleCache.keys().iterator(); + Iterator keyIter = roleCache.keys().iterator(); while (keyIter.hasNext()) { - Set key = keyIter.next(); - if (Sets.haveEmptyIntersection(key, roles) == false) { + RoleKey key = keyIter.next(); + if (Sets.haveEmptyIntersection(key.names, roles) == false) { keyIter.remove(); } } @@ -461,6 +480,31 @@ private Set getMissingRoles() { } } + private static final class RoleKey { + + private final Set names; + private final String source; + + private RoleKey(Set names, String source) { + this.names = Objects.requireNonNull(names); + this.source = Objects.requireNonNull(source); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RoleKey roleKey = (RoleKey) o; + return names.equals(roleKey.names) && + source.equals(roleKey.source); + } + + @Override + public int hashCode() { + return Objects.hash(names, source); + } + } + public static List> getSettings() { return Arrays.asList(CACHE_SIZE_SETTING, NEGATIVE_LOOKUP_CACHE_SIZE_SETTING); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java index 4f75f1158a968..cee5711780f11 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java @@ -28,7 +28,6 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache; import org.elasticsearch.xpack.core.security.authz.permission.Role; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.user.User; @@ -45,7 +44,6 @@ import java.util.HashMap; import java.util.Map; -import static org.hamcrest.Matchers.containsString; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; @@ -151,38 +149,6 @@ public void testValidateApiKey() throws Exception { assertFalse(result.isAuthenticated()); } - public void testGetRolesForApiKeyFromContext() throws Exception { - final String keyId = randomAlphaOfLength(12); - final Authentication authentication = new Authentication(new User("joe"), new RealmRef("apikey", "apikey", "node"), null, - Version.CURRENT, AuthenticationType.API_KEY, Collections.singletonMap(ApiKeyService.API_KEY_ID_KEY, keyId)); - final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); - threadContext.putTransient(ApiKeyService.API_KEY_ID_KEY, keyId); - threadContext.putTransient(ApiKeyService.API_KEY_ROLE_KEY, ReservedRolesStore.SUPERUSER_ROLE); - ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null, - ClusterServiceUtils.createClusterService(threadPool)); - PlainActionFuture roleFuture = new PlainActionFuture<>(); - service.getRoleForApiKey(authentication, threadContext, mock(CompositeRolesStore.class), new FieldPermissionsCache(Settings.EMPTY), - roleFuture); - Role role = roleFuture.get(); - assertEquals(ReservedRolesStore.SUPERUSER_ROLE, role); - } - - public void testGetRolesForApiKeyIncorrectKeyId() { - final String keyId = randomAlphaOfLength(12); - final Authentication authentication = new Authentication(new User("joe"), new RealmRef("apikey", "apikey", "node"), null, - Version.CURRENT, AuthenticationType.API_KEY, Collections.singletonMap(ApiKeyService.API_KEY_ID_KEY, keyId)); - final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); - threadContext.putTransient(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLength(11)); - threadContext.putTransient(ApiKeyService.API_KEY_ROLE_KEY, ReservedRolesStore.SUPERUSER_ROLE); - ApiKeyService service = new ApiKeyService(Settings.EMPTY, Clock.systemUTC(), null, null, - ClusterServiceUtils.createClusterService(threadPool)); - PlainActionFuture roleFuture = new PlainActionFuture<>(); - IllegalStateException ise = expectThrows(IllegalStateException.class, () -> service.getRoleForApiKey(authentication, threadContext, - mock(CompositeRolesStore.class),new FieldPermissionsCache(Settings.EMPTY), roleFuture)); - assertThat(ise.getMessage(), containsString(keyId)); - assertThat(ise.getMessage(), containsString(threadContext.getTransient(ApiKeyService.API_KEY_ID_KEY))); - } - public void testGetRolesForApiKeyNotInContext() throws Exception { Map superUserRdMap; try (XContentBuilder builder = JsonXContent.contentBuilder()) { @@ -214,10 +180,10 @@ public void testGetRolesForApiKeyNotInContext() throws Exception { listener.onFailure(new IllegalStateException("unexpected role name " + descriptors.iterator().next().getName())); } return Void.TYPE; - }).when(rolesStore).buildRoleFromDescriptors(any(Collection.class), any(FieldPermissionsCache.class), any(ActionListener.class)); + }).when(rolesStore).buildAndCacheRoleFromDescriptors(any(Collection.class), any(String.class), any(ActionListener.class)); PlainActionFuture roleFuture = new PlainActionFuture<>(); - service.getRoleForApiKey(authentication, threadContext, rolesStore, new FieldPermissionsCache(Settings.EMPTY), roleFuture); + service.getRoleForApiKey(authentication, rolesStore, roleFuture); Role role = roleFuture.get(); assertThat(role.names(), arrayContaining("superuser")); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 797a7694fb607..fa1f7274cf8db 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -211,7 +211,7 @@ public void setup() { ).when(privilegesStore).getPrivileges(any(Collection.class), any(Collection.class), any(ActionListener.class)); doAnswer((i) -> { - ActionListener callback = (ActionListener) i.getArguments()[2]; + ActionListener callback = (ActionListener) i.getArguments()[1]; Set names = (Set) i.getArguments()[0]; assertNotNull(names); Set roleDescriptors = new HashSet<>(); @@ -230,11 +230,11 @@ public void setup() { ); } return Void.TYPE; - }).when(rolesStore).roles(any(Set.class), any(FieldPermissionsCache.class), any(ActionListener.class)); + }).when(rolesStore).roles(any(Set.class), any(ActionListener.class)); apiKeyService = mock(ApiKeyService.class); authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, new AnonymousUser(settings), - apiKeyService); + apiKeyService, new FieldPermissionsCache(Settings.EMPTY)); } private void authorize(Authentication authentication, String action, TransportRequest request) { @@ -628,7 +628,8 @@ public void testDenialForAnonymousUser() throws IOException { Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "a_all").build(); final AnonymousUser anonymousUser = new AnonymousUser(settings); authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, - new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, apiKeyService); + new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, apiKeyService, + new FieldPermissionsCache(settings)); RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null); @@ -655,7 +656,7 @@ public void testDenialForAnonymousUserAuthorizationExceptionDisabled() throws IO final Authentication authentication = createAuthentication(new AnonymousUser(settings)); authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, new AnonymousUser(settings), - apiKeyService); + apiKeyService, new FieldPermissionsCache(settings)); RoleDescriptor role = new RoleDescriptor("a_all", null, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null); @@ -968,7 +969,8 @@ public void testAnonymousRolesAreAppliedToOtherUsers() throws IOException { Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "anonymous_user_role").build(); final AnonymousUser anonymousUser = new AnonymousUser(settings); authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, - new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, apiKeyService); + new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, apiKeyService, + new FieldPermissionsCache(settings)); roleMap.put("anonymous_user_role", new RoleDescriptor("anonymous_user_role", new String[]{"all"}, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null)); mockEmptyMetaData(); @@ -1004,7 +1006,8 @@ public void testAnonymousUserEnabledRoleAdded() throws IOException { Settings settings = Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "anonymous_user_role").build(); final AnonymousUser anonymousUser = new AnonymousUser(settings); authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, - new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, apiKeyService); + new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, anonymousUser, apiKeyService, + new FieldPermissionsCache(settings)); roleMap.put("anonymous_user_role", new RoleDescriptor("anonymous_user_role", new String[]{"all"}, new IndicesPrivileges[]{IndicesPrivileges.builder().indices("a").privileges("all").build()}, null)); mockEmptyMetaData(); @@ -1475,15 +1478,13 @@ public void testApiKeyAuthUsesApiKeyService() throws IOException { AuditUtil.getOrGenerateRequestId(threadContext); final Authentication authentication = createAuthentication(new User("test api key user", "api_key"), AuthenticationType.API_KEY); doAnswer(invocationOnMock -> { - ActionListener listener = (ActionListener) invocationOnMock.getArguments()[4]; + ActionListener listener = (ActionListener) invocationOnMock.getArguments()[2]; listener.onResponse(ReservedRolesStore.SUPERUSER_ROLE); return Void.TYPE; - }).when(apiKeyService).getRoleForApiKey(eq(authentication), eq(threadContext), eq(rolesStore), any(FieldPermissionsCache.class), - any(ActionListener.class)); + }).when(apiKeyService).getRoleForApiKey(eq(authentication), eq(rolesStore), any(ActionListener.class)); authorize(authentication, "cluster:admin/foo", new ClearScrollRequest()); - verify(apiKeyService).getRoleForApiKey(eq(authentication), eq(threadContext), eq(rolesStore), any(FieldPermissionsCache.class), - any(ActionListener.class)); + verify(apiKeyService).getRoleForApiKey(eq(authentication), eq(rolesStore), any(ActionListener.class)); verifyZeroInteractions(rolesStore); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java index 89ee0be915984..b7a85407a6f36 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java @@ -169,7 +169,7 @@ public void setup() { final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); doAnswer((i) -> { ActionListener callback = - (ActionListener) i.getArguments()[2]; + (ActionListener) i.getArguments()[1]; Set names = (Set) i.getArguments()[0]; assertNotNull(names); Set roleDescriptors = new HashSet<>(); @@ -188,13 +188,13 @@ public void setup() { ); } return Void.TYPE; - }).when(rolesStore).roles(any(Set.class), any(FieldPermissionsCache.class), any(ActionListener.class)); + }).when(rolesStore).roles(any(Set.class), any(ActionListener.class)); ClusterService clusterService = mock(ClusterService.class); when(clusterService.getClusterSettings()).thenReturn(new ClusterSettings(settings, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS)); authzService = new AuthorizationService(settings, rolesStore, clusterService, mock(AuditTrailService.class), new DefaultAuthenticationFailureHandler(Collections.emptyMap()), mock(ThreadPool.class), - new AnonymousUser(settings), mock(ApiKeyService.class)); + new AnonymousUser(settings), mock(ApiKeyService.class), new FieldPermissionsCache(settings)); defaultIndicesResolver = new IndicesAndAliasesResolver(settings, clusterService); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java index 6801ffd6bdf84..866b98096f461 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java @@ -79,6 +79,8 @@ public class CompositeRolesStoreTests extends ESTestCase { .put(XPackSettings.SECURITY_ENABLED.getKey(), true) .build(); + private final FieldPermissionsCache cache = new FieldPermissionsCache(Settings.EMPTY); + public void testRolesWhenDlsFlsUnlicensed() throws IOException { XPackLicenseState licenseState = mock(XPackLicenseState.class); when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(false); @@ -126,23 +128,22 @@ public void testRolesWhenDlsFlsUnlicensed() throws IOException { when(fileRolesStore.roleDescriptors(Collections.singleton("no_fls_dls"))).thenReturn(Collections.singleton(noFlsDlsRole)); CompositeRolesStore compositeRolesStore = new CompositeRolesStore(Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), - new ThreadContext(Settings.EMPTY), licenseState); + new ThreadContext(Settings.EMPTY), licenseState, cache); - FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); PlainActionFuture roleFuture = new PlainActionFuture<>(); - compositeRolesStore.roles(Collections.singleton("fls"), fieldPermissionsCache, roleFuture); + compositeRolesStore.roles(Collections.singleton("fls"), roleFuture); assertEquals(Role.EMPTY, roleFuture.actionGet()); roleFuture = new PlainActionFuture<>(); - compositeRolesStore.roles(Collections.singleton("dls"), fieldPermissionsCache, roleFuture); + compositeRolesStore.roles(Collections.singleton("dls"), roleFuture); assertEquals(Role.EMPTY, roleFuture.actionGet()); roleFuture = new PlainActionFuture<>(); - compositeRolesStore.roles(Collections.singleton("fls_dls"), fieldPermissionsCache, roleFuture); + compositeRolesStore.roles(Collections.singleton("fls_dls"), roleFuture); assertEquals(Role.EMPTY, roleFuture.actionGet()); roleFuture = new PlainActionFuture<>(); - compositeRolesStore.roles(Collections.singleton("no_fls_dls"), fieldPermissionsCache, roleFuture); + compositeRolesStore.roles(Collections.singleton("no_fls_dls"), roleFuture); assertNotEquals(Role.EMPTY, roleFuture.actionGet()); } @@ -192,23 +193,22 @@ public void testRolesWhenDlsFlsLicensed() throws IOException { when(fileRolesStore.roleDescriptors(Collections.singleton("no_fls_dls"))).thenReturn(Collections.singleton(noFlsDlsRole)); CompositeRolesStore compositeRolesStore = new CompositeRolesStore(Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), - new ThreadContext(Settings.EMPTY), licenseState); + new ThreadContext(Settings.EMPTY), licenseState, cache); - FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); PlainActionFuture roleFuture = new PlainActionFuture<>(); - compositeRolesStore.roles(Collections.singleton("fls"), fieldPermissionsCache, roleFuture); + compositeRolesStore.roles(Collections.singleton("fls"), roleFuture); assertNotEquals(Role.EMPTY, roleFuture.actionGet()); roleFuture = new PlainActionFuture<>(); - compositeRolesStore.roles(Collections.singleton("dls"), fieldPermissionsCache, roleFuture); + compositeRolesStore.roles(Collections.singleton("dls"), roleFuture); assertNotEquals(Role.EMPTY, roleFuture.actionGet()); roleFuture = new PlainActionFuture<>(); - compositeRolesStore.roles(Collections.singleton("fls_dls"), fieldPermissionsCache, roleFuture); + compositeRolesStore.roles(Collections.singleton("fls_dls"), roleFuture); assertNotEquals(Role.EMPTY, roleFuture.actionGet()); roleFuture = new PlainActionFuture<>(); - compositeRolesStore.roles(Collections.singleton("no_fls_dls"), fieldPermissionsCache, roleFuture); + compositeRolesStore.roles(Collections.singleton("no_fls_dls"), roleFuture); assertNotEquals(Role.EMPTY, roleFuture.actionGet()); } @@ -228,13 +228,12 @@ public void testNegativeLookupsAreCached() { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS)); + new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache); verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor final String roleName = randomAlphaOfLengthBetween(1, 10); PlainActionFuture future = new PlainActionFuture<>(); - final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); - compositeRolesStore.roles(Collections.singleton(roleName), fieldPermissionsCache, future); + compositeRolesStore.roles(Collections.singleton(roleName), future); final Role role = future.actionGet(); assertEquals(Role.EMPTY, role); verify(reservedRolesStore).accept(anySetOf(String.class), any(ActionListener.class)); @@ -250,7 +249,7 @@ public void testNegativeLookupsAreCached() { : Collections.singleton(roleName); for (int i = 0; i < numberOfTimesToCall; i++) { future = new PlainActionFuture<>(); - compositeRolesStore.roles(names, fieldPermissionsCache, future); + compositeRolesStore.roles(names, future); future.actionGet(); } @@ -279,13 +278,12 @@ public void testNegativeLookupsCacheDisabled() { .build(); final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(settings), - new XPackLicenseState(settings)); + new XPackLicenseState(settings), cache); verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor final String roleName = randomAlphaOfLengthBetween(1, 10); PlainActionFuture future = new PlainActionFuture<>(); - final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); - compositeRolesStore.roles(Collections.singleton(roleName), fieldPermissionsCache, future); + compositeRolesStore.roles(Collections.singleton(roleName), future); final Role role = future.actionGet(); assertEquals(Role.EMPTY, role); verify(reservedRolesStore).accept(anySetOf(String.class), any(ActionListener.class)); @@ -314,13 +312,12 @@ public void testNegativeLookupsAreNotCachedWithFailures() { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS)); + new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache); verify(fileRolesStore).addListener(any(Consumer.class)); // adds a listener in ctor final String roleName = randomAlphaOfLengthBetween(1, 10); PlainActionFuture future = new PlainActionFuture<>(); - final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); - compositeRolesStore.roles(Collections.singleton(roleName), fieldPermissionsCache, future); + compositeRolesStore.roles(Collections.singleton(roleName), future); final Role role = future.actionGet(); assertEquals(Role.EMPTY, role); verify(reservedRolesStore).accept(anySetOf(String.class), any(ActionListener.class)); @@ -333,7 +330,7 @@ public void testNegativeLookupsAreNotCachedWithFailures() { final Set names = Collections.singleton(roleName); for (int i = 0; i < numberOfTimesToCall; i++) { future = new PlainActionFuture<>(); - compositeRolesStore.roles(names, fieldPermissionsCache, future); + compositeRolesStore.roles(names, future); future.actionGet(); } @@ -392,12 +389,11 @@ public void testCustomRolesProviders() { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Arrays.asList(inMemoryProvider1, inMemoryProvider2), - new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS)); + new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache); final Set roleNames = Sets.newHashSet("roleA", "roleB", "unknown"); PlainActionFuture future = new PlainActionFuture<>(); - final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); - compositeRolesStore.roles(roleNames, fieldPermissionsCache, future); + compositeRolesStore.roles(roleNames, future); final Role role = future.actionGet(); // make sure custom roles providers populate roles correctly @@ -414,7 +410,7 @@ public void testCustomRolesProviders() { final int numberOfTimesToCall = scaledRandomIntBetween(1, 8); for (int i = 0; i < numberOfTimesToCall; i++) { future = new PlainActionFuture<>(); - compositeRolesStore.roles(Collections.singleton("unknown"), fieldPermissionsCache, future); + compositeRolesStore.roles(Collections.singleton("unknown"), future); future.actionGet(); } @@ -597,12 +593,11 @@ public void testCustomRolesProviderFailures() throws Exception { final CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Arrays.asList(inMemoryProvider1, failingProvider), - new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS)); + new ThreadContext(SECURITY_ENABLED_SETTINGS), new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache); final Set roleNames = Sets.newHashSet("roleA", "roleB", "unknown"); PlainActionFuture future = new PlainActionFuture<>(); - final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); - compositeRolesStore.roles(roleNames, fieldPermissionsCache, future); + compositeRolesStore.roles(roleNames, future); try { future.get(); fail("provider should have thrown a failure"); @@ -640,12 +635,11 @@ public void testCustomRolesProvidersLicensing() { xPackLicenseState.update(randomFrom(OperationMode.BASIC, OperationMode.GOLD, OperationMode.STANDARD), true, null); CompositeRolesStore compositeRolesStore = new CompositeRolesStore( Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), - Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState); + Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState, cache); Set roleNames = Sets.newHashSet("roleA"); PlainActionFuture future = new PlainActionFuture<>(); - FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); - compositeRolesStore.roles(roleNames, fieldPermissionsCache, future); + compositeRolesStore.roles(roleNames, future); Role role = future.actionGet(); // no roles should've been populated, as the license doesn't permit custom role providers @@ -653,13 +647,12 @@ Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(Nativ compositeRolesStore = new CompositeRolesStore( Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), - Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState); + Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState, cache); // these licenses allow custom role providers xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.TRIAL), true, null); roleNames = Sets.newHashSet("roleA"); future = new PlainActionFuture<>(); - fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); - compositeRolesStore.roles(roleNames, fieldPermissionsCache, future); + compositeRolesStore.roles(roleNames, future); role = future.actionGet(); // roleA should've been populated by the custom role provider, because the license allows it @@ -668,12 +661,11 @@ Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(Nativ // license expired, don't allow custom role providers compositeRolesStore = new CompositeRolesStore( Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), - Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState); + Arrays.asList(inMemoryProvider), new ThreadContext(Settings.EMPTY), xPackLicenseState, cache); xPackLicenseState.update(randomFrom(OperationMode.PLATINUM, OperationMode.TRIAL), false, null); roleNames = Sets.newHashSet("roleA"); future = new PlainActionFuture<>(); - fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); - compositeRolesStore.roles(roleNames, fieldPermissionsCache, future); + compositeRolesStore.roles(roleNames, future); role = future.actionGet(); assertEquals(0, role.indices().groups().length); } @@ -694,7 +686,7 @@ public void testCacheClearOnIndexHealthChange() { CompositeRolesStore compositeRolesStore = new CompositeRolesStore( Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(Settings.EMPTY), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS)) { + new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache) { @Override public void invalidateAll() { numInvalidation.incrementAndGet(); @@ -746,7 +738,7 @@ public void testCacheClearOnIndexOutOfDateChange() { CompositeRolesStore compositeRolesStore = new CompositeRolesStore(SECURITY_ENABLED_SETTINGS, fileRolesStore, nativeRolesStore, reservedRolesStore, mock(NativePrivilegeStore.class), Collections.emptyList(), new ThreadContext(SECURITY_ENABLED_SETTINGS), - new XPackLicenseState(SECURITY_ENABLED_SETTINGS)) { + new XPackLicenseState(SECURITY_ENABLED_SETTINGS), cache) { @Override public void invalidateAll() { numInvalidation.incrementAndGet();