Skip to content

Commit d16fa2d

Browse files
authored
Prevent snapshots to be mounted as system indices (#61517)
System indices can be snapshotted and are therefore potential candidates to be mounted as searchable snapshot indices. As of today nothing prevents a snapshot to be mounted under an index name starting with . and this can lead to conflicting situations because searchable snapshot indices are read-only and Elasticsearch expects some system indices to be writable; because searchable snapshot indices will soon use an internal system index (#60522) to speed up recoveries and we should prevent the system index to be itself a searchable snapshot index (leading to some deadlock situation for recovery). This commit introduces a changes to prevent snapshots to be mounted as a system index.
1 parent b033611 commit d16fa2d

File tree

5 files changed

+128
-8
lines changed

5 files changed

+128
-8
lines changed

server/src/main/java/org/elasticsearch/node/Node.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,7 @@ protected Node(final Environment initialEnvironment,
653653
b.bind(RerouteService.class).toInstance(rerouteService);
654654
b.bind(ShardLimitValidator.class).toInstance(shardLimitValidator);
655655
b.bind(FsHealthService.class).toInstance(fsHealthService);
656+
b.bind(SystemIndices.class).toInstance(systemIndices);
656657
}
657658
);
658659
injector = modules.createInjector();

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/LocalStateCompositeXPackPlugin.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
import org.elasticsearch.plugins.Plugin;
6565
import org.elasticsearch.plugins.RepositoryPlugin;
6666
import org.elasticsearch.plugins.ScriptPlugin;
67+
import org.elasticsearch.plugins.SystemIndexPlugin;
6768
import org.elasticsearch.repositories.RepositoriesService;
6869
import org.elasticsearch.repositories.Repository;
6970
import org.elasticsearch.rest.RestController;
@@ -101,7 +102,8 @@
101102
import static java.util.stream.Collectors.toList;
102103

103104
public class LocalStateCompositeXPackPlugin extends XPackPlugin implements ScriptPlugin, ActionPlugin, IngestPlugin, NetworkPlugin,
104-
ClusterPlugin, DiscoveryPlugin, MapperPlugin, AnalysisPlugin, PersistentTaskPlugin, EnginePlugin, IndexStorePlugin {
105+
ClusterPlugin, DiscoveryPlugin, MapperPlugin, AnalysisPlugin, PersistentTaskPlugin, EnginePlugin, IndexStorePlugin,
106+
SystemIndexPlugin {
105107

106108
private XPackLicenseState licenseState;
107109
private SSLService sslService;

x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/action/TransportMountSearchableSnapshotAction.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.elasticsearch.common.io.stream.StreamInput;
2525
import org.elasticsearch.common.settings.Settings;
2626
import org.elasticsearch.index.IndexNotFoundException;
27+
import org.elasticsearch.indices.SystemIndices;
2728
import org.elasticsearch.license.XPackLicenseState;
2829
import org.elasticsearch.repositories.IndexId;
2930
import org.elasticsearch.repositories.RepositoriesService;
@@ -63,6 +64,7 @@ public class TransportMountSearchableSnapshotAction extends TransportMasterNodeA
6364
private final Client client;
6465
private final RepositoriesService repositoriesService;
6566
private final XPackLicenseState licenseState;
67+
private final SystemIndices systemIndices;
6668

6769
@Inject
6870
public TransportMountSearchableSnapshotAction(
@@ -73,7 +75,8 @@ public TransportMountSearchableSnapshotAction(
7375
RepositoriesService repositoriesService,
7476
ActionFilters actionFilters,
7577
IndexNameExpressionResolver indexNameExpressionResolver,
76-
XPackLicenseState licenseState
78+
XPackLicenseState licenseState,
79+
SystemIndices systemIndices
7780
) {
7881
super(
7982
MountSearchableSnapshotAction.NAME,
@@ -87,6 +90,7 @@ public TransportMountSearchableSnapshotAction(
8790
this.client = client;
8891
this.repositoriesService = repositoriesService;
8992
this.licenseState = Objects.requireNonNull(licenseState);
93+
this.systemIndices = Objects.requireNonNull(systemIndices);
9094
}
9195

9296
@Override
@@ -132,6 +136,11 @@ protected void masterOperation(
132136
) {
133137
SearchableSnapshots.ensureValidLicense(licenseState);
134138

139+
final String mountedIndexName = request.mountedIndexName();
140+
if (systemIndices.isSystemIndex(mountedIndexName)) {
141+
throw new ElasticsearchException("system index [{}] cannot be mounted as searchable snapshots", mountedIndexName);
142+
}
143+
135144
final String repoName = request.repositoryName();
136145
final String snapName = request.snapshotName();
137146
final String indexName = request.snapshotIndexName();
@@ -168,7 +177,7 @@ protected void masterOperation(
168177
.indices(indexName)
169178
// Always rename it to the desired mounted index name
170179
.renamePattern(".+")
171-
.renameReplacement(request.mountedIndexName())
180+
.renameReplacement(mountedIndexName)
172181
// Pass through index settings, adding the index-level settings required to use searchable snapshots
173182
.indexSettings(
174183
Settings.builder()

x-pack/plugin/searchable-snapshots/src/test/java/org/elasticsearch/xpack/searchablesnapshots/LocalStateSearchableSnapshots.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,32 @@
77
package org.elasticsearch.xpack.searchablesnapshots;
88

99
import org.elasticsearch.common.settings.Settings;
10+
import org.elasticsearch.indices.SystemIndexDescriptor;
1011
import org.elasticsearch.license.XPackLicenseState;
1112
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
1213

1314
import java.nio.file.Path;
15+
import java.util.Collection;
1416

1517
public class LocalStateSearchableSnapshots extends LocalStateCompositeXPackPlugin {
1618

19+
private final SearchableSnapshots plugin;
20+
1721
public LocalStateSearchableSnapshots(final Settings settings, final Path configPath) {
1822
super(settings, configPath);
19-
LocalStateSearchableSnapshots thisVar = this;
20-
21-
plugins.add(new SearchableSnapshots(settings) {
23+
this.plugin = new SearchableSnapshots(settings) {
2224

2325
@Override
2426
protected XPackLicenseState getLicenseState() {
25-
return thisVar.getLicenseState();
27+
return LocalStateSearchableSnapshots.this.getLicenseState();
2628
}
2729

28-
});
30+
};
31+
plugins.add(plugin);
32+
}
33+
34+
@Override
35+
public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
36+
return plugin.getSystemIndexDescriptors(settings);
2937
}
3038
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.searchablesnapshots;
8+
9+
import org.elasticsearch.ElasticsearchException;
10+
import org.elasticsearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse;
11+
import org.elasticsearch.client.Client;
12+
import org.elasticsearch.client.OriginSettingClient;
13+
import org.elasticsearch.cluster.metadata.IndexMetadata;
14+
import org.elasticsearch.common.Strings;
15+
import org.elasticsearch.common.settings.Settings;
16+
import org.elasticsearch.indices.SystemIndexDescriptor;
17+
import org.elasticsearch.plugins.Plugin;
18+
import org.elasticsearch.plugins.SystemIndexPlugin;
19+
import org.elasticsearch.xpack.core.ClientHelper;
20+
import org.elasticsearch.xpack.core.searchablesnapshots.MountSearchableSnapshotAction;
21+
import org.elasticsearch.xpack.core.searchablesnapshots.MountSearchableSnapshotRequest;
22+
23+
import java.util.ArrayList;
24+
import java.util.Collection;
25+
import java.util.List;
26+
import java.util.Locale;
27+
28+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
29+
import static org.hamcrest.Matchers.containsString;
30+
import static org.hamcrest.Matchers.equalTo;
31+
32+
public class SearchableSnapshotsSystemIndicesIntegTests extends BaseSearchableSnapshotsIntegTestCase {
33+
34+
@Override
35+
protected Collection<Class<? extends Plugin>> nodePlugins() {
36+
final List<Class<? extends Plugin>> plugins = new ArrayList<>(super.nodePlugins());
37+
plugins.add(TestSystemIndexPlugin.class);
38+
return plugins;
39+
}
40+
41+
public void testCannotMountSystemIndex() throws Exception {
42+
executeTest(TestSystemIndexPlugin.INDEX_NAME, new OriginSettingClient(client(), ClientHelper.SEARCHABLE_SNAPSHOTS_ORIGIN));
43+
}
44+
45+
public void testCannotMountSnapshotBlobCacheIndex() throws Exception {
46+
executeTest(SearchableSnapshotsConstants.SNAPSHOT_BLOB_CACHE_INDEX, client());
47+
}
48+
49+
private void executeTest(final String indexName, final Client client) throws Exception {
50+
final boolean isHidden = randomBoolean();
51+
createAndPopulateIndex(indexName, Settings.builder().put(IndexMetadata.SETTING_INDEX_HIDDEN, isHidden));
52+
53+
final String repositoryName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
54+
createRepo(repositoryName);
55+
56+
final String snapshotName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
57+
final CreateSnapshotResponse snapshotResponse = client.admin()
58+
.cluster()
59+
.prepareCreateSnapshot(repositoryName, snapshotName)
60+
.setIndices(indexName)
61+
.setWaitForCompletion(true)
62+
.get();
63+
64+
final int numPrimaries = getNumShards(indexName).numPrimaries;
65+
assertThat(snapshotResponse.getSnapshotInfo().successfulShards(), equalTo(numPrimaries));
66+
assertThat(snapshotResponse.getSnapshotInfo().failedShards(), equalTo(0));
67+
68+
if (randomBoolean()) {
69+
assertAcked(client.admin().indices().prepareClose(indexName));
70+
} else {
71+
assertAcked(client.admin().indices().prepareDelete(indexName));
72+
}
73+
74+
final MountSearchableSnapshotRequest mountRequest = new MountSearchableSnapshotRequest(
75+
indexName,
76+
repositoryName,
77+
snapshotName,
78+
indexName,
79+
Settings.builder().put(IndexMetadata.SETTING_INDEX_HIDDEN, randomBoolean()).build(),
80+
Strings.EMPTY_ARRAY,
81+
true
82+
);
83+
84+
final ElasticsearchException exception = expectThrows(
85+
ElasticsearchException.class,
86+
() -> client.execute(MountSearchableSnapshotAction.INSTANCE, mountRequest).actionGet()
87+
);
88+
assertThat(exception.getMessage(), containsString("system index [" + indexName + "] cannot be mounted as searchable snapshots"));
89+
}
90+
91+
public static class TestSystemIndexPlugin extends Plugin implements SystemIndexPlugin {
92+
93+
static final String INDEX_NAME = ".test-system-index";
94+
95+
@Override
96+
public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
97+
return List.of(new SystemIndexDescriptor(INDEX_NAME, "System index for [" + getTestClass().getName() + ']'));
98+
}
99+
}
100+
}

0 commit comments

Comments
 (0)