diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java index 5705d7bf357..58b9fbf426b 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java @@ -775,6 +775,10 @@ public class RoleDescriptor implements ToXContentObject, Writeable { return this; } + public Builder indices(Collection indices) { + return indices(indices.toArray(new String[indices.size()])); + } + public Builder privileges(String... privileges) { indicesPrivileges.privileges = privileges; return this; diff --git a/x-pack/plugin/enrich/qa/rest-with-security/src/test/java/org/elasticsearch/xpack/enrich/EnrichSecurityFailureIT.java b/x-pack/plugin/enrich/qa/rest-with-security/src/test/java/org/elasticsearch/xpack/enrich/EnrichSecurityFailureIT.java index 198b2a25499..4dcc9886438 100644 --- a/x-pack/plugin/enrich/qa/rest-with-security/src/test/java/org/elasticsearch/xpack/enrich/EnrichSecurityFailureIT.java +++ b/x-pack/plugin/enrich/qa/rest-with-security/src/test/java/org/elasticsearch/xpack/enrich/EnrichSecurityFailureIT.java @@ -36,7 +36,7 @@ public class EnrichSecurityFailureIT extends ESRestTestCase { Request putPolicyRequest = new Request("PUT", "/_enrich/policy/my_policy"); putPolicyRequest.setJsonEntity("{\"type\": \"exact_match\",\"indices\": [\"my-source-index\"], \"enrich_key\": \"host\", " + "\"enrich_values\": [\"globalRank\", \"tldRank\", \"tld\"]}"); - ResponseException exc = expectThrows(ResponseException.class, () -> assertOK(client().performRequest(putPolicyRequest))); + ResponseException exc = expectThrows(ResponseException.class, () -> client().performRequest(putPolicyRequest)); assertTrue(exc.getMessage().contains("action [cluster:admin/xpack/enrich/put] is unauthorized for user [test_enrich_no_privs]")); } } diff --git a/x-pack/plugin/enrich/qa/rest-with-security/src/test/java/org/elasticsearch/xpack/enrich/EnrichSecurityIT.java b/x-pack/plugin/enrich/qa/rest-with-security/src/test/java/org/elasticsearch/xpack/enrich/EnrichSecurityIT.java index be99ca893ef..87a7c7bb48d 100644 --- a/x-pack/plugin/enrich/qa/rest-with-security/src/test/java/org/elasticsearch/xpack/enrich/EnrichSecurityIT.java +++ b/x-pack/plugin/enrich/qa/rest-with-security/src/test/java/org/elasticsearch/xpack/enrich/EnrichSecurityIT.java @@ -5,12 +5,15 @@ */ package org.elasticsearch.xpack.enrich; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.ResponseException; import org.elasticsearch.common.settings.SecureString; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; import org.elasticsearch.test.enrich.CommonEnrichRestTestCase; import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; +import static org.hamcrest.CoreMatchers.containsString; public class EnrichSecurityIT extends CommonEnrichRestTestCase { @@ -29,4 +32,15 @@ public class EnrichSecurityIT extends CommonEnrichRestTestCase { .put(ThreadContext.PREFIX + ".Authorization", token) .build(); } + + public void testInsufficientPermissionsOnNonExistentIndex() { + // This test is here because it requires a valid user that has permission to execute policy PUTs but should fail if the user + // does not have access to read the backing indices used to enrich the data. + Request putPolicyRequest = new Request("PUT", "/_enrich/policy/my_policy"); + putPolicyRequest.setJsonEntity("{\"type\": \"exact_match\",\"indices\": [\"some-other-index\"], \"enrich_key\": \"host\", " + + "\"enrich_values\": [\"globalRank\", \"tldRank\", \"tld\"]}"); + ResponseException exc = expectThrows(ResponseException.class, () -> client().performRequest(putPolicyRequest)); + assertThat(exc.getMessage(), + containsString("unable to store policy because no indices match with the specified index patterns [some-other-index]")); + } } diff --git a/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java b/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java index 7a5c87827ce..eb0b6f6969b 100644 --- a/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java +++ b/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java @@ -24,6 +24,7 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.env.Environment; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.ingest.Processor; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.plugins.ActionPlugin; import org.elasticsearch.plugins.IngestPlugin; import org.elasticsearch.plugins.Plugin; @@ -32,6 +33,7 @@ import org.elasticsearch.rest.RestHandler; import org.elasticsearch.script.ScriptService; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; +import org.elasticsearch.xpack.core.XPackPlugin; import org.elasticsearch.xpack.core.enrich.action.DeleteEnrichPolicyAction; import org.elasticsearch.xpack.core.enrich.action.ExecuteEnrichPolicyAction; import org.elasticsearch.xpack.core.enrich.action.GetEnrichPolicyAction; @@ -94,6 +96,8 @@ public class EnrichPlugin extends Plugin implements ActionPlugin, IngestPlugin { return Collections.singletonMap(EnrichProcessorFactory.TYPE, factory); } + protected XPackLicenseState getLicenseState() { return XPackPlugin.getSharedLicenseState(); } + public List> getActions() { if (enabled == false) { return Collections.emptyList(); diff --git a/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/action/TransportPutEnrichPolicyAction.java b/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/action/TransportPutEnrichPolicyAction.java index 9c0c428c32d..fbfdd1aaad5 100644 --- a/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/action/TransportPutEnrichPolicyAction.java +++ b/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/action/TransportPutEnrichPolicyAction.java @@ -9,30 +9,48 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.action.support.master.TransportMasterNodeAction; +import org.elasticsearch.client.Client; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.block.ClusterBlockException; import org.elasticsearch.cluster.block.ClusterBlockLevel; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.enrich.action.PutEnrichPolicyAction; +import org.elasticsearch.xpack.core.security.SecurityContext; +import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction; +import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest; +import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse; +import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; +import org.elasticsearch.xpack.core.security.support.Exceptions; import org.elasticsearch.xpack.enrich.EnrichStore; import java.io.IOException; public class TransportPutEnrichPolicyAction extends TransportMasterNodeAction { + private final XPackLicenseState licenseState; + private final SecurityContext securityContext; + private final Client client; + @Inject - public TransportPutEnrichPolicyAction(TransportService transportService, - ClusterService clusterService, - ThreadPool threadPool, - ActionFilters actionFilters, + public TransportPutEnrichPolicyAction(Settings settings, TransportService transportService, + ClusterService clusterService, ThreadPool threadPool, Client client, + XPackLicenseState licenseState, ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver) { super(PutEnrichPolicyAction.NAME, transportService, clusterService, threadPool, actionFilters, PutEnrichPolicyAction.Request::new, indexNameExpressionResolver); + this.licenseState = licenseState; + this.securityContext = XPackSettings.SECURITY_ENABLED.get(settings) ? + new SecurityContext(settings, threadPool.getThreadContext()) : null; + this.client = client; } @Override @@ -52,6 +70,38 @@ public class TransportPutEnrichPolicyAction extends TransportMasterNodeAction listener) { + + if (licenseState.isAuthAllowed()) { + RoleDescriptor.IndicesPrivileges privileges = RoleDescriptor.IndicesPrivileges.builder() + .indices(request.getPolicy().getIndices()) + .privileges("read") + .build(); + + String username = securityContext.getUser().principal(); + + HasPrivilegesRequest privRequest = new HasPrivilegesRequest(); + privRequest.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]); + privRequest.username(username); + privRequest.clusterPrivileges(Strings.EMPTY_ARRAY); + privRequest.indexPrivileges(privileges); + + ActionListener wrappedListener = ActionListener.wrap( + r -> { + if (r.isCompleteMatch()) { + putPolicy(request, listener); + } else { + listener.onFailure(Exceptions.authorizationError("unable to store policy because no indices match with the " + + "specified index patterns {}", request.getPolicy().getIndices(), username)); + } + }, + listener::onFailure); + client.execute(HasPrivilegesAction.INSTANCE, privRequest, wrappedListener); + } else { + putPolicy(request, listener); + } + } + + private void putPolicy(PutEnrichPolicyAction.Request request, ActionListener listener ) { EnrichStore.putPolicy(request.getName(), request.getPolicy(), clusterService, e -> { if (e == null) { listener.onResponse(new AcknowledgedResponse(true)); diff --git a/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/BasicEnrichTests.java b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/BasicEnrichTests.java index 7c515d10d6e..8bdc050f75c 100644 --- a/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/BasicEnrichTests.java +++ b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/BasicEnrichTests.java @@ -41,7 +41,7 @@ public class BasicEnrichTests extends ESSingleNodeTestCase { @Override protected Collection> getPlugins() { - return Arrays.asList(EnrichPlugin.class, ReindexPlugin.class); + return Arrays.asList(LocalStateEnrich.class, ReindexPlugin.class); } public void testIngestDataWithEnrichProcessor() { diff --git a/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichMultiNodeIT.java b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichMultiNodeIT.java index d3496c6197e..362407e05ea 100644 --- a/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichMultiNodeIT.java +++ b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichMultiNodeIT.java @@ -22,6 +22,7 @@ import org.elasticsearch.index.reindex.ReindexPlugin; import org.elasticsearch.node.Node; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.enrich.EnrichPolicy; import org.elasticsearch.xpack.core.enrich.action.DeleteEnrichPolicyAction; import org.elasticsearch.xpack.core.enrich.action.ExecuteEnrichPolicyAction; @@ -54,7 +55,7 @@ public class EnrichMultiNodeIT extends ESIntegTestCase { @Override protected Collection> nodePlugins() { - return Arrays.asList(EnrichPlugin.class, ReindexPlugin.class); + return Arrays.asList(LocalStateEnrich.class, ReindexPlugin.class); } @Override @@ -62,6 +63,11 @@ public class EnrichMultiNodeIT extends ESIntegTestCase { return nodePlugins(); } + @Override + protected Settings transportClientSettings() { + return Settings.builder().put(super.transportClientSettings()).put(XPackSettings.SECURITY_ENABLED.getKey(), false).build(); + } + public void testEnrichAPIs() { final int numPolicies = randomIntBetween(2, 4); internalCluster().startNodes(randomIntBetween(2, 3)); diff --git a/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichPolicyUpdateTests.java b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichPolicyUpdateTests.java index 1fd1204c312..1e79a3e18f4 100644 --- a/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichPolicyUpdateTests.java +++ b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichPolicyUpdateTests.java @@ -27,7 +27,7 @@ public class EnrichPolicyUpdateTests extends ESSingleNodeTestCase { @Override protected Collection> getPlugins() { - return Collections.singleton(EnrichPlugin.class); + return Collections.singleton(LocalStateEnrich.class); } public void testUpdatePolicyOnly() { @@ -56,6 +56,4 @@ public class EnrichPolicyUpdateTests extends ESSingleNodeTestCase { Pipeline pipelineInstance2 = ingestService.getPipeline("1"); assertThat(pipelineInstance2, sameInstance(pipelineInstance1)); } - - } diff --git a/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichRestartIT.java b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichRestartIT.java index c0dad54da87..f26a7e48463 100644 --- a/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichRestartIT.java +++ b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichRestartIT.java @@ -5,9 +5,11 @@ */ package org.elasticsearch.xpack.enrich; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.reindex.ReindexPlugin; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.enrich.EnrichPolicy; import org.elasticsearch.xpack.core.enrich.action.ListEnrichPolicyAction; import org.elasticsearch.xpack.core.enrich.action.PutEnrichPolicyAction; @@ -29,7 +31,7 @@ public class EnrichRestartIT extends ESIntegTestCase { @Override protected Collection> nodePlugins() { - return Arrays.asList(EnrichPlugin.class, ReindexPlugin.class); + return Arrays.asList(LocalStateEnrich.class, ReindexPlugin.class); } @Override @@ -37,6 +39,11 @@ public class EnrichRestartIT extends ESIntegTestCase { return nodePlugins(); } + @Override + protected Settings transportClientSettings() { + return Settings.builder().put(super.transportClientSettings()).put(XPackSettings.SECURITY_ENABLED.getKey(), false).build(); + } + public void testRestart() throws Exception { final int numPolicies = randomIntBetween(2, 4); internalCluster().startNode(); diff --git a/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/LocalStateEnrich.java b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/LocalStateEnrich.java new file mode 100644 index 00000000000..ca8c4299355 --- /dev/null +++ b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/LocalStateEnrich.java @@ -0,0 +1,26 @@ +/* + * 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.enrich; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.license.XPackLicenseState; +import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin; + +import java.nio.file.Path; + +public class LocalStateEnrich extends LocalStateCompositeXPackPlugin { + + public LocalStateEnrich(final Settings settings, final Path configPath) throws Exception { + super(settings, configPath); + + plugins.add(new EnrichPlugin(settings) { + @Override + protected XPackLicenseState getLicenseState() { + return LocalStateEnrich.this.getLicenseState(); + } + }); + } +}