Add role for enrich processor (#42677)

This commit adds the manage_enrich privilege, which grants access to all
of the enrich processor lifecycle actions. In addition this commit also
creates a role which grants access to the generated indices.

Relates #41939

Co-authored-by: Martijn van Groningen <martijn.v.groningen@gmail.com>
This commit is contained in:
Michael Basnight 2019-06-24 10:39:59 -05:00
parent df9f06213d
commit 6945e5d5e6
13 changed files with 253 additions and 80 deletions

View File

@ -318,9 +318,11 @@ public final class Role {
public static final String READ_CCR = "read_ccr";
public static final String MANAGE_ILM = "manage_ilm";
public static final String READ_ILM = "read_ilm";
public static final String MANAGE_ENRICH = "manage_enrich";
public static final String[] ALL_ARRAY = new String[] { NONE, ALL, MONITOR, MONITOR_ML, MONITOR_WATCHER, MONITOR_ROLLUP, MANAGE,
MANAGE_ML, MANAGE_WATCHER, MANAGE_ROLLUP, MANAGE_INDEX_TEMPLATES, MANAGE_INGEST_PIPELINES, TRANSPORT_CLIENT,
MANAGE_SECURITY, MANAGE_SAML, MANAGE_OIDC, MANAGE_TOKEN, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR, MANAGE_ILM, READ_ILM};
MANAGE_SECURITY, MANAGE_SAML, MANAGE_OIDC, MANAGE_TOKEN, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR, MANAGE_ILM, READ_ILM,
MANAGE_ENRICH };
}
/**

View File

@ -670,7 +670,7 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
List<Role> roles = response.getRoles();
assertNotNull(response);
// 27 system roles plus the three we created
assertThat(roles.size(), equalTo(30));
assertThat(roles.size(), equalTo(31));
}
{

View File

@ -60,6 +60,7 @@ public final class ClusterPrivilege extends Privilege {
private static final Automaton READ_CCR_AUTOMATON = patterns(ClusterStateAction.NAME, HasPrivilegesAction.NAME);
private static final Automaton MANAGE_ILM_AUTOMATON = patterns("cluster:admin/ilm/*");
private static final Automaton READ_ILM_AUTOMATON = patterns(GetLifecycleAction.NAME, GetStatusAction.NAME);
private static final Automaton MANAGE_ENRICH_AUTOMATON = patterns("cluster:admin/xpack/enrich/*");
public static final ClusterPrivilege NONE = new ClusterPrivilege("none", Automatons.EMPTY);
public static final ClusterPrivilege ALL = new ClusterPrivilege("all", ALL_CLUSTER_AUTOMATON);
@ -90,6 +91,7 @@ public final class ClusterPrivilege extends Privilege {
public static final ClusterPrivilege CREATE_SNAPSHOT = new ClusterPrivilege("create_snapshot", CREATE_SNAPSHOT_AUTOMATON);
public static final ClusterPrivilege MANAGE_ILM = new ClusterPrivilege("manage_ilm", MANAGE_ILM_AUTOMATON);
public static final ClusterPrivilege READ_ILM = new ClusterPrivilege("read_ilm", READ_ILM_AUTOMATON);
public static final ClusterPrivilege MANAGE_ENRICH = new ClusterPrivilege("manage_enrich", MANAGE_ENRICH_AUTOMATON);
public static final Predicate<String> ACTION_MATCHER = ClusterPrivilege.ALL.predicate();
@ -119,6 +121,7 @@ public final class ClusterPrivilege extends Privilege {
.put("create_snapshot", CREATE_SNAPSHOT)
.put("manage_ilm", MANAGE_ILM)
.put("read_ilm", READ_ILM)
.put("manage_enrich", MANAGE_ENRICH)
.immutableMap();
private static final ConcurrentHashMap<Set<String>, ClusterPrivilege> CACHE = new ConcurrentHashMap<>();

View File

@ -233,6 +233,11 @@ public class ReservedRolesStore implements BiConsumer<Set<String>, ActionListene
.privileges("view_index_metadata")
.allowRestrictedIndices(true)
.build() }, null, null, null, MetadataUtils.DEFAULT_RESERVED_METADATA, null))
.put("enrich_user", new RoleDescriptor("enrich_user", new String[]{ "manage_enrich", "manage_ingest_pipelines", "monitor" },
new RoleDescriptor.IndicesPrivileges[]{ RoleDescriptor.IndicesPrivileges.builder()
.indices(".enrich-*")
.privileges("manage", "read", "write")
.build() }, null, MetadataUtils.DEFAULT_RESERVED_METADATA))
.immutableMap();
}

View File

@ -8,6 +8,11 @@ package org.elasticsearch.xpack.core.security.authz.privilege;
import org.apache.lucene.util.automaton.Operations;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.enrich.action.DeleteEnrichPolicyAction;
import org.elasticsearch.xpack.core.enrich.action.ExecuteEnrichPolicyAction;
import org.elasticsearch.xpack.core.enrich.action.GetEnrichPolicyAction;
import org.elasticsearch.xpack.core.enrich.action.ListEnrichPolicyAction;
import org.elasticsearch.xpack.core.enrich.action.PutEnrichPolicyAction;
import org.elasticsearch.xpack.core.security.support.Automatons;
import org.junit.Rule;
import org.junit.rules.ExpectedException;
@ -151,6 +156,17 @@ public class PrivilegeTests extends ESTestCase {
assertThat(predicate.test("cluster:admin/xpack/whatever"), is(false));
}
public void testManageEnrichPrivilege() {
Predicate<String> predicate = ClusterPrivilege.MANAGE_ENRICH.predicate();
assertThat(predicate.test(DeleteEnrichPolicyAction.NAME), is(true));
assertThat(predicate.test(ExecuteEnrichPolicyAction.NAME), is(true));
assertThat(predicate.test(GetEnrichPolicyAction.NAME), is(true));
assertThat(predicate.test(ListEnrichPolicyAction.NAME), is(true));
assertThat(predicate.test(PutEnrichPolicyAction.NAME), is(true));
assertThat(predicate.test("cluster:admin/xpack/enrich/brand_new_api"), is(true));
assertThat(predicate.test("cluster:admin/xpack/whatever"), is(false));
}
public void testIlmPrivileges() {
{
Predicate<String> predicate = ClusterPrivilege.MANAGE_ILM.predicate();

View File

@ -0,0 +1,6 @@
apply plugin: 'elasticsearch.build'
test.enabled = false
dependencies {
compile project(':test:framework')
}

View File

@ -0,0 +1,86 @@
/*
* 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.test.enrich;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.junit.After;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
public abstract class CommonEnrichRestTestCase extends ESRestTestCase {
@After
private void deletePolicies() throws Exception {
Map<String, Object> responseMap = toMap(adminClient().performRequest(new Request("GET", "/_enrich/policy")));
@SuppressWarnings("unchecked")
List<Map<?,?>> policies = (List<Map<?,?>>) responseMap.get("policies");
for (Map<?, ?> entry: policies) {
client().performRequest(new Request("DELETE", "/_enrich/policy/" + entry.get("name")));
}
}
public void testBasicFlow() throws Exception {
// Create the policy:
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\"]}");
assertOK(client().performRequest(putPolicyRequest));
// Add entry to source index and then refresh:
Request indexRequest = new Request("PUT", "/my-source-index/_doc/elastic.co");
indexRequest.setJsonEntity("{\"host\": \"elastic.co\",\"globalRank\": 25,\"tldRank\": 7,\"tld\": \"co\"}");
assertOK(client().performRequest(indexRequest));
Request refreshRequest = new Request("POST", "/my-source-index/_refresh");
assertOK(client().performRequest(refreshRequest));
// Execute the policy:
Request executePolicyRequest = new Request("POST", "/_enrich/policy/my_policy/_execute");
assertOK(client().performRequest(executePolicyRequest));
// Create pipeline
Request putPipelineRequest = new Request("PUT", "/_ingest/pipeline/my_pipeline");
putPipelineRequest.setJsonEntity("{\"processors\":[" +
"{\"enrich\":{\"policy_name\":\"my_policy\",\"enrich_key\":\"host\",\"enrich_values\":[" +
"{\"source\":\"globalRank\",\"target\":\"global_rank\"}," +
"{\"source\":\"tldRank\",\"target\":\"tld_rank\"}" +
"]}}" +
"]}");
assertOK(client().performRequest(putPipelineRequest));
// Index document using pipeline with enrich processor:
indexRequest = new Request("PUT", "/my-index/_doc/1");
indexRequest.addParameter("pipeline", "my_pipeline");
indexRequest.setJsonEntity("{\"host\": \"elastic.co\"}");
assertOK(client().performRequest(indexRequest));
// Check if document has been enriched
Request getRequest = new Request("GET", "/my-index/_doc/1");
Map<String, Object> response = toMap(client().performRequest(getRequest));
Map<?, ?> _source = (Map<?, ?>) response.get("_source");
assertThat(_source.size(), equalTo(3));
assertThat(_source.get("host"), equalTo("elastic.co"));
assertThat(_source.get("global_rank"), equalTo(25));
assertThat(_source.get("tld_rank"), equalTo(7));
}
private static Map<String, Object> toMap(Response response) throws IOException {
return toMap(EntityUtils.toString(response.getEntity()));
}
private static Map<String, Object> toMap(String response) {
return XContentHelper.convertToMap(JsonXContent.jsonXContent, response, false);
}
}

View File

@ -0,0 +1,38 @@
import org.elasticsearch.gradle.test.RestIntegTestTask
apply plugin: 'elasticsearch.standalone-test'
dependencies {
testCompile project(path: xpackModule('enrich'), configuration: 'runtime')
testCompile project(path: xpackModule('core'), configuration: 'runtime')
testCompile project(path: xpackModule('enrich:qa:common'), configuration: 'runtime')}
task restTest(type: RestIntegTestTask) {
mustRunAfter(precommit)
}
restTestCluster {
distribution 'default'
setting 'xpack.license.self_generated.type', 'basic'
setting 'xpack.security.enabled', 'true'
extraConfigFile 'roles.yml', 'roles.yml'
setupCommand 'setupTestAdmin',
'bin/elasticsearch-users', 'useradd', "test_admin", '-p', 'x-pack-test-password', '-r', "superuser"
setupCommand 'setupEnrichUser',
'bin/elasticsearch-users', 'useradd', "test_enrich", '-p', 'x-pack-test-password', '-r', "enrich_user,integ_test_role"
setupCommand 'setupEnrichUserNoPrivs',
'bin/elasticsearch-users', 'useradd', "test_enrich_no_privs", '-p', 'x-pack-test-password', '-r', "enrich_no_privs"
waitCondition = { node, ant ->
File tmpFile = new File(node.cwd, 'wait.success')
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
dest: tmpFile.toString(),
username: 'test_admin',
password: 'x-pack-test-password',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}
check.dependsOn restTest
test.enabled = false

View File

@ -0,0 +1,18 @@
integ_test_role:
indices:
- names: [ 'my-index', 'my-source-index' ]
privileges:
- manage
- read
- write
enrich_no_privs:
cluster:
- manage_ingest_pipelines
- monitor
indices:
- names: [ '.enrich-my_policy*', 'my-index', 'my-source-index' ]
privileges:
- manage
- read
- write

View File

@ -0,0 +1,42 @@
/*
* 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.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.rest.ESRestTestCase;
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
public class EnrichSecurityFailureIT extends ESRestTestCase {
@Override
protected Settings restClientSettings() {
String token = basicAuthHeaderValue("test_enrich_no_privs", new SecureString("x-pack-test-password".toCharArray()));
return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
}
@Override
protected Settings restAdminSettings() {
String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray()));
return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
}
public void testFailure() {
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)));
assertTrue(exc.getMessage().contains("action [cluster:admin/xpack/enrich/put] is unauthorized for user [test_enrich_no_privs]"));
}
}

View File

@ -0,0 +1,32 @@
/*
* 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.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;
public class EnrichSecurityIT extends CommonEnrichRestTestCase {
@Override
protected Settings restClientSettings() {
String token = basicAuthHeaderValue("test_enrich", new SecureString("x-pack-test-password".toCharArray()));
return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
}
@Override
protected Settings restAdminSettings() {
String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray()));
return Settings.builder()
.put(ThreadContext.PREFIX + ".Authorization", token)
.build();
}
}

View File

@ -4,6 +4,7 @@ apply plugin: 'elasticsearch.standalone-test'
dependencies {
testCompile project(path: xpackModule('enrich'), configuration: 'runtime')
testCompile project(path: xpackModule('enrich:qa:common'), configuration: 'runtime')
}
task restTest(type: RestIntegTestTask) {

View File

@ -5,83 +5,7 @@
*/
package org.elasticsearch.xpack.enrich;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.junit.After;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
public class EnrichIT extends ESRestTestCase {
@After
private void deletePolicies() throws Exception {
Map<String, Object> responseMap = toMap(client().performRequest(new Request("GET", "/_enrich/policy")));
@SuppressWarnings("unchecked")
List<Map<?,?>> policies = (List<Map<?,?>>) responseMap.get("policies");
for (Map<?, ?> entry: policies) {
client().performRequest(new Request("DELETE", "/_enrich/policy/" + entry.get("name")));
}
}
public void testBasicFlow() throws Exception {
// Create the policy:
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\"]}");
assertOK(client().performRequest(putPolicyRequest));
// Add entry to source index and then refresh:
Request indexRequest = new Request("PUT", "/my-source-index/_doc/elastic.co");
indexRequest.setJsonEntity("{\"host\": \"elastic.co\",\"globalRank\": 25,\"tldRank\": 7,\"tld\": \"co\"}");
assertOK(client().performRequest(indexRequest));
Request refreshRequest = new Request("POST", "/my-source-index/_refresh");
assertOK(client().performRequest(refreshRequest));
// Execute the policy:
Request executePolicyRequest = new Request("POST", "/_enrich/policy/my_policy/_execute");
assertOK(client().performRequest(executePolicyRequest));
// Create pipeline
Request putPipelineRequest = new Request("PUT", "/_ingest/pipeline/my_pipeline");
putPipelineRequest.setJsonEntity("{\"processors\":[" +
"{\"enrich\":{\"policy_name\":\"my_policy\",\"enrich_key\":\"host\",\"enrich_values\":[" +
"{\"source\":\"globalRank\",\"target\":\"global_rank\"}," +
"{\"source\":\"tldRank\",\"target\":\"tld_rank\"}" +
"]}}" +
"]}");
assertOK(client().performRequest(putPipelineRequest));
// Index document using pipeline with enrich processor:
indexRequest = new Request("PUT", "/my-index/_doc/1");
indexRequest.addParameter("pipeline", "my_pipeline");
indexRequest.setJsonEntity("{\"host\": \"elastic.co\"}");
assertOK(client().performRequest(indexRequest));
// Check if document has been enriched
Request getRequest = new Request("GET", "/my-index/_doc/1");
Map<String, Object> response = toMap(client().performRequest(getRequest));
Map<?, ?> _source = (Map<?, ?>) response.get("_source");
assertThat(_source.size(), equalTo(3));
assertThat(_source.get("host"), equalTo("elastic.co"));
assertThat(_source.get("global_rank"), equalTo(25));
assertThat(_source.get("tld_rank"), equalTo(7));
}
private static Map<String, Object> toMap(Response response) throws IOException {
return toMap(EntityUtils.toString(response.getEntity()));
}
private static Map<String, Object> toMap(String response) {
return XContentHelper.convertToMap(JsonXContent.jsonXContent, response, false);
}
import org.elasticsearch.test.enrich.CommonEnrichRestTestCase;
public class EnrichIT extends CommonEnrichRestTestCase {
}