Skip to content

Commit 44f5ed6

Browse files
authored
Deprecate creation of dot-prefixed index names except for hidden and system indices (elastic#49959)
This commit deprecates the creation of dot-prefixed index names (e.g. .watches) unless they are either 1) a hidden index, or 2) registered by a plugin that extends SystemIndexPlugin. This is the first step towards more thorough protections for system indices. This commit also modifies several plugins which use dot-prefixed indices to register indices they own as system indices, and adds a plugin to register .tasks as a system index.
1 parent 8f87bb3 commit 44f5ed6

File tree

27 files changed

+663
-54
lines changed

27 files changed

+663
-54
lines changed

docs/reference/index-modules.asciidoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ indices.
7979
Indicates whether <<query-filter-context, cached filters>> are pre-loaded for
8080
nested queries. Possible values are `true` (default) and `false`.
8181

82-
`index.hidden`::
82+
[[index-hidden]] `index.hidden`::
8383

8484
Indicates whether the index should be hidden by default. Hidden indices are not
8585
returned by default when using a wildcard expression. This behavior is controlled

docs/reference/indices/create-index.asciidoc

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ PUT /twitter
1919

2020
[[indices-create-api-desc]]
2121
==== {api-description-title}
22-
You can use the create index API to add a new index to an {es} cluster. When
22+
You can use the create index API to add a new index to an {es} cluster. When
2323
creating an index, you can specify the following:
2424

2525
* Settings for the index
@@ -44,6 +44,7 @@ Index names must meet the following criteria:
4444
- Cannot start with `-`, `_`, `+`
4545
- Cannot be `.` or `..`
4646
- Cannot be longer than 255 bytes (note it is bytes, so multi-byte characters will count towards the 255 limit faster)
47+
- Names starting with `.` are deprecated, except for <<index-hidden,hidden indices>> and internal indices managed by plugins
4748
// end::index-name-reqs[]
4849
--
4950

modules/tasks/build.gradle

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
esplugin {
21+
description 'Supports the Tasks API'
22+
classname 'org.elasticsearch.tasksplugin.TasksPlugin'
23+
}
24+
25+
integTest.enabled = false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.tasksplugin;
21+
22+
import org.elasticsearch.indices.SystemIndexDescriptor;
23+
import org.elasticsearch.plugins.Plugin;
24+
import org.elasticsearch.plugins.SystemIndexPlugin;
25+
26+
import java.util.Collection;
27+
import java.util.Collections;
28+
29+
import static org.elasticsearch.tasks.TaskResultsService.TASK_INDEX;
30+
31+
/**
32+
* This plugin currently only exists to register `.tasks` as a system index.
33+
*/
34+
public class TasksPlugin extends Plugin implements SystemIndexPlugin {
35+
36+
@Override
37+
public Collection<SystemIndexDescriptor> getSystemIndexDescriptors() {
38+
return Collections.singletonList(new SystemIndexDescriptor(TASK_INDEX, this.getClass().getSimpleName()));
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.tasksplugin;
21+
22+
import org.elasticsearch.test.ESTestCase;
23+
import org.hamcrest.Matchers;
24+
25+
public class TasksPluginTests extends ESTestCase {
26+
27+
public void testDummy() {
28+
// This is a dummy test case to satisfy the conventions
29+
TasksPlugin plugin = new TasksPlugin();
30+
assertThat(plugin.getSystemIndexDescriptors(), Matchers.hasSize(1));
31+
}
32+
}

server/src/main/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ protected void masterOperation(Task task, final RolloverRequest rolloverRequest,
122122
? rolloverRequest.getNewIndexName()
123123
: generateRolloverIndexName(sourceProvidedName, indexNameExpressionResolver);
124124
final String rolloverIndexName = indexNameExpressionResolver.resolveDateMathExpression(unresolvedName);
125-
MetaDataCreateIndexService.validateIndexName(rolloverIndexName, state); // will fail if the index already exists
126125
final Boolean isHidden = IndexMetaData.INDEX_HIDDEN_SETTING.exists(rolloverRequest.getCreateIndexRequest().settings()) ?
127126
IndexMetaData.INDEX_HIDDEN_SETTING.get(rolloverRequest.getCreateIndexRequest().settings()) : null;
127+
createIndexService.validateIndexName(rolloverIndexName, state, isHidden); // fails if the index already exists
128128
checkNoDuplicatedAliasInIndexTemplate(metaData, rolloverIndexName, rolloverRequest.getAlias(), isHidden);
129129
IndicesStatsRequest statsRequest = new IndicesStatsRequest().indices(rolloverRequest.getAlias())
130130
.clear()

server/src/main/java/org/elasticsearch/cluster/metadata/MetaDataCreateIndexService.java

+43-12
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.elasticsearch.common.ValidationException;
5454
import org.elasticsearch.common.compress.CompressedXContent;
5555
import org.elasticsearch.common.io.PathUtils;
56+
import org.elasticsearch.common.logging.DeprecationLogger;
5657
import org.elasticsearch.common.settings.IndexScopedSettings;
5758
import org.elasticsearch.common.settings.Setting;
5859
import org.elasticsearch.common.settings.Settings;
@@ -70,6 +71,7 @@
7071
import org.elasticsearch.indices.IndexCreationException;
7172
import org.elasticsearch.indices.IndicesService;
7273
import org.elasticsearch.indices.InvalidIndexNameException;
74+
import org.elasticsearch.indices.SystemIndexDescriptor;
7375
import org.elasticsearch.indices.cluster.IndicesClusterStateService.AllocatedIndices.IndexRemovalReason;
7476
import org.elasticsearch.threadpool.ThreadPool;
7577

@@ -78,6 +80,7 @@
7880
import java.nio.file.Path;
7981
import java.time.Instant;
8082
import java.util.ArrayList;
83+
import java.util.Collection;
8184
import java.util.Collections;
8285
import java.util.HashMap;
8386
import java.util.List;
@@ -89,6 +92,7 @@
8992
import java.util.function.BiFunction;
9093
import java.util.function.Predicate;
9194
import java.util.function.Supplier;
95+
import java.util.stream.Collectors;
9296
import java.util.stream.IntStream;
9397

9498
import static java.util.stream.Collectors.toList;
@@ -103,6 +107,7 @@
103107
*/
104108
public class MetaDataCreateIndexService {
105109
private static final Logger logger = LogManager.getLogger(MetaDataCreateIndexService.class);
110+
private static final DeprecationLogger deprecationLogger = new DeprecationLogger(logger);
106111

107112
public static final int MAX_INDEX_NAME_BYTES = 255;
108113

@@ -115,19 +120,21 @@ public class MetaDataCreateIndexService {
115120
private final IndexScopedSettings indexScopedSettings;
116121
private final ActiveShardsObserver activeShardsObserver;
117122
private final NamedXContentRegistry xContentRegistry;
123+
private final Collection<SystemIndexDescriptor> systemIndexDescriptors;
118124
private final boolean forbidPrivateIndexSettings;
119125

120126
public MetaDataCreateIndexService(
121-
final Settings settings,
122-
final ClusterService clusterService,
123-
final IndicesService indicesService,
124-
final AllocationService allocationService,
125-
final AliasValidator aliasValidator,
126-
final Environment env,
127-
final IndexScopedSettings indexScopedSettings,
128-
final ThreadPool threadPool,
129-
final NamedXContentRegistry xContentRegistry,
130-
final boolean forbidPrivateIndexSettings) {
127+
final Settings settings,
128+
final ClusterService clusterService,
129+
final IndicesService indicesService,
130+
final AllocationService allocationService,
131+
final AliasValidator aliasValidator,
132+
final Environment env,
133+
final IndexScopedSettings indexScopedSettings,
134+
final ThreadPool threadPool,
135+
final NamedXContentRegistry xContentRegistry,
136+
final Collection<SystemIndexDescriptor> systemIndexDescriptors,
137+
final boolean forbidPrivateIndexSettings) {
131138
this.settings = settings;
132139
this.clusterService = clusterService;
133140
this.indicesService = indicesService;
@@ -137,17 +144,40 @@ public MetaDataCreateIndexService(
137144
this.indexScopedSettings = indexScopedSettings;
138145
this.activeShardsObserver = new ActiveShardsObserver(clusterService, threadPool);
139146
this.xContentRegistry = xContentRegistry;
147+
this.systemIndexDescriptors = systemIndexDescriptors;
140148
this.forbidPrivateIndexSettings = forbidPrivateIndexSettings;
141149
}
142150

143151
/**
144152
* Validate the name for an index against some static rules and a cluster state.
145153
*/
146-
public static void validateIndexName(String index, ClusterState state) {
154+
public void validateIndexName(String index, ClusterState state, @Nullable Boolean isHidden) {
147155
validateIndexOrAliasName(index, InvalidIndexNameException::new);
148156
if (!index.toLowerCase(Locale.ROOT).equals(index)) {
149157
throw new InvalidIndexNameException(index, "must be lowercase");
150158
}
159+
160+
if (index.charAt(0) == '.') {
161+
List<SystemIndexDescriptor> matchingDescriptors = systemIndexDescriptors.stream()
162+
.filter(descriptor -> descriptor.matchesIndexPattern(index))
163+
.collect(toList());
164+
if (matchingDescriptors.isEmpty() && (isHidden == null || isHidden == Boolean.FALSE)) {
165+
deprecationLogger.deprecated("index name [{}] starts with a dot '.', in the next major version, index names " +
166+
"starting with a dot are reserved for hidden indices and system indices", index);
167+
} else if (matchingDescriptors.size() > 1) {
168+
// This should be prevented by erroring on overlapping patterns at startup time, but is here just in case.
169+
StringBuilder errorMessage = new StringBuilder()
170+
.append("index name [")
171+
.append(index)
172+
.append("] is claimed as a system index by multiple system index patterns: [")
173+
.append(matchingDescriptors.stream()
174+
.map(descriptor -> "pattern: [" + descriptor.getIndexPattern() +
175+
"], description: [" + descriptor.getDescription() + "]").collect(Collectors.joining("; ")));
176+
// Throw AssertionError if assertions are enabled, or a regular exception otherwise:
177+
assert false : errorMessage.toString();
178+
throw new IllegalStateException(errorMessage.toString());
179+
}
180+
}
151181
if (state.routingTable().hasIndex(index)) {
152182
throw new ResourceAlreadyExistsException(state.routingTable().index(index).getIndex());
153183
}
@@ -653,7 +683,8 @@ private static IndexService validateActiveShardCountAndCreateIndexService(String
653683
}
654684

655685
private void validate(CreateIndexClusterStateUpdateRequest request, ClusterState state) {
656-
validateIndexName(request.index(), state);
686+
boolean isHidden = IndexMetaData.INDEX_HIDDEN_SETTING.get(request.settings());
687+
validateIndexName(request.index(), state, isHidden);
657688
validateIndexSettings(request.index(), request.settings(), forbidPrivateIndexSettings);
658689
}
659690

0 commit comments

Comments
 (0)