From afcdcb8c2adbc8727e77295d5f6b1a20df2fb391 Mon Sep 17 00:00:00 2001 From: Yang Wang Date: Tue, 3 Mar 2020 21:02:18 +1100 Subject: [PATCH] Allow _rollup_search with read privilege (#52043) Currently _rollup_search requires manage privilege to access. It should really be a read only operation. This PR changes the requirement to be read indices privilege. Resolves: #50245 --- .../rollup/action/RollupSearchAction.java | 2 +- .../action/RollupSearchActionTests.java | 20 ++ .../ml/integration/DatafeedJobsRestIT.java | 190 ++++++------------ 3 files changed, 88 insertions(+), 124 deletions(-) create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/rollup/action/RollupSearchActionTests.java diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/rollup/action/RollupSearchAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/rollup/action/RollupSearchAction.java index 30314dfde818f..ef7ed0291cc93 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/rollup/action/RollupSearchAction.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/rollup/action/RollupSearchAction.java @@ -14,7 +14,7 @@ public class RollupSearchAction extends ActionType { public static final RollupSearchAction INSTANCE = new RollupSearchAction(); - public static final String NAME = "indices:admin/xpack/rollup/search"; + public static final String NAME = "indices:data/read/xpack/rollup/search"; private RollupSearchAction() { super(NAME, SearchResponse::new); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/rollup/action/RollupSearchActionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/rollup/action/RollupSearchActionTests.java new file mode 100644 index 0000000000000..ff9586e97d5a3 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/rollup/action/RollupSearchActionTests.java @@ -0,0 +1,20 @@ +/* + * + * * 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.core.rollup.action; + +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege; +import org.elasticsearch.xpack.core.security.support.Automatons; + +public class RollupSearchActionTests extends ESTestCase { + + public void testIndexReadPrivilegeCanPerformRollupSearchAction() { + assertTrue(Automatons.predicate(IndexPrivilege.READ.getAutomaton()).test(RollupSearchAction.NAME)); + } +} diff --git a/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java b/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java index b78cdfa953645..f609b19081e6b 100644 --- a/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java +++ b/x-pack/plugin/ml/qa/native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/DatafeedJobsRestIT.java @@ -497,72 +497,15 @@ public void testInsufficientSearchPrivilegesOnPut() throws Exception { containsString("user ml_admin lacks permissions on the indices")); } - public void testInsufficientSearchPrivilegesOnPutWithRollup() throws Exception { + public void testCreationOnPutWithRollup() throws Exception { setupDataAccessRole("airline-data-aggs-rollup"); String jobId = "privs-put-job-rollup"; - Request createJobRequest = new Request("PUT", MachineLearning.BASE_PATH + "anomaly_detectors/" + jobId); - createJobRequest.setJsonEntity("{\n" - + " \"description\": \"Aggs job\",\n" - + " \"analysis_config\": {\n" - + " \"bucket_span\": \"1h\",\n" - + " \"summary_count_field_name\": \"doc_count\",\n" - + " \"detectors\": [\n" - + " {\n" - + " \"function\": \"mean\",\n" - + " \"field_name\": \"responsetime\",\n" - + " \"by_field_name\": \"airline\"\n" - + " }\n" - + " ]\n" - + " },\n" - + " \"data_description\": {\"time_field\": \"time stamp\"}\n" - + "}"); - client().performRequest(createJobRequest); - - String rollupJobId = "rollup-" + jobId; - Request createRollupRequest = new Request("PUT", "/_rollup/job/" + rollupJobId); - createRollupRequest.setJsonEntity("{\n" - + "\"index_pattern\": \"airline-data-aggs\",\n" - + " \"rollup_index\": \"airline-data-aggs-rollup\",\n" - + " \"cron\": \"*/30 * * * * ?\",\n" - + " \"page_size\" :1000,\n" - + " \"groups\" : {\n" - + " \"date_histogram\": {\n" - + " \"field\": \"time stamp\",\n" - + " \"fixed_interval\": \"2m\",\n" - + " \"delay\": \"7d\"\n" - + " },\n" - + " \"terms\": {\n" - + " \"fields\": [\"airline\"]\n" - + " }" - + " },\n" - + " \"metrics\": [\n" - + " {\n" - + " \"field\": \"responsetime\",\n" - + " \"metrics\": [\"avg\",\"min\",\"max\",\"sum\"]\n" - + " },\n" - + " {\n" - + " \"field\": \"time stamp\",\n" - + " \"metrics\": [\"min\",\"max\"]\n" - + " }\n" - + " ]\n" - + "}"); - client().performRequest(createRollupRequest); - String datafeedId = "datafeed-" + jobId; - String aggregations = "{\"buckets\":{\"date_histogram\":{\"field\":\"time stamp\",\"fixed_interval\":\"3600000ms\"}," - + "\"aggregations\":{" - + "\"time stamp\":{\"max\":{\"field\":\"time stamp\"}}," - + "\"responsetime\":{\"avg\":{\"field\":\"responsetime\"}}}}}"; - + final Response response = createJobAndDataFeed(jobId, datafeedId); - ResponseException e = expectThrows(ResponseException.class, () -> - new DatafeedBuilder(datafeedId, jobId, "airline-data-aggs-rollup") - .setAggregations(aggregations) - .setAuthHeader(BASIC_AUTH_VALUE_ML_ADMIN_WITH_SOME_DATA_ACCESS) //want to search, but no admin access - .build()); - assertThat(e.getMessage(), containsString("Cannot create datafeed")); - assertThat(e.getMessage(), - containsString("user ml_admin_plus_data lacks permissions on the indices")); + assertEquals(200, response.getStatusLine().getStatusCode()); + assertThat(EntityUtils.toString(response.getEntity()), containsString("\"datafeed_id\":\"" + datafeedId + + "\",\"job_id\":\"" + jobId + "\"")); } public void testInsufficientSearchPrivilegesOnPreview() throws Exception { @@ -953,67 +896,8 @@ public void testLookbackOnlyGivenAggregationsWithHistogramAndRollupIndex() throw public void testLookbackWithoutPermissionsAndRollup() throws Exception { setupFullAccessRole("airline-data-aggs-rollup"); String jobId = "rollup-permission-test-network-job"; - Request createJobRequest = new Request("PUT", MachineLearning.BASE_PATH + "anomaly_detectors/" + jobId); - createJobRequest.setJsonEntity("{\n" - + " \"description\": \"Aggs job\",\n" - + " \"analysis_config\": {\n" - + " \"bucket_span\": \"1h\",\n" - + " \"summary_count_field_name\": \"doc_count\",\n" - + " \"detectors\": [\n" - + " {\n" - + " \"function\": \"mean\",\n" - + " \"field_name\": \"responsetime\",\n" - + " \"by_field_name\": \"airline\"\n" - + " }\n" - + " ]\n" - + " },\n" - + " \"data_description\": {\"time_field\": \"time stamp\"}\n" - + "}"); - client().performRequest(createJobRequest); - - String rollupJobId = "rollup-" + jobId; - Request createRollupRequest = new Request("PUT", "/_rollup/job/" + rollupJobId); - createRollupRequest.setJsonEntity("{\n" - + "\"index_pattern\": \"airline-data-aggs\",\n" - + " \"rollup_index\": \"airline-data-aggs-rollup\",\n" - + " \"cron\": \"*/30 * * * * ?\",\n" - + " \"page_size\" :1000,\n" - + " \"groups\" : {\n" - + " \"date_histogram\": {\n" - + " \"field\": \"time stamp\",\n" - + " \"fixed_interval\": \"2m\",\n" - + " \"delay\": \"7d\"\n" - + " },\n" - + " \"terms\": {\n" - + " \"fields\": [\"airline\"]\n" - + " }" - + " },\n" - + " \"metrics\": [\n" - + " {\n" - + " \"field\": \"responsetime\",\n" - + " \"metrics\": [\"avg\",\"min\",\"max\",\"sum\"]\n" - + " },\n" - + " {\n" - + " \"field\": \"time stamp\",\n" - + " \"metrics\": [\"min\",\"max\"]\n" - + " }\n" - + " ]\n" - + "}"); - client().performRequest(createRollupRequest); - String datafeedId = "datafeed-" + jobId; - String aggregations = "{\"buckets\":{\"date_histogram\":{\"field\":\"time stamp\",\"fixed_interval\":\"3600000ms\"}," - + "\"aggregations\":{" - + "\"time stamp\":{\"max\":{\"field\":\"time stamp\"}}," - + "\"responsetime\":{\"avg\":{\"field\":\"responsetime\"}}}}}"; - - - // At the time we create the datafeed the user can access the network-data index that we have access to - new DatafeedBuilder(datafeedId, jobId, "airline-data-aggs-rollup") - .setAggregations(aggregations) - .setChunkingTimespan("300s") - .setAuthHeader(BASIC_AUTH_VALUE_ML_ADMIN_WITH_SOME_DATA_ACCESS) - .build(); + createJobAndDataFeed(jobId, datafeedId); // Change the role so that the user can no longer access network-data setupFullAccessRole("some-other-data"); @@ -1028,7 +912,7 @@ public void testLookbackWithoutPermissionsAndRollup() throws Exception { new Request("GET", NotificationsIndex.NOTIFICATIONS_INDEX + "/_search?size=1000&q=job_id:" + jobId)); String notificationsResponseAsString = EntityUtils.toString(notificationsResponse.getEntity()); assertThat(notificationsResponseAsString, containsString("\"message\":\"Datafeed is encountering errors extracting data: " + - "action [indices:admin/xpack/rollup/search] is unauthorized for user [ml_admin_plus_data]\"")); + "action [indices:data/read/xpack/rollup/search] is unauthorized for user [ml_admin_plus_data]\"")); } public void testLookbackWithSingleBucketAgg() throws Exception { @@ -1362,4 +1246,64 @@ private void bulkIndex(String bulk) throws IOException { String bulkResponse = EntityUtils.toString(client().performRequest(bulkRequest).getEntity()); assertThat(bulkResponse, not(containsString("\"errors\": false"))); } + + private Response createJobAndDataFeed(String jobId, String datafeedId) throws IOException { + Request createJobRequest = new Request("PUT", MachineLearning.BASE_PATH + "anomaly_detectors/" + jobId); + createJobRequest.setJsonEntity("{\n" + + " \"description\": \"Aggs job\",\n" + + " \"analysis_config\": {\n" + + " \"bucket_span\": \"1h\",\n" + + " \"summary_count_field_name\": \"doc_count\",\n" + + " \"detectors\": [\n" + + " {\n" + + " \"function\": \"mean\",\n" + + " \"field_name\": \"responsetime\",\n" + + " \"by_field_name\": \"airline\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"data_description\": {\"time_field\": \"time stamp\"}\n" + + "}"); + client().performRequest(createJobRequest); + + String rollupJobId = "rollup-" + jobId; + Request createRollupRequest = new Request("PUT", "/_rollup/job/" + rollupJobId); + createRollupRequest.setJsonEntity("{\n" + + "\"index_pattern\": \"airline-data-aggs\",\n" + + " \"rollup_index\": \"airline-data-aggs-rollup\",\n" + + " \"cron\": \"*/30 * * * * ?\",\n" + + " \"page_size\" :1000,\n" + + " \"groups\" : {\n" + + " \"date_histogram\": {\n" + + " \"field\": \"time stamp\",\n" + + " \"fixed_interval\": \"2m\",\n" + + " \"delay\": \"7d\"\n" + + " },\n" + + " \"terms\": {\n" + + " \"fields\": [\"airline\"]\n" + + " }" + + " },\n" + + " \"metrics\": [\n" + + " {\n" + + " \"field\": \"responsetime\",\n" + + " \"metrics\": [\"avg\",\"min\",\"max\",\"sum\"]\n" + + " },\n" + + " {\n" + + " \"field\": \"time stamp\",\n" + + " \"metrics\": [\"min\",\"max\"]\n" + + " }\n" + + " ]\n" + + "}"); + client().performRequest(createRollupRequest); + + String aggregations = "{\"buckets\":{\"date_histogram\":{\"field\":\"time stamp\",\"fixed_interval\":\"3600000ms\"}," + + "\"aggregations\":{" + + "\"time stamp\":{\"max\":{\"field\":\"time stamp\"}}," + + "\"responsetime\":{\"avg\":{\"field\":\"responsetime\"}}}}}"; + + return new DatafeedBuilder(datafeedId, jobId, "airline-data-aggs-rollup") + .setAggregations(aggregations) + .setAuthHeader(BASIC_AUTH_VALUE_ML_ADMIN_WITH_SOME_DATA_ACCESS) + .build(); + } }