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@c1901bc82e
This commit is contained in:
parent
9c09b88e9f
commit
18a2cf23d4
|
@ -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);
|
||||
|
|
|
@ -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<String> LICENSE_EXPIRATION_ACTION_MATCHER = HealthAndStatsPrivilege.INSTANCE.predicate();
|
||||
private static final Predicate<String> SECURITY_ACTION_MATCHER =
|
||||
new GeneralPrivilege("_security_matcher", "cluster:admin/xpack/security*").predicate();
|
||||
private static final Predicate<String> SECURITY_ACTION_MATCHER = Automatons.predicate("cluster:admin/xpack/security*");
|
||||
|
||||
private final AuthenticationService authcService;
|
||||
private final AuthorizationService authzService;
|
||||
|
|
|
@ -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<PutRol
|
|||
this.indicesPrivileges.addAll(Arrays.asList(privileges));
|
||||
}
|
||||
|
||||
public void addIndex(String[] indices, String[] privileges, FieldPermissions fieldPermissions,
|
||||
public void addIndex(String[] indices, String[] privileges, String[] grantedFields, String[] deniedFields,
|
||||
@Nullable BytesReference query) {
|
||||
this.indicesPrivileges.add(RoleDescriptor.IndicesPrivileges.builder()
|
||||
.indices(indices)
|
||||
.privileges(privileges)
|
||||
.fieldPermissions(fieldPermissions)
|
||||
.grantedFields(grantedFields)
|
||||
.deniedFields(deniedFields)
|
||||
.query(query)
|
||||
.build());
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import org.elasticsearch.client.ElasticsearchClient;
|
|||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.security.authz.permission.FieldPermissions;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
@ -58,9 +57,9 @@ public class PutRoleRequestBuilder extends ActionRequestBuilder<PutRoleRequest,
|
|||
return this;
|
||||
}
|
||||
|
||||
public PutRoleRequestBuilder addIndices(String[] indices, String[] privileges,
|
||||
FieldPermissions fieldPermissions, @Nullable BytesReference query) {
|
||||
request.addIndex(indices, privileges, fieldPermissions, query);
|
||||
public PutRoleRequestBuilder addIndices(String[] indices, String[] privileges, String[] grantedFields, String[] deniedFields,
|
||||
@Nullable BytesReference query) {
|
||||
request.addIndex(indices, privileges, grantedFields, deniedFields, query);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import org.elasticsearch.cluster.service.ClusterService;
|
|||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.security.authz.store.NativeRolesStore;
|
||||
import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportService;
|
||||
|
||||
|
@ -21,12 +21,12 @@ import java.util.List;
|
|||
public class TransportClearRolesCacheAction extends TransportNodesAction<ClearRolesCacheRequest, ClearRolesCacheResponse,
|
||||
ClearRolesCacheRequest.Node, ClearRolesCacheResponse.Node> {
|
||||
|
||||
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);
|
||||
|
|
|
@ -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<GetRolesRequest, GetRolesResponse> {
|
||||
|
||||
private final NativeRolesStore nativeRolesStore;
|
||||
|
@ -53,8 +47,12 @@ public class TransportGetRolesAction extends HandledTransportAction<GetRolesRequ
|
|||
for (String role : requestedRoles) {
|
||||
if (ReservedRolesStore.isReserved(role)) {
|
||||
RoleDescriptor rd = reservedRolesStore.roleDescriptor(role);
|
||||
assert rd != null : "No descriptor for role " + role;
|
||||
roles.add(rd);
|
||||
if (rd != null) {
|
||||
roles.add(rd);
|
||||
} else {
|
||||
listener.onFailure(new IllegalStateException("unable to obtain reserved role [" + role + "]"));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
rolesToSearchFor.add(role);
|
||||
}
|
||||
|
|
|
@ -31,28 +31,30 @@ import org.elasticsearch.common.util.concurrent.ThreadContext;
|
|||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.transport.TransportRequest;
|
||||
import org.elasticsearch.xpack.common.GroupedActionListener;
|
||||
import org.elasticsearch.xpack.security.SecurityTemplateService;
|
||||
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.audit.AuditTrailService;
|
||||
import org.elasticsearch.xpack.security.authc.Authentication;
|
||||
import org.elasticsearch.xpack.security.authc.AuthenticationFailureHandler;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.NativeRealm;
|
||||
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
|
||||
import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl;
|
||||
import org.elasticsearch.xpack.security.authz.permission.ClusterPermission;
|
||||
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.RunAsPermission;
|
||||
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.support.Automatons;
|
||||
import org.elasticsearch.xpack.security.user.AnonymousUser;
|
||||
import org.elasticsearch.xpack.security.user.SystemUser;
|
||||
import org.elasticsearch.xpack.security.user.User;
|
||||
import org.elasticsearch.xpack.security.user.XPackUser;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -70,6 +72,7 @@ public class AuthorizationService extends AbstractComponent {
|
|||
public static final String ORIGINATING_ACTION_KEY = "_originating_action_name";
|
||||
|
||||
private static final Predicate<String> MONITOR_INDEX_PREDICATE = IndexPrivilege.MONITOR.predicate();
|
||||
private static final Predicate<String> 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<Role> userRoles,
|
||||
Collection<Role> 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<Role> 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<String> 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<Role> roles) {
|
||||
GlobalPermission.Compound.Builder rolesBuilder = GlobalPermission.Compound.builder();
|
||||
for (Role role : roles) {
|
||||
rolesBuilder.add(role);
|
||||
}
|
||||
return rolesBuilder.build();
|
||||
}
|
||||
|
||||
public void roles(User user, ActionListener<Collection<Role>> roleActionListener) {
|
||||
public void roles(User user, ActionListener<Role> 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<Role> 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<Role> 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);
|
||||
|
|
|
@ -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<String> INTERNAL_PREDICATE = new AutomatonPredicate(Automatons.patterns("internal:*"));
|
||||
private static final Predicate<String> 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<Role>, Collection<Role>> consumer;
|
||||
private final BiConsumer<Role, Role> consumer;
|
||||
private final Authentication authentication;
|
||||
private volatile Collection<Role> userRoles;
|
||||
private volatile Collection<Role> 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<Role>,
|
||||
Collection<Role>> consumer) {
|
||||
public AsyncAuthorizer(Authentication authentication, ActionListener listener, BiConsumer<Role, Role> 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<Role> roles) {
|
||||
private void setUserRoles(Role roles) {
|
||||
this.userRoles = roles;
|
||||
maybeRun();
|
||||
}
|
||||
|
||||
private void setRunAsRoles(Collection<Role> roles) {
|
||||
private void setRunAsRoles(Role roles) {
|
||||
this.runAsRoles = roles;
|
||||
maybeRun();
|
||||
}
|
||||
|
|
|
@ -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<Role> userRoles;
|
||||
private final Role userRoles;
|
||||
private List<String> authorizedIndices;
|
||||
|
||||
AuthorizedIndices(User user, Collection<Role> 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<String> load() {
|
||||
if (userRoles.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Predicate<String>> predicates = new ArrayList<>();
|
||||
for (Role userRole : userRoles) {
|
||||
predicates.add(userRole.indices().allowedIndicesMatcher(action));
|
||||
}
|
||||
|
||||
Predicate<String> predicate = predicates.stream().reduce(s -> false, Predicate::or);
|
||||
Predicate<String> predicate = userRoles.indices().allowedIndicesMatcher(action);
|
||||
|
||||
List<String> 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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -88,36 +88,5 @@ public class IndicesAccessControl {
|
|||
public Set<BytesReference> 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<BytesReference> 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> 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<GlobalPermission> globals;
|
||||
|
||||
Globals(List<GlobalPermission> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<String> resolveAllowedFields(Set<String> allowedMetaFields, MapperService mapperService) {
|
||||
HashSet<String> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Long> 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<Key, FieldPermissions> cache;
|
||||
|
||||
public FieldPermissionsCache(Settings settings) {
|
||||
this.cache = CacheBuilder.<Key, FieldPermissions>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<String> grantedSet;
|
||||
if (granted != null) {
|
||||
grantedSet = new HashSet<>(granted.length);
|
||||
Collections.addAll(grantedSet, granted);
|
||||
} else {
|
||||
grantedSet = null;
|
||||
}
|
||||
|
||||
final Set<String> 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<String> granted, Set<String> 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<FieldPermissions> fieldPermissionsCollection) {
|
||||
Optional<FieldPermissions> allowAllFieldPermissions = fieldPermissionsCollection.stream()
|
||||
.filter((fp) -> Operations.isTotal(fp.getPermittedFieldsAutomaton()))
|
||||
.findFirst();
|
||||
return allowAllFieldPermissions.orElseGet(() -> {
|
||||
final Set<String> allowedFields;
|
||||
Optional<FieldPermissions> 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<String> 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<FieldPermissions> fieldPermissionsCollection,
|
||||
Set<String> allDeniedFields) {
|
||||
Set<String> 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<String> difference = Sets.difference(allDeniedFields, allowedDeniedFields);
|
||||
if (difference.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return difference.toArray(Strings.EMPTY_ARRAY);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Key {
|
||||
|
||||
private final Set<String> grantedFields;
|
||||
private final Set<String> deniedFields;
|
||||
|
||||
Key(Set<String> grantedFields, Set<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String> requestedIndicesOrAliases, MetaData metaData) {
|
||||
Map<String, IndicesAccessControl.IndexAccessControl> 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<String, IndicesAccessControl.IndexAccessControl> entry : indexPermissions.entrySet()) {
|
||||
if (!entry.getValue().isGranted()) {
|
||||
granted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new IndicesAccessControl(granted, indexPermissions);
|
||||
}
|
||||
|
||||
public static class Compound extends GlobalPermission {
|
||||
|
||||
Compound(List<GlobalPermission> 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<GlobalPermission> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<IndicesPermission.Group> {
|
||||
public final class IndicesPermission implements Iterable<IndicesPermission.Group> {
|
||||
|
||||
public static final IndicesPermission NONE = new IndicesPermission();
|
||||
|
||||
private final Function<String, Predicate<String>> loadingFunction;
|
||||
|
||||
private final ConcurrentHashMap<String, Predicate<String>> allowedIndicesMatchersForAction = new ConcurrentHashMap<>();
|
||||
|
||||
private final Group[] groups;
|
||||
|
||||
public IndicesPermission(Group... groups) {
|
||||
this.groups = groups;
|
||||
loadingFunction = (action) -> {
|
||||
List<String> 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<Group> 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<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> requestedIndicesOrAliases, MetaData metaData);
|
||||
public Predicate<String> 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<IndicesPermissio
|
|||
* The usecase for this is composite actions that are initially only authorized based on the action name (indices are not
|
||||
* checked on the coordinating node), and properly authorized later at the shard level checking their indices as well.
|
||||
*/
|
||||
boolean check(String action);
|
||||
|
||||
class Core implements IndicesPermission {
|
||||
|
||||
public static final Core NONE = new Core() {
|
||||
@Override
|
||||
public Iterator<Group> 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<String, Predicate<String>> loadingFunction;
|
||||
|
||||
private final ConcurrentHashMap<String, Predicate<String>> allowedIndicesMatchersForAction = new ConcurrentHashMap<>();
|
||||
|
||||
private final Group[] groups;
|
||||
|
||||
public Core(List<Group> groups) {
|
||||
this(groups.toArray(new Group[groups.size()]));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Core(Group... groups) {
|
||||
this.groups = groups;
|
||||
loadingFunction = (action) -> {
|
||||
List<String> 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<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> 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<String, AliasOrIndex> allAliasesAndIndices = metaData.getAliasAndIndexLookup();
|
||||
Map<String, Set<FieldPermissions>> fieldPermissionsByIndex = new HashMap<>();
|
||||
Map<String, Set<BytesReference>> roleQueriesByIndex = new HashMap<>();
|
||||
Map<String, Boolean> grantedBuilder = new HashMap<>();
|
||||
|
||||
for (String indexOrAlias : requestedIndicesOrAliases) {
|
||||
boolean granted = false;
|
||||
Set<String> 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<Group> 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<String> 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<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> requestedIndicesOrAliases,
|
||||
MetaData metaData) {
|
||||
// now... every index that is associated with the request, must be granted
|
||||
// by at least one indices permission group
|
||||
|
||||
SortedMap<String, AliasOrIndex> allAliasesAndIndices = metaData.getAliasAndIndexLookup();
|
||||
Map<String, Set<FieldPermissions>> fieldPermissionsByIndex = new HashMap<>();
|
||||
Map<String, Set<BytesReference>> roleQueriesByIndex = new HashMap<>();
|
||||
Map<String, Boolean> grantedBuilder = new HashMap<>();
|
||||
|
||||
for (String indexOrAlias : requestedIndicesOrAliases) {
|
||||
boolean granted = false;
|
||||
Set<String> 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<BytesReference> 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<String, IndicesAccessControl.IndexAccessControl> indexPermissions = new HashMap<>();
|
||||
for (Map.Entry<String, Boolean> entry : grantedBuilder.entrySet()) {
|
||||
String index = entry.getKey();
|
||||
Set<BytesReference> roleQueries = roleQueriesByIndex.get(index);
|
||||
if (roleQueries != null) {
|
||||
roleQueries = unmodifiableSet(roleQueries);
|
||||
}
|
||||
|
||||
FieldPermissions fieldPermissions = new FieldPermissions();
|
||||
Set<FieldPermissions> 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<GlobalPermission> globals;
|
||||
|
||||
public Globals(List<GlobalPermission> globals) {
|
||||
this.globals = globals;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Group> iterator() {
|
||||
return globals == null || globals.isEmpty() ?
|
||||
Collections.<Group>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<String, IndicesAccessControl.IndexAccessControl> authorize(String action, Set<String> requestedIndicesOrAliases,
|
||||
MetaData metaData) {
|
||||
if (isEmpty()) {
|
||||
return emptyMap();
|
||||
}
|
||||
|
||||
// What this code does is just merge `IndexAccessControl` instances from the permissions this class holds:
|
||||
Map<String, IndicesAccessControl.IndexAccessControl> indicesAccessControl = null;
|
||||
for (GlobalPermission permission : globals) {
|
||||
Map<String, IndicesAccessControl.IndexAccessControl> temp = permission.indices().authorize(action,
|
||||
requestedIndicesOrAliases, metaData);
|
||||
if (indicesAccessControl == null) {
|
||||
indicesAccessControl = new HashMap<>(temp);
|
||||
} else {
|
||||
for (Map.Entry<String, IndicesAccessControl.IndexAccessControl> 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> fieldPermissions = fieldPermissionsByIndex.computeIfAbsent(index, (k) -> new HashSet<>());
|
||||
fieldPermissions.add(group.getFieldPermissions());
|
||||
if (group.hasQuery()) {
|
||||
Set<BytesReference> 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<Group> {
|
||||
|
||||
private final Iterator<GlobalPermission> globals;
|
||||
private Iterator<Group> current;
|
||||
|
||||
Iter(List<GlobalPermission> globals) {
|
||||
this.globals = globals.iterator();
|
||||
advance();
|
||||
Map<String, IndicesAccessControl.IndexAccessControl> indexPermissions = new HashMap<>();
|
||||
for (Map.Entry<String, Boolean> entry : grantedBuilder.entrySet()) {
|
||||
String index = entry.getKey();
|
||||
Set<BytesReference> 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<FieldPermissions> 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<String> actionMatcher;
|
||||
private final String[] indices;
|
||||
|
@ -332,14 +168,14 @@ public interface IndicesPermission extends Permission, Iterable<IndicesPermissio
|
|||
}
|
||||
|
||||
private final FieldPermissions fieldPermissions;
|
||||
private final BytesReference query;
|
||||
private final Set<BytesReference> query;
|
||||
|
||||
public Group(IndexPrivilege privilege, FieldPermissions fieldPermissions, @Nullable BytesReference query, String... indices) {
|
||||
public Group(IndexPrivilege privilege, FieldPermissions fieldPermissions, @Nullable Set<BytesReference> 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<IndicesPermissio
|
|||
}
|
||||
|
||||
@Nullable
|
||||
public BytesReference getQuery() {
|
||||
public Set<BytesReference> getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
|
@ -366,7 +202,7 @@ public interface IndicesPermission extends Permission, Iterable<IndicesPermissio
|
|||
return check(action) && indexNameMatcher.test(index);
|
||||
}
|
||||
|
||||
public boolean hasQuery() {
|
||||
boolean hasQuery() {
|
||||
return query != null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,32 +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;
|
||||
|
||||
/**
|
||||
* Role for users that should be allowed to use the Add Data/Ingest features in the UI
|
||||
*/
|
||||
public class IngestAdminRole extends Role {
|
||||
|
||||
private static final String[] CLUSTER_PRIVILEGES = new String[] { "manage_index_templates", "manage_pipeline" };
|
||||
private static final RoleDescriptor.IndicesPrivileges[] INDICES_PRIVILEGES = new RoleDescriptor.IndicesPrivileges[0];
|
||||
|
||||
public static final String NAME = "ingest_admin";
|
||||
public static final RoleDescriptor DESCRIPTOR =
|
||||
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final IngestAdminRole INSTANCE = new IngestAdminRole();
|
||||
|
||||
private IngestAdminRole() {
|
||||
super(DESCRIPTOR.getName(),
|
||||
new ClusterPermission.Core(ClusterPrivilege.get(new Name(DESCRIPTOR.getClusterPrivileges()))),
|
||||
new IndicesPermission.Core(Role.Builder.convertFromIndicesPrivileges(DESCRIPTOR.getIndicesPrivileges())),
|
||||
RunAsPermission.Core.NONE);
|
||||
}
|
||||
}
|
|
@ -1,31 +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.monitoring.action.MonitoringBulkAction;
|
||||
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;
|
||||
|
||||
public class KibanaRole extends Role {
|
||||
|
||||
private static final String[] CLUSTER_PRIVILEGES = new String[] { "monitor", MonitoringBulkAction.NAME};
|
||||
private static final RoleDescriptor.IndicesPrivileges[] INDICES_PRIVILEGES = new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*", ".reporting-*").privileges("all").build() };
|
||||
|
||||
public static final String NAME = "kibana";
|
||||
public static final RoleDescriptor DESCRIPTOR =
|
||||
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final KibanaRole INSTANCE = new KibanaRole();
|
||||
|
||||
private KibanaRole() {
|
||||
super(DESCRIPTOR.getName(),
|
||||
new ClusterPermission.Core(ClusterPrivilege.get(new Name(DESCRIPTOR.getClusterPrivileges()))),
|
||||
new IndicesPermission.Core(Role.Builder.convertFromIndicesPrivileges(DESCRIPTOR.getIndicesPrivileges())),
|
||||
RunAsPermission.Core.NONE);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
public class KibanaUserRole extends Role {
|
||||
|
||||
private static final String[] CLUSTER_PRIVILEGES = new String[] { "monitor" };
|
||||
private static final RoleDescriptor.IndicesPrivileges[] INDICES_PRIVILEGES = new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder().indices(".kibana*").privileges("manage", "read", "index", "delete").build() };
|
||||
|
||||
public static final String NAME = "kibana_user";
|
||||
public static final RoleDescriptor DESCRIPTOR =
|
||||
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final KibanaUserRole INSTANCE = new KibanaUserRole();
|
||||
|
||||
private KibanaUserRole() {
|
||||
super(DESCRIPTOR.getName(),
|
||||
new ClusterPermission.Core(ClusterPrivilege.get(new Name(DESCRIPTOR.getClusterPrivileges()))),
|
||||
new IndicesPermission.Core(Role.Builder.convertFromIndicesPrivileges(DESCRIPTOR.getIndicesPrivileges())),
|
||||
RunAsPermission.Core.NONE);
|
||||
}
|
||||
}
|
|
@ -1,29 +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.monitoring.action.MonitoringBulkAction;
|
||||
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;
|
||||
|
||||
public class LogstashSystemRole extends Role {
|
||||
|
||||
private static final String[] CLUSTER_PRIVILEGES = new String[] { "monitor", MonitoringBulkAction.NAME};
|
||||
|
||||
public static final String NAME = "logstash_system";
|
||||
public static final RoleDescriptor DESCRIPTOR =
|
||||
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final LogstashSystemRole INSTANCE = new LogstashSystemRole();
|
||||
|
||||
private LogstashSystemRole() {
|
||||
super(DESCRIPTOR.getName(),
|
||||
new ClusterPermission.Core(ClusterPrivilege.get(new Name(DESCRIPTOR.getClusterPrivileges()))),
|
||||
new IndicesPermission.Core(Builder.convertFromIndicesPrivileges(DESCRIPTOR.getIndicesPrivileges())),
|
||||
RunAsPermission.Core.NONE);
|
||||
}
|
||||
}
|
|
@ -1,34 +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.permission.ClusterPermission.Core;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
/**
|
||||
* A built-in role that grants users the necessary privileges to use Monitoring. The user will also need the {@link KibanaUserRole}
|
||||
*/
|
||||
public class MonitoringUserRole extends Role {
|
||||
|
||||
private static final RoleDescriptor.IndicesPrivileges[] INDICES_PRIVILEGES = new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
.indices(".marvel-es-*", ".monitoring-*")
|
||||
.privileges("read")
|
||||
.build() };
|
||||
|
||||
public static final String NAME = "monitoring_user";
|
||||
public static final RoleDescriptor DESCRIPTOR =
|
||||
new RoleDescriptor(NAME, null, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final MonitoringUserRole INSTANCE = new MonitoringUserRole();
|
||||
|
||||
private MonitoringUserRole() {
|
||||
super(DESCRIPTOR.getName(),
|
||||
Core.NONE,
|
||||
new IndicesPermission.Core(Role.Builder.convertFromIndicesPrivileges(DESCRIPTOR.getIndicesPrivileges())),
|
||||
RunAsPermission.Core.NONE);
|
||||
}
|
||||
}
|
|
@ -1,15 +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;
|
||||
|
||||
/**
|
||||
* Represents a permission in the system.
|
||||
*/
|
||||
public interface Permission {
|
||||
|
||||
boolean isEmpty();
|
||||
|
||||
}
|
|
@ -1,36 +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;
|
||||
|
||||
/**
|
||||
* Built-in role that grants the necessary privileges for a remote monitoring agent.
|
||||
*/
|
||||
public class RemoteMonitoringAgentRole extends Role {
|
||||
|
||||
private static final String[] CLUSTER_PRIVILEGES = new String[] { "manage_index_templates", "manage_ingest_pipelines", "monitor" };
|
||||
private static final RoleDescriptor.IndicesPrivileges[] INDICES_PRIVILEGES = new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
.indices(".marvel-es-*", ".monitoring-*")
|
||||
.privileges("all")
|
||||
.build() };
|
||||
|
||||
public static final String NAME = "remote_monitoring_agent";
|
||||
public static final RoleDescriptor DESCRIPTOR =
|
||||
new RoleDescriptor(NAME, CLUSTER_PRIVILEGES, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final RemoteMonitoringAgentRole INSTANCE = new RemoteMonitoringAgentRole();
|
||||
|
||||
private RemoteMonitoringAgentRole() {
|
||||
super(DESCRIPTOR.getName(),
|
||||
new ClusterPermission.Core(ClusterPrivilege.get(new Name(DESCRIPTOR.getClusterPrivileges()))),
|
||||
new IndicesPermission.Core(Role.Builder.convertFromIndicesPrivileges(DESCRIPTOR.getIndicesPrivileges())),
|
||||
RunAsPermission.Core.NONE);
|
||||
}
|
||||
}
|
|
@ -1,34 +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.permission.ClusterPermission.Core;
|
||||
import org.elasticsearch.xpack.security.support.MetadataUtils;
|
||||
|
||||
/**
|
||||
* A role for users of the reporting features in xpack
|
||||
*/
|
||||
public class ReportingUserRole extends Role {
|
||||
private static final RoleDescriptor.IndicesPrivileges[] INDICES_PRIVILEGES = new RoleDescriptor.IndicesPrivileges[] {
|
||||
RoleDescriptor.IndicesPrivileges.builder()
|
||||
.indices(".reporting-*")
|
||||
.privileges("read", "write")
|
||||
.build()
|
||||
};
|
||||
|
||||
public static final String NAME = "reporting_user";
|
||||
public static final RoleDescriptor DESCRIPTOR =
|
||||
new RoleDescriptor(NAME, null, INDICES_PRIVILEGES, null, MetadataUtils.DEFAULT_RESERVED_METADATA);
|
||||
public static final ReportingUserRole INSTANCE = new ReportingUserRole();
|
||||
|
||||
private ReportingUserRole() {
|
||||
super(DESCRIPTOR.getName(),
|
||||
Core.NONE,
|
||||
new IndicesPermission.Core(Role.Builder.convertFromIndicesPrivileges(DESCRIPTOR.getIndicesPrivileges())),
|
||||
RunAsPermission.Core.NONE);
|
||||
}
|
||||
}
|
|
@ -5,110 +5,155 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authz.permission;
|
||||
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
||||
import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl;
|
||||
import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl;
|
||||
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.privilege.Privilege;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class Role extends GlobalPermission {
|
||||
public final class Role {
|
||||
|
||||
public static final Role EMPTY = Role.builder("__empty").build();
|
||||
|
||||
private final String name;
|
||||
private final ClusterPermission cluster;
|
||||
private final IndicesPermission indices;
|
||||
private final RunAsPermission runAs;
|
||||
|
||||
Role(String name, ClusterPermission.Core cluster, IndicesPermission.Core indices, RunAsPermission.Core runAs) {
|
||||
super(cluster, indices, runAs);
|
||||
Role(String name, ClusterPermission cluster, IndicesPermission indices, RunAsPermission runAs) {
|
||||
this.name = name;
|
||||
this.cluster = Objects.requireNonNull(cluster);
|
||||
this.indices = Objects.requireNonNull(indices);
|
||||
this.runAs = Objects.requireNonNull(runAs);
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClusterPermission.Core cluster() {
|
||||
return (ClusterPermission.Core) super.cluster();
|
||||
public ClusterPermission cluster() {
|
||||
return cluster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndicesPermission.Core indices() {
|
||||
return (IndicesPermission.Core) super.indices();
|
||||
public IndicesPermission indices() {
|
||||
return indices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RunAsPermission.Core runAs() {
|
||||
return (RunAsPermission.Core) super.runAs();
|
||||
public RunAsPermission runAs() {
|
||||
return runAs;
|
||||
}
|
||||
|
||||
public static Builder builder(String name) {
|
||||
return new Builder(name);
|
||||
return new Builder(name, null);
|
||||
}
|
||||
|
||||
public static Builder builder(RoleDescriptor rd) {
|
||||
return new Builder(rd);
|
||||
public static Builder builder(String name, FieldPermissionsCache fieldPermissionsCache) {
|
||||
return new Builder(name, fieldPermissionsCache);
|
||||
}
|
||||
|
||||
public static Builder builder(RoleDescriptor rd, FieldPermissionsCache fieldPermissionsCache) {
|
||||
return new Builder(rd, fieldPermissionsCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<String> requestedIndicesOrAliases, MetaData metaData,
|
||||
FieldPermissionsCache fieldPermissionsCache) {
|
||||
Map<String, IndexAccessControl> 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<String, IndicesAccessControl.IndexAccessControl> 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<IndicesPermission.Group> 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<BytesReference> 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<IndicesPermission.Group> convertFromIndicesPrivileges(RoleDescriptor.IndicesPrivileges[] indicesPrivileges) {
|
||||
static List<IndicesPermission.Group> convertFromIndicesPrivileges(RoleDescriptor.IndicesPrivileges[] indicesPrivileges,
|
||||
@Nullable FieldPermissionsCache fieldPermissionsCache) {
|
||||
List<IndicesPermission.Group> 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<BytesReference> query = privilege.getQuery() == null ? null : Collections.singleton(privilege.getQuery());
|
||||
list.add(new IndicesPermission.Group(IndexPrivilege.get(Sets.newHashSet(privilege.getPrivileges())),
|
||||
fieldPermissions,
|
||||
query,
|
||||
privilege.getIndices()));
|
||||
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> 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<GlobalPermission> globals;
|
||||
|
||||
public Globals(List<GlobalPermission> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<P extends AbstractAutomatonPrivilege<P>> extends Privilege<P> {
|
||||
|
||||
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<String> 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();
|
||||
|
||||
|
||||
}
|
|
@ -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<ClusterPrivilege> {
|
||||
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<ClusterPrivileg
|
|||
|
||||
public static final Predicate<String> ACTION_MATCHER = ClusterPrivilege.ALL.predicate();
|
||||
|
||||
private static final Set<ClusterPrivilege> values = new CopyOnWriteArraySet<>();
|
||||
private static final Map<String, ClusterPrivilege> VALUES = MapBuilder.<String, ClusterPrivilege>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<ClusterPrivilege> values() {
|
||||
return values;
|
||||
}
|
||||
|
||||
private static final ConcurrentHashMap<Name, ClusterPrivilege> cache = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentHashMap<Set<String>, 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<String> 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<String> name) {
|
||||
if (name == null || name.isEmpty()) {
|
||||
return NONE;
|
||||
}
|
||||
return CACHE.computeIfAbsent(name, ClusterPrivilege::resolve);
|
||||
}
|
||||
|
||||
private static ClusterPrivilege resolve(Set<String> name) {
|
||||
final int size = name.size();
|
||||
if (size == 0) {
|
||||
throw new IllegalArgumentException("empty set should not be used");
|
||||
}
|
||||
|
||||
Set<String> actions = new HashSet<>();
|
||||
Set<Automaton> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<GeneralPrivilege> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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<IndexPrivilege> {
|
||||
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<IndexPrivilege> {
|
|||
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<IndexPrivilege> {
|
|||
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<IndexPrivilege> 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<String, IndexPrivilege> VALUES = MapBuilder.<String, IndexPrivilege>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<String> ACTION_MATCHER = ALL.predicate();
|
||||
public static final Predicate<String> CREATE_INDEX_MATCHER = CREATE_INDEX.predicate();
|
||||
|
||||
static Set<IndexPrivilege> values() {
|
||||
return values;
|
||||
}
|
||||
|
||||
private static final ConcurrentHashMap<Name, IndexPrivilege> cache = new ConcurrentHashMap<>();
|
||||
|
||||
private IndexPrivilege(String name, String... patterns) {
|
||||
super(name, patterns);
|
||||
}
|
||||
private static final ConcurrentHashMap<Set<String>, 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<String> 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<String> 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<String> 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<String> actions = new HashSet<>();
|
||||
Set<Automaton> 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<String, IndexPrivilege> values() {
|
||||
return VALUES;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<P extends Privilege<P>> {
|
||||
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<String> name;
|
||||
protected final Automaton automaton;
|
||||
protected final Predicate<String> predicate;
|
||||
|
||||
public Privilege(String name, String... patterns) {
|
||||
this(Collections.singleton(name), patterns);
|
||||
}
|
||||
|
||||
public Name name() {
|
||||
public Privilege(Set<String> name, String... patterns) {
|
||||
this(name, patterns(patterns));
|
||||
}
|
||||
|
||||
public Privilege(Set<String> name, Automaton automaton) {
|
||||
this.name = name;
|
||||
this.automaton = automaton;
|
||||
this.predicate = Automatons.predicate(automaton);
|
||||
}
|
||||
|
||||
public Set<String> name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public abstract Predicate<String> predicate();
|
||||
|
||||
public abstract boolean implies(P other);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean isAlias(P other) {
|
||||
return this.implies(other) && other.implies((P) this);
|
||||
public Predicate<String> predicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -58,54 +66,12 @@ public abstract class Privilege<P extends Privilege<P>> {
|
|||
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<String> parts;
|
||||
|
||||
public Name(String name) {
|
||||
assert name != null && !name.contains(",");
|
||||
parts = singleton(name);
|
||||
}
|
||||
|
||||
public Name(Set<String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<SystemPrivilege> {
|
||||
public final class SystemPrivilege extends Privilege {
|
||||
|
||||
public static SystemPrivilege INSTANCE = new SystemPrivilege();
|
||||
|
||||
protected static final Predicate<String> PREDICATE = new AutomatonPredicate(patterns(
|
||||
private static final Predicate<String> 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<String> predicate() {
|
||||
return PREDICATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean implies(SystemPrivilege other) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Integer> 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<Set<String>, Role> roleCache;
|
||||
private final Set<String> 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<Set<String>, 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<Role> roleActionListener) {
|
||||
Role storedRole = getBuildInRole(role);
|
||||
if (storedRole == null) {
|
||||
nativeRolesStore.role(role, roleActionListener);
|
||||
public void roles(Set<String> roleNames, FieldPermissionsCache fieldPermissionsCache, ActionListener<Role> 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<String> roleNames, ActionListener<Set<RoleDescriptor>> roleDescriptorActionListener) {
|
||||
final Set<String> filteredRoleNames =
|
||||
roleNames.stream().filter((s) -> negativeLookupCache.contains(s) == false).collect(Collectors.toSet());
|
||||
final Set<RoleDescriptor> builtInRoleDescriptors = getBuiltInRoleDescriptors(filteredRoleNames);
|
||||
Set<String> 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<String> 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<RoleDescriptor> getBuiltInRoleDescriptors(Set<String> roleNames) {
|
||||
final Set<RoleDescriptor> descriptors = reservedRolesStore.roleDescriptors().stream()
|
||||
.filter((rd) -> roleNames.contains(rd.getName()))
|
||||
.collect(Collectors.toCollection(HashSet::new));
|
||||
|
||||
final Set<String> difference = difference(roleNames, descriptors);
|
||||
if (difference.isEmpty() == false) {
|
||||
descriptors.addAll(fileRolesStore.roleDescriptors(difference));
|
||||
}
|
||||
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
private Set<String> difference(Set<String> roleNames, Set<RoleDescriptor> descriptors) {
|
||||
Set<String> foundNames = descriptors.stream().map(RoleDescriptor::getName).collect(Collectors.toSet());
|
||||
return Sets.difference(roleNames, foundNames);
|
||||
}
|
||||
|
||||
public static Role buildRoleFromDescriptors(Set<RoleDescriptor> roleDescriptors, FieldPermissionsCache fieldPermissionsCache) {
|
||||
if (roleDescriptors.isEmpty()) {
|
||||
return Role.EMPTY;
|
||||
}
|
||||
StringBuilder nameBuilder = new StringBuilder();
|
||||
Set<String> clusterPrivileges = new HashSet<>();
|
||||
Set<String> runAs = new HashSet<>();
|
||||
Map<Set<String>, 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<String> 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<String> 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<Set<String>> keyIter = roleCache.keys().iterator();
|
||||
while (keyIter.hasNext()) {
|
||||
Set<String> key = keyIter.next();
|
||||
if (key.contains(role)) {
|
||||
keyIter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
negativeLookupCache.remove(role);
|
||||
}
|
||||
|
||||
public Map<String, Object> usageStats() {
|
||||
Map<String, Object> 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<String> indices;
|
||||
private Set<String> privileges;
|
||||
private Set<String> grantedFields = null;
|
||||
private Set<String> deniedFields = null;
|
||||
private Set<BytesReference> 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<String> combineFieldSets(Set<String> set, Set<String> other) {
|
||||
if (set == null || other == null) {
|
||||
// null = grant all so it trumps others
|
||||
return null;
|
||||
} else {
|
||||
set.addAll(other);
|
||||
return set;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Runnable> listeners = new ArrayList<>();
|
||||
|
||||
private volatile Map<String, Role> permissions;
|
||||
private volatile Map<String, RoleDescriptor> 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<RoleDescriptor> roleDescriptors(Set<String> roleNames) {
|
||||
Set<RoleDescriptor> descriptors = new HashSet<>();
|
||||
roleNames.forEach((name) -> {
|
||||
RoleDescriptor descriptor = permissions.get(name);
|
||||
if (descriptor != null) {
|
||||
descriptors.add(descriptor);
|
||||
}
|
||||
});
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public Map<String, Object> 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<String> parseFileForRoleNames(Path path, Logger logger) {
|
||||
Map<String, Role> roleMap = parseFile(path, logger, false, Settings.EMPTY);
|
||||
Map<String, RoleDescriptor> roleMap = parseFile(path, logger, false, Settings.EMPTY);
|
||||
if (roleMap == null) {
|
||||
return emptySet();
|
||||
}
|
||||
return roleMap.keySet();
|
||||
}
|
||||
|
||||
public static Map<String, Role> parseFile(Path path, Logger logger, Settings settings) {
|
||||
public static Map<String, RoleDescriptor> parseFile(Path path, Logger logger, Settings settings) {
|
||||
return parseFile(path, logger, true, settings);
|
||||
}
|
||||
|
||||
public static Map<String, Role> parseFile(Path path, Logger logger, boolean resolvePermission, Settings settings) {
|
||||
public static Map<String, RoleDescriptor> parseFile(Path path, Logger logger, boolean resolvePermission,
|
||||
Settings settings) {
|
||||
if (logger == null) {
|
||||
logger = NoOpLogger.INSTANCE;
|
||||
}
|
||||
|
||||
Map<String, Role> roles = new HashMap<>();
|
||||
Map<String, RoleDescriptor> roles = new HashMap<>();
|
||||
logger.debug("attempting to read roles file located at [{}]", path.toAbsolutePath());
|
||||
if (Files.exists(path)) {
|
||||
try {
|
||||
List<String> 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<String> roleSegments(Path path) throws IOException {
|
||||
List<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Integer> CACHE_SIZE_SETTING =
|
||||
Setting.intSetting(setting("authz.store.roles.index.cache.max_size"), 10000, Property.NodeScope);
|
||||
private static final Setting<TimeValue> 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<TimeValue> 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> state = new AtomicReference<>(State.INITIALIZED);
|
||||
private final boolean isTribeNode;
|
||||
private final Cache<String, RoleAndVersion> 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.<String, RoleAndVersion>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<Collection<RoleDescriptor>> 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<Role> listener) {
|
||||
if (state() != State.STARTED) {
|
||||
listener.onResponse(null);
|
||||
} else {
|
||||
getRoleAndVersion(roleName, new ActionListener<RoleAndVersion>() {
|
||||
@Override
|
||||
public void onResponse(RoleAndVersion roleAndVersion) {
|
||||
listener.onResponse(roleAndVersion == null ? null : roleAndVersion.getRole());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
listener.onFailure(e);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> 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<RoleAndVersion> roleActionListener) {
|
||||
private void getRoleDescriptor(final String roleId, ActionListener<RoleDescriptor> 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<GetResponse>() {
|
||||
@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<GetResponse>() {
|
||||
@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<GetResponse> listener) {
|
||||
private void executeGetRoleRequest(String role, ActionListener<GetResponse> 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 <Response> void clearRoleCache(final String role, ActionListener<Response> listener, Response response) {
|
||||
ClearRolesCacheRequest request = new ClearRolesCacheRequest().names(role);
|
||||
securityClient.clearRolesCache(request, new ActionListener<ClearRolesCacheResponse>() {
|
||||
|
@ -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<Setting<?>> settings) {
|
||||
settings.add(CACHE_SIZE_SETTING);
|
||||
settings.add(CACHE_TTL_SETTING);
|
||||
|
|
|
@ -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<String, RoleDescriptor> 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<String, RoleDescriptor> initializeReservedRoles() {
|
||||
return MapBuilder.<String, RoleDescriptor>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<String, Object> 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<RoleDescriptor> 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<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<String> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<Automaton> 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<String> predicate(String... patterns) {
|
||||
return predicate(Arrays.asList(patterns));
|
||||
}
|
||||
|
||||
public static Predicate<String> predicate(Collection<String> patterns) {
|
||||
return predicate(patterns(patterns));
|
||||
}
|
||||
|
||||
public static Predicate<String> predicate(Automaton automaton) {
|
||||
CharacterRunAutomaton runAutomaton = new CharacterRunAutomaton(automaton, DEFAULT_MAX_DETERMINIZED_STATES);
|
||||
return runAutomaton::run;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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<Role> future = new PlainActionFuture<>();
|
||||
rolesStore.role(role, future);
|
||||
assertThat(future.actionGet(), notNullValue());
|
||||
}
|
||||
PlainActionFuture<Collection<RoleDescriptor>> 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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<String> names = randomSubsetOf(size, ReservedRolesStore.names());
|
||||
|
||||
|
@ -101,15 +87,11 @@ public class TransportGetRolesActionTests extends ESTestCase {
|
|||
public void testStoreRoles() {
|
||||
final List<RoleDescriptor> 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<String> 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<RoleDescriptor> 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),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<RoleDescriptor> existingRoles = Arrays.asList(c.prepareGetRoles().get().roles());
|
||||
final int existing = existingRoles.size();
|
||||
final Map<String, Object> metadata = Collections.singletonMap("key", (Object) randomAsciiOfLengthBetween(1, 10));
|
||||
final Map<String, Object> 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++;
|
||||
|
|
|
@ -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<String, Role> roleMap = new HashMap<>();
|
||||
private Map<String, RoleDescriptor> 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<Role> callback =
|
||||
(ActionListener<Role>) i.getArguments()[2];
|
||||
Set<String> names = (Set<String>) i.getArguments()[0];
|
||||
assertNotNull(names);
|
||||
Set<RoleDescriptor> 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<Collection<Role>> rolesFuture = new PlainActionFuture<>();
|
||||
PlainActionFuture<Role> rolesFuture = new PlainActionFuture<>();
|
||||
authorizationService.roles(new User("no role user"), rolesFuture);
|
||||
final Collection<Role> 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<Collection<Role>> rolesFuture = new PlainActionFuture<>();
|
||||
PlainActionFuture<Role> rolesFuture = new PlainActionFuture<>();
|
||||
authorizationService.roles(new User("no role user"), rolesFuture);
|
||||
final Collection<Role> 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<Collection<Role>> rolesFuture = new PlainActionFuture<>();
|
||||
authorizationService.roles(new User("user with role", "role"), rolesFuture);
|
||||
final Collection<Role> 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<String, TransportRequest> randomCompositeRequest() {
|
||||
switch(randomIntBetween(0, 7)) {
|
||||
case 0:
|
||||
|
@ -833,57 +979,13 @@ public class AuthorizationServiceTests extends ESTestCase {
|
|||
}
|
||||
|
||||
public void testDoesNotUseRolesStoreForXPackUser() {
|
||||
PlainActionFuture<Collection<Role>> rolesFuture = new PlainActionFuture<>();
|
||||
PlainActionFuture<Role> rolesFuture = new PlainActionFuture<>();
|
||||
authorizationService.roles(XPackUser.INSTANCE, rolesFuture);
|
||||
final Collection<Role> 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<Collection<Role>> rolesFuture = new PlainActionFuture<>();
|
||||
authorizationService.roles(user, rolesFuture);
|
||||
final Collection<Role> 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<String, IndexAccessControl> 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));
|
||||
|
|
|
@ -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<String> 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<Role> 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<String> 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<Role> 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<String> list = authorizedIndices.get();
|
||||
assertTrue(list.isEmpty());
|
||||
}
|
||||
|
|
|
@ -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<String, String> 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() {
|
||||
|
|
|
@ -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<String, Role> roleMap;
|
||||
private Map<String, RoleDescriptor> 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<String> names = (Set<String>) i.getArguments()[0];
|
||||
assertNotNull(names);
|
||||
Set<RoleDescriptor> 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("<datetime-{now/M}>");
|
||||
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("<datetime-{now/M}>");
|
||||
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("<datetime-{now/M}>")};
|
||||
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("<datetime-{now/M}>").indices("foo", "foofoo");
|
||||
Set<String> 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<Collection<Role>> rolesListener = new PlainActionFuture<>();
|
||||
PlainActionFuture<Role> rolesListener = new PlainActionFuture<>();
|
||||
authzService.roles(user, rolesListener);
|
||||
return new AuthorizedIndices(user, rolesListener.actionGet(), action, metaData);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
};
|
||||
|
|
|
@ -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<BytesReference> notGrantedQueries = randomFrom(Collections.<BytesReference>emptySet(), null,
|
||||
Collections.<BytesReference>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<BytesReference> queries =
|
||||
randomFrom(Collections.singleton(query1), Collections.<BytesReference>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<BytesReference> notGrantedQueries = randomFrom(Collections.<BytesReference>emptySet(), null,
|
||||
Collections.<BytesReference>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<BytesReference> queries =
|
||||
randomFrom(Collections.singleton(query1), Collections.<BytesReference>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)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<BytesReference> 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<String, IndicesAccessControl.IndexAccessControl> 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"));
|
||||
|
|
|
@ -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<IndicesPermission.Group> 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);
|
||||
}
|
||||
}
|
|
@ -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<Integer> hashCodes = new AtomicReferenceArray<>(numThreads);
|
||||
List<Thread> threads = new ArrayList<>(numThreads);
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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.<GlobalPermission>unmodifiableList(Arrays.asList(noIndicesPermission, permission)));
|
||||
Iterator<IndicesPermission.Group> 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));
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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<String, IndexAccessControl> 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));
|
||||
}
|
||||
}
|
|
@ -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<String> 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<String> 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<String> 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<String> 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<String> actionName = Sets.newHashSet("foobar");
|
||||
ClusterPrivilege.get(actionName);
|
||||
}
|
||||
|
||||
public void testClusterAction() throws Exception {
|
||||
Privilege.Name actionName = new Privilege.Name("cluster:admin/snapshot/delete");
|
||||
Set<String> 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<String> 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<String> 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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Set<RoleDescriptor>> callback = (ActionListener<Set<RoleDescriptor>>) 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<Role> 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<String> 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);
|
||||
}
|
||||
}
|
|
@ -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<String, Role> roles = FileRolesStore.parseFile(path, logger, Settings.builder()
|
||||
Map<String, RoleDescriptor> 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<String, Role> roles = FileRolesStore.parseFile(path, logger, Settings.builder()
|
||||
Map<String, RoleDescriptor> 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<String, Role> roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY);
|
||||
Map<String, RoleDescriptor> 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<RoleDescriptor> 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<String, Role> roles = FileRolesStore.parseFile(file, logger, Settings.EMPTY);
|
||||
Map<String, RoleDescriptor> 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<String, Role> roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY);
|
||||
Map<String, RoleDescriptor> 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<String, Role> roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY);
|
||||
Map<String, RoleDescriptor> 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<String, Object> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<GetResponse> 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<Role> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, IndexAccessControl> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Void> 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<Void> 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<Role> 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue