Skip to content

Commit d2c3f4b

Browse files
committed
Validate read priv of enrich source indices (#43595)
This commit adds permissions validation on the indices provided in the enrich policy. These indices should be validated at store time so as not to have cryptic error messages in the event the user does not have permissions to access said indices.
1 parent adc06ff commit d2c3f4b

File tree

10 files changed

+120
-11
lines changed

10 files changed

+120
-11
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/RoleDescriptor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,10 @@ public Builder indices(String... indices) {
775775
return this;
776776
}
777777

778+
public Builder indices(Collection<String> indices) {
779+
return indices(indices.toArray(new String[indices.size()]));
780+
}
781+
778782
public Builder privileges(String... privileges) {
779783
indicesPrivileges.privileges = privileges;
780784
return this;

x-pack/plugin/enrich/qa/rest-with-security/src/test/java/org/elasticsearch/xpack/enrich/EnrichSecurityFailureIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void testFailure() {
3636
Request putPolicyRequest = new Request("PUT", "/_enrich/policy/my_policy");
3737
putPolicyRequest.setJsonEntity("{\"type\": \"exact_match\",\"indices\": [\"my-source-index\"], \"enrich_key\": \"host\", " +
3838
"\"enrich_values\": [\"globalRank\", \"tldRank\", \"tld\"]}");
39-
ResponseException exc = expectThrows(ResponseException.class, () -> assertOK(client().performRequest(putPolicyRequest)));
39+
ResponseException exc = expectThrows(ResponseException.class, () -> client().performRequest(putPolicyRequest));
4040
assertTrue(exc.getMessage().contains("action [cluster:admin/xpack/enrich/put] is unauthorized for user [test_enrich_no_privs]"));
4141
}
4242
}

x-pack/plugin/enrich/qa/rest-with-security/src/test/java/org/elasticsearch/xpack/enrich/EnrichSecurityIT.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
*/
66
package org.elasticsearch.xpack.enrich;
77

8+
import org.elasticsearch.client.Request;
9+
import org.elasticsearch.client.ResponseException;
810
import org.elasticsearch.common.settings.SecureString;
911
import org.elasticsearch.common.settings.Settings;
1012
import org.elasticsearch.common.util.concurrent.ThreadContext;
1113
import org.elasticsearch.test.enrich.CommonEnrichRestTestCase;
1214

1315
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
16+
import static org.hamcrest.CoreMatchers.containsString;
1417

1518
public class EnrichSecurityIT extends CommonEnrichRestTestCase {
1619

@@ -29,4 +32,15 @@ protected Settings restAdminSettings() {
2932
.put(ThreadContext.PREFIX + ".Authorization", token)
3033
.build();
3134
}
35+
36+
public void testInsufficientPermissionsOnNonExistentIndex() {
37+
// This test is here because it requires a valid user that has permission to execute policy PUTs but should fail if the user
38+
// does not have access to read the backing indices used to enrich the data.
39+
Request putPolicyRequest = new Request("PUT", "/_enrich/policy/my_policy");
40+
putPolicyRequest.setJsonEntity("{\"type\": \"exact_match\",\"indices\": [\"some-other-index\"], \"enrich_key\": \"host\", " +
41+
"\"enrich_values\": [\"globalRank\", \"tldRank\", \"tld\"]}");
42+
ResponseException exc = expectThrows(ResponseException.class, () -> client().performRequest(putPolicyRequest));
43+
assertThat(exc.getMessage(),
44+
containsString("unable to store policy because no indices match with the specified index patterns [some-other-index]"));
45+
}
3246
}

x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.elasticsearch.env.Environment;
2525
import org.elasticsearch.env.NodeEnvironment;
2626
import org.elasticsearch.ingest.Processor;
27+
import org.elasticsearch.license.XPackLicenseState;
2728
import org.elasticsearch.plugins.ActionPlugin;
2829
import org.elasticsearch.plugins.IngestPlugin;
2930
import org.elasticsearch.plugins.Plugin;
@@ -32,6 +33,7 @@
3233
import org.elasticsearch.script.ScriptService;
3334
import org.elasticsearch.threadpool.ThreadPool;
3435
import org.elasticsearch.watcher.ResourceWatcherService;
36+
import org.elasticsearch.xpack.core.XPackPlugin;
3537
import org.elasticsearch.xpack.core.enrich.action.DeleteEnrichPolicyAction;
3638
import org.elasticsearch.xpack.core.enrich.action.ExecuteEnrichPolicyAction;
3739
import org.elasticsearch.xpack.core.enrich.action.GetEnrichPolicyAction;
@@ -94,6 +96,8 @@ public Map<String, Processor.Factory> getProcessors(Processor.Parameters paramet
9496
return Collections.singletonMap(EnrichProcessorFactory.TYPE, factory);
9597
}
9698

99+
protected XPackLicenseState getLicenseState() { return XPackPlugin.getSharedLicenseState(); }
100+
97101
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
98102
if (enabled == false) {
99103
return Collections.emptyList();

x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/action/TransportPutEnrichPolicyAction.java

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,48 @@
99
import org.elasticsearch.action.support.ActionFilters;
1010
import org.elasticsearch.action.support.master.AcknowledgedResponse;
1111
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
12+
import org.elasticsearch.client.Client;
1213
import org.elasticsearch.cluster.ClusterState;
1314
import org.elasticsearch.cluster.block.ClusterBlockException;
1415
import org.elasticsearch.cluster.block.ClusterBlockLevel;
1516
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
1617
import org.elasticsearch.cluster.service.ClusterService;
18+
import org.elasticsearch.common.Strings;
1719
import org.elasticsearch.common.inject.Inject;
1820
import org.elasticsearch.common.io.stream.StreamInput;
21+
import org.elasticsearch.common.settings.Settings;
22+
import org.elasticsearch.license.XPackLicenseState;
1923
import org.elasticsearch.threadpool.ThreadPool;
2024
import org.elasticsearch.transport.TransportService;
25+
import org.elasticsearch.xpack.core.XPackSettings;
2126
import org.elasticsearch.xpack.core.enrich.action.PutEnrichPolicyAction;
27+
import org.elasticsearch.xpack.core.security.SecurityContext;
28+
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesAction;
29+
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequest;
30+
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse;
31+
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
32+
import org.elasticsearch.xpack.core.security.support.Exceptions;
2233
import org.elasticsearch.xpack.enrich.EnrichStore;
2334

2435
import java.io.IOException;
2536

2637
public class TransportPutEnrichPolicyAction extends TransportMasterNodeAction<PutEnrichPolicyAction.Request, AcknowledgedResponse> {
2738

39+
private final XPackLicenseState licenseState;
40+
private final SecurityContext securityContext;
41+
private final Client client;
42+
2843
@Inject
29-
public TransportPutEnrichPolicyAction(TransportService transportService,
30-
ClusterService clusterService,
31-
ThreadPool threadPool,
32-
ActionFilters actionFilters,
44+
public TransportPutEnrichPolicyAction(Settings settings, TransportService transportService,
45+
ClusterService clusterService, ThreadPool threadPool, Client client,
46+
XPackLicenseState licenseState, ActionFilters actionFilters,
3347
IndexNameExpressionResolver indexNameExpressionResolver) {
3448
super(PutEnrichPolicyAction.NAME, transportService, clusterService, threadPool, actionFilters,
3549
PutEnrichPolicyAction.Request::new, indexNameExpressionResolver);
50+
this.licenseState = licenseState;
51+
this.securityContext = XPackSettings.SECURITY_ENABLED.get(settings) ?
52+
new SecurityContext(settings, threadPool.getThreadContext()) : null;
53+
this.client = client;
3654
}
3755

3856
@Override
@@ -52,6 +70,38 @@ protected AcknowledgedResponse read(StreamInput in) throws IOException {
5270
@Override
5371
protected void masterOperation(PutEnrichPolicyAction.Request request, ClusterState state,
5472
ActionListener<AcknowledgedResponse> listener) {
73+
74+
if (licenseState.isAuthAllowed()) {
75+
RoleDescriptor.IndicesPrivileges privileges = RoleDescriptor.IndicesPrivileges.builder()
76+
.indices(request.getPolicy().getIndices())
77+
.privileges("read")
78+
.build();
79+
80+
String username = securityContext.getUser().principal();
81+
82+
HasPrivilegesRequest privRequest = new HasPrivilegesRequest();
83+
privRequest.applicationPrivileges(new RoleDescriptor.ApplicationResourcePrivileges[0]);
84+
privRequest.username(username);
85+
privRequest.clusterPrivileges(Strings.EMPTY_ARRAY);
86+
privRequest.indexPrivileges(privileges);
87+
88+
ActionListener<HasPrivilegesResponse> wrappedListener = ActionListener.wrap(
89+
r -> {
90+
if (r.isCompleteMatch()) {
91+
putPolicy(request, listener);
92+
} else {
93+
listener.onFailure(Exceptions.authorizationError("unable to store policy because no indices match with the " +
94+
"specified index patterns {}", request.getPolicy().getIndices(), username));
95+
}
96+
},
97+
listener::onFailure);
98+
client.execute(HasPrivilegesAction.INSTANCE, privRequest, wrappedListener);
99+
} else {
100+
putPolicy(request, listener);
101+
}
102+
}
103+
104+
private void putPolicy(PutEnrichPolicyAction.Request request, ActionListener<AcknowledgedResponse> listener ) {
55105
EnrichStore.putPolicy(request.getName(), request.getPolicy(), clusterService, e -> {
56106
if (e == null) {
57107
listener.onResponse(new AcknowledgedResponse(true));

x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/BasicEnrichTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public class BasicEnrichTests extends ESSingleNodeTestCase {
4141

4242
@Override
4343
protected Collection<Class<? extends Plugin>> getPlugins() {
44-
return Arrays.asList(EnrichPlugin.class, ReindexPlugin.class);
44+
return Arrays.asList(LocalStateEnrich.class, ReindexPlugin.class);
4545
}
4646

4747
public void testIngestDataWithEnrichProcessor() {

x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichMultiNodeIT.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.node.Node;
2323
import org.elasticsearch.plugins.Plugin;
2424
import org.elasticsearch.test.ESIntegTestCase;
25+
import org.elasticsearch.xpack.core.XPackSettings;
2526
import org.elasticsearch.xpack.core.enrich.EnrichPolicy;
2627
import org.elasticsearch.xpack.core.enrich.action.DeleteEnrichPolicyAction;
2728
import org.elasticsearch.xpack.core.enrich.action.ExecuteEnrichPolicyAction;
@@ -54,14 +55,19 @@ public class EnrichMultiNodeIT extends ESIntegTestCase {
5455

5556
@Override
5657
protected Collection<Class<? extends Plugin>> nodePlugins() {
57-
return Arrays.asList(EnrichPlugin.class, ReindexPlugin.class);
58+
return Arrays.asList(LocalStateEnrich.class, ReindexPlugin.class);
5859
}
5960

6061
@Override
6162
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
6263
return nodePlugins();
6364
}
6465

66+
@Override
67+
protected Settings transportClientSettings() {
68+
return Settings.builder().put(super.transportClientSettings()).put(XPackSettings.SECURITY_ENABLED.getKey(), false).build();
69+
}
70+
6571
public void testEnrichAPIs() {
6672
final int numPolicies = randomIntBetween(2, 4);
6773
internalCluster().startNodes(randomIntBetween(2, 3));

x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichPolicyUpdateTests.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class EnrichPolicyUpdateTests extends ESSingleNodeTestCase {
2727

2828
@Override
2929
protected Collection<Class<? extends Plugin>> getPlugins() {
30-
return Collections.singleton(EnrichPlugin.class);
30+
return Collections.singleton(LocalStateEnrich.class);
3131
}
3232

3333
public void testUpdatePolicyOnly() {
@@ -56,6 +56,4 @@ public void testUpdatePolicyOnly() {
5656
Pipeline pipelineInstance2 = ingestService.getPipeline("1");
5757
assertThat(pipelineInstance2, sameInstance(pipelineInstance1));
5858
}
59-
60-
6159
}

x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichRestartIT.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
*/
66
package org.elasticsearch.xpack.enrich;
77

8+
import org.elasticsearch.common.settings.Settings;
89
import org.elasticsearch.index.reindex.ReindexPlugin;
910
import org.elasticsearch.plugins.Plugin;
1011
import org.elasticsearch.test.ESIntegTestCase;
12+
import org.elasticsearch.xpack.core.XPackSettings;
1113
import org.elasticsearch.xpack.core.enrich.EnrichPolicy;
1214
import org.elasticsearch.xpack.core.enrich.action.ListEnrichPolicyAction;
1315
import org.elasticsearch.xpack.core.enrich.action.PutEnrichPolicyAction;
@@ -29,14 +31,19 @@ public class EnrichRestartIT extends ESIntegTestCase {
2931

3032
@Override
3133
protected Collection<Class<? extends Plugin>> nodePlugins() {
32-
return Arrays.asList(EnrichPlugin.class, ReindexPlugin.class);
34+
return Arrays.asList(LocalStateEnrich.class, ReindexPlugin.class);
3335
}
3436

3537
@Override
3638
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
3739
return nodePlugins();
3840
}
3941

42+
@Override
43+
protected Settings transportClientSettings() {
44+
return Settings.builder().put(super.transportClientSettings()).put(XPackSettings.SECURITY_ENABLED.getKey(), false).build();
45+
}
46+
4047
public void testRestart() throws Exception {
4148
final int numPolicies = randomIntBetween(2, 4);
4249
internalCluster().startNode();
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.enrich;
7+
8+
import org.elasticsearch.common.settings.Settings;
9+
import org.elasticsearch.license.XPackLicenseState;
10+
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
11+
12+
import java.nio.file.Path;
13+
14+
public class LocalStateEnrich extends LocalStateCompositeXPackPlugin {
15+
16+
public LocalStateEnrich(final Settings settings, final Path configPath) throws Exception {
17+
super(settings, configPath);
18+
19+
plugins.add(new EnrichPlugin(settings) {
20+
@Override
21+
protected XPackLicenseState getLicenseState() {
22+
return LocalStateEnrich.this.getLicenseState();
23+
}
24+
});
25+
}
26+
}

0 commit comments

Comments
 (0)