From 18a2cf23d4c0b08820ba01ccdc19689fd7207c26 Mon Sep 17 00:00:00 2001 From: Jay Modi Date: Fri, 30 Dec 2016 09:27:49 -0500 Subject: [PATCH] Build a single role that represents a user's permissions (elastic/elasticsearch#4449) This PR changes how we use roles and how we look at the roles of a user. Previously we looked up each role individually, parsed each into their own `Role` object, and had a wrapper that essentially served as an iterator over the roles. The same pattern was also followed for the permissions that composed a role (ClusterPermission, IndicesPermission, and RunAsPermission). This resulted in a lot of code that was hard to follow and could be inefficient. Now, we look up the roles for a user in bulk and only get the RoleDescriptor for each role. Once all role descriptors have been retrieved, we build a single Role that represents the user's permissions and we also cache this combination for better performance as authorization can happen many times for a single top level request as we authorize the top level request and any sub requests, which could be a large number in the case of shard requests. This change also enabled a large cleanup of our permission and privilege classes, which should reduce the footprint of what needs to be followed. Some of the notable changes are: * Consolidation of GeneralPrivilege and AbstractAutomatonPrivilege into the Privilege class * The DefaultRole class has been removed and the permissions it provided were moved into the AuthorizationService * The GlobalPermission class was removed as there is a single role that represents a user's permissions * The Global inner classes for the various permissions were removed * The Core inner class was removed and ClusterPermission, IndexPermission, RunAsPermission became final classes instead of interfaces * The Permission interface has been removed. The isEmpty() method defined by this interface is not needed as we can simply evaluate the permission to get the same effect * The ClusterPermission#check method only takes the action name again * The AutomatonPredicate class was removed and replaced by Automatons#predicate * IndicesAccessControl objects no longer need to be merged when evaluating permissions * MergedFieldPermissions has been removed * The Name class that was used to hold an array of strings has been removed and replaced with the use of a Set * Privilege resolution is more efficient by only combining automata once Other items: * NativeRolesStore no longer does caching, so the RoleAndVersion class could be removed * FileRolesStore doesn't need to be an AbstractLifecycleComponent Relates elastic/elasticsearch#4327 Original commit: elastic/x-pack-elasticsearch@c1901bc82e4380901fc3a3aa5e9cf1aaf3f7c958 --- .../xpack/security/Security.java | 6 +- .../action/filter/SecurityActionFilter.java | 5 +- .../security/action/role/PutRoleRequest.java | 6 +- .../action/role/PutRoleRequestBuilder.java | 7 +- .../role/TransportClearRolesCacheAction.java | 6 +- .../action/role/TransportGetRolesAction.java | 14 +- .../security/authz/AuthorizationService.java | 133 +++--- .../security/authz/AuthorizationUtils.java | 24 +- .../security/authz/AuthorizedIndices.java | 20 +- .../xpack/security/authz/RoleDescriptor.java | 88 +++- .../accesscontrol/IndicesAccessControl.java | 31 -- .../authz/permission/ClusterPermission.java | 87 +--- .../authz/permission/DefaultRole.java | 88 ---- .../authz/permission/FieldPermissions.java | 141 ++---- .../permission/FieldPermissionsCache.java | 181 ++++++++ .../authz/permission/GlobalPermission.java | 100 ----- .../authz/permission/IndicesPermission.java | 358 +++++----------- .../authz/permission/IngestAdminRole.java | 32 -- .../security/authz/permission/KibanaRole.java | 31 -- .../authz/permission/KibanaUserRole.java | 30 -- .../authz/permission/LogstashSystemRole.java | 29 -- .../authz/permission/MonitoringUserRole.java | 34 -- .../security/authz/permission/Permission.java | 15 - .../permission/RemoteMonitoringAgentRole.java | 36 -- .../authz/permission/ReportingUserRole.java | 34 -- .../xpack/security/authz/permission/Role.java | 117 +++-- .../authz/permission/RunAsPermission.java | 72 +--- .../authz/permission/SuperuserRole.java | 30 -- .../authz/permission/TransportClientRole.java | 30 -- .../privilege/AbstractAutomatonPrivilege.java | 67 --- .../authz/privilege/ClusterPrivilege.java | 133 +++--- .../authz/privilege/GeneralPrivilege.java | 37 -- .../privilege/HealthAndStatsPrivilege.java | 2 +- .../authz/privilege/IndexPrivilege.java | 151 +++---- .../security/authz/privilege/Privilege.java | 98 ++--- .../authz/privilege/SystemPrivilege.java | 20 +- .../authz/store/CompositeRolesStore.java | 259 ++++++++++- .../security/authz/store/FileRolesStore.java | 136 +++--- .../authz/store/NativeRolesStore.java | 204 ++------- .../authz/store/ReservedRolesStore.java | 126 ++---- .../security/support/AutomatonPredicate.java | 27 -- .../xpack/security/support/Automatons.java | 26 +- .../security/transport/filter/IPFilter.java | 2 - .../xpack/security/user/ElasticUser.java | 3 +- .../xpack/security/user/KibanaUser.java | 3 +- .../security/user/LogstashSystemUser.java | 4 +- .../xpack/security/user/XPackUser.java | 4 +- ...urityIndexBackwardsCompatibilityTests.java | 17 +- .../integration/ClearRolesCacheTests.java | 16 +- .../filter/SecurityActionFilterTests.java | 25 +- .../role/TransportGetRolesActionTests.java | 23 - .../esnative/ESNativeMigrateToolTests.java | 3 +- .../ESNativeRealmMigrateToolTests.java | 2 +- .../authc/esnative/NativeRealmIntegTests.java | 70 ++- .../authz/AuthorizationServiceTests.java | 404 +++++++++++------- .../authz/AuthorizedIndicesTests.java | 22 +- .../security/authz/IndexAliasesTests.java | 64 ++- .../authz/IndicesAndAliasesResolverTests.java | 53 ++- .../security/authz/RoleDescriptorTests.java | 6 +- .../IndicesAccessControlTests.java | 173 +------- .../accesscontrol/IndicesPermissionTests.java | 67 ++- .../authz/permission/DefaultRoleTests.java | 202 --------- .../permission/FieldPermissionTests.java | 109 +---- .../FieldPermissionsCacheTests.java | 120 ++++++ .../permission/IngestAdminRoleTests.java | 56 --- .../authz/permission/KibanaRoleTests.java | 73 ---- .../authz/permission/KibanaUserRoleTests.java | 72 ---- .../permission/LogstashSystemRoleTests.java | 56 --- .../permission/MonitoringUserRoleTests.java | 74 ---- .../authz/permission/PermissionTests.java | 33 +- .../RemoteMonitoringAgentRoleTests.java | 77 ---- .../permission/ReportingUserRoleTests.java | 74 ---- .../authz/permission/SuperuserRoleTests.java | 90 ---- .../authz/privilege/PrivilegeTests.java | 175 ++------ .../authz/store/CompositeRolesStoreTests.java | 75 ++++ .../authz/store/FileRolesStoreTests.java | 132 +++--- .../authz/store/NativeRolesStoreTests.java | 66 +-- .../authz/store/ReservedRolesStoreTests.java | 393 +++++++++++++---- .../transport/ServerTransportFilterTests.java | 27 +- .../xpack/security/MigrateToolIT.java | 13 +- 80 files changed, 2284 insertions(+), 3665 deletions(-) delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/DefaultRole.java create mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionsCache.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/GlobalPermission.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/IngestAdminRole.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/KibanaRole.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/KibanaUserRole.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/LogstashSystemRole.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/MonitoringUserRole.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/Permission.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/RemoteMonitoringAgentRole.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/ReportingUserRole.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRole.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/TransportClientRole.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/AbstractAutomatonPrivilege.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/GeneralPrivilege.java delete mode 100644 elasticsearch/src/main/java/org/elasticsearch/xpack/security/support/AutomatonPredicate.java delete mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/DefaultRoleTests.java create mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionsCacheTests.java delete mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/IngestAdminRoleTests.java delete mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/KibanaRoleTests.java delete mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/KibanaUserRoleTests.java delete mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/LogstashSystemRoleTests.java delete mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/MonitoringUserRoleTests.java delete mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/RemoteMonitoringAgentRoleTests.java delete mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/ReportingUserRoleTests.java delete mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRoleTests.java create mode 100644 elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java index cac0184ce2c..94a454eb560 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -96,6 +96,7 @@ import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.accesscontrol.OptOutQueryCache; import org.elasticsearch.xpack.security.authz.accesscontrol.SecurityIndexSearcherWrapper; import org.elasticsearch.xpack.security.authz.accesscontrol.SetSecurityUserProcessor; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissionsCache; import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; import org.elasticsearch.xpack.security.authz.store.FileRolesStore; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; @@ -318,10 +319,9 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin { final CompositeRolesStore allRolesStore = new CompositeRolesStore(settings, fileRolesStore, nativeRolesStore, reservedRolesStore); final AuthorizationService authzService = new AuthorizationService(settings, allRolesStore, clusterService, auditTrailService, failureHandler, threadPool, anonymousUser); - components.add(fileRolesStore); // has lifecycle components.add(nativeRolesStore); // used by roles actions components.add(reservedRolesStore); // used by roles actions - components.add(allRolesStore); // for SecurityFeatureSet + components.add(allRolesStore); // for SecurityFeatureSet and clear roles cache components.add(authzService); components.add(new SecurityLifecycleService(settings, clusterService, threadPool, indexAuditTrail, @@ -404,6 +404,8 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin { NativeRolesStore.addSettings(settingsList); AuthenticationService.addSettings(settingsList); AuthorizationService.addSettings(settingsList); + settingsList.add(CompositeRolesStore.CACHE_SIZE_SETTING); + settingsList.add(FieldPermissionsCache.CACHE_SIZE_SETTING); // encryption settings CryptoService.addSettings(settingsList); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java index b7534717c02..10933d127cd 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilter.java @@ -37,9 +37,9 @@ import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationUtils; -import org.elasticsearch.xpack.security.authz.privilege.GeneralPrivilege; import org.elasticsearch.xpack.security.authz.privilege.HealthAndStatsPrivilege; import org.elasticsearch.xpack.security.crypto.CryptoService; +import org.elasticsearch.xpack.security.support.Automatons; import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.User; @@ -55,8 +55,7 @@ import static org.elasticsearch.xpack.security.support.Exceptions.authorizationE public class SecurityActionFilter extends AbstractComponent implements ActionFilter { private static final Predicate LICENSE_EXPIRATION_ACTION_MATCHER = HealthAndStatsPrivilege.INSTANCE.predicate(); - private static final Predicate SECURITY_ACTION_MATCHER = - new GeneralPrivilege("_security_matcher", "cluster:admin/xpack/security*").predicate(); + private static final Predicate SECURITY_ACTION_MATCHER = Automatons.predicate("cluster:admin/xpack/security*"); private final AuthenticationService authcService; private final AuthorizationService authzService; diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java index 6eac954d190..f7d9445ac61 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/role/PutRoleRequest.java @@ -15,7 +15,6 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.xpack.security.authz.RoleDescriptor; import org.elasticsearch.xpack.security.support.MetadataUtils; -import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import java.io.IOException; import java.util.ArrayList; @@ -65,12 +64,13 @@ public class PutRoleRequest extends ActionRequest implements WriteRequest { - private final NativeRolesStore rolesStore; + private final CompositeRolesStore rolesStore; @Inject public TransportClearRolesCacheAction(Settings settings, ThreadPool threadPool, ClusterService clusterService, TransportService transportService, ActionFilters actionFilters, - NativeRolesStore rolesStore, IndexNameExpressionResolver indexNameExpressionResolver) { + CompositeRolesStore rolesStore, IndexNameExpressionResolver indexNameExpressionResolver) { super(settings, ClearRolesCacheAction.NAME, threadPool, clusterService, transportService, actionFilters, indexNameExpressionResolver, ClearRolesCacheRequest::new, ClearRolesCacheRequest.Node::new, ThreadPool.Names.MANAGEMENT, ClearRolesCacheResponse.Node.class); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/role/TransportGetRolesAction.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/role/TransportGetRolesAction.java index 9eb7063142d..29097391896 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/role/TransportGetRolesAction.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/action/role/TransportGetRolesAction.java @@ -5,8 +5,6 @@ */ package org.elasticsearch.xpack.security.action.role; -import org.apache.logging.log4j.message.ParameterizedMessage; -import org.apache.logging.log4j.util.Supplier; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.HandledTransportAction; @@ -16,16 +14,12 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.permission.KibanaRole; -import org.elasticsearch.xpack.security.authz.permission.LogstashSystemRole; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore; import java.util.ArrayList; import java.util.List; -import static org.elasticsearch.common.Strings.arrayToDelimitedString; - public class TransportGetRolesAction extends HandledTransportAction { private final NativeRolesStore nativeRolesStore; @@ -53,8 +47,12 @@ public class TransportGetRolesAction extends HandledTransportAction MONITOR_INDEX_PREDICATE = IndexPrivilege.MONITOR.predicate(); + private static final Predicate SAME_USER_PRIVILEGE = Automatons.predicate(ChangePasswordAction.NAME, AuthenticateAction.NAME); private final ClusterService clusterService; private final CompositeRolesStore rolesStore; @@ -78,6 +81,7 @@ public class AuthorizationService extends AbstractComponent { private final AuthenticationFailureHandler authcFailureHandler; private final ThreadContext threadContext; private final AnonymousUser anonymousUser; + private final FieldPermissionsCache fieldPermissionsCache; private final boolean isAnonymousEnabled; private final boolean anonymousAuthzExceptionEnabled; @@ -94,6 +98,7 @@ public class AuthorizationService extends AbstractComponent { this.anonymousUser = anonymousUser; this.isAnonymousEnabled = AnonymousUser.isAnonymousEnabled(settings); this.anonymousAuthzExceptionEnabled = ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.get(settings); + this.fieldPermissionsCache = new FieldPermissionsCache(settings); } /** @@ -106,8 +111,8 @@ public class AuthorizationService extends AbstractComponent { * @param request The request * @throws ElasticsearchSecurityException If the given user is no allowed to execute the given request */ - public void authorize(Authentication authentication, String action, TransportRequest request, Collection userRoles, - Collection runAsRoles) throws ElasticsearchSecurityException { + public void authorize(Authentication authentication, String action, TransportRequest request, Role userRole, + Role runAsRole) throws ElasticsearchSecurityException { final TransportRequest originalRequest = request; if (request instanceof ConcreteShardRequest) { request = ((ConcreteShardRequest) request).getRequest(); @@ -124,38 +129,20 @@ public class AuthorizationService extends AbstractComponent { } throw denial(authentication, action, request); } - Collection roles = userRoles; - // get the roles of the authenticated user, which may be different than the effective - GlobalPermission permission = permission(roles); - final boolean isRunAs = authentication.isRunAs(); - // permission can be empty as it might be that the user's role is unknown - if (permission.isEmpty()) { - if (isRunAs) { - // the request is a run as request so we should call the specific audit event for a denied run as attempt - throw denyRunAs(authentication, action, request); - } else { - throw denial(authentication, action, request); - } - } + // get the roles of the authenticated user, which may be different than the effective + Role permission = userRole; + // check if the request is a run as request + final boolean isRunAs = authentication.isRunAs(); if (isRunAs) { // if we are running as a user we looked up then the authentication must contain a lookedUpBy. If it doesn't then this user // doesn't really exist but the authc service allowed it through to avoid leaking users that exist in the system if (authentication.getLookedUpBy() == null) { throw denyRunAs(authentication, action, request); - } - - // first we must authorize for the RUN_AS action - RunAsPermission runAs = permission.runAs(); - if (runAs != null && runAs.check(authentication.getRunAsUser().principal())) { + } else if (permission.runAs().check(authentication.getRunAsUser().principal())) { grantRunAs(authentication, action, request); - roles = runAsRoles; - permission = permission(roles); - // permission can be empty as it might be that the run as user's role is unknown - if (permission.isEmpty()) { - throw denial(authentication, action, request); - } + permission = runAsRole; } else { throw denyRunAs(authentication, action, request); } @@ -164,8 +151,7 @@ public class AuthorizationService extends AbstractComponent { // first, we'll check if the action is a cluster action. If it is, we'll only check it against the cluster permissions if (ClusterPrivilege.ACTION_MATCHER.test(action)) { ClusterPermission cluster = permission.cluster(); - // we use the effectiveUser for permission checking since we are running as a user! - if (cluster != null && cluster.check(action, request, authentication)) { + if (cluster.check(action) || checkSameUserPermissions(action, request, authentication)) { setIndicesAccessControl(IndicesAccessControl.ALLOW_ALL); grant(authentication, action, request); return; @@ -210,12 +196,12 @@ public class AuthorizationService extends AbstractComponent { throw denial(authentication, action, request); } - if (permission.indices() == null || permission.indices().isEmpty()) { + if (permission.indices().check(action) == false) { throw denial(authentication, action, request); } MetaData metaData = clusterService.state().metaData(); - AuthorizedIndices authorizedIndices = new AuthorizedIndices(authentication.getRunAsUser(), roles, action, metaData); + AuthorizedIndices authorizedIndices = new AuthorizedIndices(authentication.getRunAsUser(), permission, action, metaData); Set indexNames = resolveIndexNames(authentication, action, request, metaData, authorizedIndices); assert !indexNames.isEmpty() : "every indices request needs to have its indices set thus the resolved indices must not be empty"; @@ -227,14 +213,14 @@ public class AuthorizationService extends AbstractComponent { return; } - IndicesAccessControl indicesAccessControl = permission.authorize(action, indexNames, metaData); + IndicesAccessControl indicesAccessControl = permission.authorize(action, indexNames, metaData, fieldPermissionsCache); if (!indicesAccessControl.isGranted()) { throw denial(authentication, action, request); } else if (indicesAccessControl.getIndexPermissions(SecurityTemplateService.SECURITY_INDEX_NAME) != null && indicesAccessControl.getIndexPermissions(SecurityTemplateService.SECURITY_INDEX_NAME).isGranted() && XPackUser.is(authentication.getRunAsUser()) == false && MONITOR_INDEX_PREDICATE.test(action) == false - && Arrays.binarySearch(authentication.getRunAsUser().roles(), SuperuserRole.NAME) < 0) { + && Arrays.binarySearch(authentication.getRunAsUser().roles(), ReservedRolesStore.SUPERUSER_ROLE.name()) < 0) { // only the XPackUser is allowed to work with this index, but we should allow indices monitoring actions through for debugging // purposes. These monitor requests also sometimes resolve indices concretely and then requests them logger.debug("user [{}] attempted to directly perform [{}] against the security index [{}]", @@ -253,7 +239,7 @@ public class AuthorizationService extends AbstractComponent { for (Alias alias : aliases) { aliasesAndIndices.add(alias.name()); } - indicesAccessControl = permission.authorize("indices:admin/aliases", aliasesAndIndices, metaData); + indicesAccessControl = permission.authorize("indices:admin/aliases", aliasesAndIndices, metaData, fieldPermissionsCache); if (!indicesAccessControl.isGranted()) { throw denial(authentication, "indices:admin/aliases", request); } @@ -288,16 +274,7 @@ public class AuthorizationService extends AbstractComponent { } } - // pkg-private for testing - GlobalPermission permission(Collection roles) { - GlobalPermission.Compound.Builder rolesBuilder = GlobalPermission.Compound.builder(); - for (Role role : roles) { - rolesBuilder.add(role); - } - return rolesBuilder.build(); - } - - public void roles(User user, ActionListener> roleActionListener) { + public void roles(User user, ActionListener roleActionListener) { // we need to special case the internal users in this method, if we apply the anonymous roles to every user including these system // user accounts then we run into the chance of a deadlock because then we need to get a role that we may be trying to get as the // internal user. The SystemUser is special cased as it has special privileges to execute internal actions and should never be @@ -307,8 +284,8 @@ public class AuthorizationService extends AbstractComponent { " roles"); } if (XPackUser.is(user)) { - assert XPackUser.INSTANCE.roles().length == 1 && SuperuserRole.NAME.equals(XPackUser.INSTANCE.roles()[0]); - roleActionListener.onResponse(Collections.singleton(SuperuserRole.INSTANCE)); + assert XPackUser.INSTANCE.roles().length == 1 && ReservedRolesStore.SUPERUSER_ROLE.name().equals(XPackUser.INSTANCE.roles()[0]); + roleActionListener.onResponse(ReservedRolesStore.SUPERUSER_ROLE); return; } @@ -321,15 +298,12 @@ public class AuthorizationService extends AbstractComponent { Collections.addAll(roleNames, anonymousUser.roles()); } - final Collection defaultRoles = Collections.singletonList(DefaultRole.INSTANCE); if (roleNames.isEmpty()) { - roleActionListener.onResponse(defaultRoles); + roleActionListener.onResponse(Role.EMPTY); + } else if (roleNames.contains(ReservedRolesStore.SUPERUSER_ROLE.name())) { + roleActionListener.onResponse(ReservedRolesStore.SUPERUSER_ROLE); } else { - final GroupedActionListener listener = new GroupedActionListener<>(roleActionListener, roleNames.size(), - defaultRoles); - for (String roleName : roleNames) { - rolesStore.roles(roleName, listener); - } + rolesStore.roles(roleNames, fieldPermissionsCache, roleActionListener); } } @@ -354,6 +328,49 @@ public class AuthorizationService extends AbstractComponent { action.equals(SearchTransportService.CLEAR_SCROLL_CONTEXTS_ACTION_NAME); } + static boolean checkSameUserPermissions(String action, TransportRequest request, Authentication authentication) { + final boolean actionAllowed = SAME_USER_PRIVILEGE.test(action); + if (actionAllowed) { + if (request instanceof UserRequest == false) { + assert false : "right now only a user request should be allowed"; + return false; + } + UserRequest userRequest = (UserRequest) request; + String[] usernames = userRequest.usernames(); + if (usernames == null || usernames.length != 1 || usernames[0] == null) { + assert false : "this role should only be used for actions to apply to a single user"; + return false; + } + final String username = usernames[0]; + final boolean sameUsername = authentication.getRunAsUser().principal().equals(username); + if (sameUsername && ChangePasswordAction.NAME.equals(action)) { + return checkChangePasswordAction(authentication); + } + + assert AuthenticateAction.NAME.equals(action) || sameUsername == false; + return sameUsername; + } + return false; + } + + private static boolean checkChangePasswordAction(Authentication authentication) { + // we need to verify that this user was authenticated by or looked up by a realm type that support password changes + // otherwise we open ourselves up to issues where a user in a different realm could be created with the same username + // and do malicious things + final boolean isRunAs = authentication.isRunAs(); + final String realmType; + if (isRunAs) { + realmType = authentication.getLookedUpBy().getType(); + } else { + realmType = authentication.getAuthenticatedBy().getType(); + } + + assert realmType != null; + // ensure the user was authenticated by a realm that we can change a password for. The native realm is an internal realm and + // right now only one can exist in the realm configuration - if this changes we should update this check + return ReservedRealm.TYPE.equals(realmType) || NativeRealm.TYPE.equals(realmType); + } + private ElasticsearchSecurityException denial(Authentication authentication, String action, TransportRequest request) { auditTrail.accessDenied(authentication.getUser(), action, request); return denialException(authentication, action); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationUtils.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationUtils.java index 0cf19e2d75d..731a0b86958 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationUtils.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationUtils.java @@ -10,18 +10,15 @@ import org.elasticsearch.common.util.concurrent.CountDown; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authz.permission.Role; -import org.elasticsearch.xpack.security.support.AutomatonPredicate; import org.elasticsearch.xpack.security.support.Automatons; import org.elasticsearch.xpack.security.user.SystemUser; -import java.util.Collection; -import java.util.Collections; import java.util.function.BiConsumer; import java.util.function.Predicate; public final class AuthorizationUtils { - private static final Predicate INTERNAL_PREDICATE = new AutomatonPredicate(Automatons.patterns("internal:*")); + private static final Predicate INTERNAL_PREDICATE = Automatons.predicate("internal:*"); private AuthorizationUtils() {} @@ -72,14 +69,13 @@ public final class AuthorizationUtils { public static class AsyncAuthorizer { private final ActionListener listener; - private final BiConsumer, Collection> consumer; + private final BiConsumer consumer; private final Authentication authentication; - private volatile Collection userRoles; - private volatile Collection runAsRoles; + private volatile Role userRoles; + private volatile Role runAsRoles; private CountDown countDown = new CountDown(2); // we expect only two responses!! - public AsyncAuthorizer(Authentication authentication, ActionListener listener, BiConsumer, - Collection> consumer) { + public AsyncAuthorizer(Authentication authentication, ActionListener listener, BiConsumer consumer) { this.consumer = consumer; this.listener = listener; this.authentication = authentication; @@ -87,25 +83,25 @@ public final class AuthorizationUtils { public void authorize(AuthorizationService service) { if (SystemUser.is(authentication.getUser())) { - setUserRoles(Collections.emptyList()); // we can inform the listener immediately - nothing to fetch for us on system user - setRunAsRoles(Collections.emptyList()); + setUserRoles(null); // we can inform the listener immediately - nothing to fetch for us on system user + setRunAsRoles(null); } else { service.roles(authentication.getUser(), ActionListener.wrap(this::setUserRoles, listener::onFailure)); if (authentication.isRunAs()) { assert authentication.getRunAsUser() != null : "runAs user is null but shouldn't"; service.roles(authentication.getRunAsUser(), ActionListener.wrap(this::setRunAsRoles, listener::onFailure)); } else { - setRunAsRoles(Collections.emptyList()); + setRunAsRoles(null); } } } - private void setUserRoles(Collection roles) { + private void setUserRoles(Role roles) { this.userRoles = roles; maybeRun(); } - private void setRunAsRoles(Collection roles) { + private void setRunAsRoles(Role roles) { this.runAsRoles = roles; maybeRun(); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizedIndices.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizedIndices.java index 86a60ce2283..3472f9d7786 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizedIndices.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizedIndices.java @@ -9,13 +9,12 @@ import org.elasticsearch.cluster.metadata.AliasOrIndex; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.xpack.security.SecurityTemplateService; import org.elasticsearch.xpack.security.authz.permission.Role; -import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; +import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.XPackUser; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -29,10 +28,10 @@ class AuthorizedIndices { private final User user; private final String action; private final MetaData metaData; - private final Collection userRoles; + private final Role userRoles; private List authorizedIndices; - AuthorizedIndices(User user, Collection userRoles, String action, MetaData metaData) { + AuthorizedIndices(User user, Role userRoles, String action, MetaData metaData) { this.user = user; this.userRoles = userRoles; this.action = action; @@ -47,16 +46,7 @@ class AuthorizedIndices { } private List load() { - if (userRoles.isEmpty()) { - return Collections.emptyList(); - } - - List> predicates = new ArrayList<>(); - for (Role userRole : userRoles) { - predicates.add(userRole.indices().allowedIndicesMatcher(action)); - } - - Predicate predicate = predicates.stream().reduce(s -> false, Predicate::or); + Predicate predicate = userRoles.indices().allowedIndicesMatcher(action); List indicesAndAliases = new ArrayList<>(); // TODO: can this be done smarter? I think there are usually more indices/aliases in the cluster then indices defined a roles? @@ -67,7 +57,7 @@ class AuthorizedIndices { } } - if (XPackUser.is(user) == false && Arrays.binarySearch(user.roles(), SuperuserRole.NAME) < 0) { + if (XPackUser.is(user) == false && Arrays.binarySearch(user.roles(), ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName()) < 0) { // we should filter out the .security index from wildcards indicesAndAliases.remove(SecurityTemplateService.SECURITY_INDEX_NAME); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java index aa57915bc15..3c7325d10cf 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/RoleDescriptor.java @@ -23,7 +23,6 @@ import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.xpack.common.xcontent.XContentUtils; -import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.security.support.MetadataUtils; import org.elasticsearch.xpack.security.support.Validation; @@ -241,7 +240,7 @@ public class RoleDescriptor implements ToXContent { } String currentFieldName = null; String[] names = null; - String query = null; + BytesReference query = null; String[] privileges = null; String[] grantedFields = null; String[] deniedFields = null; @@ -265,15 +264,16 @@ public class RoleDescriptor implements ToXContent { if (token == XContentParser.Token.START_OBJECT) { XContentBuilder builder = JsonXContent.contentBuilder(); XContentHelper.copyCurrentStructure(builder.generator(), parser); - query = builder.string(); - } else if (token == XContentParser.Token.VALUE_STRING){ + query = builder.bytes(); + } else if (token == XContentParser.Token.VALUE_STRING) { final String text = parser.text(); if (text.isEmpty() == false) { - query = text; + query = new BytesArray(text); } } else if (token != XContentParser.Token.VALUE_NULL) { throw new ElasticsearchParseException("failed to parse indices privileges for role [{}]. expected field [{}] " + - "value to be null, a string, or an object, but found [{}] instead", roleName, currentFieldName, token); + "value to be null, a string, an array, or an object, but found [{}] instead", roleName, currentFieldName, + token); } } else if (ParseFieldMatcher.STRICT.match(currentFieldName, Fields.FIELD_PERMISSIONS)) { if (token == XContentParser.Token.START_OBJECT) { @@ -347,7 +347,8 @@ public class RoleDescriptor implements ToXContent { return RoleDescriptor.IndicesPrivileges.builder() .indices(names) .privileges(privileges) - .fieldPermissions(new FieldPermissions(grantedFields, deniedFields)) + .grantedFields(grantedFields) + .deniedFields(deniedFields) .query(query) .build(); } @@ -362,7 +363,8 @@ public class RoleDescriptor implements ToXContent { private String[] indices; private String[] privileges; - private FieldPermissions fieldPermissions = new FieldPermissions(); + private String[] grantedFields = null; + private String[] deniedFields = null; private BytesReference query; private IndicesPrivileges() { @@ -380,8 +382,14 @@ public class RoleDescriptor implements ToXContent { return this.privileges; } - public FieldPermissions getFieldPermissions() { - return fieldPermissions; + @Nullable + public String[] getGrantedFields() { + return this.grantedFields; + } + + @Nullable + public String[] getDeniedFields() { + return this.deniedFields; } @Nullable @@ -395,9 +403,27 @@ public class RoleDescriptor implements ToXContent { sb.append("indices=[").append(Strings.arrayToCommaDelimitedString(indices)); sb.append("], privileges=[").append(Strings.arrayToCommaDelimitedString(privileges)); sb.append("], "); - sb.append(fieldPermissions.toString()); + if (grantedFields != null || deniedFields != null) { + sb.append(RoleDescriptor.Fields.FIELD_PERMISSIONS).append("=["); + if (grantedFields == null) { + sb.append(RoleDescriptor.Fields.GRANT_FIELDS).append("=null"); + } else { + sb.append(RoleDescriptor.Fields.GRANT_FIELDS).append("=[") + .append(Strings.arrayToCommaDelimitedString(grantedFields)); + sb.append("]"); + } + if (deniedFields == null) { + sb.append(", ").append(RoleDescriptor.Fields.EXCEPT_FIELDS).append("=null"); + } else { + sb.append(", ").append(RoleDescriptor.Fields.EXCEPT_FIELDS).append("=[") + .append(Strings.arrayToCommaDelimitedString(deniedFields)); + sb.append("]"); + } + sb.append("]"); + } if (query != null) { - sb.append(", query=").append(query.utf8ToString()); + sb.append(", query="); + sb.append(query.utf8ToString()); } sb.append("]"); return sb.toString(); @@ -412,7 +438,8 @@ public class RoleDescriptor implements ToXContent { if (!Arrays.equals(indices, that.indices)) return false; if (!Arrays.equals(privileges, that.privileges)) return false; - if (fieldPermissions.equals(that.fieldPermissions) == false) return false; + if (!Arrays.equals(grantedFields, that.grantedFields)) return false; + if (!Arrays.equals(deniedFields, that.deniedFields)) return false; return !(query != null ? !query.equals(that.query) : that.query != null); } @@ -420,7 +447,8 @@ public class RoleDescriptor implements ToXContent { public int hashCode() { int result = Arrays.hashCode(indices); result = 31 * result + Arrays.hashCode(privileges); - result = 31 * result + fieldPermissions.hashCode(); + result = 31 * result + Arrays.hashCode(grantedFields); + result = 31 * result + Arrays.hashCode(deniedFields); result = 31 * result + (query != null ? query.hashCode() : 0); return result; } @@ -430,7 +458,16 @@ public class RoleDescriptor implements ToXContent { builder.startObject(); builder.array("names", indices); builder.array("privileges", privileges); - builder = fieldPermissions.toXContent(builder, params); + if (grantedFields != null || deniedFields != null) { + builder.startObject(RoleDescriptor.Fields.FIELD_PERMISSIONS.getPreferredName()); + if (grantedFields != null) { + builder.array(RoleDescriptor.Fields.GRANT_FIELDS.getPreferredName(), grantedFields); + } + if (deniedFields != null) { + builder.array(RoleDescriptor.Fields.EXCEPT_FIELDS.getPreferredName(), deniedFields); + } + builder.endObject(); + } if (query != null) { builder.field("query", query.utf8ToString()); } @@ -446,7 +483,8 @@ public class RoleDescriptor implements ToXContent { @Override public void readFrom(StreamInput in) throws IOException { this.indices = in.readStringArray(); - this.fieldPermissions = new FieldPermissions(in); + this.grantedFields = in.readOptionalStringArray(); + this.deniedFields = in.readOptionalStringArray(); this.privileges = in.readStringArray(); this.query = in.readOptionalBytesReference(); } @@ -454,7 +492,8 @@ public class RoleDescriptor implements ToXContent { @Override public void writeTo(StreamOutput out) throws IOException { out.writeStringArray(indices); - fieldPermissions.writeTo(out); + out.writeOptionalStringArray(grantedFields); + out.writeOptionalStringArray(deniedFields); out.writeStringArray(privileges); out.writeOptionalBytesReference(query); } @@ -476,8 +515,13 @@ public class RoleDescriptor implements ToXContent { return this; } - public Builder fieldPermissions(FieldPermissions fieldPermissions) { - indicesPrivileges.fieldPermissions = fieldPermissions; + public Builder grantedFields(String... grantedFields) { + indicesPrivileges.grantedFields = grantedFields; + return this; + } + + public Builder deniedFields(String... deniedFields) { + indicesPrivileges.deniedFields = deniedFields; return this; } @@ -486,7 +530,11 @@ public class RoleDescriptor implements ToXContent { } public Builder query(@Nullable BytesReference query) { - indicesPrivileges.query = query; + if (query == null) { + indicesPrivileges.query = null; + } else { + indicesPrivileges.query = query; + } return this; } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControl.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControl.java index c9ec7a9c20d..2688823acff 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControl.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControl.java @@ -88,36 +88,5 @@ public class IndicesAccessControl { public Set getQueries() { return queries; } - - public IndexAccessControl merge(IndexAccessControl other) { - if (other.isGranted() == false) { - // nothing to merge - return this; - } - - final boolean granted = this.granted; - if (granted == false) { - // we do not support negatives, so if the current isn't granted - just return other - assert other.isGranted(); - return other; - } - - FieldPermissions newPermissions = FieldPermissions.merge(this.fieldPermissions, other.fieldPermissions); - - Set queries = null; - if (this.queries != null && other.getQueries() != null) { - queries = new HashSet<>(); - if (this.queries != null) { - queries.addAll(this.queries); - } - if (other.getQueries() != null) { - queries.addAll(other.getQueries()); - } - queries = unmodifiableSet(queries); - } - return new IndexAccessControl(granted, newPermissions, queries); - } - - } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/ClusterPermission.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/ClusterPermission.java index 9f1fc321209..99677028d94 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/ClusterPermission.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/ClusterPermission.java @@ -5,93 +5,30 @@ */ package org.elasticsearch.xpack.security.authz.permission; -import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; -import java.util.List; -import java.util.Objects; import java.util.function.Predicate; /** * A permission that is based on privileges for cluster wide actions */ -public interface ClusterPermission extends Permission { +public final class ClusterPermission { - boolean check(String action, TransportRequest request, Authentication authentication); + public static final ClusterPermission NONE = new ClusterPermission(ClusterPrivilege.NONE); - class Core implements ClusterPermission { + private final ClusterPrivilege privilege; + private final Predicate predicate; - public static final Core NONE = new Core(ClusterPrivilege.NONE) { - @Override - public boolean check(String action, TransportRequest request, Authentication authentication) { - return false; - } - - @Override - public boolean isEmpty() { - return true; - } - }; - - private final ClusterPrivilege privilege; - private final Predicate predicate; - - Core(ClusterPrivilege privilege) { - this.privilege = privilege; - this.predicate = privilege.predicate(); - } - - public ClusterPrivilege privilege() { - return privilege; - } - - @Override - public boolean check(String action, TransportRequest request, Authentication authentication) { - return predicate.test(action); - } - - @Override - public boolean isEmpty() { - return false; - } + ClusterPermission(ClusterPrivilege privilege) { + this.privilege = privilege; + this.predicate = privilege.predicate(); } - class Globals implements ClusterPermission { - - private final List globals; - - Globals(List globals) { - this.globals = globals; - } - - @Override - public boolean check(String action, TransportRequest request, Authentication authentication) { - if (globals == null) { - return false; - } - for (GlobalPermission global : globals) { - Objects.requireNonNull(global, "global must not be null"); - Objects.requireNonNull(global.indices(), "global.indices() must not be null"); - if (global.cluster().check(action, request, authentication)) { - return true; - } - } - return false; - } - - @Override - public boolean isEmpty() { - if (globals == null || globals.isEmpty()) { - return true; - } - for (GlobalPermission global : globals) { - if (!global.isEmpty()) { - return false; - } - } - return true; - } + public ClusterPrivilege privilege() { + return privilege; } + public boolean check(String action) { + return predicate.test(action); + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/DefaultRole.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/DefaultRole.java deleted file mode 100644 index 8ed29b0999c..00000000000 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/DefaultRole.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.xpack.security.authc.Authentication; -import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; -import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; -import org.elasticsearch.xpack.security.action.user.AuthenticateAction; -import org.elasticsearch.xpack.security.action.user.ChangePasswordAction; -import org.elasticsearch.xpack.security.action.user.UserRequest; -import org.elasticsearch.xpack.security.authz.permission.RunAsPermission.Core; -import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; -import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name; -import org.elasticsearch.transport.TransportRequest; - -/** - * A default role that will be applied to all users other than the internal {@link org.elasticsearch.xpack.security.user.SystemUser}. This - * role grants access to actions that every user should be able to execute such as the ability to change their password and execute the - * authenticate endpoint to get information about themselves - */ -public class DefaultRole extends Role { - - private static final ClusterPermission.Core CLUSTER_PERMISSION = - new SameUserClusterPermission(ClusterPrivilege.get(new Name(ChangePasswordAction.NAME, AuthenticateAction.NAME))); - private static final IndicesPermission.Core INDICES_PERMISSION = IndicesPermission.Core.NONE; - private static final RunAsPermission.Core RUN_AS_PERMISSION = Core.NONE; - - public static final String NAME = "__default_role"; - public static final DefaultRole INSTANCE = new DefaultRole(); - - private DefaultRole() { - super(NAME, CLUSTER_PERMISSION, INDICES_PERMISSION, RUN_AS_PERMISSION); - } - - private static class SameUserClusterPermission extends ClusterPermission.Core { - - private SameUserClusterPermission(ClusterPrivilege privilege) { - super(privilege); - } - - @Override - public boolean check(String action, TransportRequest request, Authentication authentication) { - final boolean actionAllowed = super.check(action, request, authentication); - if (actionAllowed) { - if (request instanceof UserRequest == false) { - assert false : "right now only a user request should be allowed"; - return false; - } - UserRequest userRequest = (UserRequest) request; - String[] usernames = userRequest.usernames(); - if (usernames == null || usernames.length != 1 || usernames[0] == null) { - assert false : "this role should only be used for actions to apply to a single user"; - return false; - } - final String username = usernames[0]; - final boolean sameUsername = authentication.getRunAsUser().principal().equals(username); - if (sameUsername && ChangePasswordAction.NAME.equals(action)) { - return checkChangePasswordAction(authentication); - } - - assert AuthenticateAction.NAME.equals(action) || sameUsername == false; - return sameUsername; - } - return false; - } - } - - static boolean checkChangePasswordAction(Authentication authentication) { - // we need to verify that this user was authenticated by or looked up by a realm type that support password changes - // otherwise we open ourselves up to issues where a user in a different realm could be created with the same username - // and do malicious things - final boolean isRunAs = authentication.isRunAs(); - final String realmType; - if (isRunAs) { - realmType = authentication.getLookedUpBy().getType(); - } else { - realmType = authentication.getAuthenticatedBy().getType(); - } - - assert realmType != null; - // ensure the user was authenticated by a realm that we can change a password for. The native realm is an internal realm and right - // now only one can exist in the realm configuration - if this changes we should update this check - return ReservedRealm.TYPE.equals(realmType) || NativeRealm.TYPE.equals(realmType); - } -} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissions.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissions.java index 25ae2980d6b..13749fa0f66 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissions.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissions.java @@ -13,8 +13,6 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.regex.Regex; -import org.elasticsearch.common.xcontent.ToXContent; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.index.mapper.AllFieldMapper; import org.elasticsearch.index.mapper.MapperService; import org.elasticsearch.xpack.security.authz.RoleDescriptor; @@ -26,14 +24,10 @@ import java.util.Collection; import java.util.HashSet; import java.util.Set; -import static org.apache.lucene.util.automaton.MinimizationOperations.minimize; -import static org.apache.lucene.util.automaton.Operations.DEFAULT_MAX_DETERMINIZED_STATES; import static org.apache.lucene.util.automaton.Operations.isTotal; import static org.apache.lucene.util.automaton.Operations.run; -import static org.apache.lucene.util.automaton.Operations.sameLanguage; import static org.apache.lucene.util.automaton.Operations.subsetOf; -import static org.apache.lucene.util.automaton.Operations.union; -import static org.elasticsearch.xpack.security.support.Automatons.minusAndDeterminize; +import static org.elasticsearch.xpack.security.support.Automatons.minusAndMinimize; /** * Stores patterns to fields which access is granted or denied to and maintains an automaton that can be used to check if permission is @@ -43,30 +37,42 @@ import static org.elasticsearch.xpack.security.support.Automatons.minusAndDeterm * 1. It has to match the patterns in grantedFieldsArray * 2. it must not match the patterns in deniedFieldsArray */ -public class FieldPermissions implements Writeable, ToXContent { +public final class FieldPermissions implements Writeable { + + public static final FieldPermissions DEFAULT = new FieldPermissions(); // the patterns for fields which we allow access to. if gratedFieldsArray is null we assume that all fields are grated access to - String[] grantedFieldsArray; + private final String[] grantedFieldsArray; // the patterns for fields which we deny access to. if this is an empty list or null we assume that we do not deny access to any // field explicitly - String[] deniedFieldsArray; + private final String[] deniedFieldsArray; // an automaton that matches all strings that match the patterns in permittedFieldsArray but does not match those that also match a // pattern in deniedFieldsArray. If permittedFieldsAutomaton is null we assume that all fields are granted access to. - Automaton permittedFieldsAutomaton; + private final Automaton permittedFieldsAutomaton; // we cannot easily determine if all fields are allowed and we can therefore also allow access to the _all field hence we deny access // to _all unless this was explicitly configured. - boolean allFieldIsAllowed = false; + private final boolean allFieldIsAllowed; + + public FieldPermissions() { + this(null, null); + } public FieldPermissions(StreamInput in) throws IOException { this(in.readOptionalStringArray(), in.readOptionalStringArray()); } public FieldPermissions(@Nullable String[] grantedFieldsArray, @Nullable String[] deniedFieldsArray) { + this(grantedFieldsArray, deniedFieldsArray, initializePermittedFieldsAutomaton(grantedFieldsArray, deniedFieldsArray), + checkAllFieldIsAllowed(grantedFieldsArray, deniedFieldsArray)); + } + + FieldPermissions(@Nullable String[] grantedFieldsArray, @Nullable String[] deniedFieldsArray, + Automaton permittedFieldsAutomaton, boolean allFieldIsAllowed) { this.grantedFieldsArray = grantedFieldsArray; this.deniedFieldsArray = deniedFieldsArray; - permittedFieldsAutomaton = initializePermittedFieldsAutomaton(grantedFieldsArray, deniedFieldsArray); - allFieldIsAllowed = checkAllFieldIsAllowed(grantedFieldsArray, deniedFieldsArray); + this.permittedFieldsAutomaton = permittedFieldsAutomaton; + this.allFieldIsAllowed = allFieldIsAllowed; } private static boolean checkAllFieldIsAllowed(String[] grantedFieldsArray, String[] deniedFieldsArray) { @@ -87,8 +93,7 @@ public class FieldPermissions implements Writeable, ToXContent { return false; } - private static Automaton initializePermittedFieldsAutomaton(final String[] grantedFieldsArray, - final String[] deniedFieldsArray) { + private static Automaton initializePermittedFieldsAutomaton(final String[] grantedFieldsArray, final String[] deniedFieldsArray) { Automaton grantedFieldsAutomaton; if (grantedFieldsArray == null || containsWildcard(grantedFieldsArray)) { grantedFieldsAutomaton = Automatons.MATCH_ALL; @@ -107,7 +112,7 @@ public class FieldPermissions implements Writeable, ToXContent { Arrays.toString(grantedFieldsArray)); } - grantedFieldsAutomaton = minusAndDeterminize(grantedFieldsAutomaton, deniedFieldsAutomaton); + grantedFieldsAutomaton = minusAndMinimize(grantedFieldsAutomaton, deniedFieldsAutomaton); return grantedFieldsAutomaton; } @@ -120,26 +125,12 @@ public class FieldPermissions implements Writeable, ToXContent { return false; } - public FieldPermissions() { - this(null, null); - } - @Override public void writeTo(StreamOutput out) throws IOException { out.writeOptionalStringArray(grantedFieldsArray); out.writeOptionalStringArray(deniedFieldsArray); } - @Nullable - String[] getGrantedFieldsArray() { - return grantedFieldsArray; - } - - @Nullable - String[] getDeniedFieldsArray() { - return deniedFieldsArray; - } - @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -164,21 +155,6 @@ public class FieldPermissions implements Writeable, ToXContent { return sb.toString(); } - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - if (grantedFieldsArray != null || deniedFieldsArray != null) { - builder.startObject(RoleDescriptor.Fields.FIELD_PERMISSIONS.getPreferredName()); - if (grantedFieldsArray != null) { - builder.array(RoleDescriptor.Fields.GRANT_FIELDS.getPreferredName(), grantedFieldsArray); - } - if (deniedFieldsArray != null) { - builder.array(RoleDescriptor.Fields.EXCEPT_FIELDS.getPreferredName(), deniedFieldsArray); - } - builder.endObject(); - } - return builder; - } - /** * Returns true if this field permission policy allows access to the field and false if not. * fieldName can be a wildcard. @@ -187,22 +163,28 @@ public class FieldPermissions implements Writeable, ToXContent { return isTotal(permittedFieldsAutomaton) || run(permittedFieldsAutomaton, fieldName); } - // Also, if one grants no access to fields and the other grants all access, merging should result in all access... - public static FieldPermissions merge(FieldPermissions p1, FieldPermissions p2) { - Automaton mergedPermittedFieldsAutomaton; - // we only allow the union of the two automatons - mergedPermittedFieldsAutomaton = union(p1.permittedFieldsAutomaton, p2.permittedFieldsAutomaton); - // need to minimize otherwise isTotal() might return false even if one of the merged ones returned true before - mergedPermittedFieldsAutomaton = minimize(mergedPermittedFieldsAutomaton, DEFAULT_MAX_DETERMINIZED_STATES); - // if one of them allows access to _all we allow it for the merged too - boolean allFieldIsAllowedInMerged = p1.allFieldIsAllowed || p2.allFieldIsAllowed; - return new MergedFieldPermissions(mergedPermittedFieldsAutomaton, allFieldIsAllowedInMerged); + Automaton getPermittedFieldsAutomaton() { + return permittedFieldsAutomaton; + } + + @Nullable + String[] getGrantedFieldsArray() { + return grantedFieldsArray; + } + + @Nullable + String[] getDeniedFieldsArray() { + return deniedFieldsArray; } public boolean hasFieldLevelSecurity() { return isTotal(permittedFieldsAutomaton) == false; } + boolean isAllFieldIsAllowed() { + return allFieldIsAllowed; + } + public Set resolveAllowedFields(Set allowedMetaFields, MapperService mapperService) { HashSet finalAllowedFields = new HashSet<>(); // we always add the allowed meta fields because we must make sure access is not denied accidentally @@ -232,59 +214,14 @@ public class FieldPermissions implements Writeable, ToXContent { // Probably incorrect - comparing Object[] arrays with Arrays.equals if (!Arrays.equals(grantedFieldsArray, that.grantedFieldsArray)) return false; // Probably incorrect - comparing Object[] arrays with Arrays.equals - if (!Arrays.equals(deniedFieldsArray, that.deniedFieldsArray)) return false; - return sameLanguage(permittedFieldsAutomaton, that.permittedFieldsAutomaton); - + return Arrays.equals(deniedFieldsArray, that.deniedFieldsArray); } @Override public int hashCode() { int result = Arrays.hashCode(grantedFieldsArray); result = 31 * result + Arrays.hashCode(deniedFieldsArray); - result = 31 * result + permittedFieldsAutomaton.hashCode(); result = 31 * result + (allFieldIsAllowed ? 1 : 0); return result; } - - /** - * When we merge field permissions we need to union all the allowed fields. We do this by a union of the automatons - * that define which fields are granted access too. However, that means that after merging we cannot know anymore - * which strings defined the automatons. Hence we make a new class that only has an automaton for the fields that - * we grant access to and that throws an exception whenever we try to access the original patterns that lead to - * the automaton. - */ - public static class MergedFieldPermissions extends FieldPermissions { - public MergedFieldPermissions(Automaton grantedFields, boolean allFieldIsAllowed) { - assert grantedFields != null; - this.permittedFieldsAutomaton = grantedFields; - this.grantedFieldsArray = null; - this.deniedFieldsArray = null; - this.allFieldIsAllowed = allFieldIsAllowed; - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - throw new UnsupportedOperationException("Cannot build xcontent for merged field permissions"); - } - - @Override - public String toString() { - throw new UnsupportedOperationException("Cannot build string for merged field permissions"); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - throw new UnsupportedOperationException("Cannot stream for merged field permissions"); - } - - @Nullable - public String[] getGrantedFieldsArray() { - throw new UnsupportedOperationException("Merged field permissions does not maintain sets"); - } - - @Nullable - public String[] getDeniedFieldsArray() { - throw new UnsupportedOperationException("Merged field permissions does not maintain sets"); - } - } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionsCache.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionsCache.java new file mode 100644 index 00000000000..121ab4eee0f --- /dev/null +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionsCache.java @@ -0,0 +1,181 @@ +/* + * 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.authz.permission; + +import org.apache.lucene.util.automaton.Operations; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.cache.Cache; +import org.elasticsearch.common.cache.CacheBuilder; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Setting.Property; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.set.Sets; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import static org.elasticsearch.xpack.security.Security.setting; + +/** + * A service for managing the caching of {@link FieldPermissions} as these may often need to be combined or created and internally they + * use an {@link org.apache.lucene.util.automaton.Automaton}, which can be costly to create once you account for minimization + */ +public final class FieldPermissionsCache { + + public static final Setting CACHE_SIZE_SETTING = Setting.longSetting( + setting("authz.store.roles.field_permissions.cache.max_size_in_bytes"), 100 * 1024 * 1024, -1L, Property.NodeScope); + private final Cache cache; + + public FieldPermissionsCache(Settings settings) { + this.cache = CacheBuilder.builder() + .setMaximumWeight(CACHE_SIZE_SETTING.get(settings)) + // this is not completely accurate but in most cases the automaton should be the most expensive aspect + .weigher((key, fieldPermissions) -> fieldPermissions.getPermittedFieldsAutomaton().ramBytesUsed()) + .build(); + } + + /** + * Gets a {@link FieldPermissions} instance that corresponds to the granted and denied parameters. The instance may come from the cache + * or if it gets created, the instance will be cached + */ + FieldPermissions getFieldPermissions(String[] granted, String[] denied) { + final Set grantedSet; + if (granted != null) { + grantedSet = new HashSet<>(granted.length); + Collections.addAll(grantedSet, granted); + } else { + grantedSet = null; + } + + final Set deniedSet; + if (denied != null) { + deniedSet = new HashSet<>(denied.length); + Collections.addAll(deniedSet, denied); + } else { + deniedSet = null; + } + + return getFieldPermissions(grantedSet, deniedSet); + } + + /** + * Gets a {@link FieldPermissions} instance that corresponds to the granted and denied parameters. The instance may come from the cache + * or if it gets created, the instance will be cached + */ + public FieldPermissions getFieldPermissions(Set granted, Set denied) { + Key fpKey = new Key(granted == null ? null : Collections.unmodifiableSet(granted), + denied == null ? null : Collections.unmodifiableSet(denied)); + try { + return cache.computeIfAbsent(fpKey, + (key) -> new FieldPermissions(key.grantedFields == null ? null : key.grantedFields.toArray(Strings.EMPTY_ARRAY), + key.deniedFields == null ? null : key.deniedFields.toArray(Strings.EMPTY_ARRAY))); + } catch (ExecutionException e) { + throw new ElasticsearchException("unable to compute field permissions", e); + } + } + + /** + * Returns a field permissions object that corresponds to the merging of the given field permissions and caches the instance if one was + * not found in the cache. + */ + FieldPermissions getFieldPermissions(Collection fieldPermissionsCollection) { + Optional allowAllFieldPermissions = fieldPermissionsCollection.stream() + .filter((fp) -> Operations.isTotal(fp.getPermittedFieldsAutomaton())) + .findFirst(); + return allowAllFieldPermissions.orElseGet(() -> { + final Set allowedFields; + Optional nullAllowedFields = fieldPermissionsCollection.stream() + .filter((fieldPermissions) -> fieldPermissions.getGrantedFieldsArray() == null) + .findFirst(); + if (nullAllowedFields.isPresent()) { + allowedFields = null; + } else { + allowedFields = fieldPermissionsCollection.stream() + .flatMap(fieldPermissions -> Arrays.stream(fieldPermissions.getGrantedFieldsArray())) + .collect(Collectors.toSet()); + } + + final Set deniedFields = fieldPermissionsCollection.stream() + .filter(fieldPermissions -> fieldPermissions.getDeniedFieldsArray() != null) + .flatMap(fieldPermissions -> Arrays.stream(fieldPermissions.getDeniedFieldsArray())) + .collect(Collectors.toSet()); + try { + return cache.computeIfAbsent(new Key(allowedFields, deniedFields), + (key) -> { + final String[] actualDeniedFields = key.deniedFields == null ? null : + computeDeniedFieldsForPermissions(fieldPermissionsCollection, key.deniedFields); + return new FieldPermissions(key.grantedFields == null ? null : key.grantedFields.toArray(Strings.EMPTY_ARRAY), + actualDeniedFields); + }); + } catch (ExecutionException e) { + throw new ElasticsearchException("unable to compute field permissions", e); + } + }); + } + + private static String[] computeDeniedFieldsForPermissions(Collection fieldPermissionsCollection, + Set allDeniedFields) { + Set allowedDeniedFields = new HashSet<>(); + fieldPermissionsCollection + .stream() + .filter(fieldPermissions -> fieldPermissions.getDeniedFieldsArray() != null) + .forEach((fieldPermissions) -> { + String[] deniedFieldsForPermission = fieldPermissions.getDeniedFieldsArray(); + fieldPermissionsCollection.forEach((fp) -> { + if (fp != fieldPermissions) { + Arrays.stream(deniedFieldsForPermission).forEach((field) -> { + if (fp.grantsAccessTo(field)) { + allowedDeniedFields.add(field); + } + }); + } + }); + }); + + Set difference = Sets.difference(allDeniedFields, allowedDeniedFields); + if (difference.isEmpty()) { + return null; + } else { + return difference.toArray(Strings.EMPTY_ARRAY); + } + } + + private static class Key { + + private final Set grantedFields; + private final Set deniedFields; + + Key(Set grantedFields, Set deniedFields) { + this.grantedFields = grantedFields == null ? null : Collections.unmodifiableSet(grantedFields); + this.deniedFields = deniedFields == null ? null : Collections.unmodifiableSet(deniedFields); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof Key)) return false; + + Key key = (Key) o; + + if (grantedFields != null ? !grantedFields.equals(key.grantedFields) : key.grantedFields != null) return false; + return deniedFields != null ? deniedFields.equals(key.deniedFields) : key.deniedFields == null; + } + + @Override + public int hashCode() { + int result = grantedFields != null ? grantedFields.hashCode() : 0; + result = 31 * result + (deniedFields != null ? deniedFields.hashCode() : 0); + return result; + } + } +} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/GlobalPermission.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/GlobalPermission.java deleted file mode 100644 index 5c0add3f370..00000000000 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/GlobalPermission.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * A composite permission that combines {@code cluster}, {@code indices} and {@code run_as} permissions - */ -public class GlobalPermission implements Permission { - - public static final GlobalPermission NONE = new GlobalPermission(ClusterPermission.Core.NONE, IndicesPermission.Core.NONE, - RunAsPermission.Core.NONE); - - private final ClusterPermission cluster; - private final IndicesPermission indices; - private final RunAsPermission runAs; - - GlobalPermission(ClusterPermission cluster, IndicesPermission indices, RunAsPermission runAs) { - this.cluster = cluster; - this.indices = indices; - this.runAs = runAs; - } - - public ClusterPermission cluster() { - return cluster; - } - - public IndicesPermission indices() { - return indices; - } - - public RunAsPermission runAs() { - return runAs; - } - - @Override - public boolean isEmpty() { - return (cluster == null || cluster.isEmpty()) && (indices == null || indices.isEmpty()) && (runAs == null || runAs.isEmpty()); - } - - /** - * Returns whether at least one group encapsulated by this indices permissions is authorized to execute the - * specified action with the requested indices/aliases. At the same time if field and/or document level security - * is configured for any group also the allowed fields and role queries are resolved. - */ - public IndicesAccessControl authorize(String action, Set requestedIndicesOrAliases, MetaData metaData) { - Map indexPermissions = indices.authorize( - action, requestedIndicesOrAliases, metaData - ); - - // At least one role / indices permission set need to match with all the requested indices/aliases: - boolean granted = true; - for (Map.Entry entry : indexPermissions.entrySet()) { - if (!entry.getValue().isGranted()) { - granted = false; - break; - } - } - return new IndicesAccessControl(granted, indexPermissions); - } - - public static class Compound extends GlobalPermission { - - Compound(List globals) { - super(new ClusterPermission.Globals(globals), new IndicesPermission.Globals(globals), new RunAsPermission.Globals(globals)); - } - - public static Compound.Builder builder() { - return new Compound.Builder(); - } - - public static class Builder { - - private List globals = new ArrayList<>(); - - private Builder() { - } - - public Compound.Builder add(GlobalPermission global) { - globals.add(global); - return this; - } - - public Compound build() { - return new Compound(Collections.unmodifiableList(globals)); - } - } - } -} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java index daa3dd7b964..6338f290e9c 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/IndicesPermission.java @@ -12,12 +12,10 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; -import org.elasticsearch.xpack.security.support.AutomatonPredicate; import org.elasticsearch.xpack.security.support.Automatons; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -30,7 +28,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import java.util.function.Predicate; -import static java.util.Collections.emptyMap; import static java.util.Collections.unmodifiableMap; import static java.util.Collections.unmodifiableSet; @@ -38,12 +35,45 @@ import static java.util.Collections.unmodifiableSet; * A permission that is based on privileges for index related actions executed * on specific indices */ -public interface IndicesPermission extends Permission, Iterable { +public final class IndicesPermission implements Iterable { + + public static final IndicesPermission NONE = new IndicesPermission(); + + private final Function> loadingFunction; + + private final ConcurrentHashMap> allowedIndicesMatchersForAction = new ConcurrentHashMap<>(); + + private final Group[] groups; + + public IndicesPermission(Group... groups) { + this.groups = groups; + loadingFunction = (action) -> { + List indices = new ArrayList<>(); + for (Group group : groups) { + if (group.actionMatcher.test(action)) { + indices.addAll(Arrays.asList(group.indices)); + } + } + return Automatons.predicate(indices); + }; + } + + @Override + public Iterator iterator() { + return Arrays.asList(groups).iterator(); + } + + public Group[] groups() { + return groups; + } /** - * Authorizes the provided action against the provided indices, given the current cluster metadata + * @return A predicate that will match all the indices that this permission + * has the privilege for executing the given action on. */ - Map authorize(String action, Set requestedIndicesOrAliases, MetaData metaData); + public Predicate allowedIndicesMatcher(String action) { + return allowedIndicesMatchersForAction.computeIfAbsent(action, loadingFunction); + } /** * Checks if the permission matches the provided action, without looking at indices. @@ -51,277 +81,83 @@ public interface IndicesPermission extends Permission, Iterable iterator() { - return Collections.emptyIterator(); - } - - @Override - public boolean isEmpty() { + public boolean check(String action) { + for (Group group : groups) { + if (group.check(action)) { return true; } - }; - - private final Function> loadingFunction; - - private final ConcurrentHashMap> allowedIndicesMatchersForAction = new ConcurrentHashMap<>(); - - private final Group[] groups; - - public Core(List groups) { - this(groups.toArray(new Group[groups.size()])); } + return false; + } - public Core(Group... groups) { - this.groups = groups; - loadingFunction = (action) -> { - List indices = new ArrayList<>(); - for (Group group : groups) { - if (group.actionMatcher.test(action)) { - indices.addAll(Arrays.asList(group.indices)); - } + /** + * Authorizes the provided action against the provided indices, given the current cluster metadata + */ + public Map authorize(String action, Set requestedIndicesOrAliases, + MetaData metaData, FieldPermissionsCache fieldPermissionsCache) { + // now... every index that is associated with the request, must be granted + // by at least one indices permission group + + SortedMap allAliasesAndIndices = metaData.getAliasAndIndexLookup(); + Map> fieldPermissionsByIndex = new HashMap<>(); + Map> roleQueriesByIndex = new HashMap<>(); + Map grantedBuilder = new HashMap<>(); + + for (String indexOrAlias : requestedIndicesOrAliases) { + boolean granted = false; + Set concreteIndices = new HashSet<>(); + AliasOrIndex aliasOrIndex = allAliasesAndIndices.get(indexOrAlias); + if (aliasOrIndex != null) { + for (IndexMetaData indexMetaData : aliasOrIndex.getIndices()) { + concreteIndices.add(indexMetaData.getIndex().getName()); } - return new AutomatonPredicate(Automatons.patterns(Collections.unmodifiableList(indices))); - }; - } + } - @Override - public Iterator iterator() { - return Arrays.asList(groups).iterator(); - } - - public Group[] groups() { - return groups; - } - - @Override - public boolean isEmpty() { - return groups == null || groups.length == 0; - } - - /** - * @return A predicate that will match all the indices that this permission - * has the privilege for executing the given action on. - */ - public Predicate allowedIndicesMatcher(String action) { - return allowedIndicesMatchersForAction.computeIfAbsent(action, loadingFunction); - } - - @Override - public boolean check(String action) { for (Group group : groups) { - if (group.check(action)) { - return true; - } - } - return false; - } - - @Override - public Map authorize(String action, Set requestedIndicesOrAliases, - MetaData metaData) { - // now... every index that is associated with the request, must be granted - // by at least one indices permission group - - SortedMap allAliasesAndIndices = metaData.getAliasAndIndexLookup(); - Map> fieldPermissionsByIndex = new HashMap<>(); - Map> roleQueriesByIndex = new HashMap<>(); - Map grantedBuilder = new HashMap<>(); - - for (String indexOrAlias : requestedIndicesOrAliases) { - boolean granted = false; - Set concreteIndices = new HashSet<>(); - AliasOrIndex aliasOrIndex = allAliasesAndIndices.get(indexOrAlias); - if (aliasOrIndex != null) { - for (IndexMetaData indexMetaData : aliasOrIndex.getIndices()) { - concreteIndices.add(indexMetaData.getIndex().getName()); - } - } - - for (Group group : groups) { - if (group.check(action, indexOrAlias)) { - granted = true; - for (String index : concreteIndices) { - if (fieldPermissionsByIndex.get(index) == null) { - fieldPermissionsByIndex.put(index, new HashSet<>()); - } - fieldPermissionsByIndex.get(index).add(group.getFieldPermissions()); - if (group.hasQuery()) { - Set roleQueries = roleQueriesByIndex.get(index); - if (roleQueries == null) { - roleQueries = new HashSet<>(); - roleQueriesByIndex.put(index, roleQueries); - } - roleQueries.add(group.getQuery()); - } - } - } - } - - if (concreteIndices.isEmpty()) { - grantedBuilder.put(indexOrAlias, granted); - } else { - for (String concreteIndex : concreteIndices) { - grantedBuilder.put(concreteIndex, granted); - } - } - } - - Map indexPermissions = new HashMap<>(); - for (Map.Entry entry : grantedBuilder.entrySet()) { - String index = entry.getKey(); - Set roleQueries = roleQueriesByIndex.get(index); - if (roleQueries != null) { - roleQueries = unmodifiableSet(roleQueries); - } - - FieldPermissions fieldPermissions = new FieldPermissions(); - Set indexFieldPermissions = fieldPermissionsByIndex.get(index); - if (indexFieldPermissions != null) { - // get the first field permission entry because we do not want the merge to overwrite granted fields with null - fieldPermissions = indexFieldPermissions.iterator().next(); - for (FieldPermissions fp : indexFieldPermissions) { - fieldPermissions = FieldPermissions.merge(fieldPermissions, fp); - } - } - indexPermissions.put(index, new IndicesAccessControl.IndexAccessControl(entry.getValue(), fieldPermissions, roleQueries)); - } - return unmodifiableMap(indexPermissions); - } - - } - - class Globals implements IndicesPermission { - - private final List globals; - - public Globals(List globals) { - this.globals = globals; - } - - @Override - public Iterator iterator() { - return globals == null || globals.isEmpty() ? - Collections.emptyIterator() : - new Globals.Iter(globals); - } - - @Override - public boolean isEmpty() { - if (globals == null || globals.isEmpty()) { - return true; - } - for (GlobalPermission global : globals) { - if (!global.indices().isEmpty()) { - return false; - } - } - return true; - } - - @Override - public boolean check(String action) { - if (globals == null) { - return false; - } - for (GlobalPermission global : globals) { - Objects.requireNonNull(global, "global must not be null"); - Objects.requireNonNull(global.indices(), "global.indices() must not be null"); - if (global.indices().check(action)) { - return true; - } - } - return false; - } - - @Override - public Map authorize(String action, Set requestedIndicesOrAliases, - MetaData metaData) { - if (isEmpty()) { - return emptyMap(); - } - - // What this code does is just merge `IndexAccessControl` instances from the permissions this class holds: - Map indicesAccessControl = null; - for (GlobalPermission permission : globals) { - Map temp = permission.indices().authorize(action, - requestedIndicesOrAliases, metaData); - if (indicesAccessControl == null) { - indicesAccessControl = new HashMap<>(temp); - } else { - for (Map.Entry entry : temp.entrySet()) { - IndicesAccessControl.IndexAccessControl existing = indicesAccessControl.get(entry.getKey()); - if (existing != null) { - indicesAccessControl.put(entry.getKey(), existing.merge(entry.getValue())); - } else { - indicesAccessControl.put(entry.getKey(), entry.getValue()); + if (group.check(action, indexOrAlias)) { + granted = true; + for (String index : concreteIndices) { + Set fieldPermissions = fieldPermissionsByIndex.computeIfAbsent(index, (k) -> new HashSet<>()); + fieldPermissions.add(group.getFieldPermissions()); + if (group.hasQuery()) { + Set roleQueries = roleQueriesByIndex.computeIfAbsent(index, (k) -> new HashSet<>()); + roleQueries.addAll(group.getQuery()); } } } } - if (indicesAccessControl == null) { - return emptyMap(); + + if (concreteIndices.isEmpty()) { + grantedBuilder.put(indexOrAlias, granted); } else { - return unmodifiableMap(indicesAccessControl); + for (String concreteIndex : concreteIndices) { + grantedBuilder.put(concreteIndex, granted); + } } } - static class Iter implements Iterator { - - private final Iterator globals; - private Iterator current; - - Iter(List globals) { - this.globals = globals.iterator(); - advance(); + Map indexPermissions = new HashMap<>(); + for (Map.Entry entry : grantedBuilder.entrySet()) { + String index = entry.getKey(); + Set roleQueries = roleQueriesByIndex.get(index); + if (roleQueries != null) { + roleQueries = unmodifiableSet(roleQueries); } - @Override - public boolean hasNext() { - return current != null && current.hasNext(); - } - - @Override - public Group next() { - Group group = current.next(); - advance(); - return group; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - private void advance() { - if (current != null && current.hasNext()) { - return; - } - if (!globals.hasNext()) { - // we've reached the end of the globals array - current = null; - return; - } - - while (globals.hasNext()) { - IndicesPermission indices = globals.next().indices(); - if (!indices.isEmpty()) { - current = indices.iterator(); - return; - } - } - - current = null; + final FieldPermissions fieldPermissions; + final Set indexFieldPermissions = fieldPermissionsByIndex.get(index); + if (indexFieldPermissions != null && indexFieldPermissions.isEmpty() == false) { + fieldPermissions = indexFieldPermissions.size() == 1 ? indexFieldPermissions.iterator().next() : + fieldPermissionsCache.getFieldPermissions(indexFieldPermissions); + } else { + fieldPermissions = FieldPermissions.DEFAULT; } + indexPermissions.put(index, new IndicesAccessControl.IndexAccessControl(entry.getValue(), fieldPermissions, roleQueries)); } + return unmodifiableMap(indexPermissions); } - class Group { + public static class Group { private final IndexPrivilege privilege; private final Predicate actionMatcher; private final String[] indices; @@ -332,14 +168,14 @@ public interface IndicesPermission extends Permission, Iterable query; - public Group(IndexPrivilege privilege, FieldPermissions fieldPermissions, @Nullable BytesReference query, String... indices) { + public Group(IndexPrivilege privilege, FieldPermissions fieldPermissions, @Nullable Set query, String... indices) { assert indices.length != 0; this.privilege = privilege; this.actionMatcher = privilege.predicate(); this.indices = indices; - this.indexNameMatcher = new AutomatonPredicate(Automatons.patterns(indices)); + this.indexNameMatcher = Automatons.predicate(indices); this.fieldPermissions = Objects.requireNonNull(fieldPermissions); this.query = query; } @@ -353,7 +189,7 @@ public interface IndicesPermission extends Permission, Iterable getQuery() { return query; } @@ -366,7 +202,7 @@ public interface IndicesPermission extends Permission, Iterable requestedIndicesOrAliases, MetaData metaData, + FieldPermissionsCache fieldPermissionsCache) { + Map indexPermissions = indices.authorize( + action, requestedIndicesOrAliases, metaData, fieldPermissionsCache + ); + + // At least one role / indices permission set need to match with all the requested indices/aliases: + boolean granted = true; + for (Map.Entry entry : indexPermissions.entrySet()) { + if (!entry.getValue().isGranted()) { + granted = false; + break; + } + } + return new IndicesAccessControl(granted, indexPermissions); } public static class Builder { private final String name; - private ClusterPermission.Core cluster = ClusterPermission.Core.NONE; - private RunAsPermission.Core runAs = RunAsPermission.Core.NONE; + private ClusterPermission cluster = ClusterPermission.NONE; + private RunAsPermission runAs = RunAsPermission.NONE; private List groups = new ArrayList<>(); + private FieldPermissionsCache fieldPermissionsCache = null; - private Builder(String name) { + private Builder(String name, FieldPermissionsCache fieldPermissionsCache) { this.name = name; + this.fieldPermissionsCache = fieldPermissionsCache; } - private Builder(RoleDescriptor rd) { + private Builder(RoleDescriptor rd, @Nullable FieldPermissionsCache fieldPermissionsCache) { this.name = rd.getName(); + this.fieldPermissionsCache = fieldPermissionsCache; if (rd.getClusterPrivileges().length == 0) { - cluster = ClusterPermission.Core.NONE; + cluster = ClusterPermission.NONE; } else { - this.cluster(ClusterPrivilege.get((new Privilege.Name(rd.getClusterPrivileges())))); + this.cluster(ClusterPrivilege.get(Sets.newHashSet(rd.getClusterPrivileges()))); } - groups.addAll(convertFromIndicesPrivileges(rd.getIndicesPrivileges())); + groups.addAll(convertFromIndicesPrivileges(rd.getIndicesPrivileges(), fieldPermissionsCache)); String[] rdRunAs = rd.getRunAs(); if (rdRunAs != null && rdRunAs.length > 0) { - this.runAs(new GeneralPrivilege(new Privilege.Name(rdRunAs), rdRunAs)); + this.runAs(new Privilege(Sets.newHashSet(rdRunAs), rdRunAs)); } } - // FIXME we should throw an exception if we have already set cluster or runAs... public Builder cluster(ClusterPrivilege privilege) { - cluster = new ClusterPermission.Core(privilege); + cluster = new ClusterPermission(privilege); return this; } - public Builder runAs(GeneralPrivilege privilege) { - runAs = new RunAsPermission.Core(privilege); + public Builder runAs(Privilege privilege) { + runAs = new RunAsPermission(privilege); return this; } public Builder add(IndexPrivilege privilege, String... indices) { - groups.add(new IndicesPermission.Group(privilege, new FieldPermissions(), null, indices)); + groups.add(new IndicesPermission.Group(privilege, FieldPermissions.DEFAULT, null, indices)); return this; } - public Builder add(FieldPermissions fieldPermissions, BytesReference query, IndexPrivilege privilege, String... indices) { + public Builder add(FieldPermissions fieldPermissions, Set query, IndexPrivilege privilege, String... indices) { groups.add(new IndicesPermission.Group(privilege, fieldPermissions, query, indices)); return this; } public Role build() { - IndicesPermission.Core indices = groups.isEmpty() ? IndicesPermission.Core.NONE : - new IndicesPermission.Core(groups.toArray(new IndicesPermission.Group[groups.size()])); + IndicesPermission indices = groups.isEmpty() ? IndicesPermission.NONE : + new IndicesPermission(groups.toArray(new IndicesPermission.Group[groups.size()])); return new Role(name, cluster, indices, runAs); } - static List convertFromIndicesPrivileges(RoleDescriptor.IndicesPrivileges[] indicesPrivileges) { + static List convertFromIndicesPrivileges(RoleDescriptor.IndicesPrivileges[] indicesPrivileges, + @Nullable FieldPermissionsCache fieldPermissionsCache) { List list = new ArrayList<>(indicesPrivileges.length); for (RoleDescriptor.IndicesPrivileges privilege : indicesPrivileges) { - list.add(new IndicesPermission.Group(IndexPrivilege.get(new Privilege.Name(privilege.getPrivileges())), - privilege.getFieldPermissions(), - privilege.getQuery(), + final FieldPermissions fieldPermissions = fieldPermissionsCache != null ? + fieldPermissionsCache.getFieldPermissions(privilege.getGrantedFields(), privilege.getDeniedFields()) : + new FieldPermissions(privilege.getGrantedFields(), privilege.getDeniedFields()); + final Set query = privilege.getQuery() == null ? null : Collections.singleton(privilege.getQuery()); + list.add(new IndicesPermission.Group(IndexPrivilege.get(Sets.newHashSet(privilege.getPrivileges())), + fieldPermissions, + query, privilege.getIndices())); } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/RunAsPermission.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/RunAsPermission.java index b517e46d666..e864ccb4e9e 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/RunAsPermission.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/RunAsPermission.java @@ -5,76 +5,28 @@ */ package org.elasticsearch.xpack.security.authz.permission; -import org.elasticsearch.xpack.security.authz.privilege.GeneralPrivilege; +import org.elasticsearch.xpack.security.authz.privilege.Privilege; -import java.util.List; import java.util.function.Predicate; /** * A permissions that is based on a general privilege that contains patterns of users that this * user can execute a request as */ -public interface RunAsPermission extends Permission { +public final class RunAsPermission { + + public static final RunAsPermission NONE = new RunAsPermission(Privilege.NONE); + + private final Predicate predicate; + + RunAsPermission(Privilege privilege) { + this.predicate = privilege.predicate(); + } /** * Checks if this permission grants run as to the specified user */ - boolean check(String username); - - class Core implements RunAsPermission { - - public static final Core NONE = new Core(GeneralPrivilege.NONE); - - private final GeneralPrivilege privilege; - private final Predicate predicate; - - public Core(GeneralPrivilege privilege) { - this.privilege = privilege; - this.predicate = privilege.predicate(); - } - - @Override - public boolean check(String username) { - return predicate.test(username); - } - - @Override - public boolean isEmpty() { - return this == NONE; - } - } - - class Globals implements RunAsPermission { - private final List globals; - - public Globals(List globals) { - this.globals = globals; - } - - @Override - public boolean check(String username) { - if (globals == null) { - return false; - } - for (GlobalPermission global : globals) { - if (global.runAs().check(username)) { - return true; - } - } - return false; - } - - @Override - public boolean isEmpty() { - if (globals == null || globals.isEmpty()) { - return true; - } - for (GlobalPermission global : globals) { - if (!global.isEmpty()) { - return false; - } - } - return true; - } + public boolean check(String username) { + return predicate.test(username); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRole.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRole.java deleted file mode 100644 index 181902bf02a..00000000000 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRole.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; -import org.elasticsearch.xpack.security.authz.privilege.GeneralPrivilege; -import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name; -import org.elasticsearch.xpack.security.support.MetadataUtils; - -public class SuperuserRole extends Role { - - public static final String NAME = "superuser"; - public static final RoleDescriptor DESCRIPTOR = new RoleDescriptor(NAME, new String[] { "all" }, - new RoleDescriptor.IndicesPrivileges[] { - RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build()}, - new String[] { "*" }, - MetadataUtils.DEFAULT_RESERVED_METADATA); - public static final SuperuserRole INSTANCE = new SuperuserRole(); - - private SuperuserRole() { - super(DESCRIPTOR.getName(), - new ClusterPermission.Core(ClusterPrivilege.get(new Name(DESCRIPTOR.getClusterPrivileges()))), - new IndicesPermission.Core(Role.Builder.convertFromIndicesPrivileges(DESCRIPTOR.getIndicesPrivileges())), - new RunAsPermission.Core(GeneralPrivilege.ALL)); - } -} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/TransportClientRole.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/TransportClientRole.java deleted file mode 100644 index 30c9a9cd7de..00000000000 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/permission/TransportClientRole.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; -import org.elasticsearch.xpack.security.authz.privilege.Privilege.Name; -import org.elasticsearch.xpack.security.support.MetadataUtils; - -/** - * Reserved role for the transport client - */ -public class TransportClientRole extends Role { - - public static final String NAME = "transport_client"; - private static final String[] CLUSTER_PRIVILEGES = new String[] { "transport_client" }; - - public static final RoleDescriptor DESCRIPTOR = - new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA); - public static final TransportClientRole INSTANCE = new TransportClientRole(); - - private TransportClientRole() { - super(DESCRIPTOR.getName(), - new ClusterPermission.Core(ClusterPrivilege.get(new Name(DESCRIPTOR.getClusterPrivileges()))), - IndicesPermission.Core.NONE, RunAsPermission.Core.NONE); - } -} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/AbstractAutomatonPrivilege.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/AbstractAutomatonPrivilege.java deleted file mode 100644 index 9ac88d28c41..00000000000 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/AbstractAutomatonPrivilege.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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.authz.privilege; - -import org.apache.lucene.util.automaton.Automaton; -import org.elasticsearch.xpack.security.support.AutomatonPredicate; -import org.elasticsearch.xpack.security.support.Automatons; - -import java.util.function.Predicate; - -import static org.apache.lucene.util.automaton.Operations.subsetOf; -import static org.elasticsearch.xpack.security.support.Automatons.patterns; - -@SuppressWarnings("unchecked") -abstract class AbstractAutomatonPrivilege

> extends Privilege

{ - - protected final Automaton automaton; - - AbstractAutomatonPrivilege(String name, String... patterns) { - super(new Name(name)); - this.automaton = patterns(patterns); - } - - AbstractAutomatonPrivilege(Name name, String... patterns) { - super(name); - this.automaton = patterns(patterns); - } - - AbstractAutomatonPrivilege(Name name, Automaton automaton) { - super(name); - this.automaton = automaton; - } - - @Override - public Predicate predicate() { - return new AutomatonPredicate(automaton); - } - - protected P plus(P other) { - if (other.implies((P) this)) { - return other; - } - if (this.implies(other)) { - return (P) this; - } - return create(name.add(other.name), Automatons.unionAndDeterminize(automaton, other.automaton)); - } - - @Override - public boolean implies(P other) { - return subsetOf(other.automaton, automaton); - } - - @Override - public String toString() { - return name.toString(); - } - - protected abstract P create(Name name, Automaton automaton); - - protected abstract P none(); - - -} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/ClusterPrivilege.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/ClusterPrivilege.java index 19f2b41037d..a22d2e73ef5 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/ClusterPrivilege.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/ClusterPrivilege.java @@ -7,30 +7,33 @@ package org.elasticsearch.xpack.security.authz.privilege; import org.apache.lucene.util.automaton.Automaton; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.xpack.security.support.Automatons; +import java.util.Collections; +import java.util.HashSet; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Predicate; -import static org.elasticsearch.xpack.security.support.Automatons.minusAndDeterminize; +import static org.elasticsearch.xpack.security.support.Automatons.minusAndMinimize; import static org.elasticsearch.xpack.security.support.Automatons.patterns; -public class ClusterPrivilege extends AbstractAutomatonPrivilege { +public final class ClusterPrivilege extends Privilege { // shared automatons private static final Automaton MANAGE_SECURITY_AUTOMATON = patterns("cluster:admin/xpack/security/*"); private static final Automaton MONITOR_AUTOMATON = patterns("cluster:monitor/*"); private static final Automaton ALL_CLUSTER_AUTOMATON = patterns("cluster:*", "indices:admin/template/*"); - private static final Automaton MANAGE_AUTOMATON = minusAndDeterminize(ALL_CLUSTER_AUTOMATON, MANAGE_SECURITY_AUTOMATON); + private static final Automaton MANAGE_AUTOMATON = minusAndMinimize(ALL_CLUSTER_AUTOMATON, MANAGE_SECURITY_AUTOMATON); private static final Automaton TRANSPORT_CLIENT_AUTOMATON = patterns("cluster:monitor/nodes/liveness", "cluster:monitor/state"); private static final Automaton MANAGE_IDX_TEMPLATE_AUTOMATON = patterns("indices:admin/template/*"); private static final Automaton MANAGE_INGEST_PIPELINE_AUTOMATON = patterns("cluster:admin/ingest/pipeline/*"); - public static final ClusterPrivilege NONE = new ClusterPrivilege(Name.NONE, Automatons.EMPTY); - public static final ClusterPrivilege ALL = new ClusterPrivilege(Name.ALL, ALL_CLUSTER_AUTOMATON); + public static final ClusterPrivilege NONE = new ClusterPrivilege("none", Automatons.EMPTY); + public static final ClusterPrivilege ALL = new ClusterPrivilege("all", ALL_CLUSTER_AUTOMATON); public static final ClusterPrivilege MONITOR = new ClusterPrivilege("monitor", MONITOR_AUTOMATON); public static final ClusterPrivilege MANAGE = new ClusterPrivilege("manage", MANAGE_AUTOMATON); public static final ClusterPrivilege MANAGE_IDX_TEMPLATES = @@ -43,89 +46,69 @@ public class ClusterPrivilege extends AbstractAutomatonPrivilege ACTION_MATCHER = ClusterPrivilege.ALL.predicate(); - private static final Set values = new CopyOnWriteArraySet<>(); + private static final Map VALUES = MapBuilder.newMapBuilder() + .put("none", NONE) + .put("all", ALL) + .put("monitor", MONITOR) + .put("manage", MANAGE) + .put("manage_index_templates", MANAGE_IDX_TEMPLATES) + .put("manage_ingest_pipelines", MANAGE_INGEST_PIPELINES) + .put("transport_client", TRANSPORT_CLIENT) + .put("manage_security", MANAGE_SECURITY) + .put("manage_pipeline", MANAGE_PIPELINE) + .immutableMap(); - static { - values.add(NONE); - values.add(ALL); - values.add(MONITOR); - values.add(MANAGE); - values.add(MANAGE_IDX_TEMPLATES); - values.add(MANAGE_INGEST_PIPELINES); - values.add(TRANSPORT_CLIENT); - values.add(MANAGE_SECURITY); - values.add(MANAGE_PIPELINE); - } - - static Set values() { - return values; - } - - private static final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap, ClusterPrivilege> CACHE = new ConcurrentHashMap<>(); private ClusterPrivilege(String name, String... patterns) { super(name, patterns); } private ClusterPrivilege(String name, Automaton automaton) { - super(new Name(name), automaton); + super(Collections.singleton(name), automaton); } - private ClusterPrivilege(Name name, Automaton automaton) { + private ClusterPrivilege(Set name, Automaton automaton) { super(name, automaton); } - public static void addCustom(String name, String... actionPatterns) { - for (String pattern : actionPatterns) { - if (!ClusterPrivilege.ACTION_MATCHER.test(pattern)) { - throw new IllegalArgumentException("cannot register custom cluster privilege [" + name + "]. " + - "cluster action must follow the 'cluster:*' format"); + public static ClusterPrivilege get(Set name) { + if (name == null || name.isEmpty()) { + return NONE; + } + return CACHE.computeIfAbsent(name, ClusterPrivilege::resolve); + } + + private static ClusterPrivilege resolve(Set name) { + final int size = name.size(); + if (size == 0) { + throw new IllegalArgumentException("empty set should not be used"); + } + + Set actions = new HashSet<>(); + Set automata = new HashSet<>(); + for (String part : name) { + part = part.toLowerCase(Locale.ROOT); + if (ACTION_MATCHER.test(part)) { + actions.add(actionToPattern(part)); + } else { + ClusterPrivilege privilege = VALUES.get(part); + if (privilege != null && size == 1) { + return privilege; + } else if (privilege != null) { + automata.add(privilege.automaton); + } else { + throw new IllegalArgumentException("unknown cluster privilege [" + name + "]. a privilege must be either " + + "one of the predefined fixed cluster privileges [" + + Strings.collectionToCommaDelimitedString(VALUES.entrySet()) + "] or a pattern over one of the available " + + "cluster actions"); + } } } - ClusterPrivilege custom = new ClusterPrivilege(name, actionPatterns); - if (values.contains(custom)) { - throw new IllegalArgumentException("cannot register custom cluster privilege [" + name + "] as it already exists."); + + if (actions.isEmpty() == false) { + automata.add(patterns(actions)); } - values.add(custom); - } - - @Override - protected ClusterPrivilege create(Name name, Automaton automaton) { - return new ClusterPrivilege(name, automaton); - } - - @Override - protected ClusterPrivilege none() { - return NONE; - } - - public static ClusterPrivilege action(String action) { - String pattern = actionToPattern(action); - return new ClusterPrivilege(action, pattern); - } - - public static ClusterPrivilege get(Name name) { - return cache.computeIfAbsent(name, (theName) -> { - ClusterPrivilege cluster = NONE; - for (String part : theName.parts) { - cluster = cluster == NONE ? resolve(part) : cluster.plus(resolve(part)); - } - return cluster; - }); - } - - private static ClusterPrivilege resolve(String name) { - name = name.toLowerCase(Locale.ROOT); - if (ACTION_MATCHER.test(name)) { - return action(name); - } - for (ClusterPrivilege cluster : values) { - if (name.equals(cluster.name.toString())) { - return cluster; - } - } - throw new IllegalArgumentException("unknown cluster privilege [" + name + "]. a privilege must be either " + - "one of the predefined fixed cluster privileges [" + Strings.collectionToCommaDelimitedString(values) + - "] or a pattern over one of the available cluster actions"); + return new ClusterPrivilege(name, Automatons.unionAndMinimize(automata)); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/GeneralPrivilege.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/GeneralPrivilege.java deleted file mode 100644 index 2b4864c4540..00000000000 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/GeneralPrivilege.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.authz.privilege; - -import org.apache.lucene.util.automaton.Automaton; -import org.elasticsearch.xpack.security.support.Automatons; - -public class GeneralPrivilege extends AbstractAutomatonPrivilege { - - public static final GeneralPrivilege NONE = new GeneralPrivilege(Name.NONE, Automatons.EMPTY); - public static final GeneralPrivilege ALL = new GeneralPrivilege(Name.ALL, Automatons.MATCH_ALL); - - public GeneralPrivilege(String name, String... patterns) { - super(name, patterns); - } - - public GeneralPrivilege(Name name, String... patterns) { - super(name, patterns); - } - - public GeneralPrivilege(Name name, Automaton automaton) { - super(name, automaton); - } - - @Override - protected GeneralPrivilege create(Name name, Automaton automaton) { - return new GeneralPrivilege(name, automaton); - } - - @Override - protected GeneralPrivilege none() { - return NONE; - } -} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/HealthAndStatsPrivilege.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/HealthAndStatsPrivilege.java index 654925c2503..1130469b901 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/HealthAndStatsPrivilege.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/HealthAndStatsPrivilege.java @@ -5,7 +5,7 @@ */ package org.elasticsearch.xpack.security.authz.privilege; -public class HealthAndStatsPrivilege extends GeneralPrivilege { +public final class HealthAndStatsPrivilege extends Privilege { public static final HealthAndStatsPrivilege INSTANCE = new HealthAndStatsPrivilege(); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/IndexPrivilege.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/IndexPrivilege.java index 6bf2a392ab7..06c15419fc5 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/IndexPrivilege.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/IndexPrivilege.java @@ -20,18 +20,22 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingAction; import org.elasticsearch.action.admin.indices.settings.get.GetSettingsAction; import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryAction; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.xpack.security.support.Automatons; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArraySet; import java.util.function.Predicate; import static org.elasticsearch.xpack.security.support.Automatons.patterns; -import static org.elasticsearch.xpack.security.support.Automatons.unionAndDeterminize; +import static org.elasticsearch.xpack.security.support.Automatons.unionAndMinimize; -public class IndexPrivilege extends AbstractAutomatonPrivilege { +public final class IndexPrivilege extends Privilege { private static final Automaton ALL_AUTOMATON = patterns("indices:*"); private static final Automaton READ_AUTOMATON = patterns("indices:data/read/*"); @@ -41,15 +45,16 @@ public class IndexPrivilege extends AbstractAutomatonPrivilege { private static final Automaton DELETE_AUTOMATON = patterns("indices:data/write/delete*"); private static final Automaton WRITE_AUTOMATON = patterns("indices:data/write/*", PutMappingAction.NAME); private static final Automaton MONITOR_AUTOMATON = patterns("indices:monitor/*"); - private static final Automaton MANAGE_AUTOMATON = unionAndDeterminize(MONITOR_AUTOMATON, patterns("indices:admin/*")); + private static final Automaton MANAGE_AUTOMATON = + unionAndMinimize(Arrays.asList(MONITOR_AUTOMATON, patterns("indices:admin/*"))); private static final Automaton CREATE_INDEX_AUTOMATON = patterns(CreateIndexAction.NAME); private static final Automaton DELETE_INDEX_AUTOMATON = patterns(DeleteIndexAction.NAME); private static final Automaton VIEW_METADATA_AUTOMATON = patterns(GetAliasesAction.NAME, AliasesExistAction.NAME, GetIndexAction.NAME, IndicesExistsAction.NAME, GetFieldMappingsAction.NAME + "*", GetMappingsAction.NAME, ClusterSearchShardsAction.NAME, TypesExistsAction.NAME, ValidateQueryAction.NAME + "*", GetSettingsAction.NAME); - public static final IndexPrivilege NONE = new IndexPrivilege(Name.NONE, Automatons.EMPTY); - public static final IndexPrivilege ALL = new IndexPrivilege(Name.ALL, ALL_AUTOMATON); + public static final IndexPrivilege NONE = new IndexPrivilege("none", Automatons.EMPTY); + public static final IndexPrivilege ALL = new IndexPrivilege("all", ALL_AUTOMATON); public static final IndexPrivilege READ = new IndexPrivilege("read", READ_AUTOMATON); public static final IndexPrivilege CREATE = new IndexPrivilege("create", CREATE_AUTOMATON); public static final IndexPrivilege INDEX = new IndexPrivilege("index", INDEX_AUTOMATON); @@ -61,106 +66,78 @@ public class IndexPrivilege extends AbstractAutomatonPrivilege { public static final IndexPrivilege CREATE_INDEX = new IndexPrivilege("create_index", CREATE_INDEX_AUTOMATON); public static final IndexPrivilege VIEW_METADATA = new IndexPrivilege("view_index_metadata", VIEW_METADATA_AUTOMATON); - private static final Set values = new CopyOnWriteArraySet<>(); - - static { - values.add(NONE); - values.add(ALL); - values.add(MANAGE); - values.add(CREATE_INDEX); - values.add(MONITOR); - values.add(READ); - values.add(INDEX); - values.add(DELETE); - values.add(WRITE); - values.add(CREATE); - values.add(DELETE_INDEX); - values.add(VIEW_METADATA); - } + private static final Map VALUES = MapBuilder.newMapBuilder() + .put("none", NONE) + .put("all", ALL) + .put("manage", MANAGE) + .put("create_index", CREATE_INDEX) + .put("monitor", MONITOR) + .put("read", READ) + .put("index", INDEX) + .put("delete", DELETE) + .put("write", WRITE) + .put("create", CREATE) + .put("delete_index", DELETE_INDEX) + .put("view_index_metadata", VIEW_METADATA) + .immutableMap(); public static final Predicate ACTION_MATCHER = ALL.predicate(); public static final Predicate CREATE_INDEX_MATCHER = CREATE_INDEX.predicate(); - static Set values() { - return values; - } - - private static final ConcurrentHashMap cache = new ConcurrentHashMap<>(); - - private IndexPrivilege(String name, String... patterns) { - super(name, patterns); - } + private static final ConcurrentHashMap, IndexPrivilege> CACHE = new ConcurrentHashMap<>(); private IndexPrivilege(String name, Automaton automaton) { - super(new Name(name), automaton); + super(Collections.singleton(name), automaton); } - private IndexPrivilege(Name name, Automaton automaton) { + private IndexPrivilege(Set name, Automaton automaton) { super(name, automaton); } - public static void addCustom(String name, String... actionPatterns) { - for (String pattern : actionPatterns) { - if (!IndexPrivilege.ACTION_MATCHER.test(pattern)) { - throw new IllegalArgumentException("cannot register custom index privilege [" + name + "]." + - " index action must follow the 'indices:*' format"); + public static IndexPrivilege get(Set name) { + return CACHE.computeIfAbsent(name, (theName) -> { + if (theName.isEmpty()) { + return NONE; + } else { + return resolve(theName); } - } - IndexPrivilege custom = new IndexPrivilege(name, actionPatterns); - if (values.contains(custom)) { - throw new IllegalArgumentException("cannot register custom index privilege [" + name + "] as it already exists."); - } - values.add(custom); - } - - @Override - protected IndexPrivilege create(Name name, Automaton automaton) { - if (name == Name.NONE) { - return NONE; - } - return new IndexPrivilege(name, automaton); - } - - @Override - protected IndexPrivilege none() { - return NONE; - } - - public static IndexPrivilege action(String action) { - return new IndexPrivilege(action, actionToPattern(action)); - } - - public static IndexPrivilege get(Name name) { - return cache.computeIfAbsent(name, (theName) -> { - IndexPrivilege index = NONE; - for (String part : theName.parts) { - index = index == NONE ? resolve(part) : index.plus(resolve(part)); - } - return index; }); } - public static IndexPrivilege union(IndexPrivilege... indices) { - IndexPrivilege result = NONE; - for (IndexPrivilege index : indices) { - result = result.plus(index); + private static IndexPrivilege resolve(Set name) { + final int size = name.size(); + if (size == 0) { + throw new IllegalArgumentException("empty set should not be used"); } - return result; - } - private static IndexPrivilege resolve(String name) { - name = name.toLowerCase(Locale.ROOT); - if (ACTION_MATCHER.test(name)) { - return action(name); - } - for (IndexPrivilege index : values) { - if (name.toLowerCase(Locale.ROOT).equals(index.name.toString())) { - return index; + Set actions = new HashSet<>(); + Set automata = new HashSet<>(); + for (String part : name) { + part = part.toLowerCase(Locale.ROOT); + if (ACTION_MATCHER.test(part)) { + actions.add(actionToPattern(part)); + } else { + IndexPrivilege indexPrivilege = VALUES.get(part); + if (indexPrivilege != null && size == 1) { + return indexPrivilege; + } else if (indexPrivilege != null) { + automata.add(indexPrivilege.automaton); + } else { + throw new IllegalArgumentException("unknown index privilege [" + name + "]. a privilege must be either " + + "one of the predefined fixed indices privileges [" + + Strings.collectionToCommaDelimitedString(VALUES.entrySet()) + "] or a pattern over one of the available index" + + " actions"); + } } } - throw new IllegalArgumentException("unknown index privilege [" + name + "]. a privilege must be either " + - "one of the predefined fixed indices privileges [" + Strings.collectionToCommaDelimitedString(values) + - "] or a pattern over one of the available index actions"); + + if (actions.isEmpty() == false) { + automata.add(patterns(actions)); + } + return new IndexPrivilege(name, Automatons.unionAndMinimize(automata)); } + static Map values() { + return VALUES; + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/Privilege.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/Privilege.java index 14a2f4283a2..382bea5fe2b 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/Privilege.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/Privilege.java @@ -5,36 +5,44 @@ */ package org.elasticsearch.xpack.security.authz.privilege; -import org.elasticsearch.common.Strings; -import org.elasticsearch.common.util.set.Sets; +import org.apache.lucene.util.automaton.Automaton; +import org.elasticsearch.xpack.security.support.Automatons; -import java.util.HashSet; +import java.util.Collections; import java.util.Set; import java.util.function.Predicate; -import static java.util.Collections.singleton; -import static java.util.Collections.unmodifiableSet; -import static org.elasticsearch.common.util.set.Sets.newHashSet; +import static org.elasticsearch.xpack.security.support.Automatons.patterns; -public abstract class Privilege

> { +public class Privilege { - protected final Name name; + public static final Privilege NONE = new Privilege(Collections.singleton("none"), Automatons.EMPTY); + public static final Privilege ALL = new Privilege(Collections.singleton("all"), Automatons.MATCH_ALL); - Privilege(Name name) { - this.name = name; + protected final Set name; + protected final Automaton automaton; + protected final Predicate predicate; + + public Privilege(String name, String... patterns) { + this(Collections.singleton(name), patterns); } - public Name name() { + public Privilege(Set name, String... patterns) { + this(name, patterns(patterns)); + } + + public Privilege(Set name, Automaton automaton) { + this.name = name; + this.automaton = automaton; + this.predicate = Automatons.predicate(automaton); + } + + public Set name() { return name; } - public abstract Predicate predicate(); - - public abstract boolean implies(P other); - - @SuppressWarnings("unchecked") - public boolean isAlias(P other) { - return this.implies(other) && other.implies((P) this); + public Predicate predicate() { + return predicate; } @Override @@ -58,54 +66,12 @@ public abstract class Privilege

> { return text + "*"; } - public static class Name { + @Override + public String toString() { + return name.toString(); + } - public static final Name NONE = new Name("none"); - public static final Name ALL = new Name("all"); - - final Set parts; - - public Name(String name) { - assert name != null && !name.contains(","); - parts = singleton(name); - } - - public Name(Set parts) { - assert !parts.isEmpty(); - this.parts = unmodifiableSet(new HashSet<>(parts)); - } - - public Name(String... parts) { - this(unmodifiableSet(newHashSet(parts))); - } - - @Override - public String toString() { - return Strings.collectionToCommaDelimitedString(parts); - } - - public Name add(Name other) { - return new Name(Sets.union(parts, other.parts)); - } - - public Name remove(Name other) { - Set parts = Sets.difference(this.parts, other.parts); - return parts.isEmpty() ? NONE : new Name(parts); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Name name = (Name) o; - - return parts.equals(name.parts); - } - - @Override - public int hashCode() { - return parts.hashCode(); - } + public Automaton getAutomaton() { + return automaton; } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/SystemPrivilege.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/SystemPrivilege.java index 2b4e2d93b1c..779792b8ae3 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/SystemPrivilege.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/privilege/SystemPrivilege.java @@ -5,35 +5,29 @@ */ package org.elasticsearch.xpack.security.authz.privilege; -import org.elasticsearch.xpack.security.support.AutomatonPredicate; +import org.elasticsearch.xpack.security.support.Automatons; +import java.util.Collections; import java.util.function.Predicate; -import static org.elasticsearch.xpack.security.support.Automatons.patterns; - -public class SystemPrivilege extends Privilege { +public final class SystemPrivilege extends Privilege { public static SystemPrivilege INSTANCE = new SystemPrivilege(); - protected static final Predicate PREDICATE = new AutomatonPredicate(patterns( + private static final Predicate PREDICATE = Automatons.predicate( "internal:*", "indices:monitor/*", // added for monitoring "cluster:monitor/*", // added for monitoring "cluster:admin/reroute", // added for DiskThresholdDecider.DiskListener "indices:admin/mapping/put" // needed for recovery and shrink api - )); + ); - SystemPrivilege() { - super(new Name("internal")); + private SystemPrivilege() { + super(Collections.singleton("internal")); } @Override public Predicate predicate() { return PREDICATE; } - - @Override - public boolean implies(SystemPrivilege other) { - return true; - } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java index 2cc09de0722..ca4615a3980 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStore.java @@ -6,12 +6,40 @@ package org.elasticsearch.xpack.security.authz.store; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.cache.Cache; +import org.elasticsearch.common.cache.CacheBuilder; import org.elasticsearch.common.component.AbstractComponent; +import org.elasticsearch.common.inject.internal.Nullable; +import org.elasticsearch.common.settings.Setting; +import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ConcurrentCollections; +import org.elasticsearch.common.util.concurrent.ReleasableLock; +import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissionsCache; import org.elasticsearch.xpack.security.authz.permission.Role; +import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; +import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; +import org.elasticsearch.xpack.security.authz.privilege.Privilege; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; + +import static org.elasticsearch.xpack.security.Security.setting; /** * A composite roles store that combines built in roles, file-based roles, and index-based roles. Checks the built in roles first, then the @@ -19,44 +47,188 @@ import java.util.Map; */ public class CompositeRolesStore extends AbstractComponent { + // the lock is used in an odd manner; when iterating over the cache we cannot have modifiers other than deletes using + // the iterator but when not iterating we can modify the cache without external locking. When making normal modifications to the cache + // the read lock is obtained so that we can allow concurrent modifications; however when we need to iterate over the keys or values of + // the cache the write lock must obtained to prevent any modifications + private final ReleasableLock readLock; + private final ReleasableLock writeLock; + + { + final ReadWriteLock iterationLock = new ReentrantReadWriteLock(); + readLock = new ReleasableLock(iterationLock.readLock()); + writeLock = new ReleasableLock(iterationLock.writeLock()); + } + + public static final Setting CACHE_SIZE_SETTING = + Setting.intSetting(setting("authz.store.roles.cache.max_size"), 10000, Property.NodeScope); + private final FileRolesStore fileRolesStore; private final NativeRolesStore nativeRolesStore; private final ReservedRolesStore reservedRolesStore; + private final Cache, Role> roleCache; + private final Set negativeLookupCache; + private final AtomicLong numInvalidation = new AtomicLong(); public CompositeRolesStore(Settings settings, FileRolesStore fileRolesStore, NativeRolesStore nativeRolesStore, ReservedRolesStore reservedRolesStore) { super(settings); this.fileRolesStore = fileRolesStore; + // invalidating all on a file based role update is heavy handed to say the least, but in general this should be infrequent so the + // impact isn't really worth the added complexity of only clearing the changed values + fileRolesStore.addListener(this::invalidateAll); this.nativeRolesStore = nativeRolesStore; this.reservedRolesStore = reservedRolesStore; - } - - private Role getBuildInRole(String role) { - // builtins first - Role builtIn = reservedRolesStore.role(role); - if (builtIn != null) { - logger.trace("loaded role [{}] from reserved roles store", role); - return builtIn; + CacheBuilder, Role> builder = CacheBuilder.builder(); + final int cacheSize = CACHE_SIZE_SETTING.get(settings); + if (cacheSize >= 0) { + builder.setMaximumWeight(cacheSize); } - - // Try the file next, then the index if it isn't there - Role fileRole = fileRolesStore.role(role); - if (fileRole != null) { - logger.trace("loaded role [{}] from file roles store", role); - return fileRole; - } - return null; + this.roleCache = builder.build(); + this.negativeLookupCache = ConcurrentCollections.newConcurrentSet(); } - public void roles(String role, ActionListener roleActionListener) { - Role storedRole = getBuildInRole(role); - if (storedRole == null) { - nativeRolesStore.role(role, roleActionListener); + public void roles(Set roleNames, FieldPermissionsCache fieldPermissionsCache, ActionListener roleActionListener) { + Role existing = roleCache.get(roleNames); + if (existing != null) { + roleActionListener.onResponse(existing); } else { - roleActionListener.onResponse(storedRole); + final long invalidationCounter = numInvalidation.get(); + roleDescriptors(roleNames, ActionListener.wrap( + (descriptors) -> { + final Role role = buildRoleFromDescriptors(descriptors, fieldPermissionsCache); + if (role != null) { + 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); + } + } + } + roleActionListener.onResponse(role); + }, + roleActionListener::onFailure)); } } + private void roleDescriptors(Set roleNames, ActionListener> roleDescriptorActionListener) { + final Set filteredRoleNames = + roleNames.stream().filter((s) -> negativeLookupCache.contains(s) == false).collect(Collectors.toSet()); + final Set builtInRoleDescriptors = getBuiltInRoleDescriptors(filteredRoleNames); + Set remainingRoleNames = difference(filteredRoleNames, builtInRoleDescriptors); + if (remainingRoleNames.isEmpty()) { + roleDescriptorActionListener.onResponse(Collections.unmodifiableSet(builtInRoleDescriptors)); + } else { + nativeRolesStore.getRoleDescriptors(remainingRoleNames.toArray(Strings.EMPTY_ARRAY), ActionListener.wrap((descriptors) -> { + builtInRoleDescriptors.addAll(descriptors); + if (builtInRoleDescriptors.size() != filteredRoleNames.size()) { + final Set missing = difference(filteredRoleNames, builtInRoleDescriptors); + assert missing.isEmpty() == false : "the missing set should not be empty if the sizes didn't match"; + negativeLookupCache.addAll(missing); + } + roleDescriptorActionListener.onResponse(Collections.unmodifiableSet(builtInRoleDescriptors)); + }, roleDescriptorActionListener::onFailure)); + } + } + + private Set getBuiltInRoleDescriptors(Set roleNames) { + final Set descriptors = reservedRolesStore.roleDescriptors().stream() + .filter((rd) -> roleNames.contains(rd.getName())) + .collect(Collectors.toCollection(HashSet::new)); + + final Set difference = difference(roleNames, descriptors); + if (difference.isEmpty() == false) { + descriptors.addAll(fileRolesStore.roleDescriptors(difference)); + } + + return descriptors; + } + + private Set difference(Set roleNames, Set descriptors) { + Set foundNames = descriptors.stream().map(RoleDescriptor::getName).collect(Collectors.toSet()); + return Sets.difference(roleNames, foundNames); + } + + public static Role buildRoleFromDescriptors(Set roleDescriptors, FieldPermissionsCache fieldPermissionsCache) { + if (roleDescriptors.isEmpty()) { + return Role.EMPTY; + } + StringBuilder nameBuilder = new StringBuilder(); + Set clusterPrivileges = new HashSet<>(); + Set runAs = new HashSet<>(); + Map, MergableIndicesPrivilege> indicesPrivilegesMap = new HashMap<>(); + for (RoleDescriptor descriptor : roleDescriptors) { + nameBuilder.append(descriptor.getName()); + nameBuilder.append('_'); + if (descriptor.getClusterPrivileges() != null) { + clusterPrivileges.addAll(Arrays.asList(descriptor.getClusterPrivileges())); + } + if (descriptor.getRunAs() != null) { + runAs.addAll(Arrays.asList(descriptor.getRunAs())); + } + IndicesPrivileges[] indicesPrivileges = descriptor.getIndicesPrivileges(); + for (IndicesPrivileges indicesPrivilege : indicesPrivileges) { + Set key = Sets.newHashSet(indicesPrivilege.getIndices()); + // if a index privilege is an explicit denial, then we treat it as non-existent since we skipped these in the past when + // merging + final boolean isExplicitDenial = + indicesPrivileges.length == 1 && "none".equalsIgnoreCase(indicesPrivilege.getPrivileges()[0]); + if (isExplicitDenial == false) { + indicesPrivilegesMap.compute(key, (k, value) -> { + if (value == null) { + return new MergableIndicesPrivilege(indicesPrivilege.getIndices(), indicesPrivilege.getPrivileges(), + indicesPrivilege.getGrantedFields(), indicesPrivilege.getDeniedFields(), indicesPrivilege.getQuery()); + } else { + value.merge(new MergableIndicesPrivilege(indicesPrivilege.getIndices(), indicesPrivilege.getPrivileges(), + indicesPrivilege.getGrantedFields(), indicesPrivilege.getDeniedFields(), indicesPrivilege.getQuery())); + return value; + } + }); + } + } + } + + final Set clusterPrivs = clusterPrivileges.isEmpty() ? null : clusterPrivileges; + final Privilege runAsPrivilege = runAs.isEmpty() ? Privilege.NONE : new Privilege(runAs, runAs.toArray(Strings.EMPTY_ARRAY)); + Role.Builder builder = Role.builder(nameBuilder.toString(), fieldPermissionsCache) + .cluster(ClusterPrivilege.get(clusterPrivs)) + .runAs(runAsPrivilege); + indicesPrivilegesMap.entrySet().forEach((entry) -> { + MergableIndicesPrivilege privilege = entry.getValue(); + builder.add(fieldPermissionsCache.getFieldPermissions(privilege.grantedFields, privilege.deniedFields), privilege.query, + IndexPrivilege.get(privilege.privileges), privilege.indices.toArray(Strings.EMPTY_ARRAY)); + }); + return builder.build(); + } + + public void invalidateAll() { + numInvalidation.incrementAndGet(); + negativeLookupCache.clear(); + try (ReleasableLock ignored = readLock.acquire()) { + roleCache.invalidateAll(); + } + } + + public void invalidate(String role) { + numInvalidation.incrementAndGet(); + + // 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(); + while (keyIter.hasNext()) { + Set key = keyIter.next(); + if (key.contains(role)) { + keyIter.remove(); + } + } + } + negativeLookupCache.remove(role); + } public Map usageStats() { Map usage = new HashMap<>(2); @@ -64,4 +236,49 @@ public class CompositeRolesStore extends AbstractComponent { usage.put("native", nativeRolesStore.usageStats()); return usage; } + + /** + * A mutable class that can be used to represent the combination of one or more {@link IndicesPrivileges} + */ + private static class MergableIndicesPrivilege { + private Set indices; + private Set privileges; + private Set grantedFields = null; + private Set deniedFields = null; + private Set query = null; + + MergableIndicesPrivilege(String[] indices, String[] privileges, @Nullable String[] grantedFields, @Nullable String[] deniedFields, + @Nullable BytesReference query) { + this.indices = Sets.newHashSet(Objects.requireNonNull(indices)); + this.privileges = Sets.newHashSet(Objects.requireNonNull(privileges)); + this.grantedFields = grantedFields == null ? null : Sets.newHashSet(grantedFields); + this.deniedFields = deniedFields == null ? null : Sets.newHashSet(deniedFields); + if (query != null) { + this.query = Sets.newHashSet(query); + } + } + + void merge(MergableIndicesPrivilege other) { + assert indices.equals(other.indices) : "index names must be equivalent in order to merge"; + this.grantedFields = combineFieldSets(this.grantedFields, other.grantedFields); + this.deniedFields = combineFieldSets(this.deniedFields, other.deniedFields); + this.privileges.addAll(other.privileges); + + if (this.query == null || other.query == null) { + this.query = null; + } else { + this.query.addAll(other.query); + } + } + + private static Set combineFieldSets(Set set, Set other) { + if (set == null || other == null) { + // null = grant all so it trumps others + return null; + } else { + set.addAll(other); + return set; + } + } + } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java index c2effeb7d66..8254e936060 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/FileRolesStore.java @@ -8,10 +8,9 @@ package org.elasticsearch.xpack.security.authz.store; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.util.Supplier; -import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.common.Nullable; -import org.elasticsearch.common.component.AbstractLifecycleComponent; +import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.XContentParser; @@ -23,8 +22,7 @@ import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.permission.IndicesPermission.Group; -import org.elasticsearch.xpack.security.authz.permission.Role; +import org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges; import org.elasticsearch.xpack.security.support.NoOpLogger; import org.elasticsearch.xpack.security.support.Validation; @@ -34,8 +32,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.regex.Pattern; @@ -43,51 +43,41 @@ import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; import static java.util.Collections.unmodifiableMap; -public class FileRolesStore extends AbstractLifecycleComponent { +public class FileRolesStore extends AbstractComponent { private static final Pattern IN_SEGMENT_LINE = Pattern.compile("^\\s+.+"); private static final Pattern SKIP_LINE = Pattern.compile("(^#.*|^\\s*)"); private final Path file; - private final Runnable listener; - private final ResourceWatcherService watcherService; + private final List listeners = new ArrayList<>(); - private volatile Map permissions; + private volatile Map permissions; - public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService) { - this(settings, env, watcherService, () -> {}); + public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService) throws IOException { + this(settings, env, watcherService, null); } - public FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, Runnable listener) { + FileRolesStore(Settings settings, Environment env, ResourceWatcherService watcherService, Runnable listener) throws IOException { super(settings); this.file = resolveFile(env); - this.listener = listener; - this.watcherService = watcherService; - permissions = emptyMap(); - } - - @Override - protected void doStart() throws ElasticsearchException { + if (listener != null) { + listeners.add(listener); + } FileWatcher watcher = new FileWatcher(file.getParent()); watcher.addListener(new FileListener()); - try { - watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH); - } catch (IOException e) { - throw new ElasticsearchException("failed to setup roles file watcher", e); - } + watcherService.add(watcher, ResourceWatcherService.Frequency.HIGH); permissions = parseFile(file, logger, settings); } - @Override - protected void doStop() throws ElasticsearchException { - } - - @Override - protected void doClose() throws ElasticsearchException { - } - - public Role role(String role) { - return permissions.get(role); + Set roleDescriptors(Set roleNames) { + Set descriptors = new HashSet<>(); + roleNames.forEach((name) -> { + RoleDescriptor descriptor = permissions.get(name); + if (descriptor != null) { + descriptors.add(descriptor); + } + }); + return descriptors; } public Map usageStats() { @@ -96,10 +86,10 @@ public class FileRolesStore extends AbstractLifecycleComponent { boolean dls = false; boolean fls = false; - for (Role role : permissions.values()) { - for (Group group : role.indices()) { - fls = fls || group.getFieldPermissions().hasFieldLevelSecurity(); - dls = dls || group.hasQuery(); + for (RoleDescriptor descriptor : permissions.values()) { + for (IndicesPrivileges indicesPrivileges : descriptor.getIndicesPrivileges()) { + fls = fls || indicesPrivileges.getGrantedFields() != null || indicesPrivileges.getDeniedFields() != null; + dls = dls || indicesPrivileges.getQuery() != null; } if (fls && dls) { break; @@ -111,40 +101,48 @@ public class FileRolesStore extends AbstractLifecycleComponent { return usageStats; } + void addListener(Runnable runnable) { + Objects.requireNonNull(runnable); + synchronized (this) { + listeners.add(runnable); + } + } + public static Path resolveFile(Environment env) { return XPackPlugin.resolveConfigFile(env, "roles.yml"); } public static Set parseFileForRoleNames(Path path, Logger logger) { - Map roleMap = parseFile(path, logger, false, Settings.EMPTY); + Map roleMap = parseFile(path, logger, false, Settings.EMPTY); if (roleMap == null) { return emptySet(); } return roleMap.keySet(); } - public static Map parseFile(Path path, Logger logger, Settings settings) { + public static Map parseFile(Path path, Logger logger, Settings settings) { return parseFile(path, logger, true, settings); } - public static Map parseFile(Path path, Logger logger, boolean resolvePermission, Settings settings) { + public static Map parseFile(Path path, Logger logger, boolean resolvePermission, + Settings settings) { if (logger == null) { logger = NoOpLogger.INSTANCE; } - Map roles = new HashMap<>(); + Map roles = new HashMap<>(); logger.debug("attempting to read roles file located at [{}]", path.toAbsolutePath()); if (Files.exists(path)) { try { List roleSegments = roleSegments(path); for (String segment : roleSegments) { - Role role = parseRole(segment, path, logger, resolvePermission, settings); - if (role != null) { - if (ReservedRolesStore.isReserved(role.name())) { + RoleDescriptor descriptor = parseRoleDescriptor(segment, path, logger, resolvePermission, settings); + if (descriptor != null) { + if (ReservedRolesStore.isReserved(descriptor.getName())) { logger.warn("role [{}] is reserved. the relevant role definition in the mapping file will be ignored", - role.name()); + descriptor.getName()); } else { - roles.put(role.name(), role); + roles.put(descriptor.getName(), descriptor); } } } @@ -195,29 +193,6 @@ public class FileRolesStore extends AbstractLifecycleComponent { return unmodifiableMap(roles); } - @Nullable - private static Role parseRole(String segment, Path path, Logger logger, boolean resolvePermissions, Settings settings) { - RoleDescriptor descriptor = parseRoleDescriptor(segment, path, logger, resolvePermissions, settings); - - if (descriptor != null) { - String roleName = descriptor.getName(); - // first check if FLS/DLS is enabled on the role... - for (RoleDescriptor.IndicesPrivileges privilege : descriptor.getIndicesPrivileges()) { - - if ((privilege.getQuery() != null || privilege.getFieldPermissions().hasFieldLevelSecurity()) - && XPackSettings.DLS_FLS_ENABLED.get(settings) == false) { - logger.error("invalid role definition [{}] in roles file [{}]. document and field level security is not " + - "enabled. set [{}] to [true] in the configuration file. skipping role...", roleName, path - .toAbsolutePath(), XPackSettings.DLS_FLS_ENABLED.getKey()); - return null; - } - } - return Role.builder(descriptor).build(); - } else { - return null; - } - } - @Nullable static RoleDescriptor parseRoleDescriptor(String segment, Path path, Logger logger, boolean resolvePermissions, Settings settings) { @@ -246,7 +221,7 @@ public class FileRolesStore extends AbstractLifecycleComponent { // we pass true as last parameter because we do not want to reject files if field permissions // are given in 2.x syntax RoleDescriptor descriptor = RoleDescriptor.parse(roleName, parser, true); - return descriptor; + return checkDescriptor(descriptor, path, logger, settings); } else { logger.error("invalid role definition [{}] in roles file [{}]. skipping role...", roleName, path.toAbsolutePath()); return null; @@ -282,6 +257,22 @@ public class FileRolesStore extends AbstractLifecycleComponent { return null; } + @Nullable + private static RoleDescriptor checkDescriptor(RoleDescriptor descriptor, Path path, Logger logger, Settings settings) { + String roleName = descriptor.getName(); + // first check if FLS/DLS is enabled on the role... + for (RoleDescriptor.IndicesPrivileges privilege : descriptor.getIndicesPrivileges()) { + if ((privilege.getQuery() != null || privilege.getGrantedFields() != null || privilege.getDeniedFields() != null) + && XPackSettings.DLS_FLS_ENABLED.get(settings) == false) { + logger.error("invalid role definition [{}] in roles file [{}]. document and field level security is not " + + "enabled. set [{}] to [true] in the configuration file. skipping role...", roleName, path + .toAbsolutePath(), XPackSettings.DLS_FLS_ENABLED.getKey()); + return null; + } + } + return descriptor; + } + private static List roleSegments(Path path) throws IOException { List segments = new ArrayList<>(); StringBuilder builder = null; @@ -329,7 +320,10 @@ public class FileRolesStore extends AbstractLifecycleComponent { "could not reload roles file [{}]. Current roles remain unmodified", file.toAbsolutePath()), e); return; } - listener.run(); + + synchronized (FileRolesStore.this) { + listeners.forEach(Runnable::run); + } } } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java index 839609a5371..a48bc6f993d 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStore.java @@ -20,7 +20,6 @@ import org.elasticsearch.action.search.MultiSearchRequestBuilder; import org.elasticsearch.action.search.MultiSearchResponse; import org.elasticsearch.action.search.MultiSearchResponse.Item; import org.elasticsearch.action.search.SearchRequest; -import org.elasticsearch.action.support.ThreadedActionListener; import org.elasticsearch.action.support.TransportActions; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterState; @@ -28,21 +27,17 @@ import org.elasticsearch.cluster.ClusterStateListener; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesReference; -import org.elasticsearch.common.cache.Cache; -import org.elasticsearch.common.cache.CacheBuilder; import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.TimeValue; -import org.elasticsearch.common.util.concurrent.ReleasableLock; import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.xpack.security.InternalClient; import org.elasticsearch.xpack.security.SecurityTemplateService; import org.elasticsearch.xpack.security.action.role.ClearRolesCacheRequest; @@ -50,8 +45,6 @@ import org.elasticsearch.xpack.security.action.role.ClearRolesCacheResponse; import org.elasticsearch.xpack.security.action.role.DeleteRoleRequest; import org.elasticsearch.xpack.security.action.role.PutRoleRequest; import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.permission.IndicesPermission.Group; -import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.client.SecurityClient; import java.util.Arrays; @@ -61,11 +54,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.elasticsearch.index.query.QueryBuilders.existsQuery; @@ -92,33 +81,19 @@ public class NativeRolesStore extends AbstractComponent implements ClusterStateL FAILED } + // these are no longer used, but leave them around for users upgrading private static final Setting CACHE_SIZE_SETTING = - Setting.intSetting(setting("authz.store.roles.index.cache.max_size"), 10000, Property.NodeScope); - private static final Setting CACHE_TTL_SETTING = - Setting.timeSetting(setting("authz.store.roles.index.cache.ttl"), TimeValue.timeValueMinutes(20), Property.NodeScope); + Setting.intSetting(setting("authz.store.roles.index.cache.max_size"), 10000, Property.NodeScope, Property.Deprecated); + private static final Setting CACHE_TTL_SETTING = Setting.timeSetting(setting("authz.store.roles.index.cache.ttl"), + TimeValue.timeValueMinutes(20), Property.NodeScope, Property.Deprecated); private static final String ROLE_DOC_TYPE = "role"; private final InternalClient client; private final AtomicReference state = new AtomicReference<>(State.INITIALIZED); private final boolean isTribeNode; - private final Cache roleCache; - // the lock is used in an odd manner; when iterating over the cache we cannot have modifiers other than deletes using - // the iterator but when not iterating we can modify the cache without external locking. When making normal modifications to the cache - // the read lock is obtained so that we can allow concurrent modifications; however when we need to iterate over the keys or values of - // the cache the write lock must obtained to prevent any modifications - private final ReleasableLock readLock; - private final ReleasableLock writeLock; - - { - final ReadWriteLock iterationLock = new ReentrantReadWriteLock(); - readLock = new ReleasableLock(iterationLock.readLock()); - writeLock = new ReleasableLock(iterationLock.writeLock()); - } private SecurityClient securityClient; - // incremented each time the cache is invalidated - private final AtomicLong numInvalidation = new AtomicLong(0); private volatile boolean securityIndexExists = false; private volatile boolean canWrite = false; @@ -126,10 +101,6 @@ public class NativeRolesStore extends AbstractComponent implements ClusterStateL public NativeRolesStore(Settings settings, InternalClient client) { super(settings); this.client = client; - this.roleCache = CacheBuilder.builder() - .setMaximumWeight(CACHE_SIZE_SETTING.get(settings)) - .setExpireAfterWrite(CACHE_TTL_SETTING.get(settings)) - .build(); this.isTribeNode = settings.getGroups("tribe", true).isEmpty() == false; } @@ -198,13 +169,13 @@ public class NativeRolesStore extends AbstractComponent implements ClusterStateL public void getRoleDescriptors(String[] names, final ActionListener> listener) { if (state() != State.STARTED) { logger.trace("attempted to get roles before service was started"); - listener.onFailure(new IllegalStateException("roles cannot be retrieved as native role service has not been started")); + listener.onResponse(Collections.emptySet()); return; } if (names != null && names.length == 1) { - getRoleAndVersion(Objects.requireNonNull(names[0]), ActionListener.wrap(roleAndVersion -> - listener.onResponse(roleAndVersion == null || roleAndVersion.getRoleDescriptor() == null ? Collections.emptyList() - : Collections.singletonList(roleAndVersion.getRoleDescriptor())), listener::onFailure)); + getRoleDescriptor(Objects.requireNonNull(names[0]), ActionListener.wrap(roleDescriptor -> + listener.onResponse(roleDescriptor == null ? Collections.emptyList() : Collections.singletonList(roleDescriptor)), + listener::onFailure)); } else { try { QueryBuilder query; @@ -303,25 +274,6 @@ public class NativeRolesStore extends AbstractComponent implements ClusterStateL } } - public void role(String roleName, ActionListener listener) { - if (state() != State.STARTED) { - listener.onResponse(null); - } else { - getRoleAndVersion(roleName, new ActionListener() { - @Override - public void onResponse(RoleAndVersion roleAndVersion) { - listener.onResponse(roleAndVersion == null ? null : roleAndVersion.getRole()); - } - - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - - } - }); - } - } - public Map usageStats() { if (state() != State.STARTED) { return Collections.emptyMap(); @@ -337,23 +289,9 @@ public class NativeRolesStore extends AbstractComponent implements ClusterStateL return usageStats; } + // FIXME this needs to be async long count = 0L; - try (final ReleasableLock ignored = writeLock.acquire()) { - for (RoleAndVersion rv : roleCache.values()) { - if (rv == RoleAndVersion.NON_EXISTENT) { - continue; - } - - count++; - Role role = rv.getRole(); - for (Group group : role.indices()) { - fls = fls || group.getFieldPermissions().hasFieldLevelSecurity(); - dls = dls || group.hasQuery(); - } - } - } - - // slow path - query for necessary information + // query for necessary information if (fls == false || dls == false) { MultiSearchRequestBuilder builder = client.prepareMultiSearch() .add(client.prepareSearch(SecurityTemplateService.SECURITY_INDEX_NAME) @@ -407,67 +345,37 @@ public class NativeRolesStore extends AbstractComponent implements ClusterStateL return usageStats; } - private void getRoleAndVersion(final String roleId, ActionListener roleActionListener) { + private void getRoleDescriptor(final String roleId, ActionListener roleActionListener) { if (securityIndexExists == false) { roleActionListener.onResponse(null); } else { - RoleAndVersion cachedRoleAndVersion = roleCache.get(roleId); - if (cachedRoleAndVersion == null) { - final long invalidationCounter = numInvalidation.get(); - executeGetRoleRequest(roleId, new ActionListener() { - @Override - public void onResponse(GetResponse response) { - final RoleAndVersion roleAndVersion; - RoleDescriptor descriptor = transformRole(response); - if (descriptor != null) { - logger.debug("loaded role [{}] from index with version [{}]", roleId, response.getVersion()); - roleAndVersion = new RoleAndVersion(descriptor, response.getVersion()); - } else { - roleAndVersion = RoleAndVersion.NON_EXISTENT; - } + executeGetRoleRequest(roleId, new ActionListener() { + @Override + public void onResponse(GetResponse response) { + final RoleDescriptor descriptor = transformRole(response); + roleActionListener.onResponse(descriptor); + } - /* 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 cacht 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*/ - try (final ReleasableLock ignored = readLock.acquire()) { - if (invalidationCounter == numInvalidation.get()) { - roleCache.computeIfAbsent(roleId, (k) -> roleAndVersion); - } - } catch (ExecutionException e) { - throw new AssertionError("failed to load constant non-null value", e); - } - roleActionListener.onResponse(roleAndVersion); + @Override + public void onFailure(Exception e) { + // if the index or the shard is not there / available we just claim the role is not there + if (TransportActions.isShardNotAvailableException(e)) { + logger.warn((Supplier) () -> new ParameterizedMessage("failed to load role [{}] index not available", + roleId), e); + roleActionListener.onResponse(null); + } else { + logger.error((Supplier) () -> new ParameterizedMessage("failed to load role [{}]", roleId), e); + roleActionListener.onFailure(e); } - - @Override - public void onFailure(Exception e) { - // if the index or the shard is not there / available we just claim the role is not there - if (TransportActions.isShardNotAvailableException(e)) { - logger.warn((Supplier) () -> new ParameterizedMessage("failed to load role [{}] index not available", - roleId), e); - roleActionListener.onResponse(RoleAndVersion.NON_EXISTENT); - } else { - logger.error((Supplier) () -> new ParameterizedMessage("failed to load role [{}]", roleId), e); - roleActionListener.onFailure(e); - } - } - }); - } else { - roleActionListener.onResponse(cachedRoleAndVersion); - } + } + }); } } - // pkg-private for testing - void executeGetRoleRequest(String role, ActionListener listener) { + private void executeGetRoleRequest(String role, ActionListener listener) { try { GetRequest request = client.prepareGet(SecurityTemplateService.SECURITY_INDEX_NAME, ROLE_DOC_TYPE, role).request(); - // TODO we use a threaded listener here to make sure we don't execute on a transport thread. This can be removed once - // all blocking operations are removed from this and NativeUserStore - client.get(request, new ThreadedActionListener<>(logger, client.threadPool(), ThreadPool.Names.LISTENER, listener, true)); + client.get(request, listener); } catch (IndexNotFoundException e) { logger.trace( (Supplier) () -> new ParameterizedMessage( @@ -487,28 +395,11 @@ public class NativeRolesStore extends AbstractComponent implements ClusterStateL if (state != State.STOPPED && state != State.FAILED) { throw new IllegalStateException("can only reset if stopped!!!"); } - invalidateAll(); this.securityIndexExists = false; this.canWrite = false; this.state.set(State.INITIALIZED); } - public void invalidateAll() { - logger.debug("invalidating all roles in cache"); - numInvalidation.incrementAndGet(); - try (final ReleasableLock ignored = readLock.acquire()) { - roleCache.invalidateAll(); - } - } - - public void invalidate(String role) { - logger.debug("invalidating role [{}] in cache", role); - numInvalidation.incrementAndGet(); - try (final ReleasableLock ignored = readLock.acquire()) { - roleCache.invalidate(role); - } - } - private void clearRoleCache(final String role, ActionListener listener, Response response) { ClearRolesCacheRequest request = new ClearRolesCacheRequest().names(role); securityClient.clearRolesCache(request, new ActionListener() { @@ -558,39 +449,6 @@ public class NativeRolesStore extends AbstractComponent implements ClusterStateL } } - private static class RoleAndVersion { - - private static final RoleAndVersion NON_EXISTENT = new RoleAndVersion(); - - private final RoleDescriptor roleDescriptor; - private final Role role; - private final long version; - - private RoleAndVersion() { - roleDescriptor = null; - role = null; - version = Long.MIN_VALUE; - } - - RoleAndVersion(RoleDescriptor roleDescriptor, long version) { - this.roleDescriptor = roleDescriptor; - this.role = Role.builder(roleDescriptor).build(); - this.version = version; - } - - RoleDescriptor getRoleDescriptor() { - return roleDescriptor; - } - - Role getRole() { - return role; - } - - long getVersion() { - return version; - } - } - public static void addSettings(List> settings) { settings.add(CACHE_SIZE_SETTING); settings.add(CACHE_TTL_SETTING); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStore.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStore.java index f2d785207cc..4211eca0f10 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStore.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStore.java @@ -5,54 +5,61 @@ */ package org.elasticsearch.xpack.security.authz.store; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; -import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.common.collect.MapBuilder; +import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.permission.IngestAdminRole; -import org.elasticsearch.xpack.security.authz.permission.KibanaRole; -import org.elasticsearch.xpack.security.authz.permission.KibanaUserRole; -import org.elasticsearch.xpack.security.authz.permission.LogstashSystemRole; -import org.elasticsearch.xpack.security.authz.permission.MonitoringUserRole; -import org.elasticsearch.xpack.security.authz.permission.RemoteMonitoringAgentRole; -import org.elasticsearch.xpack.security.authz.permission.ReportingUserRole; import org.elasticsearch.xpack.security.authz.permission.Role; -import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; -import org.elasticsearch.xpack.security.authz.permission.TransportClientRole; +import org.elasticsearch.xpack.security.support.MetadataUtils; + import org.elasticsearch.xpack.security.user.SystemUser; public class ReservedRolesStore { - public ReservedRolesStore() { - } + public static final RoleDescriptor SUPERUSER_ROLE_DESCRIPTOR = new RoleDescriptor("superuser", new String[] { "all" }, + new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build()}, + new String[] { "*" }, + MetadataUtils.DEFAULT_RESERVED_METADATA); + public static final Role SUPERUSER_ROLE = Role.builder(SUPERUSER_ROLE_DESCRIPTOR, null).build(); + private static final Map RESERVED_ROLES = initializeReservedRoles(); - public Role role(String role) { - switch (role) { - case SuperuserRole.NAME: - return SuperuserRole.INSTANCE; - case TransportClientRole.NAME: - return TransportClientRole.INSTANCE; - case KibanaUserRole.NAME: - return KibanaUserRole.INSTANCE; - case MonitoringUserRole.NAME: - return MonitoringUserRole.INSTANCE; - case RemoteMonitoringAgentRole.NAME: - return RemoteMonitoringAgentRole.INSTANCE; - case IngestAdminRole.NAME: - return IngestAdminRole.INSTANCE; - case ReportingUserRole.NAME: - return ReportingUserRole.INSTANCE; - case KibanaRole.NAME: - return KibanaRole.INSTANCE; - case LogstashSystemRole.NAME: - return LogstashSystemRole.INSTANCE; - default: - return null; - } + private static Map initializeReservedRoles() { + return MapBuilder.newMapBuilder() + .put("superuser", new RoleDescriptor("superuser", new String[] { "all" }, + new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges("all").build()}, + new String[] { "*" }, + MetadataUtils.DEFAULT_RESERVED_METADATA)) + .put("transport_client", new RoleDescriptor("transport_client", new String[] { "transport_client" }, null, null, + MetadataUtils.DEFAULT_RESERVED_METADATA)) + .put("kibana_user", new RoleDescriptor("kibana_user", new String[] { "monitor" }, new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*").privileges("manage", "read", "index", "delete") + .build() }, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) + .put("monitoring_user", new RoleDescriptor("monitoring_user", null, new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder().indices(".marvel-es-*", ".monitoring-*").privileges("read").build() }, + null, MetadataUtils.DEFAULT_RESERVED_METADATA)) + .put("remote_monitoring_agent", new RoleDescriptor("remote_monitoring_agent", + new String[] { "manage_index_templates", "manage_ingest_pipelines", "monitor" }, + new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder().indices(".marvel-es-*", ".monitoring-*").privileges("all").build() }, + null, MetadataUtils.DEFAULT_RESERVED_METADATA)) + .put("ingest_admin", new RoleDescriptor("ingest_admin", new String[] { "manage_index_templates", "manage_pipeline" }, + null, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) + .put("reporting_user", new RoleDescriptor("reporting_user", null, new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder().indices(".reporting-*").privileges("read", "write").build() }, + null, MetadataUtils.DEFAULT_RESERVED_METADATA)) + .put("kibana", new RoleDescriptor("kibana", new String[] { "monitor", MonitoringBulkAction.NAME}, + new RoleDescriptor.IndicesPrivileges[] { + RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build() }, + null, MetadataUtils.DEFAULT_RESERVED_METADATA)) + .put("logstash_system", new RoleDescriptor("logstash_system", new String[] { "monitor", MonitoringBulkAction.NAME}, + null, null, MetadataUtils.DEFAULT_RESERVED_METADATA)) + .immutableMap(); } public Map usageStats() { @@ -60,58 +67,19 @@ public class ReservedRolesStore { } public RoleDescriptor roleDescriptor(String role) { - switch (role) { - case SuperuserRole.NAME: - return SuperuserRole.DESCRIPTOR; - case TransportClientRole.NAME: - return TransportClientRole.DESCRIPTOR; - case KibanaUserRole.NAME: - return KibanaUserRole.DESCRIPTOR; - case MonitoringUserRole.NAME: - return MonitoringUserRole.DESCRIPTOR; - case RemoteMonitoringAgentRole.NAME: - return RemoteMonitoringAgentRole.DESCRIPTOR; - case IngestAdminRole.NAME: - return IngestAdminRole.DESCRIPTOR; - case ReportingUserRole.NAME: - return ReportingUserRole.DESCRIPTOR; - case KibanaRole.NAME: - return KibanaRole.DESCRIPTOR; - case LogstashSystemRole.NAME: - return LogstashSystemRole.DESCRIPTOR; - default: - return null; - } + return RESERVED_ROLES.get(role); } public Collection roleDescriptors() { - return Arrays.asList(SuperuserRole.DESCRIPTOR, TransportClientRole.DESCRIPTOR, KibanaUserRole.DESCRIPTOR, - KibanaRole.DESCRIPTOR, MonitoringUserRole.DESCRIPTOR, RemoteMonitoringAgentRole.DESCRIPTOR, - IngestAdminRole.DESCRIPTOR, ReportingUserRole.DESCRIPTOR, LogstashSystemRole.DESCRIPTOR); + return RESERVED_ROLES.values(); } public static Set names() { - return Sets.newHashSet(SuperuserRole.NAME, KibanaRole.NAME, TransportClientRole.NAME, KibanaUserRole.NAME, - MonitoringUserRole.NAME, RemoteMonitoringAgentRole.NAME, IngestAdminRole.NAME, ReportingUserRole.NAME, - LogstashSystemRole.NAME); + return RESERVED_ROLES.keySet(); } public static boolean isReserved(String role) { - switch (role) { - case SuperuserRole.NAME: - case KibanaRole.NAME: - case KibanaUserRole.NAME: - case TransportClientRole.NAME: - case MonitoringUserRole.NAME: - case RemoteMonitoringAgentRole.NAME: - case SystemUser.ROLE_NAME: - case IngestAdminRole.NAME: - case ReportingUserRole.NAME: - case LogstashSystemRole.NAME: - return true; - default: - return false; - } + return RESERVED_ROLES.containsKey(role) || SystemUser.ROLE_NAME.equals(role); } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/support/AutomatonPredicate.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/support/AutomatonPredicate.java deleted file mode 100644 index 166474b5de7..00000000000 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/support/AutomatonPredicate.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.apache.lucene.util.automaton.Automaton; -import org.apache.lucene.util.automaton.CharacterRunAutomaton; - -import java.util.function.Predicate; - -import static org.apache.lucene.util.automaton.Operations.DEFAULT_MAX_DETERMINIZED_STATES; - -public class AutomatonPredicate implements Predicate { - - private final CharacterRunAutomaton automaton; - - public AutomatonPredicate(Automaton automaton) { - this.automaton = new CharacterRunAutomaton(automaton, DEFAULT_MAX_DETERMINIZED_STATES); - } - - @Override - public boolean test(String input) { - return automaton.run(input); - } -} diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/support/Automatons.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/support/Automatons.java index 6952822d94f..51c8ecb41f6 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/support/Automatons.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/support/Automatons.java @@ -7,17 +7,18 @@ package org.elasticsearch.xpack.security.support; import org.apache.lucene.util.automaton.Automata; import org.apache.lucene.util.automaton.Automaton; +import org.apache.lucene.util.automaton.CharacterRunAutomaton; import org.apache.lucene.util.automaton.RegExp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.function.Predicate; import static org.apache.lucene.util.automaton.MinimizationOperations.minimize; import static org.apache.lucene.util.automaton.Operations.DEFAULT_MAX_DETERMINIZED_STATES; import static org.apache.lucene.util.automaton.Operations.concatenate; -import static org.apache.lucene.util.automaton.Operations.determinize; import static org.apache.lucene.util.automaton.Operations.minus; import static org.apache.lucene.util.automaton.Operations.union; @@ -106,13 +107,26 @@ public final class Automatons { return concatenate(automata); } - public static Automaton unionAndDeterminize(Automaton a1, Automaton a2) { - Automaton res = union(a1, a2); - return determinize(res, DEFAULT_MAX_DETERMINIZED_STATES); + public static Automaton unionAndMinimize(Collection automata) { + Automaton res = union(automata); + return minimize(res, DEFAULT_MAX_DETERMINIZED_STATES); } - public static Automaton minusAndDeterminize(Automaton a1, Automaton a2) { + public static Automaton minusAndMinimize(Automaton a1, Automaton a2) { Automaton res = minus(a1, a2, DEFAULT_MAX_DETERMINIZED_STATES); - return determinize(res, DEFAULT_MAX_DETERMINIZED_STATES); + return minimize(res, DEFAULT_MAX_DETERMINIZED_STATES); + } + + public static Predicate predicate(String... patterns) { + return predicate(Arrays.asList(patterns)); + } + + public static Predicate predicate(Collection patterns) { + return predicate(patterns(patterns)); + } + + public static Predicate predicate(Automaton automaton) { + CharacterRunAutomaton runAutomaton = new CharacterRunAutomaton(automaton, DEFAULT_MAX_DETERMINIZED_STATES); + return runAutomaton::run; } } diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java index 0ff4aad2119..85a921e7fbe 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/transport/filter/IPFilter.java @@ -10,7 +10,6 @@ import io.netty.handler.ipfilter.IpFilterRuleType; import org.apache.logging.log4j.Logger; import org.apache.lucene.util.SetOnce; import org.elasticsearch.common.collect.MapBuilder; -import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Setting; @@ -22,7 +21,6 @@ import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.transport.TransportSettings; import org.elasticsearch.xpack.security.audit.AuditTrailService; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/ElasticUser.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/ElasticUser.java index ff740d44ef8..6d8382fb3b0 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/ElasticUser.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/ElasticUser.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security.user; -import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; import org.elasticsearch.xpack.security.support.MetadataUtils; /** @@ -15,7 +14,7 @@ import org.elasticsearch.xpack.security.support.MetadataUtils; public class ElasticUser extends User { public static final String NAME = "elastic"; - public static final String ROLE_NAME = SuperuserRole.NAME; + private static final String ROLE_NAME = "superuser"; public ElasticUser(boolean enabled) { super(NAME, new String[] { ROLE_NAME }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, enabled); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/KibanaUser.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/KibanaUser.java index 614a0d0abd6..9c734925908 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/KibanaUser.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/KibanaUser.java @@ -5,7 +5,6 @@ */ package org.elasticsearch.xpack.security.user; -import org.elasticsearch.xpack.security.authz.permission.KibanaRole; import org.elasticsearch.xpack.security.support.MetadataUtils; /** @@ -14,7 +13,7 @@ import org.elasticsearch.xpack.security.support.MetadataUtils; public class KibanaUser extends User { public static final String NAME = "kibana"; - public static final String ROLE_NAME = KibanaRole.NAME; + public static final String ROLE_NAME = "kibana"; public KibanaUser(boolean enabled) { super(NAME, new String[]{ ROLE_NAME }, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, enabled); diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/LogstashSystemUser.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/LogstashSystemUser.java index 98e7eae6386..8b0fb06b68f 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/LogstashSystemUser.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/LogstashSystemUser.java @@ -6,8 +6,6 @@ package org.elasticsearch.xpack.security.user; import org.elasticsearch.Version; -import org.elasticsearch.xpack.security.authz.permission.KibanaRole; -import org.elasticsearch.xpack.security.authz.permission.LogstashSystemRole; import org.elasticsearch.xpack.security.support.MetadataUtils; /** @@ -16,7 +14,7 @@ import org.elasticsearch.xpack.security.support.MetadataUtils; public class LogstashSystemUser extends User { public static final String NAME = "logstash_system"; - public static final String ROLE_NAME = LogstashSystemRole.NAME; + private static final String ROLE_NAME = "logstash_system"; public static final Version DEFINED_SINCE = Version.V_5_2_0_UNRELEASED; public LogstashSystemUser(boolean enabled) { diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/XPackUser.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/XPackUser.java index c96bd9b3c4d..2f95cd9998d 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/XPackUser.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/user/XPackUser.java @@ -5,15 +5,13 @@ */ package org.elasticsearch.xpack.security.user; -import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; - /** * XPack internal user that manages xpack. Has all cluster/indices permissions for x-pack to operate. */ public class XPackUser extends User { public static final String NAME = "_xpack"; - public static final String ROLE_NAME = SuperuserRole.NAME; + private static final String ROLE_NAME = "superuser"; public static final XPackUser INSTANCE = new XPackUser(); private XPackUser() { diff --git a/elasticsearch/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityTests.java b/elasticsearch/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityTests.java index ee66d9c1a01..33ab9020d45 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/OldSecurityIndexBackwardsCompatibilityTests.java @@ -109,19 +109,19 @@ public class OldSecurityIndexBackwardsCompatibilityTests extends AbstractOldXPac RoleDescriptor.IndicesPrivileges indicesPrivileges = role.getIndicesPrivileges()[0]; assertThat(indicesPrivileges.getIndices(), arrayWithSize(2)); assertArrayEquals(new String[] { "index1", "index2" }, indicesPrivileges.getIndices()); - assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("title")); - assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("body")); + final FieldPermissions fieldPermissions = + new FieldPermissions(indicesPrivileges.getGrantedFields(), indicesPrivileges.getDeniedFields()); + assertTrue(fieldPermissions.grantsAccessTo("title")); + assertTrue(fieldPermissions.grantsAccessTo("body")); assertArrayEquals(new String[] { "all" }, indicesPrivileges.getPrivileges()); - assertEquals("{\"match\": {\"title\": \"foo\"}}", indicesPrivileges.getQuery().utf8ToString()); + assertEquals("{\"match\": {\"title\": \"foo\"}}", indicesPrivileges.getQuery().iterator().next().utf8ToString()); assertArrayEquals(new String[] { "all" }, role.getClusterPrivileges()); assertArrayEquals(new String[] { "other_user" }, role.getRunAs()); assertEquals("bwc_test_role", role.getName()); // check x-content is rendered in new format although it comes from an old index XContentBuilder builder = jsonBuilder(); - builder.startObject(); - indicesPrivileges.getFieldPermissions().toXContent(builder, null); - builder.endObject(); - assertThat(builder.string(), equalTo("{\"field_security\":{\"grant\":[\"title\",\"body\"]}}")); + indicesPrivileges.toXContent(builder, null); + assertThat(builder.string(), containsString("\"field_security\":{\"grant\":[\"title\",\"body\"]}")); logger.info("Getting users..."); assertBusy(() -> { @@ -167,7 +167,8 @@ public class OldSecurityIndexBackwardsCompatibilityTests extends AbstractOldXPac PutRoleResponse roleResponse = securityClient.preparePutRole("test_role").addIndices( new String[] { "index3" }, new String[] { "all" }, - new FieldPermissions(new String[]{"title", "body"}, null), + new String[] { "title", "body" }, + null, new BytesArray("{\"term\": {\"title\":\"not\"}}")).cluster("all") .get(); assertTrue(roleResponse.isCreated()); diff --git a/elasticsearch/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java b/elasticsearch/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java index a37109afd39..3428515fa98 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/integration/ClearRolesCacheTests.java @@ -14,14 +14,14 @@ import org.elasticsearch.xpack.security.SecurityTemplateService; import org.elasticsearch.xpack.security.action.role.DeleteRoleResponse; import org.elasticsearch.xpack.security.action.role.GetRolesResponse; import org.elasticsearch.xpack.security.action.role.PutRoleResponse; -import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; -import org.elasticsearch.xpack.security.authz.permission.Role; +import org.elasticsearch.xpack.security.authz.RoleDescriptor; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; import org.elasticsearch.xpack.security.client.SecurityClient; import org.junit.Before; import org.junit.BeforeClass; import java.util.Arrays; +import java.util.Collection; import java.util.List; import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE; @@ -52,7 +52,7 @@ public class ClearRolesCacheTests extends NativeRealmIntegTestCase { for (String role : roles) { c.preparePutRole(role) .cluster("none") - .addIndices(new String[] { "*" }, new String[] { "ALL" }, new FieldPermissions(), null) + .addIndices(new String[] { "*" }, new String[] { "ALL" }, null, null, null) .get(); logger.debug("--> created role [{}]", role); } @@ -61,11 +61,9 @@ public class ClearRolesCacheTests extends NativeRealmIntegTestCase { // warm up the caches on every node for (NativeRolesStore rolesStore : internalCluster().getInstances(NativeRolesStore.class)) { - for (String role : roles) { - PlainActionFuture future = new PlainActionFuture<>(); - rolesStore.role(role, future); - assertThat(future.actionGet(), notNullValue()); - } + PlainActionFuture> future = new PlainActionFuture<>(); + rolesStore.getRoleDescriptors(roles, future); + assertThat(future.actionGet(), notNullValue()); } } @@ -87,7 +85,7 @@ public class ClearRolesCacheTests extends NativeRealmIntegTestCase { for (String role : toModify) { PutRoleResponse response = securityClient.preparePutRole(role) .cluster("none") - .addIndices(new String[] { "*" }, new String[] { "ALL" }, new FieldPermissions(), null) + .addIndices(new String[] { "*" }, new String[] { "ALL" }, null, null, null) .runAs(role) .setRefreshPolicy(randomBoolean() ? IMMEDIATE : NONE) .get(); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java index 03ef4b07657..8b1c095a431 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/action/filter/SecurityActionFilterTests.java @@ -30,12 +30,12 @@ import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; +import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.crypto.CryptoService; import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.User; import org.junit.Before; -import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -95,15 +95,16 @@ public class SecurityActionFilterTests extends ESTestCase { callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); + final Role empty = Role.EMPTY; doAnswer((i) -> { ActionListener callback = (ActionListener) i.getArguments()[1]; - callback.onResponse(Collections.emptyList()); + callback.onResponse(empty); return Void.TYPE; }).when(authzService).roles(any(User.class), any(ActionListener.class)); doReturn(request).when(spy(filter)).unsign(user, "_action", request); filter.apply(task, "_action", request, listener, chain); - verify(authzService).authorize(authentication, "_action", request, Collections.emptyList(), Collections.emptyList()); + verify(authzService).authorize(authentication, "_action", request, empty, null); verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ContextPreservingActionListener.class)); } @@ -123,10 +124,11 @@ public class SecurityActionFilterTests extends ESTestCase { callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq(action), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); + final Role empty = Role.EMPTY; doAnswer((i) -> { ActionListener callback = (ActionListener) i.getArguments()[1]; - callback.onResponse(Collections.emptyList()); + callback.onResponse(empty); return Void.TYPE; }).when(authzService).roles(any(User.class), any(ActionListener.class)); doReturn(request).when(spy(filter)).unsign(user, action, request); @@ -135,7 +137,7 @@ public class SecurityActionFilterTests extends ESTestCase { verify(listener).onFailure(isA(IllegalArgumentException.class)); verifyNoMoreInteractions(authzService, chain); } else { - verify(authzService).authorize(authentication, action, request, Collections.emptyList(), Collections.emptyList()); + verify(authzService).authorize(authentication, action, request, empty, null); verify(chain).proceed(eq(task), eq(action), eq(request), isA(ContextPreservingActionListener.class)); } } @@ -157,11 +159,11 @@ public class SecurityActionFilterTests extends ESTestCase { doAnswer((i) -> { ActionListener callback = (ActionListener) i.getArguments()[1]; - callback.onResponse(Collections.emptyList()); + callback.onResponse(Role.EMPTY); return Void.TYPE; }).when(authzService).roles(any(User.class), any(ActionListener.class)); - doThrow(exception).when(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(Collection.class), - any(Collection.class)); + doThrow(exception).when(authzService).authorize(eq(authentication), eq("_action"), eq(request), any(Role.class), + any(Role.class)); filter.apply(task, "_action", request, listener, chain); verify(listener).onFailure(exception); verifyNoMoreInteractions(chain); @@ -182,16 +184,17 @@ public class SecurityActionFilterTests extends ESTestCase { }).when(authcService).authenticate(eq("_action"), eq(request), eq(SystemUser.INSTANCE), any(ActionListener.class)); when(cryptoService.isSigned("signed_scroll_id")).thenReturn(true); when(cryptoService.unsignAndVerify("signed_scroll_id")).thenReturn("scroll_id"); + final Role empty = Role.EMPTY; doAnswer((i) -> { ActionListener callback = (ActionListener) i.getArguments()[1]; - callback.onResponse(Collections.emptyList()); + callback.onResponse(empty); return Void.TYPE; }).when(authzService).roles(any(User.class), any(ActionListener.class)); filter.apply(task, "_action", request, listener, chain); assertThat(request.scrollId(), equalTo("scroll_id")); - verify(authzService).authorize(authentication, "_action", request, Collections.emptyList(), Collections.emptyList()); + verify(authzService).authorize(authentication, "_action", request, empty, null); verify(chain).proceed(eq(task), eq("_action"), eq(request), isA(ContextPreservingActionListener.class)); } @@ -214,7 +217,7 @@ public class SecurityActionFilterTests extends ESTestCase { doAnswer((i) -> { ActionListener callback = (ActionListener) i.getArguments()[1]; - callback.onResponse(Collections.emptyList()); + callback.onResponse(Role.EMPTY); return Void.TYPE; }).when(authzService).roles(any(User.class), any(ActionListener.class)); filter.apply(task, "_action", request, listener, chain); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/action/role/TransportGetRolesActionTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/action/role/TransportGetRolesActionTests.java index f4bef1a66a6..7d1dd1d79e1 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/action/role/TransportGetRolesActionTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/action/role/TransportGetRolesActionTests.java @@ -11,16 +11,9 @@ import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.xpack.security.SecurityContext; import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.permission.KibanaRole; -import org.elasticsearch.xpack.security.authz.permission.LogstashSystemRole; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore; -import org.elasticsearch.xpack.security.user.ElasticUser; -import org.elasticsearch.xpack.security.user.KibanaUser; -import org.elasticsearch.xpack.security.user.LogstashSystemUser; -import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; @@ -33,33 +26,26 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; public class TransportGetRolesActionTests extends ESTestCase { public void testReservedRoles() { NativeRolesStore rolesStore = mock(NativeRolesStore.class); - SecurityContext context = mock(SecurityContext.class); TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, null); TransportGetRolesAction action = new TransportGetRolesAction(Settings.EMPTY, mock(ThreadPool.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), rolesStore, transportService, new ReservedRolesStore()); - final User executingUser = randomFrom(new ElasticUser(true), new KibanaUser(true), new LogstashSystemUser(true)); - when(context.getUser()).thenReturn(executingUser); - final int size = randomIntBetween(1, ReservedRolesStore.names().size()); final List names = randomSubsetOf(size, ReservedRolesStore.names()); @@ -101,15 +87,11 @@ public class TransportGetRolesActionTests extends ESTestCase { public void testStoreRoles() { final List storeRoleDescriptors = randomRoleDescriptors(); NativeRolesStore rolesStore = mock(NativeRolesStore.class); - SecurityContext context = mock(SecurityContext.class); TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, null); TransportGetRolesAction action = new TransportGetRolesAction(Settings.EMPTY, mock(ThreadPool.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), rolesStore, transportService, new ReservedRolesStore()); - final User executingUser = randomFrom(new ElasticUser(true), new KibanaUser(true), new LogstashSystemUser(true)); - when(context.getUser()).thenReturn(executingUser); - GetRolesRequest request = new GetRolesRequest(); request.names(storeRoleDescriptors.stream().map(RoleDescriptor::getName).collect(Collectors.toList()).toArray(Strings.EMPTY_ARRAY)); @@ -157,15 +139,11 @@ public class TransportGetRolesActionTests extends ESTestCase { } NativeRolesStore rolesStore = mock(NativeRolesStore.class); - SecurityContext context = mock(SecurityContext.class); TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, null); TransportGetRolesAction action = new TransportGetRolesAction(Settings.EMPTY, mock(ThreadPool.class), mock(ActionFilters.class), mock(IndexNameExpressionResolver.class), rolesStore, transportService, new ReservedRolesStore()); - final User executingUser = randomFrom(new ElasticUser(true), new KibanaUser(true), new LogstashSystemUser(true)); - when(context.getUser()).thenReturn(executingUser); - final List expectedNames = new ArrayList<>(); if (all) { expectedNames.addAll(reservedRoleNames); @@ -225,7 +203,6 @@ public class TransportGetRolesActionTests extends ESTestCase { final Exception e = randomFrom(new ElasticsearchSecurityException(""), new IllegalStateException()); final List storeRoleDescriptors = randomRoleDescriptors(); NativeRolesStore rolesStore = mock(NativeRolesStore.class); - SecurityContext context = mock(SecurityContext.class); TransportService transportService = new TransportService(Settings.EMPTY, null, null, TransportService.NOOP_TRANSPORT_INTERCEPTOR, null); TransportGetRolesAction action = new TransportGetRolesAction(Settings.EMPTY, mock(ThreadPool.class), mock(ActionFilters.class), diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java index 6e29cf6ced9..325f68cf91e 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeMigrateToolTests.java @@ -15,7 +15,6 @@ import org.elasticsearch.env.Environment; import org.elasticsearch.test.NativeRealmIntegTestCase; import org.elasticsearch.test.SecuritySettingsSource; import org.elasticsearch.xpack.security.SecurityTemplateService; -import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.security.client.SecurityClient; import org.junit.BeforeClass; @@ -112,7 +111,7 @@ public class ESNativeMigrateToolTests extends NativeRealmIntegTestCase { .cluster("all", "none") .runAs("root", "nobody") .addIndices(new String[]{"index"}, new String[]{"read"}, - new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"query\": {\"match_all\": {}}}")) + new String[]{"body", "title"}, null, new BytesArray("{\"query\": {\"match_all\": {}}}")) .get(); addedRoles.add(rname); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java index 285f93a3726..6b44c810207 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/ESNativeRealmMigrateToolTests.java @@ -50,7 +50,7 @@ public class ESNativeRealmMigrateToolTests extends CommandTestCase { RoleDescriptor.IndicesPrivileges ip = RoleDescriptor.IndicesPrivileges.builder() .indices(new String[]{"i1", "i2", "i3"}) .privileges(new String[]{"all"}) - .fieldPermissions(new FieldPermissions(new String[]{"body"}, null)) + .grantedFields("body") .build(); RoleDescriptor.IndicesPrivileges[] ips = new RoleDescriptor.IndicesPrivileges[1]; ips[0] = ip; diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index d90b59a8356..b260821ec87 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -29,12 +29,9 @@ import org.elasticsearch.xpack.security.action.user.DeleteUserResponse; import org.elasticsearch.xpack.security.action.user.GetUsersResponse; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; -import org.elasticsearch.xpack.security.authz.permission.KibanaRole; -import org.elasticsearch.xpack.security.authz.permission.LogstashSystemRole; import org.elasticsearch.xpack.security.authz.permission.Role; -import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; import org.elasticsearch.xpack.security.authz.store.NativeRolesStore; +import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.security.client.SecurityClient; import org.elasticsearch.xpack.security.user.AnonymousUser; import org.elasticsearch.xpack.security.user.ElasticUser; @@ -94,7 +91,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { PutRoleResponse response = securityClient() .preparePutRole("native_anonymous") .cluster("ALL") - .addIndices(new String[]{"*"}, new String[]{"ALL"}, new FieldPermissions(), null) + .addIndices(new String[]{"*"}, new String[]{"ALL"}, null, null, null) .get(); assertTrue(response.isCreated()); } else { @@ -171,14 +168,13 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { SecurityClient c = securityClient(); final List existingRoles = Arrays.asList(c.prepareGetRoles().get().roles()); final int existing = existingRoles.size(); - final Map metadata = Collections.singletonMap("key", (Object) randomAsciiOfLengthBetween(1, 10)); + final Map metadata = Collections.singletonMap("key", randomAsciiOfLengthBetween(1, 10)); logger.error("--> creating role"); c.preparePutRole("test_role") .cluster("all", "none") .runAs("root", "nobody") - .addIndices(new String[]{"index"}, new String[]{"read"}, - new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"query\": " + - "{\"match_all\": {}}}")) + .addIndices(new String[]{"index"}, new String[]{"read"}, new String[]{"body", "title"}, null, + new BytesArray("{\"query\": {\"match_all\": {}}}")) .metadata(metadata) .get(); logger.error("--> waiting for .security index"); @@ -194,16 +190,14 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { c.preparePutRole("test_role2") .cluster("all", "none") .runAs("root", "nobody") - .addIndices(new String[]{"index"}, new String[]{"read"}, - new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"query\": " + - "{\"match_all\": {}}}")) + .addIndices(new String[]{"index"}, new String[]{"read"}, new String[]{"body", "title"}, null, + new BytesArray("{\"query\": {\"match_all\": {}}}")) .get(); c.preparePutRole("test_role3") .cluster("all", "none") .runAs("root", "nobody") - .addIndices(new String[]{"index"}, new String[]{"read"}, - new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"query\": " + - "{\"match_all\": {}}}")) + .addIndices(new String[]{"index"}, new String[]{"read"}, new String[]{"body", "title"}, null, + new BytesArray("{\"query\": {\"match_all\": {}}}")) .get(); logger.info("--> retrieving all roles"); @@ -229,8 +223,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { logger.error("--> creating role"); c.preparePutRole("test_role") .cluster("all") - .addIndices(new String[] { "*" }, new String[] { "read" }, - new FieldPermissions(new String[] { "body", "title" }, null), + .addIndices(new String[] { "*" }, new String[] { "read" }, new String[]{"body", "title"}, null, new BytesArray("{\"match_all\": {}}")) .get(); logger.error("--> creating user"); @@ -324,8 +317,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { logger.error("--> creating role"); c.preparePutRole("test_role") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"read"}, - new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}")) + .addIndices(new String[]{"*"}, new String[]{"read"}, new String[]{"body", "title"}, null, + new BytesArray("{\"match_all\": {}}")) .get(); logger.error("--> creating user"); c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); @@ -339,8 +332,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { assertFalse(response.isTimedOut()); c.preparePutRole("test_role") .cluster("none") - .addIndices(new String[]{"*"}, new String[]{"read"}, - new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}")) + .addIndices(new String[]{"*"}, new String[]{"read"}, new String[]{"body", "title"}, null, + new BytesArray("{\"match_all\": {}}")) .get(); if (anonymousEnabled && roleExists) { assertNoTimeout(client() @@ -354,18 +347,18 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { GetRolesResponse getRolesResponse = c.prepareGetRoles().names("test_role").get(); assertTrue("test_role does not exist!", getRolesResponse.hasRoles()); assertTrue("any cluster permission should be authorized", - Role.builder(getRolesResponse.roles()[0]).build().cluster().check("cluster:admin/foo", null, null)); + Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/foo")); c.preparePutRole("test_role") .cluster("none") - .addIndices(new String[]{"*"}, new String[]{"read"}, - new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}")) + .addIndices(new String[]{"*"}, new String[]{"read"}, new String[]{"body", "title"}, null, + new BytesArray("{\"match_all\": {}}")) .get(); getRolesResponse = c.prepareGetRoles().names("test_role").get(); assertTrue("test_role does not exist!", getRolesResponse.hasRoles()); assertFalse("no cluster permission should be authorized", - Role.builder(getRolesResponse.roles()[0]).build().cluster().check("cluster:admin/bar", null, null)); + Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/bar")); } } @@ -374,8 +367,8 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { logger.error("--> creating role"); c.preparePutRole("test_role") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"read"}, - new FieldPermissions(new String[]{"body", "title"}, null), new BytesArray("{\"match_all\": {}}")) + .addIndices(new String[]{"*"}, new String[]{"read"}, new String[]{"body", "title"}, null, + new BytesArray("{\"match_all\": {}}")) .get(); c.preparePutUser("joe", "s3krit".toCharArray(), "test_role").get(); logger.error("--> waiting for .security index"); @@ -401,11 +394,11 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { // create some roles client.preparePutRole("admin_role") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"all"}, new FieldPermissions(), null) + .addIndices(new String[]{"*"}, new String[]{"all"}, null, null, null) .get(); client.preparePutRole("read_role") .cluster("none") - .addIndices(new String[]{"*"}, new String[]{"read"}, new FieldPermissions(), null) + .addIndices(new String[]{"*"}, new String[]{"read"}, null, null, null) .get(); assertThat(client.prepareGetUsers("joes").get().hasUsers(), is(false)); @@ -501,7 +494,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { } else { client.preparePutRole("read_role") .cluster("none") - .addIndices(new String[]{"*"}, new String[]{"read"}, new FieldPermissions(), null) + .addIndices(new String[]{"*"}, new String[]{"read"}, null, null, null) .get(); } @@ -562,7 +555,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { } public void testOperationsOnReservedRoles() throws Exception { - final String name = randomFrom(SuperuserRole.NAME, KibanaRole.NAME, LogstashSystemRole.NAME); + final String name = randomFrom(ReservedRolesStore.names()); IllegalArgumentException exception = expectThrows(IllegalArgumentException.class, () -> securityClient().preparePutRole(name).cluster("monitor").get()); assertThat(exception.getMessage(), containsString("role [" + name + "] is reserved")); @@ -616,22 +609,25 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { SecurityClient client = new SecurityClient(client()); PutRoleResponse putRoleResponse = client.preparePutRole("admin_role") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"all"}, new FieldPermissions(), null) + .addIndices(new String[]{"*"}, new String[]{"all"}, null, null, null) .get(); assertThat(putRoleResponse.isCreated(), is(true)); roles++; if (fls) { PutRoleResponse roleResponse; String[] fields = new String[]{"foo"}; - FieldPermissions fieldPermissions; + final String[] grantedFields; + final String[] deniedFields; if (randomBoolean()) { - fieldPermissions = new FieldPermissions(fields, null); + grantedFields = fields; + deniedFields = null; } else { - fieldPermissions = new FieldPermissions(null, fields); + grantedFields = null; + deniedFields = fields; } roleResponse = client.preparePutRole("admin_role_fls") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"all"}, fieldPermissions, null) + .addIndices(new String[]{"*"}, new String[]{"all"}, grantedFields, deniedFields, null) .get(); assertThat(roleResponse.isCreated(), is(true)); roles++; @@ -640,7 +636,7 @@ public class NativeRealmIntegTests extends NativeRealmIntegTestCase { if (dls) { PutRoleResponse roleResponse = client.preparePutRole("admin_role_dls") .cluster("all") - .addIndices(new String[]{"*"}, new String[]{"all"}, new FieldPermissions(), new BytesArray("{ \"match_all\": {} }")) + .addIndices(new String[]{"*"}, new String[]{"all"}, null, null, new BytesArray("{ \"match_all\": {} }")) .get(); assertThat(roleResponse.isCreated(), is(true)); roles++; diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index d8b96b28efd..52e880f6e6f 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -12,6 +12,8 @@ import org.elasticsearch.action.CompositeIndicesRequest; import org.elasticsearch.action.MockIndicesRequest; import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesAction; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; @@ -63,6 +65,7 @@ import org.elasticsearch.action.termvectors.TermVectorsAction; import org.elasticsearch.action.termvectors.TermVectorsRequest; import org.elasticsearch.action.update.UpdateAction; import org.elasticsearch.action.update.UpdateRequest; +import org.elasticsearch.client.Client; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; @@ -72,26 +75,35 @@ import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.index.IndexNotFoundException; +import org.elasticsearch.license.GetLicenseAction; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.xpack.security.SecurityTemplateService; import org.elasticsearch.xpack.security.action.user.AuthenticateAction; import org.elasticsearch.xpack.security.action.user.AuthenticateRequest; +import org.elasticsearch.xpack.security.action.user.AuthenticateRequestBuilder; +import org.elasticsearch.xpack.security.action.user.ChangePasswordAction; +import org.elasticsearch.xpack.security.action.user.ChangePasswordRequest; +import org.elasticsearch.xpack.security.action.user.ChangePasswordRequestBuilder; +import org.elasticsearch.xpack.security.action.user.DeleteUserAction; +import org.elasticsearch.xpack.security.action.user.PutUserAction; +import org.elasticsearch.xpack.security.action.user.UserRequest; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.authc.Authentication; import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler; +import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; +import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; +import org.elasticsearch.xpack.security.authc.file.FileRealm; +import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; +import org.elasticsearch.xpack.security.authc.pki.PkiRealm; +import org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges; import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; -import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl; -import org.elasticsearch.xpack.security.authz.permission.DefaultRole; -import org.elasticsearch.xpack.security.authz.permission.GlobalPermission; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissionsCache; import org.elasticsearch.xpack.security.authz.permission.Role; -import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; -import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; -import org.elasticsearch.xpack.security.authz.privilege.GeneralPrivilege; -import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; +import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.security.user.AnonymousUser; import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.User; @@ -100,25 +112,22 @@ import org.junit.Before; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import static org.elasticsearch.test.SecurityTestsUtils.assertAuthenticationException; import static org.elasticsearch.test.SecurityTestsUtils.assertThrowsAuthorizationException; import static org.elasticsearch.test.SecurityTestsUtils.assertThrowsAuthorizationExceptionRunAs; import static org.hamcrest.Matchers.arrayContaining; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.either; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -133,7 +142,7 @@ public class AuthorizationServiceTests extends ESTestCase { private AuthorizationService authorizationService; private ThreadContext threadContext; private ThreadPool threadPool; - private Map roleMap = new HashMap<>(); + private Map roleMap = new HashMap<>(); private CompositeRolesStore rolesStore; @Before @@ -144,12 +153,27 @@ public class AuthorizationServiceTests extends ESTestCase { threadContext = new ThreadContext(Settings.EMPTY); threadPool = mock(ThreadPool.class); when(threadPool.getThreadContext()).thenReturn(threadContext); + final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); doAnswer((i) -> { - ActionListener callback = - (ActionListener) i.getArguments()[1]; - callback.onResponse(roleMap.get(i.getArguments()[0])); + ActionListener callback = + (ActionListener) i.getArguments()[2]; + Set names = (Set) i.getArguments()[0]; + assertNotNull(names); + Set roleDescriptors = new HashSet<>(); + for (String name : names) { + RoleDescriptor descriptor = roleMap.get(name); + if (descriptor != null) { + roleDescriptors.add(descriptor); + } + } + + if (roleDescriptors.isEmpty()) { + callback.onResponse(Role.EMPTY); + } else { + callback.onResponse(CompositeRolesStore.buildRoleFromDescriptors(roleDescriptors, fieldPermissionsCache)); + } return Void.TYPE; - }).when(rolesStore).roles(any(String.class), any(ActionListener.class)); + }).when(rolesStore).roles(any(Set.class), any(FieldPermissionsCache.class), any(ActionListener.class)); authorizationService = new AuthorizationService(Settings.EMPTY, rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(Settings.EMPTY)); } @@ -228,7 +252,8 @@ public class AuthorizationServiceTests extends ESTestCase { public void testThatNonIndicesAndNonClusterActionIsDenied() { TransportRequest request = mock(TransportRequest.class); User user = new User("test user", "a_all"); - roleMap.put("a_all", Role.builder("a_role").add(IndexPrivilege.ALL, "a").build()); + roleMap.put("a_all", new RoleDescriptor("a_role",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); assertThrowsAuthorizationException( () -> authorize(createAuthentication(user), "whatever", request), @@ -240,7 +265,7 @@ public class AuthorizationServiceTests extends ESTestCase { public void testThatRoleWithNoIndicesIsDenied() { TransportRequest request = new IndicesExistsRequest("a"); User user = new User("test user", "no_indices"); - roleMap.put("no_indices", Role.builder("no_indices").cluster(ClusterPrivilege.action("")).build()); + roleMap.put("no_indices", new RoleDescriptor("a_role",null,null,null)); mockEmptyMetaData(); assertThrowsAuthorizationException( @@ -252,7 +277,8 @@ public class AuthorizationServiceTests extends ESTestCase { public void testSearchAgainstEmptyCluster() { User user = new User("test user", "a_all"); - roleMap.put("a_all", Role.builder("a_role").add(IndexPrivilege.ALL, "a").build()); + roleMap.put("a_all", new RoleDescriptor("a_role", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); mockEmptyMetaData(); { @@ -283,7 +309,8 @@ public class AuthorizationServiceTests extends ESTestCase { public void testScrollRelatedRequestsAllowed() { User user = new User("test user", "a_all"); - roleMap.put("a_all", Role.builder("a_role").add(IndexPrivilege.ALL, "a").build()); + roleMap.put("a_all", new RoleDescriptor("a_role", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); mockEmptyMetaData(); ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); @@ -317,7 +344,8 @@ public class AuthorizationServiceTests extends ESTestCase { TransportRequest request = new GetIndexRequest().indices("b"); ClusterState state = mockEmptyMetaData(); User user = new User("test user", "a_all"); - roleMap.put("a_all", Role.builder("a_all").add(IndexPrivilege.ALL, "a").build()); + roleMap.put("a_all", new RoleDescriptor("a_role",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); assertThrowsAuthorizationException( () -> authorize(createAuthentication(user), "indices:a", request), @@ -333,7 +361,8 @@ public class AuthorizationServiceTests extends ESTestCase { request.alias(new Alias("a2")); ClusterState state = mockEmptyMetaData(); User user = new User("test user", "a_all"); - roleMap.put("a_all", Role.builder("a_all").add(IndexPrivilege.ALL, "a").build()); + roleMap.put("a_all", new RoleDescriptor("a_role",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); assertThrowsAuthorizationException( () -> authorize(createAuthentication(user), CreateIndexAction.NAME, request), @@ -349,7 +378,8 @@ public class AuthorizationServiceTests extends ESTestCase { request.alias(new Alias("a2")); ClusterState state = mockEmptyMetaData(); User user = new User("test user", "a_all"); - roleMap.put("a_all", Role.builder("a_all").add(IndexPrivilege.ALL, "a", "a2").build()); + roleMap.put("a_all", new RoleDescriptor("a_all",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a", "a2").privileges("all").build() },null)); authorize(createAuthentication(user), CreateIndexAction.NAME, request); @@ -367,7 +397,8 @@ public class AuthorizationServiceTests extends ESTestCase { authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser); - roleMap.put("a_all", Role.builder("a_all").add(IndexPrivilege.ALL, "a").build()); + roleMap.put("a_all", new RoleDescriptor("a_all",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); assertThrowsAuthorizationException( () -> authorize(createAuthentication(anonymousUser), "indices:a", request), @@ -389,7 +420,8 @@ public class AuthorizationServiceTests extends ESTestCase { authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, new AnonymousUser(settings)); - roleMap.put("a_all", Role.builder("a_all").add(IndexPrivilege.ALL, "a").build()); + roleMap.put("a_all", new RoleDescriptor("a_all",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class, () -> authorize(createAuthentication(anonymousUser), "indices:a", request)); @@ -405,7 +437,8 @@ public class AuthorizationServiceTests extends ESTestCase { TransportRequest request = new GetIndexRequest().indices("not-an-index-*").indicesOptions(options); ClusterState state = mockEmptyMetaData(); User user = new User("test user", "a_all"); - roleMap.put("a_all", Role.builder("a_all").add(IndexPrivilege.ALL, "a").build()); + roleMap.put("a_all", new RoleDescriptor("a_all",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() },null)); final IndexNotFoundException nfe = expectThrows( IndexNotFoundException.class, @@ -431,7 +464,7 @@ public class AuthorizationServiceTests extends ESTestCase { public void testRunAsRequestWithoutLookedUpBy() { AuthenticateRequest request = new AuthenticateRequest("run as me"); - roleMap.put("can run as", SuperuserRole.INSTANCE); + roleMap.put("can run as", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR); User user = new User("test user", new String[] { "can run as" }, new User("run as me", Strings.EMPTY_ARRAY)); Authentication authentication = new Authentication(user, new RealmRef("foo", "bar", "baz"), null); assertThat(user.runAs(), is(notNullValue())); @@ -446,11 +479,9 @@ public class AuthorizationServiceTests extends ESTestCase { TransportRequest request = mock(TransportRequest.class); User user = new User("test user", new String[] { "can run as" }, new User("run as me", "doesn't exist")); assertThat(user.runAs(), is(notNullValue())); - roleMap.put("can run as", Role - .builder("can run as") - .runAs(new GeneralPrivilege("", "not the right user")) - .add(IndexPrivilege.ALL, "a") - .build()); + roleMap.put("can run as", new RoleDescriptor("can run as",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, + new String[] { "not the right user" })); assertThrowsAuthorizationExceptionRunAs( () -> authorize(createAuthentication(user), "indices:a", request), @@ -463,11 +494,9 @@ public class AuthorizationServiceTests extends ESTestCase { TransportRequest request = new GetIndexRequest().indices("a"); User user = new User("test user", new String[] { "can run as" }, new User("run as me", "b")); assertThat(user.runAs(), is(notNullValue())); - roleMap.put("can run as", Role - .builder("can run as") - .runAs(new GeneralPrivilege("", "run as me")) - .add(IndexPrivilege.ALL, "a") - .build()); + roleMap.put("can run as", new RoleDescriptor("can run as",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, + new String[] { "run as me" })); if (randomBoolean()) { ClusterState state = mock(ClusterState.class); @@ -477,10 +506,8 @@ public class AuthorizationServiceTests extends ESTestCase { .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) .numberOfShards(1).numberOfReplicas(0).build(), true) .build()); - roleMap.put("b", Role - .builder("b") - .add(IndexPrivilege.ALL, "b") - .build()); + roleMap.put("b", new RoleDescriptor("b",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("b").privileges("all").build() }, null)); } else { mockEmptyMetaData(); } @@ -497,11 +524,9 @@ public class AuthorizationServiceTests extends ESTestCase { TransportRequest request = new GetIndexRequest().indices("b"); User user = new User("test user", new String[] { "can run as" }, new User("run as me", "b")); assertThat(user.runAs(), is(notNullValue())); - roleMap.put("can run as", Role - .builder("can run as") - .runAs(new GeneralPrivilege("", "run as me")) - .add(IndexPrivilege.ALL, "a") - .build()); + roleMap.put("can run as", new RoleDescriptor("can run as",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, + new String[] { "run as me" })); ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.builder() @@ -509,10 +534,8 @@ public class AuthorizationServiceTests extends ESTestCase { .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) .numberOfShards(1).numberOfReplicas(0).build(), true) .build()); - roleMap.put("b", Role - .builder("b") - .add(IndexPrivilege.ALL, "b") - .build()); + roleMap.put("b", new RoleDescriptor("b",null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("b").privileges("all").build() }, null)); authorize(createAuthentication(user), "indices:a", request); verify(auditTrail).runAsGranted(user, "indices:a", request); @@ -522,10 +545,8 @@ public class AuthorizationServiceTests extends ESTestCase { public void testNonXPackUserCannotExecuteOperationAgainstSecurityIndex() { User user = new User("all_access_user", "all_access"); - roleMap.put("all_access", Role.builder("all_access") - .add(IndexPrivilege.ALL, "*") - .cluster(ClusterPrivilege.ALL) - .build()); + roleMap.put("all_access", new RoleDescriptor("all access",new String[] { "all" }, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("*").privileges("all").build() }, null)); ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.builder() @@ -577,10 +598,8 @@ public class AuthorizationServiceTests extends ESTestCase { public void testGrantedNonXPackUserCanExecuteMonitoringOperationsAgainstSecurityIndex() { User user = new User("all_access_user", "all_access"); - roleMap.put("all_access", Role.builder("all_access") - .add(IndexPrivilege.ALL, "*") - .cluster(ClusterPrivilege.ALL) - .build()); + roleMap.put("all_access", new RoleDescriptor("all access",new String[] { "all" }, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("*").privileges("all").build() }, null)); ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.builder() @@ -609,8 +628,8 @@ public class AuthorizationServiceTests extends ESTestCase { } public void testXPackUserAndSuperusersCanExecuteOperationAgainstSecurityIndex() { - final User superuser = new User("custom_admin", SuperuserRole.NAME); - roleMap.put(SuperuserRole.NAME, Role.builder(SuperuserRole.DESCRIPTOR).build()); + final User superuser = new User("custom_admin", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName()); + roleMap.put(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName(), ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR); ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.builder() @@ -646,8 +665,8 @@ public class AuthorizationServiceTests extends ESTestCase { } public void testXPackUserAndSuperusersCanExecuteOperationAgainstSecurityIndexWithWildcard() { - final User superuser = new User("custom_admin", SuperuserRole.NAME); - roleMap.put(SuperuserRole.NAME, Role.builder(SuperuserRole.DESCRIPTOR).build()); + final User superuser = new User("custom_admin", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName()); + roleMap.put(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName(), ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR); ClusterState state = mock(ClusterState.class); when(clusterService.state()).thenReturn(state); when(state.metaData()).thenReturn(MetaData.builder() @@ -674,10 +693,8 @@ public class AuthorizationServiceTests extends ESTestCase { final AnonymousUser anonymousUser = new AnonymousUser(settings); authorizationService = new AuthorizationService(settings, rolesStore, clusterService, auditTrail, new DefaultAuthenticationFailureHandler(), threadPool, anonymousUser); - roleMap.put("anonymous_user_role", Role.builder("anonymous_user_role") - .cluster(ClusterPrivilege.ALL) - .add(IndexPrivilege.ALL, "a") - .build()); + roleMap.put("anonymous_user_role", new RoleDescriptor("anonymous_user_role",new String[] { "all" }, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); mockEmptyMetaData(); // sanity check the anonymous user @@ -691,44 +708,24 @@ public class AuthorizationServiceTests extends ESTestCase { } public void testDefaultRoleUserWithoutRoles() { - PlainActionFuture> rolesFuture = new PlainActionFuture<>(); + PlainActionFuture rolesFuture = new PlainActionFuture<>(); authorizationService.roles(new User("no role user"), rolesFuture); - final Collection roles = rolesFuture.actionGet(); - assertEquals(1, roles.size()); - assertEquals(DefaultRole.NAME, roles.iterator().next().name()); + final Role roles = rolesFuture.actionGet(); + assertEquals(Role.EMPTY, roles); } - public void testDefaultRoleUserWithoutRolesAnonymousUserEnabled() { + public void testAnonymousUserEnabledRoleAdded() { 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(), threadPool, anonymousUser); - roleMap.put("anonymous_user_role", Role.builder("anonymous_user_role") - .cluster(ClusterPrivilege.ALL) - .add(IndexPrivilege.ALL, "a") - .build()); + roleMap.put("anonymous_user_role", new RoleDescriptor("anonymous_user_role",new String[] { "all" }, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); mockEmptyMetaData(); - PlainActionFuture> rolesFuture = new PlainActionFuture<>(); + PlainActionFuture rolesFuture = new PlainActionFuture<>(); authorizationService.roles(new User("no role user"), rolesFuture); - final Collection roles = rolesFuture.actionGet(); - assertEquals(2, roles.size()); - for (Role role : roles) { - assertThat(role.name(), either(equalTo(DefaultRole.NAME)).or(equalTo("anonymous_user_role"))); - } - } - - public void testDefaultRoleUserWithSomeRole() { - roleMap.put("role", Role.builder("role") - .cluster(ClusterPrivilege.ALL) - .add(IndexPrivilege.ALL, "a") - .build()); - PlainActionFuture> rolesFuture = new PlainActionFuture<>(); - authorizationService.roles(new User("user with role", "role"), rolesFuture); - final Collection roles = rolesFuture.actionGet(); - assertEquals(2, roles.size()); - for (Role role : roles) { - assertThat(role.name(), either(equalTo(DefaultRole.NAME)).or(equalTo("role"))); - } + final Role roles = rolesFuture.actionGet(); + assertThat(roles.name(), containsString("anonymous_user_role")); } public void testCompositeActionsAreImmediatelyRejected() { @@ -737,7 +734,7 @@ public class AuthorizationServiceTests extends ESTestCase { String action = compositeRequest.v1(); TransportRequest request = compositeRequest.v2(); User user = new User("test user", "no_indices"); - roleMap.put("no_indices", Role.builder("no_indices").cluster(ClusterPrivilege.action("")).build()); + roleMap.put("no_indices", new RoleDescriptor("no_indices", null, null, null)); assertThrowsAuthorizationException( () -> authorize(createAuthentication(user), action, request), action, "test user"); verify(auditTrail).accessDenied(user, action, request); @@ -750,8 +747,9 @@ public class AuthorizationServiceTests extends ESTestCase { String action = compositeRequest.v1(); TransportRequest request = compositeRequest.v2(); User user = new User("test user", "role"); - roleMap.put("role", Role.builder("role").add(IndexPrivilege.ALL, - randomBoolean() ? "a" : "index").build()); + roleMap.put("role", new RoleDescriptor("role", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices(randomBoolean() ? "a" : "index").privileges("all").build() }, + null)); authorize(createAuthentication(user), action, request); verify(auditTrail).accessGranted(user, action, request); verifyNoMoreInteractions(auditTrail); @@ -761,8 +759,9 @@ public class AuthorizationServiceTests extends ESTestCase { String action = randomCompositeRequest().v1(); TransportRequest request = mock(TransportRequest.class); User user = new User("test user", "role"); - roleMap.put("role", Role.builder("role").add(IndexPrivilege.ALL, - randomBoolean() ? "a" : "index").build()); + roleMap.put("role", new RoleDescriptor("role", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices(randomBoolean() ? "a" : "index").privileges("all").build() }, + null)); IllegalStateException illegalStateException = expectThrows(IllegalStateException.class, () -> authorize(createAuthentication(user), action, request)); assertThat(illegalStateException.getMessage(), containsString("Composite actions must implement CompositeIndicesRequest")); @@ -797,15 +796,162 @@ public class AuthorizationServiceTests extends ESTestCase { TransportRequest request = new MockIndicesRequest(IndicesOptions.strictExpandOpen(), "index"); User userAllowed = new User("userAllowed", "roleAllowed"); - roleMap.put("roleAllowed", Role.builder("roleAllowed").add(IndexPrivilege.ALL, "index").build()); + roleMap.put("roleAllowed", new RoleDescriptor("roleAllowed", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("index").privileges("all").build() }, null)); User userDenied = new User("userDenied", "roleDenied"); - roleMap.put("roleDenied", Role.builder("roleDenied").add(IndexPrivilege.ALL, "a").build()); + roleMap.put("roleDenied", new RoleDescriptor("roleDenied", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a").privileges("all").build() }, null)); mockEmptyMetaData(); authorize(createAuthentication(userAllowed), action, request); assertThrowsAuthorizationException( () -> authorize(createAuthentication(userDenied), action, request), action, "userDenied"); } + public void testSameUserPermission() { + final User user = new User("joe"); + final boolean changePasswordRequest = randomBoolean(); + final TransportRequest request = changePasswordRequest ? + new ChangePasswordRequestBuilder(mock(Client.class)).username(user.principal()).request() : + new AuthenticateRequestBuilder(mock(Client.class)).username(user.principal()).request(); + final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME; + final Authentication authentication = mock(Authentication.class); + final RealmRef authenticatedBy = mock(RealmRef.class); + when(authentication.getUser()).thenReturn(user); + when(authentication.getRunAsUser()).thenReturn(user); + when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); + when(authenticatedBy.getType()) + .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12)); + + assertThat(request, instanceOf(UserRequest.class)); + assertTrue(AuthorizationService.checkSameUserPermissions(action, request, authentication)); + } + + public void testSameUserPermissionDoesNotAllowNonMatchingUsername() { + final User user = new User("joe"); + final boolean changePasswordRequest = randomBoolean(); + final String username = randomFrom("", "joe" + randomAsciiOfLengthBetween(1, 5), randomAsciiOfLengthBetween(3, 10)); + final TransportRequest request = changePasswordRequest ? + new ChangePasswordRequestBuilder(mock(Client.class)).username(username).request() : + new AuthenticateRequestBuilder(mock(Client.class)).username(username).request(); + final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME; + final Authentication authentication = mock(Authentication.class); + final RealmRef authenticatedBy = mock(RealmRef.class); + when(authentication.getUser()).thenReturn(user); + when(authentication.getRunAsUser()).thenReturn(user); + when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); + when(authenticatedBy.getType()) + .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12)); + + assertThat(request, instanceOf(UserRequest.class)); + assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); + + final User user2 = new User("admin", new String[] { "bar" }, user); + when(authentication.getUser()).thenReturn(user2); + when(authentication.getRunAsUser()).thenReturn(user); + final RealmRef lookedUpBy = mock(RealmRef.class); + when(authentication.getLookedUpBy()).thenReturn(lookedUpBy); + when(lookedUpBy.getType()) + .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12)); + // this should still fail since the username is still different + assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); + + if (request instanceof ChangePasswordRequest) { + ((ChangePasswordRequest)request).username("joe"); + } else { + ((AuthenticateRequest)request).username("joe"); + } + assertTrue(AuthorizationService.checkSameUserPermissions(action, request, authentication)); + } + + public void testSameUserPermissionDoesNotAllowOtherActions() { + final User user = mock(User.class); + final TransportRequest request = mock(TransportRequest.class); + final String action = randomFrom(PutUserAction.NAME, DeleteUserAction.NAME, ClusterHealthAction.NAME, ClusterStateAction.NAME, + ClusterStatsAction.NAME, GetLicenseAction.NAME); + final Authentication authentication = mock(Authentication.class); + final RealmRef authenticatedBy = mock(RealmRef.class); + when(authentication.getUser()).thenReturn(user); + when(authentication.getRunAsUser()).thenReturn(randomBoolean() ? user : new User("runAs")); + when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); + when(authenticatedBy.getType()) + .thenReturn(randomAsciiOfLengthBetween(4, 12)); + + assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); + verifyZeroInteractions(user, request, authentication); + } + + public void testSameUserPermissionRunAsChecksAuthenticatedBy() { + final String username = "joe"; + final User runAs = new User(username); + final User user = new User("admin", new String[] { "bar" }, runAs); + final boolean changePasswordRequest = randomBoolean(); + final TransportRequest request = changePasswordRequest ? + new ChangePasswordRequestBuilder(mock(Client.class)).username(username).request() : + new AuthenticateRequestBuilder(mock(Client.class)).username(username).request(); + final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME; + final Authentication authentication = mock(Authentication.class); + final RealmRef authenticatedBy = mock(RealmRef.class); + final RealmRef lookedUpBy = mock(RealmRef.class); + when(authentication.getUser()).thenReturn(user); + when(authentication.getRunAsUser()).thenReturn(runAs); + when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); + when(authentication.getLookedUpBy()).thenReturn(lookedUpBy); + when(authentication.isRunAs()).thenReturn(true); + when(lookedUpBy.getType()) + .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12)); + assertTrue(AuthorizationService.checkSameUserPermissions(action, request, authentication)); + + when(authentication.getRunAsUser()).thenReturn(user); + assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); + } + + public void testSameUserPermissionDoesNotAllowChangePasswordForOtherRealms() { + final User user = new User("joe"); + final ChangePasswordRequest request = new ChangePasswordRequestBuilder(mock(Client.class)).username(user.principal()).request(); + final String action = ChangePasswordAction.NAME; + final Authentication authentication = mock(Authentication.class); + final RealmRef authenticatedBy = mock(RealmRef.class); + when(authentication.getUser()).thenReturn(user); + when(authentication.getRunAsUser()).thenReturn(user); + when(authentication.isRunAs()).thenReturn(false); + when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); + when(authenticatedBy.getType()).thenReturn(randomFrom(LdapRealm.LDAP_TYPE, FileRealm.TYPE, LdapRealm.AD_TYPE, PkiRealm.TYPE, + randomAsciiOfLengthBetween(4, 12))); + + assertThat(request, instanceOf(UserRequest.class)); + assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); + verify(authenticatedBy).getType(); + verify(authentication).getRunAsUser(); + verify(authentication).getAuthenticatedBy(); + verify(authentication).isRunAs(); + verifyNoMoreInteractions(authenticatedBy, authentication); + } + + public void testSameUserPermissionDoesNotAllowChangePasswordForLookedUpByOtherRealms() { + final User runAs = new User("joe"); + final User user = new User("admin", new String[] { "bar" }, runAs); + final ChangePasswordRequest request = new ChangePasswordRequestBuilder(mock(Client.class)).username(runAs.principal()).request(); + final String action = ChangePasswordAction.NAME; + final Authentication authentication = mock(Authentication.class); + final RealmRef authenticatedBy = mock(RealmRef.class); + final RealmRef lookedUpBy = mock(RealmRef.class); + when(authentication.getUser()).thenReturn(user); + when(authentication.getRunAsUser()).thenReturn(runAs); + when(authentication.isRunAs()).thenReturn(true); + when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); + when(authentication.getLookedUpBy()).thenReturn(lookedUpBy); + when(lookedUpBy.getType()).thenReturn(randomFrom(LdapRealm.LDAP_TYPE, FileRealm.TYPE, LdapRealm.AD_TYPE, PkiRealm.TYPE, + randomAsciiOfLengthBetween(4, 12))); + + assertThat(request, instanceOf(UserRequest.class)); + assertFalse(AuthorizationService.checkSameUserPermissions(action, request, authentication)); + verify(authentication).getLookedUpBy(); + verify(authentication).getRunAsUser(); + verify(authentication).isRunAs(); + verify(lookedUpBy).getType(); + verifyNoMoreInteractions(authentication, lookedUpBy, authenticatedBy); + } + private static Tuple randomCompositeRequest() { switch(randomIntBetween(0, 7)) { case 0: @@ -833,57 +979,13 @@ public class AuthorizationServiceTests extends ESTestCase { } public void testDoesNotUseRolesStoreForXPackUser() { - PlainActionFuture> rolesFuture = new PlainActionFuture<>(); + PlainActionFuture rolesFuture = new PlainActionFuture<>(); authorizationService.roles(XPackUser.INSTANCE, rolesFuture); - final Collection roles = rolesFuture.actionGet(); - assertThat(roles, contains(SuperuserRole.INSTANCE)); + final Role roles = rolesFuture.actionGet(); + assertThat(roles, equalTo(ReservedRolesStore.SUPERUSER_ROLE)); verifyZeroInteractions(rolesStore); } - public void testPermissionIncludesAnonymousUserPermissions() { - 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(), threadPool, anonymousUser); - final boolean roleExists = randomBoolean(); - final Role anonymousRole = Role.builder("a_all").add(IndexPrivilege.ALL, "a").build(); - if (roleExists) { - roleMap.put("a_all", anonymousRole); - } - final MetaData metaData = MetaData.builder() - .put(new IndexMetaData.Builder("a") - .settings(Settings.builder().put("index.version.created", Version.CURRENT).build()) - .numberOfShards(1).numberOfReplicas(0).build(), true) - .build(); - - User user = new User("no_roles"); - PlainActionFuture> rolesFuture = new PlainActionFuture<>(); - authorizationService.roles(user, rolesFuture); - final Collection roles = rolesFuture.actionGet(); - GlobalPermission globalPermission = authorizationService.permission(roles); - verify(rolesStore).roles(eq("a_all"), any(ActionListener.class)); - - if (roleExists) { - assertThat(roles, containsInAnyOrder(anonymousRole, DefaultRole.INSTANCE)); - assertFalse(globalPermission.isEmpty()); - // by default all users have a DefaultRole that grants cluster actions like change password - assertFalse(globalPermission.cluster().isEmpty()); - assertFalse(globalPermission.indices().isEmpty()); - Map authzMap = - globalPermission.indices().authorize(SearchAction.NAME, Collections.singleton("a"), metaData); - assertTrue(authzMap.containsKey("a")); - assertTrue(authzMap.get("a").isGranted()); - assertFalse(authzMap.get("a").getFieldPermissions().hasFieldLevelSecurity()); - assertNull(authzMap.get("a").getQueries()); - } else { - assertThat(roles, contains(DefaultRole.INSTANCE)); - assertFalse(globalPermission.isEmpty()); - // by default all users have a DefaultRole that grants cluster actions like change password - assertFalse(globalPermission.cluster().isEmpty()); - assertTrue(globalPermission.indices().isEmpty()); - } - } - public void testGetRolesForSystemUserThrowsException() { IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> authorizationService.roles(SystemUser.INSTANCE, null)); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java index 3ae44471c90..605b97a01d9 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizedIndicesTests.java @@ -11,14 +11,15 @@ import org.elasticsearch.cluster.metadata.AliasMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissionsCache; import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; +import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; import org.elasticsearch.xpack.security.user.User; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.List; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -27,15 +28,18 @@ public class AuthorizedIndicesTests extends ESTestCase { public void testAuthorizedIndicesUserWithoutRoles() { User user = new User("test user"); - AuthorizedIndices authorizedIndices = new AuthorizedIndices(user, Collections.emptyList(), "", MetaData.EMPTY_META_DATA); + AuthorizedIndices authorizedIndices = new AuthorizedIndices(user, Role.EMPTY, "", + MetaData.EMPTY_META_DATA); List list = authorizedIndices.get(); assertTrue(list.isEmpty()); } public void testAuthorizedIndicesUserWithSomeRoles() { User user = new User("test user", "a_star", "b"); - Role aStarRole = Role.builder("a_star").add(IndexPrivilege.ALL, "a*").build(); - Role bRole = Role.builder("b").add(IndexPrivilege.READ, "b").build(); + RoleDescriptor aStarRole = new RoleDescriptor("a_star", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("a*").privileges("all").build() }, null); + RoleDescriptor bRole = new RoleDescriptor("b", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("b").privileges("READ").build() }, null); Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); MetaData metaData = MetaData.builder() .put(new IndexMetaData.Builder("a1").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) @@ -50,7 +54,8 @@ public class AuthorizedIndicesTests extends ESTestCase { .putAlias(new AliasMetaData.Builder("ba").build()) .build(), true) .build(); - Collection roles = Arrays.asList(aStarRole, bRole); + Role roles = + CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(aStarRole, bRole), new FieldPermissionsCache(Settings.EMPTY)); AuthorizedIndices authorizedIndices = new AuthorizedIndices(user, roles, SearchAction.NAME, metaData); List list = authorizedIndices.get(); assertThat(list, containsInAnyOrder("a1", "a2", "aaaaaa", "b", "ab")); @@ -61,8 +66,7 @@ public class AuthorizedIndicesTests extends ESTestCase { public void testAuthorizedIndicesUserWithSomeRolesEmptyMetaData() { User user = new User("test user", "role"); Role role = Role.builder("role").add(IndexPrivilege.ALL, "*").build(); - Collection roles = Collections.singletonList(role); - AuthorizedIndices authorizedIndices = new AuthorizedIndices(user, roles, SearchAction.NAME, MetaData.EMPTY_META_DATA); + AuthorizedIndices authorizedIndices = new AuthorizedIndices(user, role, SearchAction.NAME, MetaData.EMPTY_META_DATA); List list = authorizedIndices.get(); assertTrue(list.isEmpty()); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java index 1cc883a2e96..3b3060815b4 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/IndexAliasesTests.java @@ -5,6 +5,7 @@ */ package org.elasticsearch.xpack.security.authz; +import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.action.admin.indices.alias.Alias; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesAction; import org.elasticsearch.action.admin.indices.alias.get.GetAliasesAction; @@ -105,9 +106,8 @@ public class IndexAliasesTests extends SecurityIntegTestCase { client().filterWithHeader(headers).admin().indices().prepareAliases().addAlias("test_1", "test_alias")::get, IndicesAliasesAction.NAME, "create_only"); - IndexNotFoundException indexNotFoundException = expectThrows(IndexNotFoundException.class, - client().filterWithHeader(headers).admin().indices().prepareAliases().addAlias("test_*", "test_alias")::get); - assertThat(indexNotFoundException.toString(), containsString("[test_*]")); + assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices().prepareAliases() + .addAlias("test_*", "test_alias")::get, IndicesAliasesAction.NAME, "create_only"); } public void testCreateIndexAndAliasesCreateOnlyPermission() { @@ -130,13 +130,11 @@ public class IndexAliasesTests extends SecurityIntegTestCase { client().filterWithHeader(headers).admin().indices().prepareAliases().removeAlias("test_1", "alias_1")::get, IndicesAliasesAction.NAME, "create_only"); - IndexNotFoundException indexNotFoundException = expectThrows(IndexNotFoundException.class, - client().filterWithHeader(headers).admin().indices().prepareAliases().removeAlias("test_1", "alias_*")::get); - assertThat(indexNotFoundException.toString(), containsString("[alias_*")); + assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices().prepareAliases() + .removeAlias("test_1", "alias_*")::get, IndicesAliasesAction.NAME, "create_only"); - indexNotFoundException = expectThrows(IndexNotFoundException.class, - client().filterWithHeader(headers).admin().indices().prepareAliases().removeAlias("test_1", "_all")::get); - assertThat(indexNotFoundException.toString(), containsString("[_all]")); + assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices().prepareAliases() + .removeAlias("test_1", "_all")::get, IndicesAliasesAction.NAME, "create_only"); } public void testGetAliasesCreateOnlyPermissionStrict() { @@ -147,24 +145,21 @@ public class IndexAliasesTests extends SecurityIntegTestCase { assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices().prepareGetAliases("test_1") .setIndices("test_1").setIndicesOptions(IndicesOptions.strictExpand())::get, GetAliasesAction.NAME, "create_only"); - IndexNotFoundException indexNotFoundException = expectThrows(IndexNotFoundException.class, client().filterWithHeader(headers) + assertThrowsAuthorizationException(client().filterWithHeader(headers) .admin().indices().prepareGetAliases("_all") - .setIndices("test_1").setIndicesOptions(IndicesOptions.strictExpand())::get); - assertThat(indexNotFoundException.toString(), containsString("[_all]")); + .setIndices("test_1").setIndicesOptions(IndicesOptions.strictExpand())::get, GetAliasesAction.NAME, "create_only"); - indexNotFoundException = expectThrows(IndexNotFoundException.class, client().filterWithHeader(headers).admin().indices() - .prepareGetAliases().setIndices("test_1").setIndicesOptions(IndicesOptions.strictExpand())::get); - assertThat(indexNotFoundException.toString(), containsString("[_all]")); + assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices() + .prepareGetAliases().setIndices("test_1").setIndicesOptions(IndicesOptions.strictExpand())::get, + GetAliasesAction.NAME, "create_only"); - GetAliasesResponse getAliasesResponse = client().filterWithHeader(headers).admin().indices().prepareGetAliases("test_alias") - .setIndices("test_*").setIndicesOptions(IndicesOptions.strictExpand()).get(); - assertEquals(0, getAliasesResponse.getAliases().size()); + assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices().prepareGetAliases("test_alias") + .setIndices("test_*").setIndicesOptions(IndicesOptions.strictExpand())::get, GetAliasesAction.NAME, "create_only"); //this throws exception no matter what the indices options are because the aliases part cannot be resolved to any alias //and there is no way to "allow_no_aliases" like we can do with indices. - indexNotFoundException = expectThrows(IndexNotFoundException.class, - client().filterWithHeader(headers).admin().indices().prepareGetAliases()::get); - assertThat(indexNotFoundException.toString(), containsString("[_all]")); + assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices().prepareGetAliases()::get, + GetAliasesAction.NAME, "create_only"); } public void testGetAliasesCreateOnlyPermissionIgnoreUnavailable() { @@ -172,28 +167,23 @@ public class IndexAliasesTests extends SecurityIntegTestCase { Map headers = Collections.singletonMap(BASIC_AUTH_HEADER, basicAuthHeaderValue("create_only", new SecuredString("test123".toCharArray()))); - GetAliasesResponse getAliasesResponse = client().filterWithHeader(headers).admin().indices().prepareGetAliases("test_1") - .setIndices("test_1").setIndicesOptions(IndicesOptions.lenientExpandOpen()).get(); - assertEquals(0, getAliasesResponse.getAliases().size()); + assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices().prepareGetAliases("test_1") + .setIndices("test_1").setIndicesOptions(IndicesOptions.lenientExpandOpen())::get, GetAliasesAction.NAME, "create_only"); - IndexNotFoundException indexNotFoundException = expectThrows(IndexNotFoundException.class, client().filterWithHeader(headers) - .admin().indices().prepareGetAliases("_all") - .setIndices("test_1").setIndicesOptions(IndicesOptions.lenientExpandOpen())::get); - assertThat(indexNotFoundException.toString(), containsString("[_all]")); + assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices().prepareGetAliases("_all") + .setIndices("test_1").setIndicesOptions(IndicesOptions.lenientExpandOpen())::get, GetAliasesAction.NAME, "create_only"); - indexNotFoundException = expectThrows(IndexNotFoundException.class, client().filterWithHeader(headers).admin().indices() - .prepareGetAliases().setIndices("test_1").setIndicesOptions(IndicesOptions.lenientExpandOpen())::get); - assertThat(indexNotFoundException.toString(), containsString("[_all]")); + assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices().prepareGetAliases().setIndices("test_1") + .setIndicesOptions(IndicesOptions.lenientExpandOpen())::get, GetAliasesAction.NAME, "create_only"); - getAliasesResponse = client().filterWithHeader(headers).admin().indices().prepareGetAliases("test_alias") - .setIndices("test_*").setIndicesOptions(IndicesOptions.lenientExpandOpen()).get(); - assertEquals(0, getAliasesResponse.getAliases().size()); + assertThrowsAuthorizationException( + client().filterWithHeader(headers).admin().indices().prepareGetAliases("test_alias") + .setIndices("test_*").setIndicesOptions(IndicesOptions.lenientExpandOpen())::get, GetAliasesAction.NAME, "create_only"); //this throws exception no matter what the indices options are because the aliases part cannot be resolved to any alias //and there is no way to "allow_no_aliases" like we can do with indices. - indexNotFoundException = expectThrows(IndexNotFoundException.class, client().filterWithHeader(headers).admin().indices() - .prepareGetAliases().setIndicesOptions(IndicesOptions.lenientExpandOpen())::get); - assertThat(indexNotFoundException.toString(), containsString("[_all]")); + assertThrowsAuthorizationException(client().filterWithHeader(headers).admin().indices() + .prepareGetAliases().setIndicesOptions(IndicesOptions.lenientExpandOpen())::get, GetAliasesAction.NAME, "create_only"); } public void testCreateIndexThenAliasesCreateAndAliasesPermission() { diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java index 15fefd1912e..c1fcafe3d4e 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolverTests.java @@ -41,19 +41,19 @@ import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.xpack.security.SecurityTemplateService; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.authc.DefaultAuthenticationFailureHandler; +import org.elasticsearch.xpack.security.authz.RoleDescriptor.IndicesPrivileges; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissionsCache; import org.elasticsearch.xpack.security.authz.permission.Role; -import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; -import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; -import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; +import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.security.user.AnonymousUser; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.XPackUser; import org.junit.Before; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -77,7 +77,7 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { private AuthorizationService authzService; private IndicesAndAliasesResolver defaultIndicesResolver; private IndexNameExpressionResolver indexNameExpressionResolver; - private Map roleMap; + private Map roleMap; @Before public void setup() { @@ -113,16 +113,33 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { String[] authorizedIndices = new String[] { "bar", "bar-closed", "foofoobar", "foofoo", "missing", "foofoo-closed"}; String[] dashIndices = new String[]{"-index10", "-index11", "-index20", "-index21"}; roleMap = new HashMap<>(); - roleMap.put("role", Role.builder("role").add(IndexPrivilege.ALL, authorizedIndices).build()); - roleMap.put("dash", Role.builder("dash").add(IndexPrivilege.ALL, dashIndices).build()); - roleMap.put("test", Role.builder("test").cluster(ClusterPrivilege.MONITOR).build()); - roleMap.put(SuperuserRole.NAME, Role.builder(SuperuserRole.DESCRIPTOR).build()); + roleMap.put("role", new RoleDescriptor("role", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices(authorizedIndices).privileges("all").build() }, null)); + roleMap.put("dash", new RoleDescriptor("dash", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices(dashIndices).privileges("all").build() }, null)); + roleMap.put("test", new RoleDescriptor("role", new String[] { "monitor" }, null, null)); + roleMap.put(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName(), ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR); + final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); doAnswer((i) -> { ActionListener callback = - (ActionListener) i.getArguments()[1]; - callback.onResponse(roleMap.get(i.getArguments()[0])); + (ActionListener) i.getArguments()[2]; + Set names = (Set) i.getArguments()[0]; + assertNotNull(names); + Set roleDescriptors = new HashSet<>(); + for (String name : names) { + RoleDescriptor descriptor = roleMap.get(name); + if (descriptor != null) { + roleDescriptors.add(descriptor); + } + } + + if (roleDescriptors.isEmpty()) { + callback.onResponse(Role.EMPTY); + } else { + callback.onResponse(CompositeRolesStore.buildRoleFromDescriptors(roleDescriptors, fieldPermissionsCache)); + } return Void.TYPE; - }).when(rolesStore).roles(any(String.class), any(ActionListener.class)); + }).when(rolesStore).roles(any(Set.class), any(FieldPermissionsCache.class), any(ActionListener.class)); ClusterService clusterService = mock(ClusterService.class); authzService = new AuthorizationService(settings, rolesStore, clusterService, @@ -1048,8 +1065,8 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { public void testNonXPackUserAccessingSecurityIndex() { User allAccessUser = new User("all_access", "all_access"); - roleMap.put("all_access", - Role.builder("all_access").add(IndexPrivilege.ALL, "*").cluster(ClusterPrivilege.ALL).build()); + roleMap.put("all_access", new RoleDescriptor("all_access", new String[] { "all" }, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices("*").privileges("all").build() }, null)); { SearchRequest request = new SearchRequest(); @@ -1093,7 +1110,8 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { // make the user authorized String dateTimeIndex = indexNameExpressionResolver.resolveDateMathExpression(""); String[] authorizedIndices = new String[] { "bar", "bar-closed", "foofoobar", "foofoo", "missing", "foofoo-closed", dateTimeIndex}; - roleMap.put("role", Role.builder("role").add(IndexPrivilege.ALL, authorizedIndices).build()); + roleMap.put("role", new RoleDescriptor("role", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices(authorizedIndices).privileges("all").build() }, null)); SearchRequest request = new SearchRequest(""); if (randomBoolean()) { @@ -1130,7 +1148,8 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { // make the user authorized String[] authorizedIndices = new String[] { "bar", "bar-closed", "foofoobar", "foofoo", "missing", "foofoo-closed", indexNameExpressionResolver.resolveDateMathExpression("")}; - roleMap.put("role", Role.builder("role").add(IndexPrivilege.ALL, authorizedIndices).build()); + roleMap.put("role", new RoleDescriptor("role", null, + new IndicesPrivileges[] { IndicesPrivileges.builder().indices(authorizedIndices).privileges("all").build() }, null)); GetAliasesRequest request = new GetAliasesRequest("").indices("foo", "foofoo"); Set indices = defaultIndicesResolver.resolve(request, metaData, buildAuthorizedIndices(user, GetAliasesAction.NAME)); //the union of all indices and aliases gets returned @@ -1144,7 +1163,7 @@ public class IndicesAndAliasesResolverTests extends ESTestCase { // TODO with the removal of DeleteByQuery is there another way to test resolving a write action? private AuthorizedIndices buildAuthorizedIndices(User user, String action) { - PlainActionFuture> rolesListener = new PlainActionFuture<>(); + PlainActionFuture rolesListener = new PlainActionFuture<>(); authzService.roles(user, rolesListener); return new AuthorizedIndices(user, rolesListener.actionGet(), action, metaData); } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java index ced68d2c634..5078dda6ee1 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/RoleDescriptorTests.java @@ -39,7 +39,7 @@ public class RoleDescriptorTests extends ESTestCase { RoleDescriptor.IndicesPrivileges.builder() .indices("i1", "i2") .privileges("read") - .fieldPermissions(new FieldPermissions(new String[]{"body", "title"}, null)) + .grantedFields("body", "title") .query("{\"query\": {\"match_all\": {}}}") .build() }; @@ -54,7 +54,7 @@ public class RoleDescriptorTests extends ESTestCase { RoleDescriptor.IndicesPrivileges.builder() .indices("i1", "i2") .privileges("read") - .fieldPermissions(new FieldPermissions(new String[]{"body", "title"}, null)) + .grantedFields("body", "title") .query("{\"query\": {\"match_all\": {}}}") .build() }; @@ -119,7 +119,7 @@ public class RoleDescriptorTests extends ESTestCase { RoleDescriptor.IndicesPrivileges.builder() .indices("i1", "i2") .privileges("read") - .fieldPermissions(new FieldPermissions(new String[]{"body", "title"}, null)) + .grantedFields("body", "title") .query("{\"query\": {\"match_all\": {}}}") .build() }; diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControlTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControlTests.java index 88964d2a0b7..5e1db413662 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControlTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesAccessControlTests.java @@ -5,21 +5,9 @@ */ package org.elasticsearch.xpack.security.authz.accesscontrol; -import org.apache.lucene.util.automaton.Automaton; -import org.elasticsearch.common.bytes.BytesArray; -import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl; -import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import java.util.Collections; -import java.util.Set; - -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; /** * Unit tests for {@link IndicesAccessControl} @@ -28,164 +16,7 @@ public class IndicesAccessControlTests extends ESTestCase { public void testEmptyIndicesAccessControl() { IndicesAccessControl indicesAccessControl = new IndicesAccessControl(true, Collections.emptyMap()); - assertThat(indicesAccessControl.isGranted(), is(true)); - assertThat(indicesAccessControl.getIndexPermissions(randomAsciiOfLengthBetween(3,20)), nullValue()); - } - - public void testMergeFields() { - IndexAccessControl indexAccessControl = new IndexAccessControl(true, new FieldPermissions(new String[]{"a", "c"}, null), null); - IndexAccessControl other = new IndexAccessControl(true,new FieldPermissions(new String[]{"b"}, null), null); - - IndexAccessControl merge1 = indexAccessControl.merge(other); - assertTrue(merge1.getFieldPermissions().grantsAccessTo("a")); - assertTrue(merge1.getFieldPermissions().grantsAccessTo("b")); - assertTrue(merge1.getFieldPermissions().grantsAccessTo("c")); - assertTrue(merge1.getFieldPermissions().hasFieldLevelSecurity()); - assertThat(merge1.isGranted(), is(true)); - assertThat(merge1.getQueries(), nullValue()); - - IndexAccessControl merge2 = other.merge(indexAccessControl); - assertTrue(merge2.getFieldPermissions().grantsAccessTo("a")); - assertTrue(merge2.getFieldPermissions().grantsAccessTo("b")); - assertTrue(merge2.getFieldPermissions().grantsAccessTo("c")); - assertTrue(merge2.getFieldPermissions().hasFieldLevelSecurity()); - assertThat(merge2.isGranted(), is(true)); - assertThat(merge2.getQueries(), nullValue()); - } - - public void testMergeEmptyAndNullFields() { - IndexAccessControl indexAccessControl = new IndexAccessControl(true, new FieldPermissions(new String[]{}, null), null); - IndexAccessControl other = new IndexAccessControl(true, new FieldPermissions(), null); - - IndexAccessControl merge1 = indexAccessControl.merge(other); - assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); - assertThat(merge1.isGranted(), is(true)); - assertThat(merge1.getQueries(), nullValue()); - - IndexAccessControl merge2 = other.merge(indexAccessControl); - assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); - assertThat(merge2.isGranted(), is(true)); - assertThat(merge2.getQueries(), nullValue()); - } - - public void testMergeNullFields() { - IndexAccessControl indexAccessControl = new IndexAccessControl(true, new FieldPermissions(new String[]{"a", "b"}, null), null); - IndexAccessControl other = new IndexAccessControl(true, new FieldPermissions(), null); - - IndexAccessControl merge1 = indexAccessControl.merge(other); - assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); - assertThat(merge1.isGranted(), is(true)); - assertThat(merge1.getQueries(), nullValue()); - - IndexAccessControl merge2 = other.merge(indexAccessControl); - assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); - assertThat(merge2.isGranted(), is(true)); - assertThat(merge2.getQueries(), nullValue()); - } - - public void testMergeQueries() { - BytesReference query1 = new BytesArray(new byte[] { 0x1 }); - BytesReference query2 = new BytesArray(new byte[] { 0x2 }); - IndexAccessControl indexAccessControl = new IndexAccessControl(true, new FieldPermissions(), Collections.singleton - (query1)); - IndexAccessControl other = new IndexAccessControl(true, new FieldPermissions(), Collections.singleton(query2)); - - IndexAccessControl merge1 = indexAccessControl.merge(other); - assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); - assertThat(merge1.isGranted(), is(true)); - assertThat(merge1.getQueries(), containsInAnyOrder(query1, query2)); - - IndexAccessControl merge2 = other.merge(indexAccessControl); - assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); - assertThat(merge2.isGranted(), is(true)); - assertThat(merge1.getQueries(), containsInAnyOrder(query1, query2)); - } - - public void testMergeNullQuery() { - BytesReference query1 = new BytesArray(new byte[] { 0x1 }); - IndexAccessControl indexAccessControl = new IndexAccessControl(true, new FieldPermissions(), Collections.singleton - (query1)); - IndexAccessControl other = new IndexAccessControl(true, new FieldPermissions(), null); - - IndexAccessControl merge1 = indexAccessControl.merge(other); - assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); - assertThat(merge1.isGranted(), is(true)); - assertThat(merge1.getQueries(), nullValue()); - - IndexAccessControl merge2 = other.merge(indexAccessControl); - assertFalse(merge1.getFieldPermissions().hasFieldLevelSecurity()); - assertThat(merge2.isGranted(), is(true)); - assertThat(merge1.getQueries(), nullValue()); - } - - public void testMergeNotGrantedAndGranted() { - final String[] notGrantedFields = randomFrom(new String[]{}, new String[]{"baz"}, null); - final Set notGrantedQueries = randomFrom(Collections.emptySet(), null, - Collections.singleton(new BytesArray(new byte[] { randomByte() }))); - final IndexAccessControl indexAccessControl = new IndexAccessControl(false, new FieldPermissions(notGrantedFields, null), - notGrantedQueries); - - final BytesReference query1 = new BytesArray(new byte[] { 0x1 }); - final String[] fields = - randomFrom(new String[]{"foo"}, new String[]{"foo", "bar"}, new String[]{}, null); - final Set queries = - randomFrom(Collections.singleton(query1), Collections.emptySet(), null); - final IndexAccessControl other = new IndexAccessControl(true, new FieldPermissions(fields, null), queries); - - IndexAccessControl merged = indexAccessControl.merge(other); - assertThat(merged.isGranted(), is(true)); - assertThat(merged.getQueries(), equalTo(queries)); - if (fields == null) { - assertFalse(merged.getFieldPermissions().hasFieldLevelSecurity()); - } else { - assertTrue(merged.getFieldPermissions().hasFieldLevelSecurity()); - if (notGrantedFields != null) { - for (String field : notGrantedFields) { - assertFalse(merged.getFieldPermissions().grantsAccessTo(field)); - } - } - for (String field : fields) { - assertTrue(merged.getFieldPermissions().grantsAccessTo(field)); - } - } - merged = other.merge(indexAccessControl); - assertThat(merged.isGranted(), is(true)); - assertThat(merged.getQueries(), equalTo(queries)); - if (fields == null) { - assertFalse(merged.getFieldPermissions().hasFieldLevelSecurity()); - } else { - assertTrue(merged.getFieldPermissions().hasFieldLevelSecurity()); - if (notGrantedFields != null) { - for (String field : notGrantedFields) { - assertFalse(merged.getFieldPermissions().grantsAccessTo(field)); - } - } - for (String field : fields) { - assertTrue(merged.getFieldPermissions().grantsAccessTo(field)); - } - } - } - - public void testMergeNotGranted() { - final String[] notGrantedFields = randomFrom(new String[]{}, new String[]{"baz"}, null); - final Set notGrantedQueries = randomFrom(Collections.emptySet(), null, - Collections.singleton(new BytesArray(new byte[] { randomByte() }))); - final IndexAccessControl indexAccessControl = new IndexAccessControl(false, new FieldPermissions(notGrantedFields, null), - notGrantedQueries); - - final BytesReference query1 = new BytesArray(new byte[] { 0x1 }); - final String[] fields = - randomFrom(new String[]{"foo"}, new String[]{"foo", "bar"}, new String[]{}, null); - final Set queries = - randomFrom(Collections.singleton(query1), Collections.emptySet(), null); - final IndexAccessControl other = new IndexAccessControl(false, new FieldPermissions(fields, null), queries); - - IndexAccessControl merged = indexAccessControl.merge(other); - assertThat(merged.isGranted(), is(false)); - assertThat(merged.getQueries(), equalTo(notGrantedQueries)); - - merged = other.merge(indexAccessControl); - assertThat(merged.isGranted(), is(false)); - assertThat(merged.getQueries(), equalTo(queries)); + assertTrue(indicesAccessControl.isGranted()); + assertNull(indicesAccessControl.getIndexPermissions(randomAsciiOfLengthBetween(3,20))); } } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java index 07b3471515f..9bf6ac6f24a 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/accesscontrol/IndicesPermissionTests.java @@ -19,12 +19,15 @@ import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.security.authz.RoleDescriptor; import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissionsCache; import org.elasticsearch.xpack.security.authz.permission.IndicesPermission; import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege; import java.io.IOException; +import java.util.Collections; import java.util.Map; +import java.util.Set; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; @@ -41,21 +44,24 @@ public class IndicesPermissionTests extends ESTestCase { ) .putAlias(AliasMetaData.builder("_alias")); MetaData md = MetaData.builder().put(imbBuilder).build(); + FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); // basics: - BytesReference query = new BytesArray("{}"); + Set query = Collections.singleton(new BytesArray("{}")); String[] fields = new String[]{"_field"}; - Role role = Role.builder("_role").add(new FieldPermissions(fields, null), query, IndexPrivilege.ALL, "_index").build(); - IndicesAccessControl permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), md); + Role role = Role.builder("_role") + .add(new FieldPermissions(fields, null), query, IndexPrivilege.ALL, "_index").build(); + IndicesAccessControl permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), md, fieldPermissionsCache); assertThat(permissions.getIndexPermissions("_index"), notNullValue()); assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().grantsAccessTo("_field")); assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity()); assertThat(permissions.getIndexPermissions("_index").getQueries().size(), equalTo(1)); - assertThat(permissions.getIndexPermissions("_index").getQueries().iterator().next(), equalTo(query)); + assertThat(permissions.getIndexPermissions("_index").getQueries(), equalTo(query)); // no document level security: - role = Role.builder("_role").add(new FieldPermissions(fields, null), null, IndexPrivilege.ALL, "_index").build(); - permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), md); + role = Role.builder("_role") + .add(new FieldPermissions(fields, null), null, IndexPrivilege.ALL, "_index").build(); + permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), md, fieldPermissionsCache); assertThat(permissions.getIndexPermissions("_index"), notNullValue()); assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().grantsAccessTo("_field")); assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity()); @@ -63,48 +69,64 @@ public class IndicesPermissionTests extends ESTestCase { // no field level security: role = Role.builder("_role").add(new FieldPermissions(), query, IndexPrivilege.ALL, "_index").build(); - permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), md); + permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_index"), md, fieldPermissionsCache); assertThat(permissions.getIndexPermissions("_index"), notNullValue()); assertFalse(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity()); assertThat(permissions.getIndexPermissions("_index").getQueries().size(), equalTo(1)); - assertThat(permissions.getIndexPermissions("_index").getQueries().iterator().next(), equalTo(query)); + assertThat(permissions.getIndexPermissions("_index").getQueries(), equalTo(query)); // index group associated with an alias: role = Role.builder("_role").add(new FieldPermissions(fields, null), query, IndexPrivilege.ALL, "_alias").build(); - permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_alias"), md); + permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_alias"), md, fieldPermissionsCache); assertThat(permissions.getIndexPermissions("_index"), notNullValue()); assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().grantsAccessTo("_field")); assertTrue(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity()); assertThat(permissions.getIndexPermissions("_index").getQueries().size(), equalTo(1)); - assertThat(permissions.getIndexPermissions("_index").getQueries().iterator().next(), equalTo(query)); + assertThat(permissions.getIndexPermissions("_index").getQueries(), equalTo(query)); // match all fields String[] allFields = randomFrom(new String[]{"*"}, new String[]{"foo", "*"}, new String[]{randomAsciiOfLengthBetween(1, 10), "*"}); - role = Role.builder("_role").add(new FieldPermissions(allFields, null), query, IndexPrivilege.ALL, "_alias").build(); - permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_alias"), md); + role = Role.builder("_role") + .add(new FieldPermissions(allFields, null), query, IndexPrivilege.ALL, "_alias").build(); + permissions = role.authorize(SearchAction.NAME, Sets.newHashSet("_alias"), md, fieldPermissionsCache); assertThat(permissions.getIndexPermissions("_index"), notNullValue()); assertFalse(permissions.getIndexPermissions("_index").getFieldPermissions().hasFieldLevelSecurity()); assertThat(permissions.getIndexPermissions("_index").getQueries().size(), equalTo(1)); - assertThat(permissions.getIndexPermissions("_index").getQueries().iterator().next(), equalTo(query)); + assertThat(permissions.getIndexPermissions("_index").getQueries(), equalTo(query)); } - public void testIndicesPriviledgesStreaming() throws IOException { + public void testIndicesPrivilegesStreaming() throws IOException { BytesStreamOutput out = new BytesStreamOutput(); String[] allowed = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; String[] denied = new String[]{allowed[0] + randomAsciiOfLength(5), allowed[1] + randomAsciiOfLength(5), allowed[2] + randomAsciiOfLength(5)}; - FieldPermissions fieldPermissions = new FieldPermissions(allowed, denied); RoleDescriptor.IndicesPrivileges.Builder indicesPrivileges = RoleDescriptor.IndicesPrivileges.builder(); - indicesPrivileges.fieldPermissions(fieldPermissions); + indicesPrivileges.grantedFields(allowed); + indicesPrivileges.deniedFields(denied); indicesPrivileges.query("{match_all:{}}"); indicesPrivileges.indices(randomAsciiOfLength(5), randomAsciiOfLength(5), randomAsciiOfLength(5)); indicesPrivileges.privileges("all", "read", "priv"); indicesPrivileges.build().writeTo(out); out.close(); StreamInput in = out.bytes().streamInput(); - RoleDescriptor.IndicesPrivileges readIndicesPriviledges = RoleDescriptor.IndicesPrivileges.createFrom(in); - assertEquals(readIndicesPriviledges, indicesPrivileges.build()); + RoleDescriptor.IndicesPrivileges readIndicesPrivileges = RoleDescriptor.IndicesPrivileges.createFrom(in); + assertEquals(readIndicesPrivileges, indicesPrivileges.build()); + + out = new BytesStreamOutput(); + out.setVersion(Version.V_5_0_0); + indicesPrivileges = RoleDescriptor.IndicesPrivileges.builder(); + indicesPrivileges.grantedFields(allowed); + indicesPrivileges.deniedFields(denied); + indicesPrivileges.query("{match_all:{}}"); + indicesPrivileges.indices(readIndicesPrivileges.getIndices()); + indicesPrivileges.privileges("all", "read", "priv"); + indicesPrivileges.build().writeTo(out); + out.close(); + in = out.bytes().streamInput(); + in.setVersion(Version.V_5_0_0); + RoleDescriptor.IndicesPrivileges readIndicesPrivileges2 = RoleDescriptor.IndicesPrivileges.createFrom(in); + assertEquals(readIndicesPrivileges, readIndicesPrivileges2); } // tests that field permissions are merged correctly when we authorize with several groups and don't crash when an index has no group @@ -115,12 +137,13 @@ public class IndicesPermissionTests extends ESTestCase { .put(new IndexMetaData.Builder("a2").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) .build(); + FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); IndicesPermission.Group group1 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(), null, "a1"); IndicesPermission.Group group2 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(null, new String[]{"denied_field"}), null, "a1"); - IndicesPermission.Core core = new IndicesPermission.Core(group1, group2); + IndicesPermission core = new IndicesPermission(group1, group2); Map authzMap = - core.authorize(SearchAction.NAME, Sets.newHashSet("a1", "ba"), metaData); + core.authorize(SearchAction.NAME, Sets.newHashSet("a1", "ba"), metaData, fieldPermissionsCache); assertTrue(authzMap.get("a1").getFieldPermissions().grantsAccessTo("denied_field")); assertTrue(authzMap.get("a1").getFieldPermissions().grantsAccessTo(randomAsciiOfLength(5))); // did not define anything for ba so we allow all @@ -137,8 +160,8 @@ public class IndicesPermissionTests extends ESTestCase { , new String[]{"denied_field"}), null, "a2"); IndicesPermission.Group group4 = new IndicesPermission.Group(IndexPrivilege.ALL, new FieldPermissions(new String[]{"*_field2"} , new String[]{"denied_field2"}), null, "a2"); - core = new IndicesPermission.Core(group1, group2, group3, group4); - authzMap = core.authorize(SearchAction.NAME, Sets.newHashSet("a1", "a2"), metaData); + core = new IndicesPermission(group1, group2, group3, group4); + authzMap = core.authorize(SearchAction.NAME, Sets.newHashSet("a1", "a2"), metaData, fieldPermissionsCache); assertFalse(authzMap.get("a1").getFieldPermissions().hasFieldLevelSecurity()); assertFalse(authzMap.get("a2").getFieldPermissions().grantsAccessTo("denied_field2")); assertFalse(authzMap.get("a2").getFieldPermissions().grantsAccessTo("denied_field")); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/DefaultRoleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/DefaultRoleTests.java deleted file mode 100644 index 8d0d0797b81..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/DefaultRoleTests.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; -import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; -import org.elasticsearch.client.Client; -import org.elasticsearch.license.GetLicenseAction; -import org.elasticsearch.xpack.security.action.user.AuthenticateRequestBuilder; -import org.elasticsearch.xpack.security.action.user.ChangePasswordRequestBuilder; -import org.elasticsearch.xpack.security.authc.Authentication; -import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; -import org.elasticsearch.xpack.security.authc.esnative.NativeRealm; -import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; -import org.elasticsearch.xpack.security.authc.file.FileRealm; -import org.elasticsearch.xpack.security.authc.ldap.LdapRealm; -import org.elasticsearch.xpack.security.authc.pki.PkiRealm; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.xpack.security.action.user.AuthenticateAction; -import org.elasticsearch.xpack.security.action.user.AuthenticateRequest; -import org.elasticsearch.xpack.security.action.user.ChangePasswordAction; -import org.elasticsearch.xpack.security.action.user.ChangePasswordRequest; -import org.elasticsearch.xpack.security.action.user.DeleteUserAction; -import org.elasticsearch.xpack.security.action.user.PutUserAction; -import org.elasticsearch.xpack.security.action.user.UserRequest; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.transport.TransportRequest; - -import java.util.Iterator; - -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; -import static org.mockito.Mockito.when; - -/** - * Unit tests for the {@link DefaultRole} - */ -public class DefaultRoleTests extends ESTestCase { - - public void testDefaultRoleHasNoIndicesPrivileges() { - Iterator iter = DefaultRole.INSTANCE.indices().iterator(); - assertThat(iter.hasNext(), is(false)); - } - - public void testDefaultRoleHasNoRunAsPrivileges() { - assertThat(DefaultRole.INSTANCE.runAs().isEmpty(), is(true)); - } - - public void testDefaultRoleAllowsUser() { - final User user = new User("joe"); - final boolean changePasswordRequest = randomBoolean(); - final TransportRequest request = changePasswordRequest ? - new ChangePasswordRequestBuilder(mock(Client.class)).username(user.principal()).request() : - new AuthenticateRequestBuilder(mock(Client.class)).username(user.principal()).request(); - final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME; - final Authentication authentication = mock(Authentication.class); - final RealmRef authenticatedBy = mock(RealmRef.class); - when(authentication.getUser()).thenReturn(user); - when(authentication.getRunAsUser()).thenReturn(user); - when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); - when(authenticatedBy.getType()) - .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12)); - - assertThat(request, instanceOf(UserRequest.class)); - assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(true)); - } - - public void testDefaultRoleDoesNotAllowNonMatchingUsername() { - final User user = new User("joe"); - final boolean changePasswordRequest = randomBoolean(); - final String username = randomFrom("", "joe" + randomAsciiOfLengthBetween(1, 5), randomAsciiOfLengthBetween(3, 10)); - final TransportRequest request = changePasswordRequest ? - new ChangePasswordRequestBuilder(mock(Client.class)).username(username).request() : - new AuthenticateRequestBuilder(mock(Client.class)).username(username).request(); - final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME; - final Authentication authentication = mock(Authentication.class); - final RealmRef authenticatedBy = mock(RealmRef.class); - when(authentication.getUser()).thenReturn(user); - when(authentication.getRunAsUser()).thenReturn(user); - when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); - when(authenticatedBy.getType()) - .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12)); - - assertThat(request, instanceOf(UserRequest.class)); - assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false)); - - final User user2 = new User("admin", new String[] { "bar" }, user); - when(authentication.getUser()).thenReturn(user2); - when(authentication.getRunAsUser()).thenReturn(user); - final RealmRef lookedUpBy = mock(RealmRef.class); - when(authentication.getLookedUpBy()).thenReturn(lookedUpBy); - when(lookedUpBy.getType()) - .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12)); - // this should still fail since the username is still different - assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false)); - - if (request instanceof ChangePasswordRequest) { - ((ChangePasswordRequest)request).username("joe"); - } else { - ((AuthenticateRequest)request).username("joe"); - } - assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(true)); - } - - public void testDefaultRoleDoesNotAllowOtherActions() { - final User user = mock(User.class); - final TransportRequest request = mock(TransportRequest.class); - final String action = randomFrom(PutUserAction.NAME, DeleteUserAction.NAME, ClusterHealthAction.NAME, ClusterStateAction.NAME, - ClusterStatsAction.NAME, GetLicenseAction.NAME); - final Authentication authentication = mock(Authentication.class); - final RealmRef authenticatedBy = mock(RealmRef.class); - when(authentication.getUser()).thenReturn(user); - when(authentication.getRunAsUser()).thenReturn(randomBoolean() ? user : new User("runAs")); - when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); - when(authenticatedBy.getType()) - .thenReturn(randomAsciiOfLengthBetween(4, 12)); - - assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false)); - verifyZeroInteractions(user, request, authentication); - } - - public void testDefaultRoleWithRunAsChecksAuthenticatedBy() { - final String username = "joe"; - final User runAs = new User(username); - final User user = new User("admin", new String[] { "bar" }, runAs); - final boolean changePasswordRequest = randomBoolean(); - final TransportRequest request = changePasswordRequest ? - new ChangePasswordRequestBuilder(mock(Client.class)).username(username).request() : - new AuthenticateRequestBuilder(mock(Client.class)).username(username).request(); - final String action = changePasswordRequest ? ChangePasswordAction.NAME : AuthenticateAction.NAME; - final Authentication authentication = mock(Authentication.class); - final RealmRef authenticatedBy = mock(RealmRef.class); - final RealmRef lookedUpBy = mock(RealmRef.class); - when(authentication.getUser()).thenReturn(user); - when(authentication.getRunAsUser()).thenReturn(runAs); - when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); - when(authentication.getLookedUpBy()).thenReturn(lookedUpBy); - when(authentication.isRunAs()).thenReturn(true); - when(lookedUpBy.getType()) - .thenReturn(changePasswordRequest ? randomFrom(ReservedRealm.TYPE, NativeRealm.TYPE) : randomAsciiOfLengthBetween(4, 12)); - - assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(true)); - - when(authentication.getRunAsUser()).thenReturn(user); - assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false)); - } - - public void testDefaultRoleDoesNotAllowChangePasswordForOtherRealms() { - final User user = new User("joe"); - final ChangePasswordRequest request = new ChangePasswordRequestBuilder(mock(Client.class)).username(user.principal()).request(); - final String action = ChangePasswordAction.NAME; - final Authentication authentication = mock(Authentication.class); - final RealmRef authenticatedBy = mock(RealmRef.class); - when(authentication.getUser()).thenReturn(user); - when(authentication.getRunAsUser()).thenReturn(user); - when(authentication.isRunAs()).thenReturn(false); - when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); - when(authenticatedBy.getType()).thenReturn(randomFrom(LdapRealm.LDAP_TYPE, FileRealm.TYPE, LdapRealm.AD_TYPE, PkiRealm.TYPE, - randomAsciiOfLengthBetween(4, 12))); - - assertThat(request, instanceOf(UserRequest.class)); - assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false)); - verify(authenticatedBy).getType(); - verify(authentication).getRunAsUser(); - verify(authentication).getAuthenticatedBy(); - verify(authentication).isRunAs(); - verifyNoMoreInteractions(authenticatedBy, authentication); - } - - public void testDefaultRoleDoesNotAllowChangePasswordForLookedUpByOtherRealms() { - final User runAs = new User("joe"); - final User user = new User("admin", new String[] { "bar" }, runAs); - final ChangePasswordRequest request = new ChangePasswordRequestBuilder(mock(Client.class)).username(runAs.principal()).request(); - final String action = ChangePasswordAction.NAME; - final Authentication authentication = mock(Authentication.class); - final RealmRef authenticatedBy = mock(RealmRef.class); - final RealmRef lookedUpBy = mock(RealmRef.class); - when(authentication.getUser()).thenReturn(user); - when(authentication.getRunAsUser()).thenReturn(runAs); - when(authentication.isRunAs()).thenReturn(true); - when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); - when(authentication.getLookedUpBy()).thenReturn(lookedUpBy); - when(lookedUpBy.getType()).thenReturn(randomFrom(LdapRealm.LDAP_TYPE, FileRealm.TYPE, LdapRealm.AD_TYPE, PkiRealm.TYPE, - randomAsciiOfLengthBetween(4, 12))); - - assertThat(request, instanceOf(UserRequest.class)); - assertThat(DefaultRole.INSTANCE.cluster().check(action, request, authentication), is(false)); - verify(authentication).getLookedUpBy(); - verify(authentication).getRunAsUser(); - verify(authentication).isRunAs(); - verify(lookedUpBy).getType(); - verifyNoMoreInteractions(authentication, lookedUpBy, authenticatedBy); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionTests.java index 2e35d8e49ce..2e5d8cc07e4 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionTests.java @@ -29,9 +29,8 @@ public class FieldPermissionTests extends ESTestCase { "\"except\": [\"f3\",\"f4\"]" + "}}]}"; RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(q), false); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), - new String[]{"f1", "f2", "f3", "f4"}); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray(), new String[]{"f3", "f4"}); + assertArrayEquals(rd.getIndicesPrivileges()[0].getGrantedFields(), new String[] { "f1", "f2", "f3", "f4" }); + assertArrayEquals(rd.getIndicesPrivileges()[0].getDeniedFields(), new String[] { "f3", "f4" }); q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + "\"field_security\": {" + @@ -39,25 +38,24 @@ public class FieldPermissionTests extends ESTestCase { "\"grant\": [\"f1\", \"f2\", \"f3\", \"f4\"]" + "}}]}"; rd = RoleDescriptor.parse("test", new BytesArray(q), false); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), - new String[]{"f1", "f2", "f3", "f4"}); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray(), new String[]{"f3", "f4"}); + assertArrayEquals(rd.getIndicesPrivileges()[0].getGrantedFields(), new String[] { "f1", "f2", "f3", "f4" }); + assertArrayEquals(rd.getIndicesPrivileges()[0].getDeniedFields(), new String[] { "f3", "f4" }); q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + "\"field_security\": {" + "\"grant\": [\"f1\", \"f2\"]" + "}}]}"; rd = RoleDescriptor.parse("test", new BytesArray(q), false); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{"f1", "f2"}); - assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + assertArrayEquals(rd.getIndicesPrivileges()[0].getGrantedFields(), new String[] { "f1", "f2" }); + assertNull(rd.getIndicesPrivileges()[0].getDeniedFields()); q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + "\"field_security\": {" + "\"grant\": []" + "}}]}"; rd = RoleDescriptor.parse("test", new BytesArray(q), false); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{}); - assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + assertArrayEquals(rd.getIndicesPrivileges()[0].getGrantedFields(), new String[] {}); + assertNull(rd.getIndicesPrivileges()[0].getDeniedFields()); q = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + "\"field_security\": {" + @@ -65,8 +63,8 @@ public class FieldPermissionTests extends ESTestCase { "\"grant\": []" + "}}]}"; rd = RoleDescriptor.parse("test", new BytesArray(q), false); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{}); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray(), new String[]{}); + assertArrayEquals(rd.getIndicesPrivileges()[0].getGrantedFields(), new String[] {}); + assertArrayEquals(rd.getIndicesPrivileges()[0].getDeniedFields(), new String[] {}); final String exceptWithoutGrant = "{\"indices\": [ {\"names\": \"idx2\", \"privileges\": [\"p3\"], " + "\"field_security\": {" + @@ -122,10 +120,10 @@ public class FieldPermissionTests extends ESTestCase { " \"except\": [\"f2\"]}," + "\"privileges\": [\"p3\"]}]}"; rd = RoleDescriptor.parse("test", new BytesArray(q), false); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{}); - assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); - assertArrayEquals(rd.getIndicesPrivileges()[1].getFieldPermissions().getGrantedFieldsArray(), new String[]{"*"}); - assertArrayEquals(rd.getIndicesPrivileges()[1].getFieldPermissions().getDeniedFieldsArray(), new String[]{"f2"}); + assertArrayEquals(rd.getIndicesPrivileges()[0].getGrantedFields(), new String[] {}); + assertNull(rd.getIndicesPrivileges()[0].getDeniedFields()); + assertArrayEquals(rd.getIndicesPrivileges()[1].getGrantedFields(), new String[] {"*"}); + assertArrayEquals(rd.getIndicesPrivileges()[1].getDeniedFields(), new String[] {"f2"}); } // test old syntax for field permissions @@ -134,8 +132,8 @@ public class FieldPermissionTests extends ESTestCase { "\"fields\": [\"f1\", \"f2\"]" + "}]}"; RoleDescriptor rd = RoleDescriptor.parse("test", new BytesArray(q), true); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{"f1", "f2"}); - assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + assertArrayEquals(rd.getIndicesPrivileges()[0].getGrantedFields(), new String[]{"f1", "f2"}); + assertNull(rd.getIndicesPrivileges()[0].getDeniedFields()); final String failingQuery = q; ElasticsearchParseException e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray @@ -147,8 +145,8 @@ public class FieldPermissionTests extends ESTestCase { "\"fields\": []" + "}]}"; rd = RoleDescriptor.parse("test", new BytesArray(q), true); - assertArrayEquals(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray(), new String[]{}); - assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + assertArrayEquals(rd.getIndicesPrivileges()[0].getGrantedFields(), new String[]{}); + assertNull(rd.getIndicesPrivileges()[0].getDeniedFields()); final String failingQuery2 = q; e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray (failingQuery2), false)); @@ -159,76 +157,14 @@ public class FieldPermissionTests extends ESTestCase { "\"fields\": null" + "}]}"; rd = RoleDescriptor.parse("test", new BytesArray(q), true); - assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getGrantedFieldsArray()); - assertNull(rd.getIndicesPrivileges()[0].getFieldPermissions().getDeniedFieldsArray()); + assertNull(rd.getIndicesPrivileges()[0].getGrantedFields()); + assertNull(rd.getIndicesPrivileges()[0].getDeniedFields()); final String failingQuery3 = q; e = expectThrows(ElasticsearchParseException.class, () -> RoleDescriptor.parse("test", new BytesArray(failingQuery3), false)); assertThat(e.getDetailedMessage(), containsString("[\"fields\": [...]] format has changed for field permissions in role [test]" + ", use [\"field_security\": {\"grant\":[...],\"except\":[...]}] instead")); } - public void testMergeFieldPermissions() { - String allowedPrefix1 = randomAsciiOfLength(5); - String allowedPrefix2 = randomAsciiOfLength(5); - String[] allowed1 = new String[]{allowedPrefix1 + "*"}; - String[] allowed2 = new String[]{allowedPrefix2 + "*"}; - String[] denied1 = new String[]{allowedPrefix1 + "a"}; - String[] denied2 = new String[]{allowedPrefix2 + "a"}; - FieldPermissions fieldPermissions1 = new FieldPermissions(allowed1, denied1); - FieldPermissions fieldPermissions2 = new FieldPermissions(allowed2, denied2); - FieldPermissions mergedFieldPermissions = FieldPermissions.merge(fieldPermissions1, fieldPermissions2); - assertTrue(mergedFieldPermissions.grantsAccessTo(allowedPrefix1 + "b")); - assertTrue(mergedFieldPermissions.grantsAccessTo(allowedPrefix2 + "b")); - assertFalse(mergedFieldPermissions.grantsAccessTo(denied1[0])); - assertFalse(mergedFieldPermissions.grantsAccessTo(denied2[0])); - - allowed1 = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; - allowed2 = null; - denied1 = new String[]{allowed1[0] + "a", allowed1[1] + "a"}; - denied2 = null; - fieldPermissions1 = new FieldPermissions(allowed1, denied1); - fieldPermissions2 = new FieldPermissions(allowed2, denied2); - mergedFieldPermissions = FieldPermissions.merge(fieldPermissions1, fieldPermissions2); - assertFalse(mergedFieldPermissions.hasFieldLevelSecurity()); - - allowed1 = new String[]{}; - allowed2 = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; - denied1 = new String[]{}; - denied2 = new String[]{allowed2[0] + "a", allowed2[1] + "a"}; - fieldPermissions1 = new FieldPermissions(allowed1, denied1); - fieldPermissions2 = new FieldPermissions(allowed2, denied2); - mergedFieldPermissions = FieldPermissions.merge(fieldPermissions1, fieldPermissions2); - for (String field : allowed2) { - assertTrue(mergedFieldPermissions.grantsAccessTo(field)); - } - for (String field : denied2) { - assertFalse(mergedFieldPermissions.grantsAccessTo(field)); - } - - allowed1 = randomBoolean() ? null : new String[]{"*"}; - allowed2 = randomBoolean() ? null : new String[]{"*"}; - denied1 = new String[]{"a"}; - denied2 = new String[]{"b"}; - fieldPermissions1 = new FieldPermissions(allowed1, denied1); - fieldPermissions2 = new FieldPermissions(allowed2, denied2); - mergedFieldPermissions = FieldPermissions.merge(fieldPermissions1, fieldPermissions2); - assertTrue(mergedFieldPermissions.grantsAccessTo("a")); - assertTrue(mergedFieldPermissions.grantsAccessTo("b")); - - // test merge does not remove _all - allowed1 = new String[]{"_all"}; - allowed2 = new String[]{}; - denied1 = null; - denied2 = null; - fieldPermissions1 = new FieldPermissions(allowed1, denied1); - assertTrue(fieldPermissions1.allFieldIsAllowed); - fieldPermissions2 = new FieldPermissions(allowed2, denied2); - assertFalse(fieldPermissions2.allFieldIsAllowed); - mergedFieldPermissions = FieldPermissions.merge(fieldPermissions1, fieldPermissions2); - assertTrue(mergedFieldPermissions.grantsAccessTo("_all")); - assertTrue(mergedFieldPermissions.allFieldIsAllowed); - } - public void testFieldPermissionsStreaming() throws IOException { BytesStreamOutput out = new BytesStreamOutput(); String[] allowed = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; @@ -245,10 +181,7 @@ public class FieldPermissionTests extends ESTestCase { public void testFieldPermissionsHashCodeThreadSafe() throws Exception { final int numThreads = scaledRandomIntBetween(4, 16); - final FieldPermissions fieldPermissions = randomBoolean() ? - new FieldPermissions(new String[] { "*" }, new String[] { "foo" }) : - FieldPermissions.merge(new FieldPermissions(new String[] { "f*" }, new String[] { "foo" }), - new FieldPermissions(new String[] { "b*" }, new String[] { "bar" })); + final FieldPermissions fieldPermissions = new FieldPermissions(new String[] { "*" }, new String[] { "foo" }); final CountDownLatch latch = new CountDownLatch(numThreads + 1); final AtomicReferenceArray hashCodes = new AtomicReferenceArray<>(numThreads); List threads = new ArrayList<>(numThreads); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionsCacheTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionsCacheTests.java new file mode 100644 index 00000000000..9b32ffe8340 --- /dev/null +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/FieldPermissionsCacheTests.java @@ -0,0 +1,120 @@ +/* + * 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.authz.permission; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; + +import java.util.Arrays; + +public class FieldPermissionsCacheTests extends ESTestCase { + + public void testFieldPermissionsCaching() { + FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); + String[] allowed = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; + String[] denied = new String[]{allowed[0] + randomAsciiOfLength(5), allowed[1] + randomAsciiOfLength(5), + allowed[2] + randomAsciiOfLength(5)}; + FieldPermissions fieldPermissions = fieldPermissionsCache.getFieldPermissions(allowed, denied); + assertNotNull(fieldPermissions); + final String[] allowed2 = randomBoolean() ? allowed : Arrays.copyOf(allowed, allowed.length); + final String[] denied2 = randomBoolean() ? denied : Arrays.copyOf(denied, denied.length); + assertSame(fieldPermissions, fieldPermissionsCache.getFieldPermissions(allowed2, denied2)); + } + + public void testMergeFieldPermissions() { + FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); + String allowedPrefix1 = randomAsciiOfLength(5); + String allowedPrefix2 = randomAsciiOfLength(5); + String[] allowed1 = new String[]{allowedPrefix1 + "*"}; + String[] allowed2 = new String[]{allowedPrefix2 + "*"}; + String[] denied1 = new String[]{allowedPrefix1 + "a"}; + String[] denied2 = new String[]{allowedPrefix2 + "a"}; + FieldPermissions fieldPermissions1 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed1, denied1) : + new FieldPermissions(allowed1, denied1); + FieldPermissions fieldPermissions2 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed2, denied2) : + new FieldPermissions(allowed2, denied2); + FieldPermissions mergedFieldPermissions = + fieldPermissionsCache.getFieldPermissions(Arrays.asList(fieldPermissions1, fieldPermissions2)); + assertTrue(mergedFieldPermissions.grantsAccessTo(allowedPrefix1 + "b")); + assertTrue(mergedFieldPermissions.grantsAccessTo(allowedPrefix2 + "b")); + assertFalse(mergedFieldPermissions.grantsAccessTo(denied1[0])); + assertFalse(mergedFieldPermissions.grantsAccessTo(denied2[0])); + + allowed1 = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; + allowed2 = null; + denied1 = new String[]{allowed1[0] + "a", allowed1[1] + "a"}; + denied2 = null; + fieldPermissions1 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed1, denied1) : + new FieldPermissions(allowed1, denied1); + fieldPermissions2 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed2, denied2) : + new FieldPermissions(allowed2, denied2); + mergedFieldPermissions = + fieldPermissionsCache.getFieldPermissions(Arrays.asList(fieldPermissions1, fieldPermissions2)); + assertFalse(mergedFieldPermissions.hasFieldLevelSecurity()); + + allowed1 = new String[]{}; + allowed2 = new String[]{randomAsciiOfLength(5) + "*", randomAsciiOfLength(5) + "*"}; + denied1 = new String[]{}; + denied2 = new String[]{allowed2[0] + "a", allowed2[1] + "a"}; + fieldPermissions1 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed1, denied1) : + new FieldPermissions(allowed1, denied1); + fieldPermissions2 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed2, denied2) : + new FieldPermissions(allowed2, denied2); + mergedFieldPermissions = + fieldPermissionsCache.getFieldPermissions(Arrays.asList(fieldPermissions1, fieldPermissions2)); + for (String field : allowed2) { + assertTrue(mergedFieldPermissions.grantsAccessTo(field)); + } + for (String field : denied2) { + assertFalse(mergedFieldPermissions.grantsAccessTo(field)); + } + + allowed1 = randomBoolean() ? null : new String[]{"*"}; + allowed2 = randomBoolean() ? null : new String[]{"*"}; + denied1 = new String[]{"a"}; + denied2 = new String[]{"b"}; + fieldPermissions1 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed1, denied1) : + new FieldPermissions(allowed1, denied1); + fieldPermissions2 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed2, denied2) : + new FieldPermissions(allowed2, denied2); + mergedFieldPermissions = + fieldPermissionsCache.getFieldPermissions(Arrays.asList(fieldPermissions1, fieldPermissions2)); + assertTrue(mergedFieldPermissions.grantsAccessTo("a")); + assertTrue(mergedFieldPermissions.grantsAccessTo("b")); + + // test merge does not remove _all + allowed1 = new String[]{"_all"}; + allowed2 = new String[]{}; + denied1 = null; + denied2 = null; + fieldPermissions1 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed1, denied1) : + new FieldPermissions(allowed1, denied1); + fieldPermissions2 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed2, denied2) : + new FieldPermissions(allowed2, denied2); + mergedFieldPermissions = + fieldPermissionsCache.getFieldPermissions(Arrays.asList(fieldPermissions1, fieldPermissions2)); + assertTrue(fieldPermissions1.isAllFieldIsAllowed()); + assertFalse(fieldPermissions2.isAllFieldIsAllowed()); + assertTrue(mergedFieldPermissions.grantsAccessTo("_all")); + assertTrue(mergedFieldPermissions.isAllFieldIsAllowed()); + + allowed1 = new String[] { "a*" }; + allowed2 = new String[] { "b*" }; + denied1 = new String[] { "aa*" }; + denied2 = null; + fieldPermissions1 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed1, denied1) : + new FieldPermissions(allowed1, denied1); + fieldPermissions2 = randomBoolean() ? fieldPermissionsCache.getFieldPermissions(allowed2, denied2) : + new FieldPermissions(allowed2, denied2); + mergedFieldPermissions = + fieldPermissionsCache.getFieldPermissions(Arrays.asList(fieldPermissions1, fieldPermissions2)); + assertTrue(mergedFieldPermissions.grantsAccessTo("a")); + assertTrue(mergedFieldPermissions.grantsAccessTo("b")); + assertFalse(mergedFieldPermissions.grantsAccessTo("aa")); + assertFalse(mergedFieldPermissions.grantsAccessTo("aa1")); + assertTrue(mergedFieldPermissions.grantsAccessTo("a1")); + } +} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/IngestAdminRoleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/IngestAdminRoleTests.java deleted file mode 100644 index efb4d61ae84..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/IngestAdminRoleTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; -import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; -import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateAction; -import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesAction; -import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; -import org.elasticsearch.action.get.GetAction; -import org.elasticsearch.action.index.IndexAction; -import org.elasticsearch.action.ingest.DeletePipelineAction; -import org.elasticsearch.action.ingest.GetPipelineAction; -import org.elasticsearch.action.ingest.PutPipelineAction; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; -import org.elasticsearch.xpack.security.authc.Authentication; - -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; - -public class IngestAdminRoleTests extends ESTestCase { - - public void testClusterPermissions() { - final TransportRequest request = new TransportRequest.Empty(); - final Authentication authentication = mock(Authentication.class); - assertThat(IngestAdminRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); - assertThat(IngestAdminRole.INSTANCE.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true)); - assertThat(IngestAdminRole.INSTANCE.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(true)); - assertThat(IngestAdminRole.INSTANCE.cluster().check(PutPipelineAction.NAME, request, authentication), is(true)); - assertThat(IngestAdminRole.INSTANCE.cluster().check(GetPipelineAction.NAME, request, authentication), is(true)); - assertThat(IngestAdminRole.INSTANCE.cluster().check(DeletePipelineAction.NAME, request, authentication), is(true)); - - - assertThat(IngestAdminRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); - assertThat(IngestAdminRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); - assertThat(IngestAdminRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); - } - - public void testNoIndicesPermissions() { - assertThat(IngestAdminRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); - assertThat(IngestAdminRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo").test(randomAsciiOfLengthBetween(8, 24)), - is(false)); - assertThat(IngestAdminRole.INSTANCE.indices().allowedIndicesMatcher(GetAction.NAME).test(randomAsciiOfLengthBetween(8, 24)), - is(false)); - } - - public void testHasReservedMetadata() { - assertThat(IngestAdminRole.DESCRIPTOR.getMetadata(), hasEntry("_reserved", true)); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/KibanaRoleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/KibanaRoleTests.java deleted file mode 100644 index 451257a36f6..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/KibanaRoleTests.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; -import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; -import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; -import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; -import org.elasticsearch.action.admin.indices.create.CreateIndexAction; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; -import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction; -import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; -import org.elasticsearch.action.delete.DeleteAction; -import org.elasticsearch.action.index.IndexAction; -import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; -import org.elasticsearch.xpack.security.authc.Authentication; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.transport.TransportRequest; - -import java.util.Arrays; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; - -/** - * Tests for the kibana role - */ -public class KibanaRoleTests extends ESTestCase { - - public void testCluster() { - final TransportRequest request = new TransportRequest.Empty(); - final Authentication authentication = mock(Authentication.class); - assertThat(KibanaRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); - assertThat(KibanaRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); - assertThat(KibanaRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); - assertThat(KibanaRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); - assertThat(KibanaRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); - assertThat(KibanaRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); - assertThat(KibanaRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); - } - - public void testRunAs() { - assertThat(KibanaRole.INSTANCE.runAs().isEmpty(), is(true)); - } - - public void testUnauthorizedIndices() { - assertThat(KibanaRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); - assertThat(KibanaRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false)); - assertThat(KibanaRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo").test(randomAsciiOfLengthBetween(8, 24)), is(false)); - } - - public void testKibanaIndices() { - Arrays.asList(".kibana", ".kibana-devnull").forEach(this::testAllIndexAccess); - } - - public void testReportingIndices() { - testAllIndexAccess(".reporting-" + randomAsciiOfLength(randomIntBetween(0, 13))); - } - - private void testAllIndexAccess(String index) { - assertThat(KibanaRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo").test(index), is(true)); - assertThat(KibanaRole.INSTANCE.indices().allowedIndicesMatcher("indices:bar").test(index), is(true)); - assertThat(KibanaRole.INSTANCE.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(true)); - assertThat(KibanaRole.INSTANCE.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(true)); - assertThat(KibanaRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(true)); - assertThat(KibanaRole.INSTANCE.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(true)); - assertThat(KibanaRole.INSTANCE.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(true)); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/KibanaUserRoleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/KibanaUserRoleTests.java deleted file mode 100644 index 36ba19047e6..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/KibanaUserRoleTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; -import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; -import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; -import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; -import org.elasticsearch.action.admin.indices.create.CreateIndexAction; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; -import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction; -import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; -import org.elasticsearch.action.delete.DeleteAction; -import org.elasticsearch.action.index.IndexAction; -import org.elasticsearch.action.search.MultiSearchAction; -import org.elasticsearch.action.search.SearchAction; -import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; -import org.elasticsearch.xpack.security.authc.Authentication; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.transport.TransportRequest; - -import java.util.Arrays; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; - -public class KibanaUserRoleTests extends ESTestCase { - - public void testCluster() { - final Authentication authentication = mock(Authentication.class); - final TransportRequest request = new TransportRequest.Empty(); - assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); - assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); - assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); - assertThat(KibanaUserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); - assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); - assertThat(KibanaUserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); - assertThat(KibanaUserRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); - } - - public void testRunAs() { - assertThat(KibanaUserRole.INSTANCE.runAs().isEmpty(), is(true)); - } - - public void testUnauthorizedIndices() { - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false)); - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo") - .test(randomAsciiOfLengthBetween(8, 24)), is(false)); - } - - public void testKibanaIndices() { - Arrays.asList(".kibana", ".kibana-devnull").forEach(this::testIndexAccess); - } - - private void testIndexAccess(String index) { - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo").test(index), is(false)); - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher("indices:bar").test(index), is(false)); - - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(true)); - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(true)); - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(true)); - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(true)); - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true)); - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(index), is(true)); - assertThat(KibanaUserRole.INSTANCE.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(true)); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/LogstashSystemRoleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/LogstashSystemRoleTests.java deleted file mode 100644 index 5e14b5aed66..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/LogstashSystemRoleTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.authz.permission; - -import java.util.Arrays; - -import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; -import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; -import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; -import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; -import org.elasticsearch.action.admin.indices.create.CreateIndexAction; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; -import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction; -import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; -import org.elasticsearch.action.delete.DeleteAction; -import org.elasticsearch.action.index.IndexAction; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; -import org.elasticsearch.xpack.security.authc.Authentication; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; - -/** - * Tests for the logstash_system role - */ -public class LogstashSystemRoleTests extends ESTestCase { - - public void testCluster() { - final TransportRequest request = new TransportRequest.Empty(); - final Authentication authentication = mock(Authentication.class); - assertThat(LogstashSystemRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); - assertThat(LogstashSystemRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); - assertThat(LogstashSystemRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); - assertThat(LogstashSystemRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); - assertThat(LogstashSystemRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); - assertThat(LogstashSystemRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); - assertThat(LogstashSystemRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); - } - - public void testRunAs() { - assertThat(LogstashSystemRole.INSTANCE.runAs().isEmpty(), is(true)); - } - - public void testUnauthorizedIndices() { - assertThat(LogstashSystemRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); - assertThat(LogstashSystemRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false)); - assertThat(LogstashSystemRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo").test(randomAsciiOfLengthBetween(8, 24)), - is(false)); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/MonitoringUserRoleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/MonitoringUserRoleTests.java deleted file mode 100644 index d721eb8678b..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/MonitoringUserRoleTests.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; -import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; -import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; -import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; -import org.elasticsearch.action.admin.indices.create.CreateIndexAction; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; -import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction; -import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; -import org.elasticsearch.action.delete.DeleteAction; -import org.elasticsearch.action.get.GetAction; -import org.elasticsearch.action.index.IndexAction; -import org.elasticsearch.action.search.SearchAction; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; -import org.elasticsearch.xpack.security.authc.Authentication; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; - -/** - * Tests for the builtin monitoring user - */ -public class MonitoringUserRoleTests extends ESTestCase { - - public void testCluster() { - final TransportRequest request = new TransportRequest.Empty(); - final Authentication authentication = mock(Authentication.class); - assertThat(MonitoringUserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); - assertThat(MonitoringUserRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); - assertThat(MonitoringUserRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); - assertThat(MonitoringUserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); - assertThat(MonitoringUserRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); - assertThat(MonitoringUserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); - assertThat(MonitoringUserRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); - } - - public void testRunAs() { - assertThat(MonitoringUserRole.INSTANCE.runAs().isEmpty(), is(true)); - } - - public void testUnauthorizedIndices() { - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test("foo"), is(false)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test(".reporting"), is(false)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test(".kibana"), is(false)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo").test(randomAsciiOfLengthBetween(8, 24)), - is(false)); - } - - public void testMonitoringIndices() { - testReadAccess(".monitoring-" + randomAsciiOfLength(randomIntBetween(0, 13))); - testReadAccess(".marvel-es-" + randomAsciiOfLength(randomIntBetween(0, 13))); - } - - private void testReadAccess(String index) { - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo").test(index), is(false)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher("indices:bar").test(index), is(false)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(false)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(false)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(false)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(false)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(false)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true)); - assertThat(MonitoringUserRole.INSTANCE.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true)); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/PermissionTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/PermissionTests.java index f940af3fc48..f2f9bda5e8f 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/PermissionTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/PermissionTests.java @@ -6,20 +6,14 @@ package org.elasticsearch.xpack.security.authz.permission; import org.elasticsearch.action.get.GetAction; -import org.elasticsearch.xpack.security.authz.privilege.ClusterPrivilege; -import org.elasticsearch.xpack.security.authz.privilege.GeneralPrivilege; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.security.authz.privilege.Privilege; import org.junit.Before; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; import java.util.function.Predicate; import static org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege.MONITOR; import static org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege.READ; -import static org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege.union; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -29,9 +23,9 @@ public class PermissionTests extends ESTestCase { @Before public void init() { Role.Builder builder = Role.builder("test"); - builder.add(union(MONITOR), "test_*", "/foo.*/"); - builder.add(union(READ), "baz_*foo", "/fool.*bar/"); - builder.add(union(MONITOR), "/bar.*/"); + builder.add(MONITOR, "test_*", "/foo.*/"); + builder.add(READ, "baz_*foo", "/fool.*bar/"); + builder.add(MONITOR, "/bar.*/"); permission = builder.build(); } @@ -45,23 +39,6 @@ public class PermissionTests extends ESTestCase { assertThat(matcher1, is(matcher2)); } - public void testIndicesGlobalsIterator() { - Role.Builder builder = Role.builder("tc_role"); - builder.cluster(ClusterPrivilege.action("cluster:monitor/nodes/info")); - Role noIndicesPermission = builder.build(); - - IndicesPermission.Globals indicesGlobals = new IndicesPermission.Globals( - Collections.unmodifiableList(Arrays.asList(noIndicesPermission, permission))); - Iterator iterator = indicesGlobals.iterator(); - assertThat(iterator.hasNext(), is(equalTo(true))); - int count = 0; - while (iterator.hasNext()) { - iterator.next(); - count++; - } - assertThat(count, is(equalTo(permission.indices().groups().length))); - } - public void testBuildEmptyRole() { Role.Builder permission = Role.builder("some_role"); Role role = permission.build(); @@ -73,7 +50,7 @@ public class PermissionTests extends ESTestCase { public void testRunAs() { Role permission = Role.builder("some_role") - .runAs(new GeneralPrivilege("name", "user1", "run*")) + .runAs(new Privilege("name", "user1", "run*")) .build(); assertThat(permission.runAs().check("user1"), is(true)); assertThat(permission.runAs().check("user"), is(false)); diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/RemoteMonitoringAgentRoleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/RemoteMonitoringAgentRoleTests.java deleted file mode 100644 index a450e972212..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/RemoteMonitoringAgentRoleTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; -import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; -import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; -import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; -import org.elasticsearch.action.admin.indices.create.CreateIndexAction; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; -import org.elasticsearch.action.admin.indices.get.GetIndexAction; -import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction; -import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; -import org.elasticsearch.action.delete.DeleteAction; -import org.elasticsearch.action.get.GetAction; -import org.elasticsearch.action.index.IndexAction; -import org.elasticsearch.action.search.SearchAction; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; -import org.elasticsearch.xpack.security.authc.Authentication; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; - -/** - * Tests for the remote monitoring agent role - */ -public class RemoteMonitoringAgentRoleTests extends ESTestCase { - - public void testCluster() { - final TransportRequest request = new TransportRequest.Empty(); - final Authentication authentication = mock(Authentication.class); - assertThat(RemoteMonitoringAgentRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), - is(false)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); - } - - public void testRunAs() { - assertThat(RemoteMonitoringAgentRole.INSTANCE.runAs().isEmpty(), is(true)); - } - - public void testUnauthorizedIndices() { - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test("foo"), is(false)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test(".reporting"), is(false)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test(".kibana"), is(false)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo") - .test(randomAsciiOfLengthBetween(8, 24)), is(false)); - } - - public void testKibanaIndices() { - testAllIndexAccess(".monitoring-" + randomAsciiOfLength(randomIntBetween(0, 13))); - testAllIndexAccess(".marvel-es-" + randomAsciiOfLength(randomIntBetween(0, 13))); - } - - private void testAllIndexAccess(String index) { - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo").test(index), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher("indices:bar").test(index), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true)); - assertThat(RemoteMonitoringAgentRole.INSTANCE.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(index), is(true)); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/ReportingUserRoleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/ReportingUserRoleTests.java deleted file mode 100644 index 34544b51a45..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/ReportingUserRoleTests.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; -import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; -import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; -import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; -import org.elasticsearch.action.admin.indices.create.CreateIndexAction; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; -import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction; -import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; -import org.elasticsearch.action.bulk.BulkAction; -import org.elasticsearch.action.delete.DeleteAction; -import org.elasticsearch.action.get.GetAction; -import org.elasticsearch.action.index.IndexAction; -import org.elasticsearch.action.search.SearchAction; -import org.elasticsearch.action.update.UpdateAction; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.transport.TransportRequest; -import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; -import org.elasticsearch.xpack.security.authc.Authentication; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; - -/** - * Unit tests for the built in reporting user role - */ -public class ReportingUserRoleTests extends ESTestCase { - - public void testCluster() { - final TransportRequest request = new TransportRequest.Empty(); - final Authentication authentication = mock(Authentication.class); - assertThat(ReportingUserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); - assertThat(ReportingUserRole.INSTANCE.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); - assertThat(ReportingUserRole.INSTANCE.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); - assertThat(ReportingUserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); - assertThat(ReportingUserRole.INSTANCE.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); - assertThat(ReportingUserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); - assertThat(ReportingUserRole.INSTANCE.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); - } - - public void testRunAs() { - assertThat(ReportingUserRole.INSTANCE.runAs().isEmpty(), is(true)); - } - - public void testUnauthorizedIndices() { - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test("foo"), is(false)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test(".reporting"), is(false)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test(".kibana"), is(false)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo").test(randomAsciiOfLengthBetween(8, 24)), - is(false)); - } - - public void testReadWriteAccess() { - final String index = ".reporting-" + randomAsciiOfLength(randomIntBetween(0, 13)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher("indices:foo").test(index), is(false)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher("indices:bar").test(index), is(false)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(false)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(false)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(false)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(true)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(UpdateAction.NAME).test(index), is(true)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(true)); - assertThat(ReportingUserRole.INSTANCE.indices().allowedIndicesMatcher(BulkAction.NAME).test(index), is(true)); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRoleTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRoleTests.java deleted file mode 100644 index 3545fc12d9d..00000000000 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/permission/SuperuserRoleTests.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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.authz.permission; - -import org.elasticsearch.Version; -import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; -import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; -import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction; -import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; -import org.elasticsearch.action.index.IndexAction; -import org.elasticsearch.action.search.SearchAction; -import org.elasticsearch.cluster.metadata.AliasMetaData; -import org.elasticsearch.cluster.metadata.IndexMetaData; -import org.elasticsearch.cluster.metadata.MetaData; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.util.set.Sets; -import org.elasticsearch.xpack.security.action.role.PutRoleAction; -import org.elasticsearch.xpack.security.action.user.PutUserAction; -import org.elasticsearch.xpack.security.authc.Authentication; -import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl; -import org.elasticsearch.xpack.security.user.User; -import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.transport.TransportRequest; - -import java.util.Map; - -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Tests for the superuser role - */ -public class SuperuserRoleTests extends ESTestCase { - - public void testCluster() { - final User user = new User("joe", SuperuserRole.NAME); - final Authentication authentication = mock(Authentication.class); - when(authentication.getUser()).thenReturn(user); - final TransportRequest request = new TransportRequest.Empty(); - - assertThat(SuperuserRole.INSTANCE.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); - assertThat(SuperuserRole.INSTANCE.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(true)); - assertThat(SuperuserRole.INSTANCE.cluster().check(PutUserAction.NAME, request, authentication), is(true)); - assertThat(SuperuserRole.INSTANCE.cluster().check(PutRoleAction.NAME, request, authentication), is(true)); - assertThat(SuperuserRole.INSTANCE.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); - assertThat(SuperuserRole.INSTANCE.cluster().check("internal:admin/foo", request, authentication), is(false)); - } - - public void testIndices() { - final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); - final MetaData metaData = new MetaData.Builder() - .put(new IndexMetaData.Builder("a1").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) - .put(new IndexMetaData.Builder("a2").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) - .put(new IndexMetaData.Builder("aaaaaa").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) - .put(new IndexMetaData.Builder("bbbbb").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) - .put(new IndexMetaData.Builder("b") - .settings(indexSettings) - .numberOfShards(1) - .numberOfReplicas(0) - .putAlias(new AliasMetaData.Builder("ab").build()) - .putAlias(new AliasMetaData.Builder("ba").build()) - .build(), true) - .build(); - - Map authzMap = - SuperuserRole.INSTANCE.indices().authorize(SearchAction.NAME, Sets.newHashSet("a1", "ba"), metaData); - assertThat(authzMap.get("a1").isGranted(), is(true)); - assertThat(authzMap.get("b").isGranted(), is(true)); - authzMap = SuperuserRole.INSTANCE.indices().authorize(DeleteIndexAction.NAME, Sets.newHashSet("a1", "ba"), metaData); - assertThat(authzMap.get("a1").isGranted(), is(true)); - assertThat(authzMap.get("b").isGranted(), is(true)); - authzMap = SuperuserRole.INSTANCE.indices().authorize(IndexAction.NAME, Sets.newHashSet("a2", "ba"), metaData); - assertThat(authzMap.get("a2").isGranted(), is(true)); - assertThat(authzMap.get("b").isGranted(), is(true)); - authzMap = SuperuserRole.INSTANCE.indices().authorize(UpdateSettingsAction.NAME, Sets.newHashSet("aaaaaa", "ba"), metaData); - assertThat(authzMap.get("aaaaaa").isGranted(), is(true)); - assertThat(authzMap.get("b").isGranted(), is(true)); - assertTrue(SuperuserRole.INSTANCE.indices().check(SearchAction.NAME)); - assertFalse(SuperuserRole.INSTANCE.indices().check("unknown")); - } - - public void testRunAs() { - assertThat(SuperuserRole.INSTANCE.runAs().check(randomAsciiOfLengthBetween(1, 30)), is(true)); - } -} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/privilege/PrivilegeTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/privilege/PrivilegeTests.java index a8e48882f3b..d3bedd7d62c 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/privilege/PrivilegeTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/privilege/PrivilegeTests.java @@ -5,20 +5,16 @@ */ package org.elasticsearch.xpack.security.authz.privilege; -import org.elasticsearch.action.ingest.DeletePipelineAction; -import org.elasticsearch.action.ingest.GetPipelineAction; -import org.elasticsearch.action.ingest.PutPipelineAction; -import org.elasticsearch.action.ingest.SimulatePipelineAction; -import org.elasticsearch.xpack.security.support.AutomatonPredicate; +import org.apache.lucene.util.automaton.Operations; +import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.xpack.security.support.Automatons; import org.elasticsearch.test.ESTestCase; import org.junit.Rule; import org.junit.rules.ExpectedException; +import java.util.Set; import java.util.function.Predicate; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -26,24 +22,8 @@ public class PrivilegeTests extends ESTestCase { @Rule public ExpectedException thrown = ExpectedException.none(); - public void testName() throws Exception { - Privilege.Name name12 = new Privilege.Name("name1", "name2"); - Privilege.Name name34 = new Privilege.Name("name3", "name4"); - Privilege.Name name1234 = randomBoolean() ? name12.add(name34) : name34.add(name12); - assertThat(name1234, equalTo(new Privilege.Name("name1", "name2", "name3", "name4"))); - - Privilege.Name name1 = name12.remove(new Privilege.Name("name2")); - assertThat(name1, equalTo(new Privilege.Name("name1"))); - - Privilege.Name name = name1.remove(new Privilege.Name("name1")); - assertThat(name, is(Privilege.Name.NONE)); - - Privilege.Name none = new Privilege.Name("name1", "name2", "none").remove(name12); - assertThat(none, is(Privilege.Name.NONE)); - } - public void testSubActionPattern() throws Exception { - AutomatonPredicate predicate = new AutomatonPredicate(Automatons.patterns("foo*")); + Predicate predicate = Automatons.predicate("foo*"); assertThat(predicate.test("foo[n][nodes]"), is(true)); assertThat(predicate.test("foo[n]"), is(true)); assertThat(predicate.test("bar[n][nodes]"), is(false)); @@ -51,36 +31,36 @@ public class PrivilegeTests extends ESTestCase { } public void testCluster() throws Exception { - Privilege.Name name = new Privilege.Name("monitor"); + Set name = Sets.newHashSet("monitor"); ClusterPrivilege cluster = ClusterPrivilege.get(name); assertThat(cluster, is(ClusterPrivilege.MONITOR)); - // since "all" implies "monitor", this should collapse to All - name = new Privilege.Name("monitor", "all"); + // since "all" implies "monitor", this should be the same language as All + name = Sets.newHashSet("monitor", "all"); cluster = ClusterPrivilege.get(name); - assertThat(cluster, is(ClusterPrivilege.ALL)); + assertTrue(Operations.sameLanguage(ClusterPrivilege.ALL.automaton, cluster.automaton)); - name = new Privilege.Name("monitor", "none"); + name = Sets.newHashSet("monitor", "none"); cluster = ClusterPrivilege.get(name); - assertThat(cluster, is(ClusterPrivilege.MONITOR)); + assertTrue(Operations.sameLanguage(ClusterPrivilege.MONITOR.automaton, cluster.automaton)); - Privilege.Name name2 = new Privilege.Name("none", "monitor"); + Set name2 = Sets.newHashSet("none", "monitor"); ClusterPrivilege cluster2 = ClusterPrivilege.get(name2); assertThat(cluster, is(cluster2)); } public void testClusterTemplateActions() throws Exception { - Privilege.Name name = new Privilege.Name("indices:admin/template/delete"); + Set name = Sets.newHashSet("indices:admin/template/delete"); ClusterPrivilege cluster = ClusterPrivilege.get(name); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/delete"), is(true)); - name = new Privilege.Name("indices:admin/template/get"); + name = Sets.newHashSet("indices:admin/template/get"); cluster = ClusterPrivilege.get(name); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/get"), is(true)); - name = new Privilege.Name("indices:admin/template/put"); + name = Sets.newHashSet("indices:admin/template/put"); cluster = ClusterPrivilege.get(name); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("indices:admin/template/put"), is(true)); @@ -88,56 +68,20 @@ public class PrivilegeTests extends ESTestCase { public void testClusterInvalidName() throws Exception { thrown.expect(IllegalArgumentException.class); - Privilege.Name actionName = new Privilege.Name("foobar"); + Set actionName = Sets.newHashSet("foobar"); ClusterPrivilege.get(actionName); } public void testClusterAction() throws Exception { - Privilege.Name actionName = new Privilege.Name("cluster:admin/snapshot/delete"); + Set actionName = Sets.newHashSet("cluster:admin/snapshot/delete"); ClusterPrivilege cluster = ClusterPrivilege.get(actionName); assertThat(cluster, notNullValue()); assertThat(cluster.predicate().test("cluster:admin/snapshot/delete"), is(true)); assertThat(cluster.predicate().test("cluster:admin/snapshot/dele"), is(false)); } - public void testClusterAddCustom() throws Exception { - ClusterPrivilege.addCustom("foo", "cluster:bar"); - boolean found = false; - for (ClusterPrivilege cluster : ClusterPrivilege.values()) { - if ("foo".equals(cluster.name.toString())) { - found = true; - assertThat(cluster.predicate().test("cluster:bar"), is(true)); - } - } - assertThat(found, is(true)); - ClusterPrivilege cluster = ClusterPrivilege.get(new Privilege.Name("foo")); - assertThat(cluster, notNullValue()); - assertThat(cluster.name().toString(), is("foo")); - assertThat(cluster.predicate().test("cluster:bar"), is(true)); - } - - public void testClusterAddCustomInvalidPattern() throws Exception { - try { - ClusterPrivilege.addCustom("foo", "bar"); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString("cannot register custom cluster privilege [foo]")); - assertThat(e.getMessage(), containsString("must follow the 'cluster:*' format")); - } - } - - public void testClusterAddCustomAlreadyExists() throws Exception { - try { - ClusterPrivilege.addCustom("all", "bar"); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString("cannot register custom cluster privilege [all]")); - assertThat(e.getMessage(), containsString("must follow the 'cluster:*' format")); - } - } - public void testIndexAction() throws Exception { - Privilege.Name actionName = new Privilege.Name("indices:admin/mapping/delete"); + Set actionName = Sets.newHashSet("indices:admin/mapping/delete"); IndexPrivilege index = IndexPrivilege.get(actionName); assertThat(index, notNullValue()); assertThat(index.predicate().test("indices:admin/mapping/delete"), is(true)); @@ -145,87 +89,20 @@ public class PrivilegeTests extends ESTestCase { } public void testIndexCollapse() throws Exception { - IndexPrivilege[] values = IndexPrivilege.values().toArray(new IndexPrivilege[IndexPrivilege.values().size()]); + IndexPrivilege[] values = IndexPrivilege.values().values().toArray(new IndexPrivilege[IndexPrivilege.values().size()]); IndexPrivilege first = values[randomIntBetween(0, values.length-1)]; IndexPrivilege second = values[randomIntBetween(0, values.length-1)]; - Privilege.Name name = new Privilege.Name(first.name().toString(), second.name().toString()); + Set name = Sets.newHashSet(first.name().iterator().next(), second.name().iterator().next()); IndexPrivilege index = IndexPrivilege.get(name); - if (first.implies(second)) { - assertThat(index, is(first)); - } - - if (second.implies(first)) { - assertThat(index, is(second)); - } - } - - public void testIndexImplies() throws Exception { - IndexPrivilege[] values = IndexPrivilege.values().toArray(new IndexPrivilege[IndexPrivilege.values().size()]); - IndexPrivilege first = values[randomIntBetween(0, values.length-1)]; - IndexPrivilege second = values[randomIntBetween(0, values.length-1)]; - - Privilege.Name name = new Privilege.Name(first.name().toString(), second.name().toString()); - IndexPrivilege index = IndexPrivilege.get(name); - - assertThat(index.implies(first), is(true)); - assertThat(index.implies(second), is(true)); - - if (first.implies(second)) { - assertThat(index, is(first)); - } - - if (second.implies(first)) { - if (index != second) { - IndexPrivilege idx = IndexPrivilege.get(name); - idx.name().toString(); - } - assertThat(index, is(second)); - } - - for (IndexPrivilege other : IndexPrivilege.values()) { - if (first.implies(other) || second.implies(other) || index.isAlias(other)) { - assertThat("index privilege [" + index + "] should imply [" + other + "]", index.implies(other), is(true)); - } else if (other.implies(first) && other.implies(second)) { - assertThat("index privilege [" + index + "] should not imply [" + other + "]", index.implies(other), is(false)); - } - } - } - - public void testIndexAddCustom() throws Exception { - IndexPrivilege.addCustom("foo", "indices:bar"); - boolean found = false; - for (IndexPrivilege index : IndexPrivilege.values()) { - if ("foo".equals(index.name.toString())) { - found = true; - assertThat(index.predicate().test("indices:bar"), is(true)); - } - } - assertThat(found, is(true)); - IndexPrivilege index = IndexPrivilege.get(new Privilege.Name("foo")); - assertThat(index, notNullValue()); - assertThat(index.name().toString(), is("foo")); - assertThat(index.predicate().test("indices:bar"), is(true)); - } - - public void testIndexAddCustomInvalidPattern() throws Exception { - try { - IndexPrivilege.addCustom("foo", "bar"); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString("cannot register custom index privilege [foo]")); - assertThat(e.getMessage(), containsString("must follow the 'indices:*' format")); - } - } - - public void testIndexAddCustomAlreadyExists() throws Exception { - try { - IndexPrivilege.addCustom("all", "bar"); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - assertThat(e.getMessage(), containsString("cannot register custom index privilege [all]")); - assertThat(e.getMessage(), containsString("must follow the 'indices:*' format")); + if (Operations.subsetOf(second.getAutomaton(), first.getAutomaton())) { + assertTrue(Operations.sameLanguage(index.getAutomaton(), first.getAutomaton())); + } else if (Operations.subsetOf(first.getAutomaton(), second.getAutomaton())) { + assertTrue(Operations.sameLanguage(index.getAutomaton(), second.getAutomaton())); + } else { + assertFalse(Operations.sameLanguage(index.getAutomaton(), first.getAutomaton())); + assertFalse(Operations.sameLanguage(index.getAutomaton(), second.getAutomaton())); } } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java new file mode 100644 index 00000000000..0a8513a44a4 --- /dev/null +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java @@ -0,0 +1,75 @@ +/* + * 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.authz.store; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.PlainActionFuture; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissionsCache; +import org.elasticsearch.xpack.security.authz.permission.Role; + +import java.util.Collections; +import java.util.Set; + +import static org.elasticsearch.mock.orig.Mockito.times; +import static org.elasticsearch.mock.orig.Mockito.verifyNoMoreInteractions; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anySetOf; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class CompositeRolesStoreTests extends ESTestCase { + + public void testNegativeLookupsAreCached() { + final FileRolesStore fileRolesStore = mock(FileRolesStore.class); + when(fileRolesStore.roleDescriptors(anySetOf(String.class))).thenReturn(Collections.emptySet()); + final NativeRolesStore nativeRolesStore = mock(NativeRolesStore.class); + doAnswer((invocationOnMock) -> { + ActionListener> callback = (ActionListener>) invocationOnMock.getArguments()[1]; + callback.onResponse(Collections.emptySet()); + return null; + }).when(nativeRolesStore).getRoleDescriptors(isA(String[].class), any(ActionListener.class)); + final ReservedRolesStore reservedRolesStore = spy(new ReservedRolesStore()); + + final CompositeRolesStore compositeRolesStore = + new CompositeRolesStore(Settings.EMPTY, fileRolesStore, nativeRolesStore, reservedRolesStore); + verify(fileRolesStore).addListener(any(Runnable.class)); // adds a listener in ctor + + final String roleName = randomAsciiOfLengthBetween(1, 10); + PlainActionFuture future = new PlainActionFuture<>(); + final FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); + compositeRolesStore.roles(Collections.singleton(roleName), fieldPermissionsCache, future); + final Role role = future.actionGet(); + assertEquals(Role.EMPTY, role); + verify(reservedRolesStore).roleDescriptors(); + verify(fileRolesStore).roleDescriptors(eq(Collections.singleton(roleName))); + verify(nativeRolesStore).getRoleDescriptors(isA(String[].class), any(ActionListener.class)); + + final int numberOfTimesToCall = scaledRandomIntBetween(0, 32); + final boolean getSuperuserRole = randomBoolean(); + final Set names = getSuperuserRole ? Sets.newHashSet(roleName, ReservedRolesStore.SUPERUSER_ROLE.name()) : + Collections.singleton(roleName); + for (int i = 0; i < numberOfTimesToCall; i++) { + future = new PlainActionFuture<>(); + compositeRolesStore.roles(names, fieldPermissionsCache, future); + future.actionGet(); + } + + if (getSuperuserRole) { + // the superuser role was requested so we get the role descriptors again + verify(reservedRolesStore, times(2)).roleDescriptors(); + } + verifyNoMoreInteractions(fileRolesStore, reservedRolesStore, nativeRolesStore); + } +} diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java index ca7ad5e9c8d..4ca21a61f08 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java @@ -7,6 +7,8 @@ package org.elasticsearch.xpack.security.authz.store; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; +import org.apache.lucene.util.automaton.MinimizationOperations; +import org.apache.lucene.util.automaton.Operations; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.env.Environment; import org.elasticsearch.test.ESTestCase; @@ -39,6 +41,7 @@ import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; @@ -55,13 +58,15 @@ public class FileRolesStoreTests extends ESTestCase { public void testParseFile() throws Exception { Path path = getDataPath("roles.yml"); - Map roles = FileRolesStore.parseFile(path, logger, Settings.builder() + Map roles = FileRolesStore.parseFile(path, logger, Settings.builder() .put(XPackSettings.DLS_FLS_ENABLED.getKey(), true) .build()); assertThat(roles, notNullValue()); assertThat(roles.size(), is(9)); - Role role = roles.get("role1"); + RoleDescriptor descriptor = roles.get("role1"); + assertNotNull(descriptor); + Role role = Role.builder(descriptor, null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("role1")); assertThat(role.cluster(), notNullValue()); @@ -69,7 +74,7 @@ public class FileRolesStoreTests extends ESTestCase { assertThat(role.indices(), notNullValue()); assertThat(role.indices().groups(), notNullValue()); assertThat(role.indices().groups().length, is(2)); - assertThat(role.runAs(), is(RunAsPermission.Core.NONE)); + assertThat(role.runAs(), is(RunAsPermission.NONE)); IndicesPermission.Group group = role.indices().groups()[0]; assertThat(group.indices(), notNullValue()); @@ -84,10 +89,12 @@ public class FileRolesStoreTests extends ESTestCase { assertThat(group.indices().length, is(1)); assertThat(group.indices()[0], equalTo("idx3")); assertThat(group.privilege(), notNullValue()); - assertThat(group.privilege().implies(IndexPrivilege.READ), is(true)); - assertThat(group.privilege().implies(IndexPrivilege.WRITE),is(true)); + assertTrue(Operations.subsetOf(IndexPrivilege.READ.getAutomaton(), group.privilege().getAutomaton())); + assertTrue(Operations.subsetOf(IndexPrivilege.WRITE.getAutomaton(), group.privilege().getAutomaton())); - role = roles.get("role1.ab"); + descriptor = roles.get("role1.ab"); + assertNotNull(descriptor); + role = Role.builder(descriptor, null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("role1.ab")); assertThat(role.cluster(), notNullValue()); @@ -95,65 +102,77 @@ public class FileRolesStoreTests extends ESTestCase { assertThat(role.indices(), notNullValue()); assertThat(role.indices().groups(), notNullValue()); assertThat(role.indices().groups().length, is(0)); - assertThat(role.runAs(), is(RunAsPermission.Core.NONE)); + assertThat(role.runAs(), is(RunAsPermission.NONE)); - role = roles.get("role2"); + descriptor = roles.get("role2"); + assertNotNull(descriptor); + role = Role.builder(descriptor, null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("role2")); assertThat(role.cluster(), notNullValue()); - assertThat(role.cluster().privilege(), is(ClusterPrivilege.ALL)); // MONITOR is collapsed into ALL + assertTrue(Operations.sameLanguage(role.cluster().privilege().getAutomaton(), ClusterPrivilege.ALL.getAutomaton())); assertThat(role.indices(), notNullValue()); - assertThat(role.indices(), is(IndicesPermission.Core.NONE)); - assertThat(role.runAs(), is(RunAsPermission.Core.NONE)); + assertThat(role.indices(), is(IndicesPermission.NONE)); + assertThat(role.runAs(), is(RunAsPermission.NONE)); - role = roles.get("role3"); + descriptor = roles.get("role3"); + assertNotNull(descriptor); + role = Role.builder(descriptor, null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("role3")); assertThat(role.cluster(), notNullValue()); - assertThat(role.cluster(), is(ClusterPermission.Core.NONE)); + assertThat(role.cluster(), is(ClusterPermission.NONE)); assertThat(role.indices(), notNullValue()); assertThat(role.indices().groups(), notNullValue()); assertThat(role.indices().groups().length, is(1)); - assertThat(role.runAs(), is(RunAsPermission.Core.NONE)); + assertThat(role.runAs(), is(RunAsPermission.NONE)); group = role.indices().groups()[0]; assertThat(group.indices(), notNullValue()); assertThat(group.indices().length, is(1)); assertThat(group.indices()[0], equalTo("/.*_.*/")); assertThat(group.privilege(), notNullValue()); - assertThat(group.privilege().isAlias(IndexPrivilege.union(IndexPrivilege.READ, IndexPrivilege.WRITE)), is(true)); + assertTrue(Operations.sameLanguage(group.privilege().getAutomaton(), + MinimizationOperations.minimize(Operations.union(IndexPrivilege.READ.getAutomaton(), IndexPrivilege.WRITE.getAutomaton()), + Operations.DEFAULT_MAX_DETERMINIZED_STATES))); - role = roles.get("role4"); - assertThat(role, nullValue()); + descriptor = roles.get("role4"); + assertNull(descriptor); - role = roles.get("role_run_as"); + descriptor = roles.get("role_run_as"); + assertNotNull(descriptor); + role = Role.builder(descriptor, null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("role_run_as")); assertThat(role.cluster(), notNullValue()); - assertThat(role.cluster(), is(ClusterPermission.Core.NONE)); - assertThat(role.indices(), is(IndicesPermission.Core.NONE)); + assertThat(role.cluster(), is(ClusterPermission.NONE)); + assertThat(role.indices(), is(IndicesPermission.NONE)); assertThat(role.runAs(), notNullValue()); assertThat(role.runAs().check("user1"), is(true)); assertThat(role.runAs().check("user2"), is(true)); assertThat(role.runAs().check("user" + randomIntBetween(3, 9)), is(false)); - role = roles.get("role_run_as1"); + descriptor = roles.get("role_run_as1"); + assertNotNull(descriptor); + role = Role.builder(descriptor, null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("role_run_as1")); assertThat(role.cluster(), notNullValue()); - assertThat(role.cluster(), is(ClusterPermission.Core.NONE)); - assertThat(role.indices(), is(IndicesPermission.Core.NONE)); + assertThat(role.cluster(), is(ClusterPermission.NONE)); + assertThat(role.indices(), is(IndicesPermission.NONE)); assertThat(role.runAs(), notNullValue()); assertThat(role.runAs().check("user1"), is(true)); assertThat(role.runAs().check("user2"), is(true)); assertThat(role.runAs().check("user" + randomIntBetween(3, 9)), is(false)); - role = roles.get("role_fields"); + descriptor = roles.get("role_fields"); + assertNotNull(descriptor); + role = Role.builder(descriptor, null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("role_fields")); assertThat(role.cluster(), notNullValue()); - assertThat(role.cluster(), is(ClusterPermission.Core.NONE)); - assertThat(role.runAs(), is(RunAsPermission.Core.NONE)); + assertThat(role.cluster(), is(ClusterPermission.NONE)); + assertThat(role.runAs(), is(RunAsPermission.NONE)); assertThat(role.indices(), notNullValue()); assertThat(role.indices().groups(), notNullValue()); assertThat(role.indices().groups().length, is(1)); @@ -163,17 +182,19 @@ public class FileRolesStoreTests extends ESTestCase { assertThat(group.indices().length, is(1)); assertThat(group.indices()[0], equalTo("field_idx")); assertThat(group.privilege(), notNullValue()); - assertThat(group.privilege().isAlias(IndexPrivilege.READ), is(true)); + assertTrue(Operations.sameLanguage(group.privilege().getAutomaton(), IndexPrivilege.READ.getAutomaton())); assertTrue(group.getFieldPermissions().grantsAccessTo("foo")); assertTrue(group.getFieldPermissions().grantsAccessTo("boo")); assertTrue(group.getFieldPermissions().hasFieldLevelSecurity()); - role = roles.get("role_query"); + descriptor = roles.get("role_query"); + assertNotNull(descriptor); + role = Role.builder(descriptor, null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("role_query")); assertThat(role.cluster(), notNullValue()); - assertThat(role.cluster(), is(ClusterPermission.Core.NONE)); - assertThat(role.runAs(), is(RunAsPermission.Core.NONE)); + assertThat(role.cluster(), is(ClusterPermission.NONE)); + assertThat(role.runAs(), is(RunAsPermission.NONE)); assertThat(role.indices(), notNullValue()); assertThat(role.indices().groups(), notNullValue()); assertThat(role.indices().groups().length, is(1)); @@ -183,16 +204,18 @@ public class FileRolesStoreTests extends ESTestCase { assertThat(group.indices().length, is(1)); assertThat(group.indices()[0], equalTo("query_idx")); assertThat(group.privilege(), notNullValue()); - assertThat(group.privilege().isAlias(IndexPrivilege.READ), is(true)); + assertTrue(Operations.sameLanguage(group.privilege().getAutomaton(), IndexPrivilege.READ.getAutomaton())); assertFalse(group.getFieldPermissions().hasFieldLevelSecurity()); assertThat(group.getQuery(), notNullValue()); - role = roles.get("role_query_fields"); + descriptor = roles.get("role_query_fields"); + assertNotNull(descriptor); + role = Role.builder(descriptor, null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("role_query_fields")); assertThat(role.cluster(), notNullValue()); - assertThat(role.cluster(), is(ClusterPermission.Core.NONE)); - assertThat(role.runAs(), is(RunAsPermission.Core.NONE)); + assertThat(role.cluster(), is(ClusterPermission.NONE)); + assertThat(role.runAs(), is(RunAsPermission.NONE)); assertThat(role.indices(), notNullValue()); assertThat(role.indices().groups(), notNullValue()); assertThat(role.indices().groups().length, is(1)); @@ -202,7 +225,7 @@ public class FileRolesStoreTests extends ESTestCase { assertThat(group.indices().length, is(1)); assertThat(group.indices()[0], equalTo("query_fields_idx")); assertThat(group.privilege(), notNullValue()); - assertThat(group.privilege().isAlias(IndexPrivilege.READ), is(true)); + assertTrue(Operations.sameLanguage(group.privilege().getAutomaton(), IndexPrivilege.READ.getAutomaton())); assertTrue(group.getFieldPermissions().grantsAccessTo("foo")); assertTrue(group.getFieldPermissions().grantsAccessTo("boo")); assertTrue(group.getFieldPermissions().hasFieldLevelSecurity()); @@ -212,7 +235,7 @@ public class FileRolesStoreTests extends ESTestCase { public void testParseFileWithFLSAndDLSDisabled() throws Exception { Path path = getDataPath("roles.yml"); Logger logger = CapturingLogger.newCapturingLogger(Level.ERROR); - Map roles = FileRolesStore.parseFile(path, logger, Settings.builder() + Map roles = FileRolesStore.parseFile(path, logger, Settings.builder() .put(XPackSettings.DLS_FLS_ENABLED.getKey(), false) .build()); assertThat(roles, notNullValue()); @@ -241,7 +264,7 @@ public class FileRolesStoreTests extends ESTestCase { public void testDefaultRolesFile() throws Exception { // TODO we should add the config dir to the resources so we don't copy this stuff around... Path path = getDataPath("default_roles.yml"); - Map roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY); + Map roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY); assertThat(roles, notNullValue()); assertThat(roles.size(), is(0)); } @@ -268,12 +291,13 @@ public class FileRolesStoreTests extends ESTestCase { watcherService = new ResourceWatcherService(settings, threadPool); final CountDownLatch latch = new CountDownLatch(1); FileRolesStore store = new FileRolesStore(settings, env, watcherService, latch::countDown); - store.start(); - Role role = store.role("role1"); - assertThat(role, notNullValue()); - role = store.role("role5"); - assertThat(role, nullValue()); + Set descriptors = store.roleDescriptors(Collections.singleton("role1")); + assertThat(descriptors, notNullValue()); + assertEquals(1, descriptors.size()); + descriptors = store.roleDescriptors(Collections.singleton("role5")); + assertThat(descriptors, notNullValue()); + assertTrue(descriptors.isEmpty()); watcherService.start(); @@ -290,11 +314,14 @@ public class FileRolesStoreTests extends ESTestCase { fail("Waited too long for the updated file to be picked up"); } - role = store.role("role5"); + descriptors = store.roleDescriptors(Collections.singleton("role5")); + assertThat(descriptors, notNullValue()); + assertEquals(1, descriptors.size()); + Role role = Role.builder(descriptors.iterator().next(), null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("role5")); - assertThat(role.cluster().check("cluster:monitor/foo/bar", null, null), is(true)); - assertThat(role.cluster().check("cluster:admin/foo/bar", null, null), is(false)); + assertThat(role.cluster().check("cluster:monitor/foo/bar"), is(true)); + assertThat(role.cluster().check("cluster:admin/foo/bar"), is(false)); } finally { if (watcherService != null) { @@ -307,17 +334,19 @@ public class FileRolesStoreTests extends ESTestCase { public void testThatEmptyFileDoesNotResultInLoop() throws Exception { Path file = createTempFile(); Files.write(file, Collections.singletonList("#"), StandardCharsets.UTF_8); - Map roles = FileRolesStore.parseFile(file, logger, Settings.EMPTY); + Map roles = FileRolesStore.parseFile(file, logger, Settings.EMPTY); assertThat(roles.keySet(), is(empty())); } public void testThatInvalidRoleDefinitions() throws Exception { Path path = getDataPath("invalid_roles.yml"); Logger logger = CapturingLogger.newCapturingLogger(Level.ERROR); - Map roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY); + Map roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY); assertThat(roles.size(), is(1)); assertThat(roles, hasKey("valid_role")); - Role role = roles.get("valid_role"); + RoleDescriptor descriptor = roles.get("valid_role"); + assertNotNull(descriptor); + Role role = Role.builder(descriptor, null).build(); assertThat(role, notNullValue()); assertThat(role.name(), equalTo("valid_role")); @@ -354,7 +383,7 @@ public class FileRolesStoreTests extends ESTestCase { Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); Path path = getDataPath("reserved_roles.yml"); - Map roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY); + Map roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY); assertThat(roles, notNullValue()); assertThat(roles.size(), is(1)); @@ -387,7 +416,6 @@ public class FileRolesStoreTests extends ESTestCase { .build(); Environment env = new Environment(settings); FileRolesStore store = new FileRolesStore(settings, env, mock(ResourceWatcherService.class)); - store.start(); Map usageStats = store.usageStats(); @@ -404,7 +432,7 @@ public class FileRolesStoreTests extends ESTestCase { RoleDescriptor role = FileRolesStore.parseRoleDescriptor(roleString, path, logger, true, Settings.EMPTY); RoleDescriptor.IndicesPrivileges indicesPrivileges = role.getIndicesPrivileges()[0]; - assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("foo")); - assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("boo")); + assertThat(indicesPrivileges.getGrantedFields(), arrayContaining("foo", "boo")); + assertNull(indicesPrivileges.getDeniedFields()); } } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java index b401e97bd49..37163753c9c 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/NativeRolesStoreTests.java @@ -5,30 +5,16 @@ */ package org.elasticsearch.xpack.security.authz.store; -import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.get.GetResponse; -import org.elasticsearch.action.support.PlainActionFuture; -import org.elasticsearch.cluster.ClusterChangedEvent; -import org.elasticsearch.cluster.ClusterName; -import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.common.bytes.BytesArray; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.index.get.GetResult; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.xpack.security.InternalClient; -import org.elasticsearch.xpack.security.SecurityTemplateService; import org.elasticsearch.xpack.security.authz.RoleDescriptor; -import org.elasticsearch.xpack.security.authz.permission.Role; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; -import java.util.concurrent.atomic.AtomicBoolean; -import static org.elasticsearch.xpack.security.test.SecurityTestUtils.getClusterStateWithSecurityIndex; -import static org.mockito.Mockito.mock; +import static org.hamcrest.Matchers.arrayContaining; public class NativeRolesStoreTests extends ESTestCase { @@ -38,52 +24,10 @@ public class NativeRolesStoreTests extends ESTestCase { byte[] bytes = Files.readAllBytes(path); String roleString = new String(bytes, Charset.defaultCharset()); RoleDescriptor role = NativeRolesStore.transformRole("role1", new BytesArray(roleString), logger); + assertNotNull(role); + assertNotNull(role.getIndicesPrivileges()); RoleDescriptor.IndicesPrivileges indicesPrivileges = role.getIndicesPrivileges()[0]; - assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("foo")); - assertTrue(indicesPrivileges.getFieldPermissions().grantsAccessTo("boo")); - } - - public void testNegativeLookupsAreCached() { - final InternalClient internalClient = mock(InternalClient.class); - final AtomicBoolean methodCalled = new AtomicBoolean(false); - final NativeRolesStore rolesStore = new NativeRolesStore(Settings.EMPTY, internalClient) { - @Override - public State state() { - return State.STARTED; - } - - @Override - void executeGetRoleRequest(String role, ActionListener listener) { - if (methodCalled.compareAndSet(false, true)) { - listener.onResponse(new GetResponse(new GetResult(SecurityTemplateService.SECURITY_INDEX_NAME, "role", - role, -1, false, BytesArray.EMPTY, Collections.emptyMap()))); - } else { - fail("method called more than once!"); - } - } - }; - - // setup the roles store so the security index exists - rolesStore.clusterChanged(new ClusterChangedEvent("negative_lookups", getClusterStateWithSecurityIndex(), getEmptyClusterState())); - - final String roleName = randomAsciiOfLengthBetween(1, 10); - PlainActionFuture future = new PlainActionFuture<>(); - rolesStore.role(roleName, future); - Role role = future.actionGet(); - assertTrue(methodCalled.get()); - assertNull(role); - - final int numberOfRetries = scaledRandomIntBetween(1, 20); - for (int i = 0; i < numberOfRetries; i++) { - future = new PlainActionFuture<>(); - rolesStore.role(roleName, future); - role = future.actionGet(); - assertTrue(methodCalled.get()); - assertNull(role); - } - } - - private ClusterState getEmptyClusterState() { - return ClusterState.builder(new ClusterName(NativeRolesStoreTests.class.getName())).build(); + assertThat(indicesPrivileges.getGrantedFields(), arrayContaining("foo", "boo")); + assertNull(indicesPrivileges.getDeniedFields()); } } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStoreTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStoreTests.java index 2f883b7dbc8..f65941b790c 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStoreTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/authz/store/ReservedRolesStoreTests.java @@ -5,91 +5,338 @@ */ package org.elasticsearch.xpack.security.authz.store; -import org.elasticsearch.xpack.security.SecurityContext; -import org.elasticsearch.xpack.security.authz.permission.IngestAdminRole; -import org.elasticsearch.xpack.security.authz.permission.KibanaRole; -import org.elasticsearch.xpack.security.authz.permission.KibanaUserRole; -import org.elasticsearch.xpack.security.authz.permission.LogstashSystemRole; -import org.elasticsearch.xpack.security.authz.permission.MonitoringUserRole; -import org.elasticsearch.xpack.security.authz.permission.RemoteMonitoringAgentRole; -import org.elasticsearch.xpack.security.authz.permission.ReportingUserRole; -import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; -import org.elasticsearch.xpack.security.authz.permission.TransportClientRole; -import org.elasticsearch.xpack.security.user.ElasticUser; -import org.elasticsearch.xpack.security.user.KibanaUser; +import org.elasticsearch.Version; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction; +import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteAction; +import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsAction; +import org.elasticsearch.action.admin.cluster.state.ClusterStateAction; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsAction; +import org.elasticsearch.action.admin.indices.create.CreateIndexAction; +import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction; +import org.elasticsearch.action.admin.indices.get.GetIndexAction; +import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsAction; +import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateAction; +import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesAction; +import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateAction; +import org.elasticsearch.action.bulk.BulkAction; +import org.elasticsearch.action.delete.DeleteAction; +import org.elasticsearch.action.get.GetAction; +import org.elasticsearch.action.index.IndexAction; +import org.elasticsearch.action.ingest.DeletePipelineAction; +import org.elasticsearch.action.ingest.GetPipelineAction; +import org.elasticsearch.action.ingest.PutPipelineAction; +import org.elasticsearch.action.search.MultiSearchAction; +import org.elasticsearch.action.search.SearchAction; +import org.elasticsearch.action.update.UpdateAction; +import org.elasticsearch.cluster.metadata.AliasMetaData; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.set.Sets; +import org.elasticsearch.xpack.monitoring.action.MonitoringBulkAction; +import org.elasticsearch.xpack.security.action.role.PutRoleAction; +import org.elasticsearch.xpack.security.action.user.PutUserAction; +import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissionsCache; +import org.elasticsearch.xpack.security.authz.permission.Role; import org.elasticsearch.xpack.security.user.SystemUser; -import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.test.ESTestCase; -import org.junit.Before; -import static org.hamcrest.Matchers.contains; +import java.util.Arrays; +import java.util.Map; + +import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.hamcrest.Matchers.sameInstance; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; /** * Unit tests for the {@link ReservedRolesStore} */ public class ReservedRolesStoreTests extends ESTestCase { - private final User user = new User("joe"); - private SecurityContext securityContext; - private ReservedRolesStore reservedRolesStore; - - @Before - public void setupMocks() { - securityContext = mock(SecurityContext.class); - when(securityContext.getUser()).thenReturn(user); - reservedRolesStore = new ReservedRolesStore(); - } - - public void testRetrievingReservedRoles() { - assertThat(reservedRolesStore.role(SuperuserRole.NAME), sameInstance(SuperuserRole.INSTANCE)); - assertThat(reservedRolesStore.roleDescriptor(SuperuserRole.NAME), sameInstance(SuperuserRole.DESCRIPTOR)); - - assertThat(reservedRolesStore.role(TransportClientRole.NAME), sameInstance(TransportClientRole.INSTANCE)); - assertThat(reservedRolesStore.roleDescriptor(TransportClientRole.NAME), sameInstance(TransportClientRole.DESCRIPTOR)); - - assertThat(reservedRolesStore.role(KibanaUserRole.NAME), sameInstance(KibanaUserRole.INSTANCE)); - assertThat(reservedRolesStore.roleDescriptor(KibanaUserRole.NAME), sameInstance(KibanaUserRole.DESCRIPTOR)); - - assertThat(reservedRolesStore.role(KibanaRole.NAME), sameInstance(KibanaRole.INSTANCE)); - assertThat(reservedRolesStore.roleDescriptor(KibanaRole.NAME), sameInstance(KibanaRole.DESCRIPTOR)); - - assertThat(reservedRolesStore.role(IngestAdminRole.NAME), sameInstance(IngestAdminRole.INSTANCE)); - assertThat(reservedRolesStore.roleDescriptor(IngestAdminRole.NAME), sameInstance(IngestAdminRole.DESCRIPTOR)); - - assertThat(reservedRolesStore.role(MonitoringUserRole.NAME), sameInstance(MonitoringUserRole.INSTANCE)); - assertThat(reservedRolesStore.roleDescriptor(MonitoringUserRole.NAME), sameInstance(MonitoringUserRole.DESCRIPTOR)); - - assertThat(reservedRolesStore.role(RemoteMonitoringAgentRole.NAME), sameInstance(RemoteMonitoringAgentRole.INSTANCE)); - assertThat(reservedRolesStore.roleDescriptor(RemoteMonitoringAgentRole.NAME), sameInstance(RemoteMonitoringAgentRole.DESCRIPTOR)); - - assertThat(reservedRolesStore.role(ReportingUserRole.NAME), sameInstance(ReportingUserRole.INSTANCE)); - assertThat(reservedRolesStore.roleDescriptor(ReportingUserRole.NAME), sameInstance(ReportingUserRole.DESCRIPTOR)); - - assertThat(reservedRolesStore.role(LogstashSystemRole.NAME), sameInstance(LogstashSystemRole.INSTANCE)); - assertThat(reservedRolesStore.roleDescriptor(LogstashSystemRole.NAME), sameInstance(LogstashSystemRole.DESCRIPTOR)); - - assertThat(reservedRolesStore.roleDescriptors(), contains(SuperuserRole.DESCRIPTOR, TransportClientRole.DESCRIPTOR, - KibanaUserRole.DESCRIPTOR, KibanaRole.DESCRIPTOR, MonitoringUserRole.DESCRIPTOR, RemoteMonitoringAgentRole.DESCRIPTOR, - IngestAdminRole.DESCRIPTOR, ReportingUserRole.DESCRIPTOR, LogstashSystemRole.DESCRIPTOR)); - - assertThat(reservedRolesStore.role(SystemUser.ROLE_NAME), nullValue()); - } - public void testIsReserved() { - assertThat(ReservedRolesStore.isReserved(KibanaRole.NAME), is(true)); - assertThat(ReservedRolesStore.isReserved(SuperuserRole.NAME), is(true)); + assertThat(ReservedRolesStore.isReserved("kibana"), is(true)); + assertThat(ReservedRolesStore.isReserved("superuser"), is(true)); assertThat(ReservedRolesStore.isReserved("foobar"), is(false)); assertThat(ReservedRolesStore.isReserved(SystemUser.ROLE_NAME), is(true)); - assertThat(ReservedRolesStore.isReserved(TransportClientRole.NAME), is(true)); - assertThat(ReservedRolesStore.isReserved(KibanaUserRole.NAME), is(true)); - assertThat(ReservedRolesStore.isReserved(IngestAdminRole.NAME), is(true)); - assertThat(ReservedRolesStore.isReserved(RemoteMonitoringAgentRole.NAME), is(true)); - assertThat(ReservedRolesStore.isReserved(MonitoringUserRole.NAME), is(true)); - assertThat(ReservedRolesStore.isReserved(ReportingUserRole.NAME), is(true)); + assertThat(ReservedRolesStore.isReserved("transport_client"), is(true)); + assertThat(ReservedRolesStore.isReserved("kibana_user"), is(true)); + assertThat(ReservedRolesStore.isReserved("ingest_admin"), is(true)); + assertThat(ReservedRolesStore.isReserved("remote_monitoring_agent"), is(true)); + assertThat(ReservedRolesStore.isReserved("monitoring_user"), is(true)); + assertThat(ReservedRolesStore.isReserved("reporting_user"), is(true)); + } + + public void testIngestAdminRole() { + RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("ingest_admin"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + Role ingestAdminRole = Role.builder(roleDescriptor, null).build(); + assertThat(ingestAdminRole.cluster().check(PutIndexTemplateAction.NAME), is(true)); + assertThat(ingestAdminRole.cluster().check(GetIndexTemplatesAction.NAME), is(true)); + assertThat(ingestAdminRole.cluster().check(DeleteIndexTemplateAction.NAME), is(true)); + assertThat(ingestAdminRole.cluster().check(PutPipelineAction.NAME), is(true)); + assertThat(ingestAdminRole.cluster().check(GetPipelineAction.NAME), is(true)); + assertThat(ingestAdminRole.cluster().check(DeletePipelineAction.NAME), is(true)); + + assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME), is(false)); + assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false)); + assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME), is(false)); + + assertThat(ingestAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); + assertThat(ingestAdminRole.indices().allowedIndicesMatcher("indices:foo").test(randomAsciiOfLengthBetween(8, 24)), + is(false)); + assertThat(ingestAdminRole.indices().allowedIndicesMatcher(GetAction.NAME).test(randomAsciiOfLengthBetween(8, 24)), + is(false)); + } + + public void testKibanaRole() { + RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + Role kibanaRole = Role.builder(roleDescriptor, null).build(); + assertThat(kibanaRole.cluster().check(ClusterHealthAction.NAME), is(true)); + assertThat(kibanaRole.cluster().check(ClusterStateAction.NAME), is(true)); + assertThat(kibanaRole.cluster().check(ClusterStatsAction.NAME), is(true)); + assertThat(kibanaRole.cluster().check(PutIndexTemplateAction.NAME), is(false)); + assertThat(kibanaRole.cluster().check(ClusterRerouteAction.NAME), is(false)); + assertThat(kibanaRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false)); + assertThat(kibanaRole.cluster().check(MonitoringBulkAction.NAME), is(true)); + + assertThat(kibanaRole.runAs().check(randomAsciiOfLengthBetween(1, 12)), is(false)); + + assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false)); + assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:foo").test(randomAsciiOfLengthBetween(8, 24)), is(false)); + + Arrays.asList(".kibana", ".kibana-devnull", ".reporting-" + randomAsciiOfLength(randomIntBetween(0, 13))).forEach((index) -> { + logger.info("index name [{}]", index); + assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher("indices:bar").test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(true)); + assertThat(kibanaRole.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(true)); + }); + } + + public void testKibanaUserRole() { + RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_user"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + Role kibanaUserRole = Role.builder(roleDescriptor, null).build(); + assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME), is(true)); + assertThat(kibanaUserRole.cluster().check(ClusterStateAction.NAME), is(true)); + assertThat(kibanaUserRole.cluster().check(ClusterStatsAction.NAME), is(true)); + assertThat(kibanaUserRole.cluster().check(PutIndexTemplateAction.NAME), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterRerouteAction.NAME), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false)); + assertThat(kibanaUserRole.cluster().check(MonitoringBulkAction.NAME), is(false)); + + assertThat(kibanaUserRole.runAs().check(randomAsciiOfLengthBetween(1, 12)), is(false)); + + assertThat(kibanaUserRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); + assertThat(kibanaUserRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false)); + assertThat(kibanaUserRole.indices().allowedIndicesMatcher("indices:foo") + .test(randomAsciiOfLengthBetween(8, 24)), is(false)); + + Arrays.asList(".kibana", ".kibana-devnull").forEach((index) -> { + logger.info("index name [{}]", index); + assertThat(kibanaUserRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(false)); + assertThat(kibanaUserRole.indices().allowedIndicesMatcher("indices:bar").test(index), is(false)); + + assertThat(kibanaUserRole.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(true)); + assertThat(kibanaUserRole.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(true)); + assertThat(kibanaUserRole.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(true)); + assertThat(kibanaUserRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(true)); + assertThat(kibanaUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true)); + assertThat(kibanaUserRole.indices().allowedIndicesMatcher(MultiSearchAction.NAME).test(index), is(true)); + assertThat(kibanaUserRole.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(true)); + }); + } + + public void testMonitoringUserRole() { + RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("monitoring_user"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + Role monitoringUserRole = Role.builder(roleDescriptor, null).build(); + assertThat(monitoringUserRole.cluster().check(ClusterHealthAction.NAME), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterStateAction.NAME), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterStatsAction.NAME), is(false)); + assertThat(monitoringUserRole.cluster().check(PutIndexTemplateAction.NAME), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterRerouteAction.NAME), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false)); + assertThat(monitoringUserRole.cluster().check(MonitoringBulkAction.NAME), is(false)); + + assertThat(monitoringUserRole.runAs().check(randomAsciiOfLengthBetween(1, 12)), is(false)); + + assertThat(monitoringUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test("foo"), is(false)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(".reporting"), is(false)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(".kibana"), is(false)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher("indices:foo").test(randomAsciiOfLengthBetween(8, 24)), + is(false)); + + Arrays.asList(".monitoring-" + randomAsciiOfLength(randomIntBetween(0, 13)), + ".marvel-es-" + randomAsciiOfLength(randomIntBetween(0, 13))).forEach((index) -> { + assertThat(monitoringUserRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(false)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher("indices:bar").test(index), is(false)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(false)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(false)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(false)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(false)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(false)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true)); + assertThat(monitoringUserRole.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true)); + }); + } + + public void testRemoteMonitoringAgentRole() { + RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("remote_monitoring_agent"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + Role remoteMonitoringAgentRole = Role.builder(roleDescriptor, null).build(); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME), + is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME), is(false)); + + assertThat(remoteMonitoringAgentRole.runAs().check(randomAsciiOfLengthBetween(1, 12)), is(false)); + + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(SearchAction.NAME).test("foo"), is(false)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(".reporting"), is(false)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(".kibana"), is(false)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher("indices:foo") + .test(randomAsciiOfLengthBetween(8, 24)), is(false)); + + Arrays.asList(".monitoring-" + randomAsciiOfLength(randomIntBetween(0, 13)), + ".marvel-es-" + randomAsciiOfLength(randomIntBetween(0, 13))).forEach((index) -> { + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(true)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher("indices:bar").test(index), is(true)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(true)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(true)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(true)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(true)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(true)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true)); + assertThat(remoteMonitoringAgentRole.indices().allowedIndicesMatcher(GetIndexAction.NAME).test(index), is(true)); + }); + } + + public void testReportingUserRole() { + RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("reporting_user"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + Role reportingUserRole = Role.builder(roleDescriptor, null).build(); + assertThat(reportingUserRole.cluster().check(ClusterHealthAction.NAME), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterStateAction.NAME), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterStatsAction.NAME), is(false)); + assertThat(reportingUserRole.cluster().check(PutIndexTemplateAction.NAME), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterRerouteAction.NAME), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false)); + assertThat(reportingUserRole.cluster().check(MonitoringBulkAction.NAME), is(false)); + + assertThat(reportingUserRole.runAs().check(randomAsciiOfLengthBetween(1, 12)), is(false)); + + assertThat(reportingUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test("foo"), is(false)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(".reporting"), is(false)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(".kibana"), is(false)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher("indices:foo").test(randomAsciiOfLengthBetween(8, 24)), + is(false)); + + final String index = ".reporting-" + randomAsciiOfLength(randomIntBetween(0, 13)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher("indices:foo").test(index), is(false)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher("indices:bar").test(index), is(false)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(DeleteIndexAction.NAME).test(index), is(false)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(CreateIndexAction.NAME).test(index), is(false)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(UpdateSettingsAction.NAME).test(index), is(false)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(index), is(true)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(GetAction.NAME).test(index), is(true)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(index), is(true)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(UpdateAction.NAME).test(index), is(true)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(DeleteAction.NAME).test(index), is(true)); + assertThat(reportingUserRole.indices().allowedIndicesMatcher(BulkAction.NAME).test(index), is(true)); + } + + public void testSuperuserRole() { + RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("superuser"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + Role superuserRole = Role.builder(roleDescriptor, null).build(); + assertThat(superuserRole.cluster().check(ClusterHealthAction.NAME), is(true)); + assertThat(superuserRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(true)); + assertThat(superuserRole.cluster().check(PutUserAction.NAME), is(true)); + assertThat(superuserRole.cluster().check(PutRoleAction.NAME), is(true)); + assertThat(superuserRole.cluster().check(PutIndexTemplateAction.NAME), is(true)); + assertThat(superuserRole.cluster().check("internal:admin/foo"), is(false)); + + final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); + final MetaData metaData = new MetaData.Builder() + .put(new IndexMetaData.Builder("a1").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) + .put(new IndexMetaData.Builder("a2").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) + .put(new IndexMetaData.Builder("aaaaaa").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) + .put(new IndexMetaData.Builder("bbbbb").settings(indexSettings).numberOfShards(1).numberOfReplicas(0).build(), true) + .put(new IndexMetaData.Builder("b") + .settings(indexSettings) + .numberOfShards(1) + .numberOfReplicas(0) + .putAlias(new AliasMetaData.Builder("ab").build()) + .putAlias(new AliasMetaData.Builder("ba").build()) + .build(), true) + .build(); + + FieldPermissionsCache fieldPermissionsCache = new FieldPermissionsCache(Settings.EMPTY); + Map authzMap = + superuserRole.indices().authorize(SearchAction.NAME, Sets.newHashSet("a1", "ba"), metaData, fieldPermissionsCache); + assertThat(authzMap.get("a1").isGranted(), is(true)); + assertThat(authzMap.get("b").isGranted(), is(true)); + authzMap = superuserRole.indices().authorize(DeleteIndexAction.NAME, Sets.newHashSet("a1", "ba"), metaData, fieldPermissionsCache); + assertThat(authzMap.get("a1").isGranted(), is(true)); + assertThat(authzMap.get("b").isGranted(), is(true)); + authzMap = superuserRole.indices().authorize(IndexAction.NAME, Sets.newHashSet("a2", "ba"), metaData, fieldPermissionsCache); + assertThat(authzMap.get("a2").isGranted(), is(true)); + assertThat(authzMap.get("b").isGranted(), is(true)); + authzMap = superuserRole.indices() + .authorize(UpdateSettingsAction.NAME, Sets.newHashSet("aaaaaa", "ba"), metaData, fieldPermissionsCache); + assertThat(authzMap.get("aaaaaa").isGranted(), is(true)); + assertThat(authzMap.get("b").isGranted(), is(true)); + assertTrue(superuserRole.indices().check(SearchAction.NAME)); + assertFalse(superuserRole.indices().check("unknown")); + + assertThat(superuserRole.runAs().check(randomAsciiOfLengthBetween(1, 30)), is(true)); + } + + public void testLogstashSystemRole() { + RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("logstash_system"); + assertNotNull(roleDescriptor); + assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); + + Role logstashSystemRole = Role.builder(roleDescriptor, null).build(); + assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME), is(true)); + assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME), is(true)); + assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME), is(true)); + assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME), is(false)); + assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME), is(false)); + assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME), is(false)); + assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME), is(true)); + + assertThat(logstashSystemRole.runAs().check(randomAsciiOfLengthBetween(1, 30)), is(false)); + + assertThat(logstashSystemRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); + assertThat(logstashSystemRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false)); + assertThat(logstashSystemRole.indices().allowedIndicesMatcher("indices:foo").test(randomAsciiOfLengthBetween(8, 24)), + is(false)); } } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java index af7f629b4c3..61f0a135fe2 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/transport/ServerTransportFilterTests.java @@ -26,13 +26,12 @@ import org.elasticsearch.xpack.security.authc.Authentication.RealmRef; import org.elasticsearch.xpack.security.authc.AuthenticationService; import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.permission.Role; -import org.elasticsearch.xpack.security.authz.permission.SuperuserRole; +import org.elasticsearch.xpack.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.security.user.SystemUser; import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.XPackUser; import org.junit.Before; -import java.util.Collection; import java.util.Collections; import static org.elasticsearch.mock.orig.Mockito.times; @@ -85,7 +84,7 @@ public class ServerTransportFilterTests extends ESTestCase { PlainActionFuture future = new PlainActionFuture<>(); filter.inbound("_action", request, channel, future); //future.get(); // don't block it's not called really just mocked - verify(authzService).authorize(authentication, "_action", request, Collections.emptyList(), Collections.emptyList()); + verify(authzService).authorize(authentication, "_action", request, null, null); } public void testInboundDestructiveOperations() throws Exception { @@ -108,7 +107,7 @@ public class ServerTransportFilterTests extends ESTestCase { verify(listener).onFailure(isA(IllegalArgumentException.class)); verifyNoMoreInteractions(authzService); } else { - verify(authzService).authorize(authentication, action, request, Collections.emptyList(), Collections.emptyList()); + verify(authzService).authorize(authentication, action, request, null, null); } } @@ -143,24 +142,23 @@ public class ServerTransportFilterTests extends ESTestCase { callback.onResponse(authentication); return Void.TYPE; }).when(authcService).authenticate(eq("_action"), eq(request), eq(null), any(ActionListener.class)); + final Role empty = Role.EMPTY; doAnswer((i) -> { ActionListener callback = (ActionListener) i.getArguments()[1]; - callback.onResponse(Collections.emptyList()); + callback.onResponse(empty); return Void.TYPE; }).when(authzService).roles(any(User.class), any(ActionListener.class)); when(authentication.getUser()).thenReturn(XPackUser.INSTANCE); when(authentication.getRunAsUser()).thenReturn(XPackUser.INSTANCE); PlainActionFuture future = new PlainActionFuture<>(); doThrow(authorizationError("authz failed")).when(authzService).authorize(authentication, "_action", request, - Collections.emptyList(), Collections.emptyList()); - try { + empty, null); + ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, () -> { filter.inbound("_action", request, channel, future); future.actionGet(); - fail("expected filter inbound to throw an authorization exception on authorization error"); - } catch (ElasticsearchSecurityException e) { - assertThat(e.getMessage(), equalTo("authz failed")); - } + }); + assertThat(e.getMessage(), equalTo("authz failed")); } public void testClientProfileRejectsNodeActions() throws Exception { @@ -182,11 +180,10 @@ public class ServerTransportFilterTests extends ESTestCase { ServerTransportFilter filter = getNodeFilter(); TransportRequest request = mock(TransportRequest.class); Authentication authentication = new Authentication(new User("test", "superuser"), new RealmRef("test", "test", "node1"), null); - final Collection userRoles = Collections.singletonList(SuperuserRole.INSTANCE); doAnswer((i) -> { ActionListener callback = (ActionListener) i.getArguments()[1]; - callback.onResponse(authentication.getUser().equals(i.getArguments()[0]) ? userRoles : Collections.emptyList()); + callback.onResponse(authentication.getUser().equals(i.getArguments()[0]) ? ReservedRolesStore.SUPERUSER_ROLE : null); return Void.TYPE; }).when(authzService).roles(any(User.class), any(ActionListener.class)); doAnswer((i) -> { @@ -205,12 +202,12 @@ public class ServerTransportFilterTests extends ESTestCase { filter.inbound(internalAction, request, channel, new PlainActionFuture<>()); verify(authcService).authenticate(eq(internalAction), eq(request), eq(null), any(ActionListener.class)); verify(authzService).roles(eq(authentication.getUser()), any(ActionListener.class)); - verify(authzService).authorize(authentication, internalAction, request, userRoles, Collections.emptyList()); + verify(authzService).authorize(authentication, internalAction, request, ReservedRolesStore.SUPERUSER_ROLE, null); filter.inbound(nodeOrShardAction, request, channel, new PlainActionFuture<>()); verify(authcService).authenticate(eq(nodeOrShardAction), eq(request), eq(null), any(ActionListener.class)); verify(authzService, times(2)).roles(eq(authentication.getUser()), any(ActionListener.class)); - verify(authzService).authorize(authentication, nodeOrShardAction, request, userRoles, Collections.emptyList()); + verify(authzService).authorize(authentication, nodeOrShardAction, request, ReservedRolesStore.SUPERUSER_ROLE, null); verifyNoMoreInteractions(authcService, authzService); } diff --git a/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java b/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java index 1a697a55356..6e209f175d6 100644 --- a/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java +++ b/qa/security-migrate-tests/src/test/java/org/elasticsearch/xpack/security/MigrateToolIT.java @@ -22,6 +22,7 @@ import org.elasticsearch.xpack.security.action.user.PutUserResponse; import org.elasticsearch.xpack.security.authc.esnative.ESNativeRealmMigrateTool; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.security.client.SecurityClient; import org.elasticsearch.xpack.security.user.User; import org.junit.Before; @@ -92,17 +93,19 @@ public class MigrateToolIT extends MigrateToolTestCase { RoleDescriptor.IndicesPrivileges[] ips = rd.getIndicesPrivileges(); assertEquals(ips.length, 2); for (RoleDescriptor.IndicesPrivileges ip : ips) { + final FieldPermissions fieldPermissions = new FieldPermissions(ip.getGrantedFields(), ip.getDeniedFields()); if (Arrays.equals(ip.getIndices(), new String[]{"index1", "index2"})) { assertArrayEquals(ip.getPrivileges(), new String[]{"read", "write", "create_index", "indices:admin/refresh"}); - assertTrue(ip.getFieldPermissions().hasFieldLevelSecurity()); - assertTrue(ip.getFieldPermissions().grantsAccessTo("bar")); - assertTrue(ip.getFieldPermissions().grantsAccessTo("foo")); + assertTrue(fieldPermissions.hasFieldLevelSecurity()); + assertTrue(fieldPermissions.grantsAccessTo("bar")); + assertTrue(fieldPermissions.grantsAccessTo("foo")); assertNotNull(ip.getQuery()); - assertThat(ip.getQuery().utf8ToString(), containsString("{\"bool\":{\"must_not\":{\"match\":{\"hidden\":true}}}}")); + assertThat(ip.getQuery().iterator().next().utf8ToString(), + containsString("{\"bool\":{\"must_not\":{\"match\":{\"hidden\":true}}}}")); } else { assertArrayEquals(ip.getIndices(), new String[]{"*"}); assertArrayEquals(ip.getPrivileges(), new String[]{"read"}); - assertFalse(ip.getFieldPermissions().hasFieldLevelSecurity()); + assertFalse(fieldPermissions.hasFieldLevelSecurity()); assertNull(ip.getQuery()); } }