Enhance security for alias and resize operations (elastic/x-pack-elasticsearch#3302)

This commit adds additional checks around resize operations and alias creation operations to
add an extra layer of security around these APIs.

Original commit: elastic/x-pack-elasticsearch@b79f16673c
This commit is contained in:
Jay Modi 2017-12-12 12:01:27 -07:00 committed by GitHub
parent 2ca729afc2
commit d48ad6dbd6
12 changed files with 402 additions and 18 deletions

View File

@ -82,7 +82,9 @@ import org.elasticsearch.xpack.extensions.XPackExtension;
import org.elasticsearch.xpack.extensions.XPackExtensionsService; import org.elasticsearch.xpack.extensions.XPackExtensionsService;
import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter; import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter;
import org.elasticsearch.xpack.security.action.interceptor.BulkShardRequestInterceptor; import org.elasticsearch.xpack.security.action.interceptor.BulkShardRequestInterceptor;
import org.elasticsearch.xpack.security.action.interceptor.IndicesAliasesRequestInterceptor;
import org.elasticsearch.xpack.security.action.interceptor.RequestInterceptor; import org.elasticsearch.xpack.security.action.interceptor.RequestInterceptor;
import org.elasticsearch.xpack.security.action.interceptor.ResizeRequestInterceptor;
import org.elasticsearch.xpack.security.action.interceptor.SearchRequestInterceptor; import org.elasticsearch.xpack.security.action.interceptor.SearchRequestInterceptor;
import org.elasticsearch.xpack.security.action.interceptor.UpdateRequestInterceptor; import org.elasticsearch.xpack.security.action.interceptor.UpdateRequestInterceptor;
import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheAction; import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheAction;
@ -433,10 +435,12 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus
final Set<RequestInterceptor> requestInterceptors; final Set<RequestInterceptor> requestInterceptors;
if (XPackSettings.DLS_FLS_ENABLED.get(settings)) { if (XPackSettings.DLS_FLS_ENABLED.get(settings)) {
requestInterceptors = Sets.newHashSet( requestInterceptors = Collections.unmodifiableSet(Sets.newHashSet(
new SearchRequestInterceptor(settings, threadPool, licenseState), new SearchRequestInterceptor(settings, threadPool, licenseState),
new UpdateRequestInterceptor(settings, threadPool, licenseState), new UpdateRequestInterceptor(settings, threadPool, licenseState),
new BulkShardRequestInterceptor(settings, threadPool, licenseState)); new BulkShardRequestInterceptor(settings, threadPool, licenseState),
new ResizeRequestInterceptor(settings, threadPool, licenseState, auditTrailService),
new IndicesAliasesRequestInterceptor(threadPool.getThreadContext(), licenseState, auditTrailService)));
} else { } else {
requestInterceptors = Collections.emptySet(); requestInterceptors = Collections.emptySet();
} }

View File

@ -18,7 +18,6 @@ import org.elasticsearch.action.support.ActionFilterChain;
import org.elasticsearch.action.support.ContextPreservingActionListener; import org.elasticsearch.action.support.ContextPreservingActionListener;
import org.elasticsearch.action.support.DestructiveOperations; import org.elasticsearch.action.support.DestructiveOperations;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.license.LicenseUtils;
@ -56,7 +55,6 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
private final SecurityContext securityContext; private final SecurityContext securityContext;
private final DestructiveOperations destructiveOperations; private final DestructiveOperations destructiveOperations;
@Inject
public SecurityActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService, public SecurityActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService,
XPackLicenseState licenseState, Set<RequestInterceptor> requestInterceptors, ThreadPool threadPool, XPackLicenseState licenseState, Set<RequestInterceptor> requestInterceptors, ThreadPool threadPool,
SecurityContext securityContext, DestructiveOperations destructiveOperations) { SecurityContext securityContext, DestructiveOperations destructiveOperations) {
@ -172,7 +170,7 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
*/ */
for (RequestInterceptor interceptor : requestInterceptors) { for (RequestInterceptor interceptor : requestInterceptors) {
if (interceptor.supports(request)) { if (interceptor.supports(request)) {
interceptor.intercept(request, user); interceptor.intercept(request, user, runAsRoles != null ? runAsRoles : userRoles, securityAction);
} }
} }
listener.onResponse(null); listener.onResponse(null);

View File

@ -10,7 +10,6 @@ import org.elasticsearch.action.bulk.BulkItemRequest;
import org.elasticsearch.action.bulk.BulkShardRequest; import org.elasticsearch.action.bulk.BulkShardRequest;
import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
@ -19,6 +18,7 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.security.authz.permission.Role;
import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.User;
/** /**
@ -29,14 +29,13 @@ public class BulkShardRequestInterceptor extends AbstractComponent implements Re
private final ThreadContext threadContext; private final ThreadContext threadContext;
private final XPackLicenseState licenseState; private final XPackLicenseState licenseState;
@Inject
public BulkShardRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) { public BulkShardRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) {
super(settings); super(settings);
this.threadContext = threadPool.getThreadContext(); this.threadContext = threadPool.getThreadContext();
this.licenseState = licenseState; this.licenseState = licenseState;
} }
public void intercept(BulkShardRequest request, User user) { public void intercept(BulkShardRequest request, User user, Role userPermissions, String action) {
if (licenseState.isDocumentAndFieldLevelSecurityAllowed() == false) { if (licenseState.isDocumentAndFieldLevelSecurityAllowed() == false) {
return; return;
} }

View File

@ -12,6 +12,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.xpack.security.authz.AuthorizationService; import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.security.authz.permission.Role;
import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.User;
/** /**
@ -31,7 +32,7 @@ abstract class FieldAndDocumentLevelSecurityRequestInterceptor<Request extends I
this.licenseState = licenseState; this.licenseState = licenseState;
} }
public void intercept(Request request, User user) { public void intercept(Request request, User user, Role userPermissions, String action) {
if (licenseState.isDocumentAndFieldLevelSecurityAllowed() == false) { if (licenseState.isDocumentAndFieldLevelSecurityAllowed() == false) {
return; return;
} }

View File

@ -0,0 +1,84 @@
/*
* 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.action.interceptor;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.security.authz.permission.Role;
import org.elasticsearch.xpack.security.support.Exceptions;
import org.elasticsearch.xpack.security.user.User;
import java.util.HashMap;
import java.util.Map;
public final class IndicesAliasesRequestInterceptor implements RequestInterceptor<IndicesAliasesRequest> {
private final ThreadContext threadContext;
private final XPackLicenseState licenseState;
private final AuditTrailService auditTrailService;
public IndicesAliasesRequestInterceptor(ThreadContext threadContext, XPackLicenseState licenseState,
AuditTrailService auditTrailService) {
this.threadContext = threadContext;
this.licenseState = licenseState;
this.auditTrailService = auditTrailService;
}
@Override
public void intercept(IndicesAliasesRequest request, User user, Role userPermissions, String action) {
if (licenseState.isDocumentAndFieldLevelSecurityAllowed()) {
IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationService.INDICES_PERMISSIONS_KEY);
for (IndicesAliasesRequest.AliasActions aliasAction : request.getAliasActions()) {
if (aliasAction.actionType() == IndicesAliasesRequest.AliasActions.Type.ADD) {
for (String index : aliasAction.indices()) {
IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index);
if (indexAccessControl != null) {
final boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity();
final boolean dls = indexAccessControl.getQueries() != null;
if (fls || dls) {
throw new ElasticsearchSecurityException("Alias requests are not allowed for users who have " +
"field or document level security enabled on one of the indices", RestStatus.BAD_REQUEST);
}
}
}
}
}
}
Map<String, Automaton> permissionsMap = new HashMap<>();
for (IndicesAliasesRequest.AliasActions aliasAction : request.getAliasActions()) {
if (aliasAction.actionType() == IndicesAliasesRequest.AliasActions.Type.ADD) {
for (String index : aliasAction.indices()) {
Automaton indexPermissions = permissionsMap.computeIfAbsent(index, userPermissions.indices()::allowedActionsMatcher);
for (String alias : aliasAction.aliases()) {
Automaton aliasPermissions =
permissionsMap.computeIfAbsent(alias, userPermissions.indices()::allowedActionsMatcher);
if (Operations.subsetOf(aliasPermissions, indexPermissions) == false) {
// TODO we've already audited a access granted event so this is going to look ugly
auditTrailService.accessDenied(user, action, request, userPermissions.names());
throw Exceptions.authorizationError("Adding an alias is not allowed when the alias " +
"has more permissions than any of the indices");
}
}
}
}
}
}
@Override
public boolean supports(TransportRequest request) {
return request instanceof IndicesAliasesRequest;
}
}

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.xpack.security.action.interceptor; package org.elasticsearch.xpack.security.action.interceptor;
import org.elasticsearch.xpack.security.authz.permission.Role;
import org.elasticsearch.xpack.security.user.User; import org.elasticsearch.xpack.security.user.User;
import org.elasticsearch.transport.TransportRequest; import org.elasticsearch.transport.TransportRequest;
@ -17,7 +18,7 @@ public interface RequestInterceptor<Request> {
* If {@link #supports(TransportRequest)} returns <code>true</code> this interceptor will introspect the request * If {@link #supports(TransportRequest)} returns <code>true</code> this interceptor will introspect the request
* and potentially modify it. * and potentially modify it.
*/ */
void intercept(Request request, User user); void intercept(Request request, User user, Role userPermissions, String action);
/** /**
* Returns whether this request interceptor should intercept the specified request. * Returns whether this request interceptor should intercept the specified request.

View File

@ -0,0 +1,70 @@
/*
* 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.action.interceptor;
import org.apache.lucene.util.automaton.Automaton;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.security.authz.permission.Role;
import org.elasticsearch.xpack.security.support.Exceptions;
import org.elasticsearch.xpack.security.user.User;
public final class ResizeRequestInterceptor extends AbstractComponent implements RequestInterceptor<ResizeRequest> {
private final ThreadContext threadContext;
private final XPackLicenseState licenseState;
private final AuditTrailService auditTrailService;
public ResizeRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState,
AuditTrailService auditTrailService) {
super(settings);
this.threadContext = threadPool.getThreadContext();
this.licenseState = licenseState;
this.auditTrailService = auditTrailService;
}
@Override
public void intercept(ResizeRequest request, User user, Role userPermissions, String action) {
if (licenseState.isDocumentAndFieldLevelSecurityAllowed()) {
IndicesAccessControl indicesAccessControl = threadContext.getTransient(AuthorizationService.INDICES_PERMISSIONS_KEY);
IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(request.getSourceIndex());
if (indexAccessControl != null) {
final boolean fls = indexAccessControl.getFieldPermissions().hasFieldLevelSecurity();
final boolean dls = indexAccessControl.getQueries() != null;
if (fls || dls) {
throw new ElasticsearchSecurityException("Resize requests are not allowed for users when " +
"field or document level security is enabled on the source index", RestStatus.BAD_REQUEST);
}
}
}
// ensure that the user would have the same level of access OR less on the target index
final Automaton sourceIndexPermissions = userPermissions.indices().allowedActionsMatcher(request.getSourceIndex());
final Automaton targetIndexPermissions = userPermissions.indices().allowedActionsMatcher(request.getTargetIndexRequest().index());
if (Operations.subsetOf(targetIndexPermissions, sourceIndexPermissions) == false) {
// TODO we've already audited a access granted event so this is going to look ugly
auditTrailService.accessDenied(user, action, request, userPermissions.names());
throw Exceptions.authorizationError("Resizing an index is not allowed when the target index " +
"has more permissions than the source index");
}
}
@Override
public boolean supports(TransportRequest request) {
return request instanceof ResizeRequest;
}
}

View File

@ -7,7 +7,6 @@ package org.elasticsearch.xpack.security.action.interceptor;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
@ -19,7 +18,6 @@ import org.elasticsearch.transport.TransportRequest;
*/ */
public class SearchRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor<SearchRequest> { public class SearchRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor<SearchRequest> {
@Inject
public SearchRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) { public SearchRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) {
super(settings, threadPool.getThreadContext(), licenseState); super(settings, threadPool.getThreadContext(), licenseState);
} }

View File

@ -7,7 +7,6 @@ package org.elasticsearch.xpack.security.action.interceptor;
import org.elasticsearch.ElasticsearchSecurityException; import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
@ -23,7 +22,6 @@ import org.elasticsearch.transport.TransportRequest;
*/ */
public class UpdateRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor<UpdateRequest> { public class UpdateRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor<UpdateRequest> {
@Inject
public UpdateRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) { public UpdateRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) {
super(settings, threadPool.getThreadContext(), licenseState); super(settings, threadPool.getThreadContext(), licenseState);
} }

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.xpack.security.authz.permission; package org.elasticsearch.xpack.security.authz.permission;
import org.apache.lucene.util.automaton.Automaton;
import org.elasticsearch.cluster.metadata.AliasOrIndex; import org.elasticsearch.cluster.metadata.AliasOrIndex;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
@ -90,6 +91,16 @@ public final class IndicesPermission implements Iterable<IndicesPermission.Group
return false; return false;
} }
public Automaton allowedActionsMatcher(String index) {
List<Automaton> automatonList = new ArrayList<>();
for (Group group : groups) {
if (group.indexNameMatcher.test(index)) {
automatonList.add(group.privilege.getAutomaton());
}
}
return automatonList.isEmpty() ? Automatons.EMPTY : Automatons.unionAndMinimize(automatonList);
}
/** /**
* Authorizes the provided action against the provided indices, given the current cluster metadata * Authorizes the provided action against the provided indices, given the current cluster metadata
*/ */

View File

@ -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.action.interceptor;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesAction;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.security.authz.permission.FieldPermissions;
import org.elasticsearch.xpack.security.authz.permission.FieldPermissionsDefinition;
import org.elasticsearch.xpack.security.authz.permission.Role;
import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.security.user.User;
import java.util.Collections;
import java.util.Set;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class IndicesAliasesRequestInterceptorTests extends ESTestCase {
public void testInterceptorThrowsWhenFLSDLSEnabled() {
XPackLicenseState licenseState = mock(XPackLicenseState.class);
when(licenseState.isAuditingAllowed()).thenReturn(true);
when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(true);
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
AuditTrailService auditTrailService = new AuditTrailService(Settings.EMPTY, Collections.emptyList(), licenseState);
User user = new User("john", "role");
final FieldPermissions fieldPermissions;
final boolean useFls = randomBoolean();
if (useFls) {
fieldPermissions = new FieldPermissions(new FieldPermissionsDefinition(new String[] { "foo" }, null));
} else {
fieldPermissions = new FieldPermissions();
}
final boolean useDls = (useFls == false) || randomBoolean();
final Set<BytesReference> queries;
if (useDls) {
queries = Collections.singleton(new BytesArray(randomAlphaOfLengthBetween(2, 8)));
} else {
queries = null;
}
Role role = Role.builder().add(fieldPermissions, queries, IndexPrivilege.ALL, "foo").build();
final String action = IndicesAliasesAction.NAME;
IndicesAccessControl accessControl = new IndicesAccessControl(true, Collections.singletonMap("foo",
new IndicesAccessControl.IndexAccessControl(true, fieldPermissions, queries)));
threadContext.putTransient(AuthorizationService.INDICES_PERMISSIONS_KEY, accessControl);
IndicesAliasesRequestInterceptor interceptor =
new IndicesAliasesRequestInterceptor(threadContext, licenseState, auditTrailService);
IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest();
if (randomBoolean()) {
indicesAliasesRequest.addAliasAction(IndicesAliasesRequest.AliasActions.remove().index("bar").alias(randomAlphaOfLength(4)));
}
indicesAliasesRequest.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("foo").alias(randomAlphaOfLength(4)));
if (randomBoolean()) {
indicesAliasesRequest.addAliasAction(IndicesAliasesRequest.AliasActions.removeIndex().index("foofoo"));
}
ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class,
() -> interceptor.intercept(indicesAliasesRequest, user, role, action));
assertEquals("Alias requests are not allowed for users who have field or document level security enabled on one of the indices",
securityException.getMessage());
}
public void testInterceptorThrowsWhenTargetHasGreaterPermissions() throws Exception {
XPackLicenseState licenseState = mock(XPackLicenseState.class);
when(licenseState.isAuditingAllowed()).thenReturn(true);
when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(true);
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
AuditTrailService auditTrailService = new AuditTrailService(Settings.EMPTY, Collections.emptyList(), licenseState);
User user = new User("john", "role");
Role role = Role.builder()
.add(IndexPrivilege.ALL, "alias")
.add(IndexPrivilege.READ, "index")
.build();
final String action = IndicesAliasesAction.NAME;
IndicesAccessControl accessControl = new IndicesAccessControl(true, Collections.emptyMap());
threadContext.putTransient(AuthorizationService.INDICES_PERMISSIONS_KEY, accessControl);
IndicesAliasesRequestInterceptor interceptor =
new IndicesAliasesRequestInterceptor(threadContext, licenseState, auditTrailService);
final IndicesAliasesRequest indicesAliasesRequest = new IndicesAliasesRequest();
if (randomBoolean()) {
indicesAliasesRequest.addAliasAction(IndicesAliasesRequest.AliasActions.remove().index("bar").alias(randomAlphaOfLength(4)));
}
indicesAliasesRequest.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("index").alias("alias"));
if (randomBoolean()) {
indicesAliasesRequest.addAliasAction(IndicesAliasesRequest.AliasActions.removeIndex().index("foofoo"));
}
ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class,
() -> interceptor.intercept(indicesAliasesRequest, user, role, action));
assertEquals("Adding an alias is not allowed when the alias has more permissions than any of the indices",
securityException.getMessage());
// swap target and source for success
final IndicesAliasesRequest successRequest = new IndicesAliasesRequest();
if (randomBoolean()) {
successRequest.addAliasAction(IndicesAliasesRequest.AliasActions.remove().index("bar").alias(randomAlphaOfLength(4)));
}
successRequest.addAliasAction(IndicesAliasesRequest.AliasActions.add().index("alias").alias("index"));
if (randomBoolean()) {
successRequest.addAliasAction(IndicesAliasesRequest.AliasActions.removeIndex().index("foofoo"));
}
interceptor.intercept(successRequest, user, role, action);
}
}

View File

@ -0,0 +1,100 @@
/*
* 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.action.interceptor;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.admin.indices.shrink.ResizeAction;
import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
import org.elasticsearch.action.admin.indices.shrink.ShrinkAction;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.security.authz.permission.FieldPermissions;
import org.elasticsearch.xpack.security.authz.permission.FieldPermissionsDefinition;
import org.elasticsearch.xpack.security.authz.permission.Role;
import org.elasticsearch.xpack.security.authz.privilege.IndexPrivilege;
import org.elasticsearch.xpack.security.user.User;
import java.util.Collections;
import java.util.Set;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ResizeRequestInterceptorTests extends ESTestCase {
public void testResizeRequestInterceptorThrowsWhenFLSDLSEnabled() {
XPackLicenseState licenseState = mock(XPackLicenseState.class);
when(licenseState.isAuditingAllowed()).thenReturn(true);
when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(true);
ThreadPool threadPool = mock(ThreadPool.class);
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
when(threadPool.getThreadContext()).thenReturn(threadContext);
AuditTrailService auditTrailService = new AuditTrailService(Settings.EMPTY, Collections.emptyList(), licenseState);
User user = new User("john", "role");
final FieldPermissions fieldPermissions;
final boolean useFls = randomBoolean();
if (useFls) {
fieldPermissions = new FieldPermissions(new FieldPermissionsDefinition(new String[] { "foo" }, null));
} else {
fieldPermissions = new FieldPermissions();
}
final boolean useDls = (useFls == false) || randomBoolean();
final Set<BytesReference> queries;
if (useDls) {
queries = Collections.singleton(new BytesArray(randomAlphaOfLengthBetween(2, 8)));
} else {
queries = null;
}
Role role = Role.builder().add(fieldPermissions, queries, IndexPrivilege.ALL, "foo").build();
final String action = randomFrom(ShrinkAction.NAME, ResizeAction.NAME);
IndicesAccessControl accessControl = new IndicesAccessControl(true, Collections.singletonMap("foo",
new IndicesAccessControl.IndexAccessControl(true, fieldPermissions, queries)));
threadContext.putTransient(AuthorizationService.INDICES_PERMISSIONS_KEY, accessControl);
ResizeRequestInterceptor resizeRequestInterceptor =
new ResizeRequestInterceptor(Settings.EMPTY, threadPool, licenseState, auditTrailService);
ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class,
() -> resizeRequestInterceptor.intercept(new ResizeRequest("bar", "foo"), user, role, action));
assertEquals("Resize requests are not allowed for users when field or document level security is enabled on the source index",
securityException.getMessage());
}
public void testResizeRequestInterceptorThrowsWhenTargetHasGreaterPermissions() throws Exception {
XPackLicenseState licenseState = mock(XPackLicenseState.class);
when(licenseState.isAuditingAllowed()).thenReturn(true);
when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(true);
ThreadPool threadPool = mock(ThreadPool.class);
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
when(threadPool.getThreadContext()).thenReturn(threadContext);
AuditTrailService auditTrailService = new AuditTrailService(Settings.EMPTY, Collections.emptyList(), licenseState);
User user = new User("john", "role");
Role role = Role.builder()
.add(IndexPrivilege.ALL, "target")
.add(IndexPrivilege.READ, "source")
.build();
final String action = randomFrom(ShrinkAction.NAME, ResizeAction.NAME);
IndicesAccessControl accessControl = new IndicesAccessControl(true, Collections.emptyMap());
threadContext.putTransient(AuthorizationService.INDICES_PERMISSIONS_KEY, accessControl);
ResizeRequestInterceptor resizeRequestInterceptor =
new ResizeRequestInterceptor(Settings.EMPTY, threadPool, licenseState, auditTrailService);
ElasticsearchSecurityException securityException = expectThrows(ElasticsearchSecurityException.class,
() -> resizeRequestInterceptor.intercept(new ResizeRequest("target", "source"), user, role, action));
assertEquals("Resizing an index is not allowed when the target index has more permissions than the source index",
securityException.getMessage());
// swap target and source for success
resizeRequestInterceptor.intercept(new ResizeRequest("source", "target"), user, role, action);
}
}