From 9e8c2133a94dc1eeefe642b8ffc60908f6e108be Mon Sep 17 00:00:00 2001
From: jaymode
Date: Tue, 28 Jan 2020 08:18:11 -0700
Subject: [PATCH 01/18] Introduce system index APIs for Kibana
This commit introduces a module for Kibana that exposes REST APIs that
will be used by Kibana for access to its system indices.
---
modules/kibana/build.gradle | 27 +++++
.../elasticsearch/kibana/KibanaPlugin.java | 113 ++++++++++++++++++
.../kibana/KibanaPluginTests.java | 46 +++++++
.../elasticsearch/action/ActionModule.java | 8 +-
.../common/io/stream/StreamInput.java | 17 +++
.../common/io/stream/StreamOutput.java | 16 +++
.../common/settings/ClusterSettings.java | 18 ++-
.../common/util/concurrent/ThreadContext.java | 77 +++++++++---
.../java/org/elasticsearch/node/Node.java | 2 +-
.../plugins/SystemIndexPlugin.java | 4 +-
.../elasticsearch/rest/BaseRestHandler.java | 53 ++++++++
.../elasticsearch/rest/RestController.java | 22 +++-
.../transport/InboundMessage.java | 2 +-
.../transport/TransportLogger.java | 8 +-
.../util/concurrent/ThreadContextTests.java | 6 +-
.../authc/AuthenticationServiceTests.java | 2 +-
16 files changed, 395 insertions(+), 26 deletions(-)
create mode 100644 modules/kibana/build.gradle
create mode 100644 modules/kibana/src/main/java/org/elasticsearch/kibana/KibanaPlugin.java
create mode 100644 modules/kibana/src/test/java/org/elasticsearch/kibana/KibanaPluginTests.java
diff --git a/modules/kibana/build.gradle b/modules/kibana/build.gradle
new file mode 100644
index 0000000000000..923b558ca9ff8
--- /dev/null
+++ b/modules/kibana/build.gradle
@@ -0,0 +1,27 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+esplugin {
+ description 'Plugin exposing APIs for Kibana system indices'
+ classname 'org.elasticsearch.kibana.KibanaPlugin'
+}
+
+dependencies {
+ compileOnly project(path: ':modules:reindex', configuration: 'runtime')
+}
diff --git a/modules/kibana/src/main/java/org/elasticsearch/kibana/KibanaPlugin.java b/modules/kibana/src/main/java/org/elasticsearch/kibana/KibanaPlugin.java
new file mode 100644
index 0000000000000..dda47fd23ff52
--- /dev/null
+++ b/modules/kibana/src/main/java/org/elasticsearch/kibana/KibanaPlugin.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.kibana;
+
+import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
+import org.elasticsearch.cluster.node.DiscoveryNodes;
+import org.elasticsearch.common.settings.ClusterSettings;
+import org.elasticsearch.common.settings.IndexScopedSettings;
+import org.elasticsearch.common.settings.Setting;
+import org.elasticsearch.common.settings.Setting.Property;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.settings.SettingsFilter;
+import org.elasticsearch.index.reindex.RestDeleteByQueryAction;
+import org.elasticsearch.indices.SystemIndexDescriptor;
+import org.elasticsearch.plugins.Plugin;
+import org.elasticsearch.plugins.SystemIndexPlugin;
+import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestController;
+import org.elasticsearch.rest.RestHandler;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.action.document.RestBulkAction;
+import org.elasticsearch.rest.action.document.RestDeleteAction;
+import org.elasticsearch.rest.action.document.RestGetAction;
+import org.elasticsearch.rest.action.document.RestMultiGetAction;
+import org.elasticsearch.rest.action.search.RestSearchAction;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import static java.util.Collections.unmodifiableList;
+
+public class KibanaPlugin extends Plugin implements SystemIndexPlugin {
+
+ public static final Setting> KIBANA_INDEX_NAMES_SETTING = Setting.listSetting("kibana.system_indices",
+ unmodifiableList(Arrays.asList(".kibana", ".kibana_task_manager")), Function.identity(), Property.NodeScope);
+
+ @Override
+ public Collection getSystemIndexDescriptors(Settings settings) {
+ return KIBANA_INDEX_NAMES_SETTING.get(settings).stream()
+ .map(pattern -> new SystemIndexDescriptor(pattern, "System indices used by kibana"))
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ @Override
+ public List getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings,
+ IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
+ IndexNameExpressionResolver indexNameExpressionResolver,
+ Supplier nodesInCluster) {
+ final List allowedIndexPatterns = KIBANA_INDEX_NAMES_SETTING.get(settings);
+ return List.of(
+ new KibanaWrappedRestHandler(new RestGetAction(), allowedIndexPatterns),
+ new KibanaWrappedRestHandler(new RestMultiGetAction(settings), allowedIndexPatterns),
+ new KibanaWrappedRestHandler(new RestSearchAction(), allowedIndexPatterns),
+ new KibanaWrappedRestHandler(new RestBulkAction(settings), allowedIndexPatterns),
+ new KibanaWrappedRestHandler(new RestDeleteAction(), allowedIndexPatterns),
+ new KibanaWrappedRestHandler(new RestDeleteByQueryAction(), allowedIndexPatterns));
+ }
+
+ @Override
+ public List> getSettings() {
+ return List.of(KIBANA_INDEX_NAMES_SETTING);
+ }
+
+ static class KibanaWrappedRestHandler extends BaseRestHandler.Wrapper {
+
+ private final List allowedIndexPatterns;
+
+ KibanaWrappedRestHandler(BaseRestHandler delegate, List allowedIndexPatterns) {
+ super(delegate);
+ this.allowedIndexPatterns = allowedIndexPatterns;
+ }
+
+ @Override
+ public String getName() {
+ return "kibana_" + super.getName();
+ }
+
+ @Override
+ public List routes() {
+ return super.routes().stream().map(route -> new Route(route.getMethod(), "/_kibana" + route.getPath()))
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ @Override
+ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
+ client.threadPool().getThreadContext().allowSystemIndexAccess(allowedIndexPatterns);
+ return super.prepareRequest(request, client);
+ }
+ }
+}
diff --git a/modules/kibana/src/test/java/org/elasticsearch/kibana/KibanaPluginTests.java b/modules/kibana/src/test/java/org/elasticsearch/kibana/KibanaPluginTests.java
new file mode 100644
index 0000000000000..caf612364ff5f
--- /dev/null
+++ b/modules/kibana/src/test/java/org/elasticsearch/kibana/KibanaPluginTests.java
@@ -0,0 +1,46 @@
+
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.kibana;
+
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.indices.SystemIndexDescriptor;
+import org.elasticsearch.test.ESTestCase;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+
+public class KibanaPluginTests extends ESTestCase {
+
+ public void testKibanaIndexNames() {
+ assertThat(new KibanaPlugin().getSettings(), contains(KibanaPlugin.KIBANA_INDEX_NAMES_SETTING));
+ assertThat(new KibanaPlugin().getSystemIndexDescriptors(Settings.EMPTY).stream()
+ .map(SystemIndexDescriptor::getIndexPattern).collect(Collectors.toUnmodifiableList()),
+ contains(".kibana", ".kibana_task_manager"));
+ final List names = List.of("." + randomAlphaOfLength(4), "." + randomAlphaOfLength(6));
+ final List namesFromDescriptors = new KibanaPlugin().getSystemIndexDescriptors(
+ Settings.builder().putList(KibanaPlugin.KIBANA_INDEX_NAMES_SETTING.getKey(), names).build()
+ ).stream().map(SystemIndexDescriptor::getIndexPattern).collect(Collectors.toUnmodifiableList());
+ assertThat(namesFromDescriptors, is(names));
+ }
+}
diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java
index 62c265bc17a78..baa73e518b2a7 100644
--- a/server/src/main/java/org/elasticsearch/action/ActionModule.java
+++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java
@@ -21,6 +21,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.elasticsearch.Build;
import org.elasticsearch.action.admin.cluster.allocation.ClusterAllocationExplainAction;
import org.elasticsearch.action.admin.cluster.allocation.TransportClusterAllocationExplainAction;
import org.elasticsearch.action.admin.cluster.configuration.AddVotingConfigExclusionsAction;
@@ -412,9 +413,14 @@ public ActionModule(Settings settings, IndexNameExpressionResolver indexNameExpr
indicesAliasesRequestRequestValidators = new RequestValidators<>(
actionPlugins.stream().flatMap(p -> p.indicesAliasesRequestValidators().stream()).collect(Collectors.toList()));
- restController = new RestController(headers, restWrapper, nodeClient, circuitBreakerService, usageService);
+ final boolean restrictSystemIndices = isSnapshot() && RestController.RESTRICT_SYSTEM_INDICES.get(settings);
+ restController = new RestController(headers, restWrapper, nodeClient, circuitBreakerService, usageService, restrictSystemIndices);
}
+ // pkg-private for testing
+ boolean isSnapshot() {
+ return Build.CURRENT.isSnapshot();
+ }
public Map> getActions() {
return actions;
diff --git a/server/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java b/server/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java
index edc3e98d2f1b1..fbb297468eb04 100644
--- a/server/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java
+++ b/server/src/main/java/org/elasticsearch/common/io/stream/StreamInput.java
@@ -1145,6 +1145,23 @@ public List readStringList() throws IOException {
return readList(StreamInput::readString);
}
+ /**
+ * Reads an optional list of strings. The list is expected to have been written using
+ * {@link StreamOutput#writeOptionalStringCollection(Collection)}. If the returned list contains any entries it will be mutable.
+ * If it is empty it might be immutable.
+ *
+ * @return the list of strings
+ * @throws IOException if an I/O exception occurs reading the list
+ */
+ public List readOptionalStringList() throws IOException {
+ final boolean isPresent = readBoolean();
+ if (isPresent) {
+ return readList(StreamInput::readString);
+ } else {
+ return null;
+ }
+ }
+
/**
* Reads a set of objects. If the returned set contains any entries it will be mutable. If it is empty it might be immutable.
*/
diff --git a/server/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java b/server/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java
index acdda3732ed6a..88bc2b632ba95 100644
--- a/server/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java
+++ b/server/src/main/java/org/elasticsearch/common/io/stream/StreamOutput.java
@@ -1138,6 +1138,22 @@ public void writeStringCollection(final Collection collection) throws IO
writeCollection(collection, StreamOutput::writeString);
}
+ /**
+ * Writes an optional collection of a strings. The corresponding collection can be read from a stream input using
+ * {@link StreamInput#readList(Writeable.Reader)}.
+ *
+ * @param collection the collection of strings
+ * @throws IOException if an I/O exception occurs writing the collection
+ */
+ public void writeOptionalStringCollection(final Collection collection) throws IOException {
+ if (collection != null) {
+ writeBoolean(true);
+ writeCollection(collection, StreamOutput::writeString);
+ } else {
+ writeBoolean(false);
+ }
+ }
+
/**
* Writes a list of {@link NamedWriteable} objects.
*/
diff --git a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java
index 4861cea792144..88397b26d528c 100644
--- a/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java
+++ b/server/src/main/java/org/elasticsearch/common/settings/ClusterSettings.java
@@ -19,6 +19,7 @@
package org.elasticsearch.common.settings;
import org.apache.logging.log4j.LogManager;
+import org.elasticsearch.Build;
import org.elasticsearch.action.admin.cluster.configuration.TransportAddVotingConfigExclusionsAction;
import org.elasticsearch.action.admin.indices.close.TransportCloseIndexAction;
import org.elasticsearch.action.search.TransportSearchAction;
@@ -97,6 +98,7 @@
import org.elasticsearch.plugins.PluginsService;
import org.elasticsearch.repositories.fs.FsRepository;
import org.elasticsearch.rest.BaseRestHandler;
+import org.elasticsearch.rest.RestController;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.SearchModule;
import org.elasticsearch.search.SearchService;
@@ -111,6 +113,7 @@
import org.elasticsearch.watcher.ResourceWatcherService;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
@@ -180,7 +183,9 @@ public void apply(Settings value, Settings current, Settings previous) {
}
}
- public static Set> BUILT_IN_CLUSTER_SETTINGS = Set.of(
+ public static final Set> BUILT_IN_CLUSTER_SETTINGS;
+ static {
+ final Set> alwaysEnabled = Set.of(
AwarenessAllocationDecider.CLUSTER_ROUTING_ALLOCATION_AWARENESS_ATTRIBUTE_SETTING,
AwarenessAllocationDecider.CLUSTER_ROUTING_ALLOCATION_AWARENESS_FORCE_GROUP_SETTING,
BalancedShardsAllocator.INDEX_BALANCE_FACTOR_SETTING,
@@ -474,6 +479,15 @@ public void apply(Settings value, Settings current, Settings previous) {
HandshakingTransportAddressConnector.PROBE_CONNECT_TIMEOUT_SETTING,
HandshakingTransportAddressConnector.PROBE_HANDSHAKE_TIMEOUT_SETTING);
- static List> BUILT_IN_SETTING_UPGRADERS = Collections.emptyList();
+ if (Build.CURRENT.isSnapshot()) {
+ Set> modifiable = new HashSet<>(alwaysEnabled);
+ modifiable.add(RestController.RESTRICT_SYSTEM_INDICES);
+ BUILT_IN_CLUSTER_SETTINGS = Set.copyOf(modifiable);
+ } else {
+ BUILT_IN_CLUSTER_SETTINGS = alwaysEnabled;
+ }
+ }
+
+ static final List> BUILT_IN_SETTING_UPGRADERS = Collections.emptyList();
}
diff --git a/server/src/main/java/org/elasticsearch/common/util/concurrent/ThreadContext.java b/server/src/main/java/org/elasticsearch/common/util/concurrent/ThreadContext.java
index e4cb14857932f..65fb419eb3040 100644
--- a/server/src/main/java/org/elasticsearch/common/util/concurrent/ThreadContext.java
+++ b/server/src/main/java/org/elasticsearch/common/util/concurrent/ThreadContext.java
@@ -20,6 +20,7 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.elasticsearch.Version;
import org.elasticsearch.action.support.ContextPreservingActionListener;
import org.elasticsearch.client.OriginSettingClient;
import org.elasticsearch.common.collect.Tuple;
@@ -48,6 +49,7 @@
import java.util.stream.Collector;
import java.util.stream.Stream;
+import static java.util.Collections.emptyList;
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_WARNING_HEADER_COUNT;
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_MAX_WARNING_HEADER_SIZE;
@@ -62,7 +64,7 @@
* Consumers of ThreadContext usually don't need to interact with adding or stashing contexts. Every elasticsearch thread is managed by
* a thread pool or executor being responsible for stashing and restoring the threads context. For instance if a network request is
* received, all headers are deserialized from the network and directly added as the headers of the threads {@link ThreadContext}
- * (see {@link #readHeaders(StreamInput)}. In order to not modify the context that is currently active on this thread the network code
+ * (see {@link #readFrom(StreamInput)}. In order to not modify the context that is currently active on this thread the network code
* uses a try/with pattern to stash it's current context, read headers into a fresh one and once the request is handled or a handler thread
* is forked (which in turn inherits the context) it restores the previous context. For instance:
*
@@ -230,17 +232,18 @@ public void writeTo(StreamOutput out) throws IOException {
}
/**
- * Reads the headers from the stream into the current context
+ * Reads the values from the stream into the current context
*/
- public void readHeaders(StreamInput in) throws IOException {
+ public void readFrom(StreamInput in) throws IOException {
final Tuple