shield: Enable fls and dls by default

* allow bulk requests with updates if fls/dls isn't configured for targetted index
* throw a hard error if the current call can't resolve an associated transport request
marvel: node stats collector should use the client instead of the internal apis, because otherwise the index searcher wrapper can't locate the transport request that is associated with current call and would then throw an exception, which then prevents the marvel agent from collecting stats.
* if both field or document level security is enabled then all forbidden operations should fail

Original commit: elastic/x-pack-elasticsearch@b2c40d6559
This commit is contained in:
Martijn van Groningen 2015-12-10 13:03:27 +01:00
parent 0a69d22cdc
commit de37a6e3ed
15 changed files with 162 additions and 88 deletions

View File

@ -7,8 +7,10 @@ package org.elasticsearch.marvel.agent.collector.node;
import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest;
import org.elasticsearch.action.admin.indices.stats.CommonStatsFlags;
import org.elasticsearch.bootstrap.BootstrapInfo;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.routing.allocation.decider.DiskThresholdDecider;
@ -20,6 +22,7 @@ import org.elasticsearch.marvel.agent.collector.AbstractCollector;
import org.elasticsearch.marvel.agent.exporter.MarvelDoc;
import org.elasticsearch.marvel.agent.settings.MarvelSettings;
import org.elasticsearch.marvel.license.MarvelLicensee;
import org.elasticsearch.marvel.shield.SecuredClient;
import org.elasticsearch.node.service.NodeService;
import java.util.ArrayList;
@ -38,7 +41,7 @@ public class NodeStatsCollector extends AbstractCollector<NodeStatsCollector> {
public static final String NAME = "node-stats-collector";
public static final String TYPE = "node_stats";
private final NodeService nodeService;
private final Client client;
private final DiscoveryService discoveryService;
private final NodeEnvironment nodeEnvironment;
@ -46,10 +49,10 @@ public class NodeStatsCollector extends AbstractCollector<NodeStatsCollector> {
@Inject
public NodeStatsCollector(Settings settings, ClusterService clusterService, MarvelSettings marvelSettings, MarvelLicensee marvelLicensee,
NodeService nodeService, DiscoveryService discoveryService, NodeEnvironment nodeEnvironment,
SecuredClient client, DiscoveryService discoveryService, NodeEnvironment nodeEnvironment,
DiskThresholdDecider diskThresholdDecider) {
super(settings, NAME, clusterService, marvelSettings, marvelLicensee);
this.nodeService = nodeService;
this.client = client;
this.discoveryService = discoveryService;
this.nodeEnvironment = nodeEnvironment;
this.diskThresholdDecider = diskThresholdDecider;
@ -70,7 +73,14 @@ public class NodeStatsCollector extends AbstractCollector<NodeStatsCollector> {
protected Collection<MarvelDoc> doCollect() throws Exception {
List<MarvelDoc> results = new ArrayList<>(1);
NodeStats nodeStats = nodeService.stats(CommonStatsFlags.ALL, true, true, true, true, true, false, false, false,false, false);
NodesStatsRequest request = new NodesStatsRequest("_local");
request.indices(CommonStatsFlags.ALL);
request.os(true);
request.jvm(true);
request.process(true);
request.threadPool(true);
request.fs(true);
NodeStats nodeStats = client.admin().cluster().nodesStats(request).actionGet().getAt(0);
// Here we are calling directly the DiskThresholdDecider to retrieve the high watermark value
// It would be nicer to use a settings API like documented in #6732

View File

@ -15,6 +15,7 @@ import org.elasticsearch.marvel.agent.collector.AbstractCollectorTestCase;
import org.elasticsearch.marvel.agent.exporter.MarvelDoc;
import org.elasticsearch.marvel.agent.settings.MarvelSettings;
import org.elasticsearch.marvel.license.MarvelLicensee;
import org.elasticsearch.marvel.shield.SecuredClient;
import org.elasticsearch.node.service.NodeService;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
@ -91,7 +92,7 @@ public class NodeStatsCollectorTests extends AbstractCollectorTestCase {
internalCluster().getInstance(ClusterService.class, nodeId),
internalCluster().getInstance(MarvelSettings.class, nodeId),
internalCluster().getInstance(MarvelLicensee.class, nodeId),
internalCluster().getInstance(NodeService.class, nodeId),
internalCluster().getInstance(SecuredClient.class, nodeId),
internalCluster().getInstance(DiscoveryService.class, nodeId),
internalCluster().getInstance(NodeEnvironment.class, nodeId),
internalCluster().getInstance(DiskThresholdDecider.class, nodeId));

View File

@ -316,7 +316,7 @@ public class ShieldPlugin extends Plugin {
}
public static boolean flsDlsEnabled(Settings settings) {
return settings.getAsBoolean(DLS_FLS_ENABLED_SETTING, false);
return settings.getAsBoolean(DLS_FLS_ENABLED_SETTING, true);
}
private void failIfShieldQueryCacheIsNotActive(Settings settings, boolean nodeSettings) {

View File

@ -7,45 +7,49 @@ package org.elasticsearch.shield.action.interceptor;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.DocumentRequest;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.bulk.BulkRequest;
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.rest.RestStatus;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.xpack.XPackPlugin;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authz.InternalAuthorizationService;
import org.elasticsearch.shield.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.transport.TransportRequest;
/**
* Simular to {@link UpdateRequestInterceptor}, but checks if there are update requests embedded in a bulk request.
*/
public class BulkRequestInterceptor extends FieldSecurityRequestInterceptor<BulkRequest> {
public class BulkRequestInterceptor extends AbstractComponent implements RequestInterceptor<BulkRequest> {
@Inject
public BulkRequestInterceptor(Settings settings) {
super(settings);
}
@Override
public void intercept(BulkRequest request, User user) {
// FIXME remove this method override once we support bulk updates with DLS and FLS enabled overall. We'll still
// need this interceptor because individual users may still have FLS/DLS enabled and we'll want to reject only
// their requests. Also update the message to remove "document"
if (ShieldPlugin.flsDlsEnabled(this.settings)) {
disableFeatures(request);
}
}
@Override
protected void disableFeatures(BulkRequest bulkRequest) {
for (ActionRequest actionRequest : bulkRequest.requests()) {
if (actionRequest instanceof UpdateRequest) {
throw new ElasticsearchSecurityException("Can't execute an bulk request with update requests embedded if document and field level security is enabled", RestStatus.BAD_REQUEST);
IndicesAccessControl indicesAccessControl = ((TransportRequest) request).getFromContext(InternalAuthorizationService.INDICES_PERMISSIONS_KEY);
for (IndicesRequest indicesRequest : request.subRequests()) {
for (String index : indicesRequest.indices()) {
IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index);
if (indexAccessControl != null) {
boolean fls = indexAccessControl.getFields() != null;
boolean dls = indexAccessControl.getQueries() != null;
if (fls || dls) {
logger.debug("intercepted request for index [{}] with field level or document level security enabled, disabling features", index);
if (indicesRequest instanceof UpdateRequest) {
throw new ElasticsearchSecurityException("Can't execute an bulk request with update requests embedded if field or document level security is enabled", RestStatus.BAD_REQUEST);
}
}
}
logger.trace("intercepted request for index [{}] with neither field level or document level security not enabled, doing nothing", index);
}
}
}
@Override
public boolean supports(TransportRequest request) {
return request instanceof BulkRequest;

View File

@ -22,9 +22,9 @@ import java.util.List;
* Base class for interceptors that disables features when field level security is configured for indices a request
* is going to execute on.
*/
public abstract class FieldSecurityRequestInterceptor<Request> extends AbstractComponent implements RequestInterceptor<Request> {
public abstract class FieldAndDocumentLevelSecurityRequestInterceptor<Request> extends AbstractComponent implements RequestInterceptor<Request> {
public FieldSecurityRequestInterceptor(Settings settings) {
public FieldAndDocumentLevelSecurityRequestInterceptor(Settings settings) {
super(settings);
}
@ -41,13 +41,16 @@ public abstract class FieldSecurityRequestInterceptor<Request> extends AbstractC
for (IndicesRequest indicesRequest : indicesRequests) {
for (String index : indicesRequest.indices()) {
IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index);
if (indexAccessControl != null && indexAccessControl.getFields() != null) {
logger.debug("intercepted request for index [{}] with field level security enabled, disabling features", index);
disableFeatures(request);
return;
} else {
logger.trace("intercepted request for index [{}] with field level security not enabled, doing nothing", index);
if (indexAccessControl != null) {
boolean fls = indexAccessControl.getFields() != null;
boolean dls = indexAccessControl.getQueries() != null;
if (fls || dls) {
logger.debug("intercepted request for index [{}] with field level or document level security enabled, disabling features", index);
disableFeatures(request);
return;
}
}
logger.trace("intercepted request for index [{}] with neither field level or document level security not enabled, doing nothing", index);
}
}
}

View File

@ -5,26 +5,16 @@
*/
package org.elasticsearch.shield.action.interceptor;
import org.elasticsearch.action.CompositeIndicesRequest;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.RealtimeRequest;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.support.LoggerMessageFormat;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authz.InternalAuthorizationService;
import org.elasticsearch.shield.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.transport.TransportRequest;
import java.util.Collections;
import java.util.List;
/**
* If field level or document level security is enabled this interceptor disables the realtime feature of get, multi get, termsvector and
* multi termsvector requests.
*/
public class RealtimeRequestInterceptor extends AbstractComponent implements RequestInterceptor<RealtimeRequest> {
public class RealtimeRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor<RealtimeRequest> {
@Inject
public RealtimeRequestInterceptor(Settings settings) {
@ -32,28 +22,8 @@ public class RealtimeRequestInterceptor extends AbstractComponent implements Req
}
@Override
public void intercept(RealtimeRequest request, User user) {
List<? extends IndicesRequest> indicesRequests;
if (request instanceof CompositeIndicesRequest) {
indicesRequests = ((CompositeIndicesRequest) request).subRequests();
} else if (request instanceof IndicesRequest) {
indicesRequests = Collections.singletonList((IndicesRequest) request);
} else {
throw new IllegalArgumentException(LoggerMessageFormat.format("Expected a request of type [{}] or [{}] but got [{}] instead", CompositeIndicesRequest.class, IndicesRequest.class, request.getClass()));
}
IndicesAccessControl indicesAccessControl = ((TransportRequest) request).getFromContext(InternalAuthorizationService.INDICES_PERMISSIONS_KEY);
for (IndicesRequest indicesRequest : indicesRequests) {
for (String index : indicesRequest.indices()) {
IndicesAccessControl.IndexAccessControl indexAccessControl = indicesAccessControl.getIndexPermissions(index);
if (indexAccessControl != null && (indexAccessControl.getFields() != null || indexAccessControl.getQueries() != null)) {
logger.debug("intercepted request for index [{}] with field level or document level security enabled, forcefully disabling realtime", index);
request.realtime(false);
return;
} else {
logger.trace("intercepted request for index [{}] with field level security and document level not enabled, doing nothing", index);
}
}
}
protected void disableFeatures(RealtimeRequest realtimeRequest) {
realtimeRequest.realtime(false);
}
@Override

View File

@ -13,7 +13,7 @@ import org.elasticsearch.transport.TransportRequest;
/**
* If field level security is enabled this interceptor disables the request cache for search requests.
*/
public class SearchRequestInterceptor extends FieldSecurityRequestInterceptor<SearchRequest> {
public class SearchRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor<SearchRequest> {
@Inject
public SearchRequestInterceptor(Settings settings) {

View File

@ -13,13 +13,13 @@ import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.transport.TransportRequest;
/**
* A request interceptor that fails update request if field level security is enabled.
* A request interceptor that fails update request if field or document level security is enabled.
*
* It can be dangerous for users if document where to be update via a role that has field level security enabled,
* It can be dangerous for users if document where to be update via a role that has fls or dls enabled,
* because only the fields that a role can see would be used to perform the update and without knowing the user may
* remove the other fields, not visible for him, from the document being updated.
*/
public class UpdateRequestInterceptor extends FieldSecurityRequestInterceptor<UpdateRequest> {
public class UpdateRequestInterceptor extends FieldAndDocumentLevelSecurityRequestInterceptor<UpdateRequest> {
@Inject
public UpdateRequestInterceptor(Settings settings) {
@ -28,7 +28,7 @@ public class UpdateRequestInterceptor extends FieldSecurityRequestInterceptor<Up
@Override
protected void disableFeatures(UpdateRequest updateRequest) {
throw new ElasticsearchSecurityException("Can't execute an update request if field level security is enabled", RestStatus.BAD_REQUEST);
throw new ElasticsearchSecurityException("Can't execute an update request if field or document level security is enabled", RestStatus.BAD_REQUEST);
}
@Override

View File

@ -7,6 +7,8 @@ package org.elasticsearch.shield.authz.accesscontrol;
import org.elasticsearch.transport.TransportRequest;
import java.util.Objects;
/**
* A thread local based holder of the currnet {@link TransportRequest} instance.
*/
@ -40,7 +42,7 @@ public final class RequestContext {
private final TransportRequest request;
public RequestContext(TransportRequest request) {
this.request = request;
this.request = Objects.requireNonNull(request);
}
/**

View File

@ -83,8 +83,7 @@ public class ShieldIndexSearcherWrapper extends IndexSearcherWrapper {
try {
RequestContext context = RequestContext.current();
if (context == null) {
logger.debug("couldn't locate the current request, field level security will only allow meta fields");
return FieldSubsetReader.wrap(reader, allowedMetaFields);
throw new IllegalStateException("can't locate the origin of the current request");
}
IndicesAccessControl indicesAccessControl = context.getRequest().getFromContext(InternalAuthorizationService.INDICES_PERMISSIONS_KEY);

View File

@ -10,6 +10,7 @@ import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.Node;
import org.elasticsearch.shield.ShieldPlugin;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.test.ShieldIntegTestCase;
@ -29,7 +30,7 @@ public class BulkUpdateTests extends ShieldIntegTestCase {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
.put(Node.HTTP_ENABLED, true)
//.put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, false) //FIXME randomize once DLS/FLS works with Bulk updates...
.put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, randomBoolean())
.build();
}

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.integration;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.percolate.PercolateResponse;
@ -13,8 +14,11 @@ import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.termvectors.MultiTermVectorsResponse;
import org.elasticsearch.action.termvectors.TermVectorsRequest;
import org.elasticsearch.action.termvectors.TermVectorsResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.indices.cache.request.IndicesRequestCache;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.children.Children;
import org.elasticsearch.search.aggregations.bucket.global.Global;
@ -459,4 +463,81 @@ public class DocumentLevelSecurityTests extends ShieldIntegTestCase {
assertThat(response.getMatches()[0].getId().string(), equalTo("1"));
}
public void testRequestCache() throws Exception {
assertAcked(client().admin().indices().prepareCreate("test")
.setSettings(Settings.builder().put(IndicesRequestCache.INDEX_CACHE_REQUEST_ENABLED, true))
.addMapping("type1", "field1", "type=string", "field2", "type=string")
);
client().prepareIndex("test", "type1", "1").setSource("field1", "value1")
.get();
client().prepareIndex("test", "type1", "2").setSource("field2", "value2")
.get();
refresh();
int max = scaledRandomIntBetween(4, 32);
for (int i = 0; i < max; i++) {
Boolean requestCache = randomFrom(true, null);
SearchResponse response = client().prepareSearch("test")
.setSize(0)
.setQuery(termQuery("field1", "value1"))
.setRequestCache(requestCache)
.putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))
.get();
assertNoFailures(response);
assertHitCount(response, 1);
response = client().prepareSearch("test")
.setSize(0)
.setQuery(termQuery("field1", "value1"))
.setRequestCache(requestCache)
.putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("user2", USERS_PASSWD))
.get();
assertNoFailures(response);
assertHitCount(response, 0);
}
}
public void testUpdateApiIsBlocked() throws Exception {
assertAcked(client().admin().indices().prepareCreate("test")
.addMapping("type", "field1", "type=string", "field2", "type=string")
);
client().prepareIndex("test", "type", "1").setSource("field1", "value1")
.setRefresh(true)
.get();
// With document level security enabled the update is not allowed:
try {
client().prepareUpdate("test", "type", "1").setDoc("field1", "value2")
.putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))
.get();
fail("failed, because update request shouldn't be allowed if document level security is enabled");
} catch (ElasticsearchSecurityException e) {
assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST));
assertThat(e.getMessage(), equalTo("Can't execute an update request if field or document level security is enabled"));
}
assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field1").toString(), equalTo("value1"));
// With no document level security enabled the update is allowed:
client().prepareUpdate("test", "type", "1").setDoc("field1", "value2")
.get();
assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field1").toString(), equalTo("value2"));
// With document level security enabled the update in bulk is not allowed:
try {
client().prepareBulk()
.putHeader(BASIC_AUTH_HEADER, basicAuthHeaderValue("user1", USERS_PASSWD))
.add(new UpdateRequest("test", "type", "1").doc("field1", "value3"))
.get();
fail("failed, because bulk request with updates shouldn't be allowed if field or document level security is enabled");
} catch (ElasticsearchSecurityException e) {
assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST));
assertThat(e.getMessage(), equalTo("Can't execute an bulk request with update requests embedded if field or document level security is enabled"));
}
assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field1").toString(), equalTo("value2"));
client().prepareBulk()
.add(new UpdateRequest("test", "type", "1").doc("field1", "value3"))
.get();
assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field1").toString(), equalTo("value3"));
}
}

View File

@ -767,7 +767,7 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase {
fail("failed, because update request shouldn't be allowed if field level security is enabled");
} catch (ElasticsearchSecurityException e) {
assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST));
assertThat(e.getMessage(), equalTo("Can't execute an update request if field level security is enabled"));
assertThat(e.getMessage(), equalTo("Can't execute an update request if field or document level security is enabled"));
}
assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field2").toString(), equalTo("value1"));
@ -785,21 +785,14 @@ public class FieldLevelSecurityTests extends ShieldIntegTestCase {
fail("failed, because bulk request with updates shouldn't be allowed if field level security is enabled");
} catch (ElasticsearchSecurityException e) {
assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST));
assertThat(e.getMessage(), equalTo("Can't execute an bulk request with update requests embedded if document and field level security is enabled"));
assertThat(e.getMessage(), equalTo("Can't execute an bulk request with update requests embedded if field or document level security is enabled"));
}
assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field2").toString(), equalTo("value2"));
// FIXME this should work once we can support update requests within BulkRequests...
// With no field level security enabled the update in bulk is allowed:
try {
client().prepareBulk()
.add(new UpdateRequest("test", "type", "1").doc("field2", "value3"))
.get();
assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field2").toString(), equalTo("value3"));
} catch (ElasticsearchSecurityException e) {
assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST));
assertThat(e.getMessage(), equalTo("Can't execute an bulk request with update requests embedded if document and field level security is enabled"));
}
client().prepareBulk()
.add(new UpdateRequest("test", "type", "1").doc("field2", "value3"))
.get();
assertThat(client().prepareGet("test", "type", "1").get().getSource().get("field2").toString(), equalTo("value3"));
}
public void testQuery_withRoleWithFieldWildcards() throws Exception {

View File

@ -93,6 +93,16 @@ public class ShieldIndexSearcherWrapperUnitTests extends ESTestCase {
esIn.close();
}
public void testUnkownOriginOfCurrentCall() {
RequestContext.setCurrent(null);
try {
shieldIndexSearcherWrapper.wrap(esIn);
fail("exception expected");
} catch (IllegalStateException e) {
assertThat(e.getMessage(), equalTo("can't locate the origin of the current request"));
}
}
public void testDefaultMetaFields() throws Exception {
XContentBuilder mappingSource = jsonBuilder().startObject().startObject("type")
.startObject("properties")

View File

@ -206,7 +206,7 @@ public class FileRolesStoreTests extends ESTestCase {
Path path = getDataPath("roles.yml");
CapturingLogger logger = new CapturingLogger(CapturingLogger.Level.ERROR);
Map<String, Permission.Global.Role> roles = FileRolesStore.parseFile(path, Collections.<Permission.Global.Role>emptySet(),
logger, randomBoolean() ? Settings.builder().put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, false).build() : Settings.EMPTY);
logger, Settings.builder().put(ShieldPlugin.DLS_FLS_ENABLED_SETTING, false).build());
assertThat(roles, notNullValue());
assertThat(roles.size(), is(7));
assertThat(roles.get("role_fields"), nullValue());