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:
parent
2ca729afc2
commit
d48ad6dbd6
|
@ -82,7 +82,9 @@ import org.elasticsearch.xpack.extensions.XPackExtension;
|
|||
import org.elasticsearch.xpack.extensions.XPackExtensionsService;
|
||||
import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter;
|
||||
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.ResizeRequestInterceptor;
|
||||
import org.elasticsearch.xpack.security.action.interceptor.SearchRequestInterceptor;
|
||||
import org.elasticsearch.xpack.security.action.interceptor.UpdateRequestInterceptor;
|
||||
import org.elasticsearch.xpack.security.action.realm.ClearRealmCacheAction;
|
||||
|
@ -433,10 +435,12 @@ public class Security implements ActionPlugin, IngestPlugin, NetworkPlugin, Clus
|
|||
|
||||
final Set<RequestInterceptor> requestInterceptors;
|
||||
if (XPackSettings.DLS_FLS_ENABLED.get(settings)) {
|
||||
requestInterceptors = Sets.newHashSet(
|
||||
requestInterceptors = Collections.unmodifiableSet(Sets.newHashSet(
|
||||
new SearchRequestInterceptor(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 {
|
||||
requestInterceptors = Collections.emptySet();
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import org.elasticsearch.action.support.ActionFilterChain;
|
|||
import org.elasticsearch.action.support.ContextPreservingActionListener;
|
||||
import org.elasticsearch.action.support.DestructiveOperations;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.license.LicenseUtils;
|
||||
|
@ -56,7 +55,6 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
|
|||
private final SecurityContext securityContext;
|
||||
private final DestructiveOperations destructiveOperations;
|
||||
|
||||
@Inject
|
||||
public SecurityActionFilter(Settings settings, AuthenticationService authcService, AuthorizationService authzService,
|
||||
XPackLicenseState licenseState, Set<RequestInterceptor> requestInterceptors, ThreadPool threadPool,
|
||||
SecurityContext securityContext, DestructiveOperations destructiveOperations) {
|
||||
|
@ -166,13 +164,13 @@ public class SecurityActionFilter extends AbstractComponent implements ActionFil
|
|||
authzService.authorize(authentication, securityAction, request, userRoles, runAsRoles);
|
||||
final User user = authentication.getUser();
|
||||
|
||||
/*
|
||||
* We use a separate concept for code that needs to be run after authentication and authorization that could
|
||||
* affect the running of the action. This is done to make it more clear of the state of the request.
|
||||
*/
|
||||
/*
|
||||
* We use a separate concept for code that needs to be run after authentication and authorization that could
|
||||
* affect the running of the action. This is done to make it more clear of the state of the request.
|
||||
*/
|
||||
for (RequestInterceptor interceptor : requestInterceptors) {
|
||||
if (interceptor.supports(request)) {
|
||||
interceptor.intercept(request, user);
|
||||
interceptor.intercept(request, user, runAsRoles != null ? runAsRoles : userRoles, securityAction);
|
||||
}
|
||||
}
|
||||
listener.onResponse(null);
|
||||
|
|
|
@ -10,7 +10,6 @@ import org.elasticsearch.action.bulk.BulkItemRequest;
|
|||
import org.elasticsearch.action.bulk.BulkShardRequest;
|
||||
import org.elasticsearch.action.update.UpdateRequest;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.license.XPackLicenseState;
|
||||
|
@ -19,6 +18,7 @@ import org.elasticsearch.threadpool.ThreadPool;
|
|||
import org.elasticsearch.transport.TransportRequest;
|
||||
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.user.User;
|
||||
|
||||
/**
|
||||
|
@ -29,14 +29,13 @@ public class BulkShardRequestInterceptor extends AbstractComponent implements Re
|
|||
private final ThreadContext threadContext;
|
||||
private final XPackLicenseState licenseState;
|
||||
|
||||
@Inject
|
||||
public BulkShardRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) {
|
||||
super(settings);
|
||||
this.threadContext = threadPool.getThreadContext();
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext;
|
|||
import org.elasticsearch.license.XPackLicenseState;
|
||||
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.user.User;
|
||||
|
||||
/**
|
||||
|
@ -31,7 +32,7 @@ abstract class FieldAndDocumentLevelSecurityRequestInterceptor<Request extends I
|
|||
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) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
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.transport.TransportRequest;
|
||||
|
||||
|
@ -17,7 +18,7 @@ public interface RequestInterceptor<Request> {
|
|||
* If {@link #supports(TransportRequest)} returns <code>true</code> this interceptor will introspect the request
|
||||
* 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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ package org.elasticsearch.xpack.security.action.interceptor;
|
|||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.license.XPackLicenseState;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
@ -19,7 +18,6 @@ import org.elasticsearch.transport.TransportRequest;
|
|||
*/
|
||||
public class SearchRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor<SearchRequest> {
|
||||
|
||||
@Inject
|
||||
public SearchRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) {
|
||||
super(settings, threadPool.getThreadContext(), licenseState);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ package org.elasticsearch.xpack.security.action.interceptor;
|
|||
|
||||
import org.elasticsearch.ElasticsearchSecurityException;
|
||||
import org.elasticsearch.action.update.UpdateRequest;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.license.XPackLicenseState;
|
||||
import org.elasticsearch.rest.RestStatus;
|
||||
|
@ -23,7 +22,6 @@ import org.elasticsearch.transport.TransportRequest;
|
|||
*/
|
||||
public class UpdateRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor<UpdateRequest> {
|
||||
|
||||
@Inject
|
||||
public UpdateRequestInterceptor(Settings settings, ThreadPool threadPool, XPackLicenseState licenseState) {
|
||||
super(settings, threadPool.getThreadContext(), licenseState);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
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.IndexMetaData;
|
||||
import org.elasticsearch.cluster.metadata.MetaData;
|
||||
|
@ -90,6 +91,16 @@ public final class IndicesPermission implements Iterable<IndicesPermission.Group
|
|||
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
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue