Просмотр исходного кода

Adjust manage_follow_index privilege for promote data stream api (#67773)

This fixes the manage_follow_index builtin privilege so that it can be used
for managing data streams in a follower cluster. In order to successfully
unfollow a data stream the promote data stream and rollover APIs need to be
executed. (This is additional to the close and unfollow APIs).
Martijn van Groningen 4 лет назад
Родитель
Сommit
e42c009884

+ 1 - 1
docs/reference/data-streams/promote-data-stream-api.asciidoc

@@ -32,7 +32,7 @@ POST /_data_stream/_promote/my-data-stream
 [[promote-data-stream-api-prereqs]]
 ==== {api-prereq-title}
 
-* If the {es} {security-features} are enabled, you must have the `manage`
+* If the {es} {security-features} are enabled, you must have the `manage_follow_index`
 <<privileges-list-cluster,cluster privilege>> to use this API.
 
 [[promote-data-stream-api-path-params]]

+ 0 - 54
x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/AutoFollowIT.java

@@ -10,27 +10,22 @@ import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 import org.elasticsearch.client.ResponseException;
 import org.elasticsearch.client.RestClient;
-import org.elasticsearch.cluster.metadata.DataStream;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.xcontent.ObjectPath;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.json.JsonXContent;
-import org.elasticsearch.common.xcontent.support.XContentMapValues;
 
 import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
-import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
-import static org.elasticsearch.rest.action.search.RestSearchAction.TOTAL_HITS_AS_INT_PARAM;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasEntry;
 import static org.hamcrest.Matchers.hasKey;
-import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.is;
 import static org.hamcrest.Matchers.notNullValue;
@@ -678,55 +673,6 @@ public class AutoFollowIT extends ESCCRRestTestCase {
         return (Integer) response.get("number_of_successful_follow_indices");
     }
 
-    private void createAutoFollowPattern(RestClient client, String name, String pattern, String remoteCluster) throws IOException {
-        Request request = new Request("PUT", "/_ccr/auto_follow/" + name);
-        try (XContentBuilder bodyBuilder = JsonXContent.contentBuilder()) {
-            bodyBuilder.startObject();
-            {
-                bodyBuilder.startArray("leader_index_patterns");
-                {
-                    bodyBuilder.value(pattern);
-                }
-                bodyBuilder.endArray();
-                bodyBuilder.field("remote_cluster", remoteCluster);
-            }
-            bodyBuilder.endObject();
-            request.setJsonEntity(Strings.toString(bodyBuilder));
-        }
-        assertOK(client.performRequest(request));
-    }
-
-    private static String backingIndexName(String dataStreamName, int generation) {
-        return DataStream.getDefaultBackingIndexName(dataStreamName, generation);
-    }
-
-    private static void verifyDocuments(final RestClient client,
-                                        final String index,
-                                        final int expectedNumDocs) throws IOException {
-        final Request request = new Request("GET", "/" + index + "/_search");
-        request.addParameter(TOTAL_HITS_AS_INT_PARAM, "true");
-        Map<String, ?> response = toMap(client.performRequest(request));
-
-        int numDocs = (int) XContentMapValues.extractValue("hits.total", response);
-        assertThat(index, numDocs, equalTo(expectedNumDocs));
-    }
-
-    static void verifyDataStream(final RestClient client,
-                                         final String name,
-                                         final String... expectedBackingIndices) throws IOException {
-        Request request = new Request("GET", "/_data_stream/" + name);
-        Map<String, ?> response = toMap(client.performRequest(request));
-        List<?> retrievedDataStreams = (List<?>) response.get("data_streams");
-        assertThat(retrievedDataStreams, hasSize(1));
-        List<?> actualBackingIndices = (List<?>) ((Map<?, ?>) retrievedDataStreams.get(0)).get("indices");
-        assertThat(actualBackingIndices, hasSize(expectedBackingIndices.length));
-        for (int i = 0; i < expectedBackingIndices.length; i++) {
-            Map<?, ?> actualBackingIndex = (Map<?, ?>) actualBackingIndices.get(i);
-            String expectedBackingIndex = expectedBackingIndices[i];
-            assertThat(actualBackingIndex.get("index_name"), equalTo(expectedBackingIndex));
-        }
-    }
-
     private void deleteDataStream(String name) throws IOException {
         try (RestClient leaderClient = buildLeaderClient()) {
             Request deleteTemplateRequest = new Request("DELETE", "/_data_stream/" + name);

+ 1 - 0
x-pack/plugin/ccr/qa/security/follower-roles.yml

@@ -8,6 +8,7 @@ ccruser:
         - read
         - write
         - manage_follow_index
+        - view_index_metadata
     - names: [ 'clean-follower' ]
       privileges:
         - monitor

+ 1 - 0
x-pack/plugin/ccr/qa/security/leader-roles.yml

@@ -7,3 +7,4 @@ ccruser:
         - monitor
         - read
         - manage_leader_index
+        - view_index_metadata

+ 49 - 0
x-pack/plugin/ccr/qa/security/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexSecurityIT.java

@@ -17,8 +17,11 @@ import org.elasticsearch.index.seqno.ReplicationTracker;
 import org.elasticsearch.test.rest.yaml.ObjectPath;
 
 import java.io.IOException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
@@ -253,4 +256,50 @@ public class FollowIndexSecurityIT extends ESCCRRestTestCase {
         }
     }
 
+    public void testUnPromoteAndFollowDataStream() throws Exception {
+        if ("follow".equals(targetCluster) == false) {
+            return;
+        }
+
+        var numDocs = 64;
+        var dataStreamName = "logs-eu-monitor1";
+        var dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss", Locale.ROOT);
+
+        // Setup
+        {
+            createAutoFollowPattern(adminClient(), "test_pattern", "logs-eu*", "leader_cluster");
+        }
+        // Create data stream and ensure that is is auto followed
+        {
+            try (var leaderClient = buildLeaderClient()) {
+                for (var i = 0; i < numDocs; i++) {
+                    var indexRequest = new Request("POST", "/" + dataStreamName + "/_doc");
+                    indexRequest.addParameter("refresh", "true");
+                    indexRequest.setJsonEntity("{\"@timestamp\": \"" + dateFormat.format(new Date()) + "\",\"message\":\"abc\"}");
+                    assertOK(leaderClient.performRequest(indexRequest));
+                }
+                verifyDataStream(leaderClient, dataStreamName, backingIndexName(dataStreamName, 1));
+                verifyDocuments(leaderClient, dataStreamName, numDocs);
+            }
+            assertBusy(() -> {
+                verifyDataStream(client(), dataStreamName, backingIndexName(dataStreamName, 1));
+                ensureYellow(dataStreamName);
+                verifyDocuments(client(), dataStreamName, numDocs);
+            });
+        }
+        // promote and unfollow
+        {
+            var promoteRequest = new Request("POST", "/_data_stream/_promote/" + dataStreamName);
+            assertOK(client().performRequest(promoteRequest));
+            // Now that the data stream is a non replicated data stream, rollover.
+            var rolloverRequest = new Request("POST", "/" +  dataStreamName + "/_rollover");
+            assertOK(client().performRequest(rolloverRequest));
+            // Unfollow .ds-logs-eu-monitor1-000001,
+            // which is now possible because this index can now be closed as it is no longer the write index.
+            pauseFollow(backingIndexName(dataStreamName, 1));
+            closeIndex(backingIndexName(dataStreamName, 1));
+            unfollow(backingIndexName(dataStreamName, 1));
+        }
+    }
+
 }

+ 51 - 0
x-pack/plugin/ccr/qa/src/main/java/org/elasticsearch/xpack/ccr/ESCCRRestTestCase.java

@@ -11,6 +11,7 @@ import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 import org.elasticsearch.client.ResponseException;
 import org.elasticsearch.client.RestClient;
+import org.elasticsearch.cluster.metadata.DataStream;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.ToXContent;
@@ -31,6 +32,7 @@ import static org.hamcrest.Matchers.endsWith;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.hamcrest.Matchers.hasSize;
 
 public class ESCCRRestTestCase extends ESRestTestCase {
 
@@ -160,6 +162,17 @@ public class ESCCRRestTestCase extends ESRestTestCase {
         }
     }
 
+    protected static void verifyDocuments(final RestClient client,
+                                          final String index,
+                                          final int expectedNumDocs) throws IOException {
+        final Request request = new Request("GET", "/" + index + "/_search");
+        request.addParameter(TOTAL_HITS_AS_INT_PARAM, "true");
+        Map<String, ?> response = toMap(client.performRequest(request));
+
+        int numDocs = (int) XContentMapValues.extractValue("hits.total", response);
+        assertThat(index, numDocs, equalTo(expectedNumDocs));
+    }
+
     protected static void verifyCcrMonitoring(final String expectedLeaderIndex, final String expectedFollowerIndex) throws IOException {
         Request request = new Request("GET", "/.monitoring-*/_search");
         request.setJsonEntity("{\"query\": {\"term\": {\"ccr_stats.leader_index\": \"" + expectedLeaderIndex + "\"}}}");
@@ -290,6 +303,44 @@ public class ESCCRRestTestCase extends ESRestTestCase {
         return RestStatus.OK.getStatus() == response.getStatusLine().getStatusCode();
     }
 
+    protected static void verifyDataStream(final RestClient client,
+                                 final String name,
+                                 final String... expectedBackingIndices) throws IOException {
+        Request request = new Request("GET", "/_data_stream/" + name);
+        Map<String, ?> response = toMap(client.performRequest(request));
+        List<?> retrievedDataStreams = (List<?>) response.get("data_streams");
+        assertThat(retrievedDataStreams, hasSize(1));
+        List<?> actualBackingIndices = (List<?>) ((Map<?, ?>) retrievedDataStreams.get(0)).get("indices");
+        assertThat(actualBackingIndices, hasSize(expectedBackingIndices.length));
+        for (int i = 0; i < expectedBackingIndices.length; i++) {
+            Map<?, ?> actualBackingIndex = (Map<?, ?>) actualBackingIndices.get(i);
+            String expectedBackingIndex = expectedBackingIndices[i];
+            assertThat(actualBackingIndex.get("index_name"), equalTo(expectedBackingIndex));
+        }
+    }
+
+    protected static void createAutoFollowPattern(RestClient client, String name, String pattern, String remoteCluster) throws IOException {
+        Request request = new Request("PUT", "/_ccr/auto_follow/" + name);
+        try (XContentBuilder bodyBuilder = JsonXContent.contentBuilder()) {
+            bodyBuilder.startObject();
+            {
+                bodyBuilder.startArray("leader_index_patterns");
+                {
+                    bodyBuilder.value(pattern);
+                }
+                bodyBuilder.endArray();
+                bodyBuilder.field("remote_cluster", remoteCluster);
+            }
+            bodyBuilder.endObject();
+            request.setJsonEntity(Strings.toString(bodyBuilder));
+        }
+        assertOK(client.performRequest(request));
+    }
+
+    protected static String backingIndexName(String dataStreamName, int generation) {
+        return DataStream.getDefaultBackingIndexName(dataStreamName, generation);
+    }
+
     protected RestClient buildLeaderClient() throws IOException {
         assert "leader".equals(targetCluster) == false;
         return buildClient(System.getProperty("tests.leader_host"));

+ 14 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/PromoteDataStreamAction.java

@@ -8,6 +8,8 @@ package org.elasticsearch.xpack.core.action;
 
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.ActionType;
+import org.elasticsearch.action.IndicesRequest;
+import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.action.support.master.AcknowledgedResponse;
 import org.elasticsearch.action.support.master.MasterNodeRequest;
 import org.elasticsearch.common.io.stream.StreamInput;
@@ -27,9 +29,9 @@ public class PromoteDataStreamAction extends ActionType<AcknowledgedResponse> {
         super(NAME, AcknowledgedResponse::readFrom);
     }
 
-    public static class Request extends MasterNodeRequest<PromoteDataStreamAction.Request> {
+    public static class Request extends MasterNodeRequest<PromoteDataStreamAction.Request> implements IndicesRequest {
 
-        private String name;
+        private final String name;
 
         public Request(String name) {
             this.name = Objects.requireNonNull(name);
@@ -53,6 +55,16 @@ public class PromoteDataStreamAction extends ActionType<AcknowledgedResponse> {
             this.name = in.readString();
         }
 
+        @Override
+        public String[] indices() {
+            return new String[]{name};
+        }
+
+        @Override
+        public IndicesOptions indicesOptions() {
+            return IndicesOptions.strictSingleIndexNoExpandForbidClosed();
+        }
+
         @Override
         public void writeTo(StreamOutput out) throws IOException {
             super.writeTo(out);

+ 3 - 1
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilege.java

@@ -19,6 +19,7 @@ import org.elasticsearch.action.admin.indices.mapping.get.GetFieldMappingsAction
 import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsAction;
 import org.elasticsearch.action.admin.indices.mapping.put.AutoPutMappingAction;
 import org.elasticsearch.action.admin.indices.resolve.ResolveIndexAction;
+import org.elasticsearch.action.admin.indices.rollover.RolloverAction;
 import org.elasticsearch.action.admin.indices.settings.get.GetSettingsAction;
 import org.elasticsearch.action.admin.indices.validate.query.ValidateQueryAction;
 import org.elasticsearch.action.fieldcaps.FieldCapabilitiesAction;
@@ -26,6 +27,7 @@ import org.elasticsearch.common.Strings;
 import org.elasticsearch.xpack.core.action.CreateDataStreamAction;
 import org.elasticsearch.xpack.core.action.DeleteDataStreamAction;
 import org.elasticsearch.xpack.core.action.GetDataStreamAction;
+import org.elasticsearch.xpack.core.action.PromoteDataStreamAction;
 import org.elasticsearch.xpack.core.ccr.action.ForgetFollowerAction;
 import org.elasticsearch.xpack.core.ccr.action.PutFollowAction;
 import org.elasticsearch.xpack.core.ccr.action.UnfollowAction;
@@ -72,7 +74,7 @@ public final class IndexPrivilege extends Privilege {
             GetSettingsAction.NAME, ExplainLifecycleAction.NAME, GetDataStreamAction.NAME, ResolveIndexAction.NAME,
             FieldCapabilitiesAction.NAME + "*");
     private static final Automaton MANAGE_FOLLOW_INDEX_AUTOMATON = patterns(PutFollowAction.NAME, UnfollowAction.NAME,
-        CloseIndexAction.NAME + "*");
+        CloseIndexAction.NAME + "*", PromoteDataStreamAction.NAME, RolloverAction.NAME);
     private static final Automaton MANAGE_LEADER_INDEX_AUTOMATON = patterns(ForgetFollowerAction.NAME + "*");
     private static final Automaton MANAGE_ILM_AUTOMATON = patterns("indices:admin/ilm/*");
     private static final Automaton MAINTENANCE_AUTOMATON = patterns("indices:admin/refresh*", "indices:admin/flush*",