Skip to content

Commit 22e9d37

Browse files
jaymodewilliamrandolphalbertzaharovits
authored
System indices treated as restricted indices (#74212)
System indices should be treated as a special set of indices and not be accessible by all users. The existing security codebase has the notion of restricted indices, which are currently a subset of system indices. This change unifies the two concepts by making system indices the set of restricted indices. This means that going forward, consumers of system indices will need access to restricted indices. Our intention is that this will be handled internally as much as possible. For example, restricted index access can be used implicitly by setting a valid origin on a request or using a system index plugin. In these cases, the security module will apply internally defined privileges when necessary. The main impact of this change for developers is that system index deletions will require superuser privileges, so we have to make sure we are using an admin role for test cleanup. Closes #69298 Co-authored-by: William Brafford <[email protected]> Co-authored-by: Albert Zaharovits <[email protected]>
1 parent fbc9821 commit 22e9d37

File tree

34 files changed

+886
-526
lines changed

34 files changed

+886
-526
lines changed

client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/EnrollmentIT.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,17 @@ protected Settings restClientSettings() {
5959
.build();
6060
}
6161

62+
/**
63+
* Cleanup for these tests requires the admin client to have access to the
64+
* truststore, so we use the same settings that we're using for the rest
65+
* client in this test class.
66+
* @return Settings for the admin client.
67+
*/
68+
@Override
69+
protected Settings restAdminSettings() {
70+
return restClientSettings();
71+
}
72+
6273
public void testEnrollNode() throws Exception {
6374
final NodeEnrollmentResponse nodeEnrollmentResponse =
6475
execute(highLevelClient().security()::enrollNode, highLevelClient().security()::enrollNodeAsync, RequestOptions.DEFAULT);

client/rest-high-level/qa/ssl-enabled/src/javaRestTest/java/org/elasticsearch/client/documentation/EnrollmentDocumentationIT.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ protected Settings restClientSettings() {
5252
.build();
5353
}
5454

55+
/**
56+
* Cleanup for these tests requires the admin client to have access to the
57+
* truststore, so we use the same settings that we're using for the rest
58+
* client in this test class.
59+
* @return Settings for the admin client.
60+
*/
61+
@Override
62+
protected Settings restAdminSettings() {
63+
return restClientSettings();
64+
}
65+
5566
public void testNodeEnrollment() throws Exception {
5667
RestHighLevelClient client = highLevelClient();
5768

client/rest-high-level/src/test/java/org/elasticsearch/client/ESRestHighLevelClientTestCase.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.client.cluster.RemoteInfoRequest;
2323
import org.elasticsearch.client.cluster.RemoteInfoResponse;
2424
import org.elasticsearch.client.indices.CreateIndexRequest;
25+
import org.elasticsearch.common.settings.SecureString;
2526
import org.elasticsearch.core.Booleans;
2627
import org.elasticsearch.core.CheckedRunnable;
2728
import org.elasticsearch.common.bytes.BytesReference;
@@ -66,6 +67,7 @@ public abstract class ESRestHighLevelClientTestCase extends ESRestTestCase {
6667
protected static final String CONFLICT_PIPELINE_ID = "conflict_pipeline";
6768

6869
private static RestHighLevelClient restHighLevelClient;
70+
private static RestHighLevelClient adminRestHighLevelClient;
6971
private static boolean async = Booleans.parseBoolean(System.getProperty("tests.rest.async", "false"));
7072

7173
@Before
@@ -74,18 +76,35 @@ public void initHighLevelClient() throws IOException {
7476
if (restHighLevelClient == null) {
7577
restHighLevelClient = new HighLevelClient(client());
7678
}
79+
if (adminRestHighLevelClient == null) {
80+
adminRestHighLevelClient = new HighLevelClient(adminClient());
81+
}
7782
}
7883

7984
@AfterClass
8085
public static void cleanupClient() throws IOException {
8186
IOUtils.close(restHighLevelClient);
87+
IOUtils.close(adminRestHighLevelClient);
8288
restHighLevelClient = null;
89+
adminRestHighLevelClient = null;
8390
}
8491

8592
protected static RestHighLevelClient highLevelClient() {
8693
return restHighLevelClient;
8794
}
8895

96+
@Override
97+
protected Settings restAdminSettings() {
98+
String token = basicAuthHeaderValue("admin_user", new SecureString("admin-password".toCharArray()));
99+
return Settings.builder()
100+
.put(ThreadContext.PREFIX + ".Authorization", token)
101+
.build();
102+
}
103+
104+
protected static RestHighLevelClient adminHighLevelClient() {
105+
return adminRestHighLevelClient;
106+
}
107+
89108
/**
90109
* Executes the provided request using either the sync method or its async variant, both provided as functions
91110
*/

client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningGetResultsIT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ private void addModelSnapshotIndexRequests(BulkRequest bulkRequest) throws IOExc
274274

275275
@After
276276
public void deleteJob() throws IOException {
277-
new MlTestStateCleaner(logger, highLevelClient()).clearMlMetadata();
277+
new MlTestStateCleaner(logger, adminHighLevelClient()).clearMlMetadata();
278278
}
279279

280280
public void testGetModelSnapshots() throws IOException {

client/rest-high-level/src/test/java/org/elasticsearch/client/MlTestStateCleaner.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.action.ingest.DeletePipelineRequest;
1313
import org.elasticsearch.client.core.PageParams;
1414
import org.elasticsearch.client.feature.ResetFeaturesRequest;
15+
import org.elasticsearch.client.feature.ResetFeaturesResponse;
1516
import org.elasticsearch.client.ml.GetTrainedModelsStatsRequest;
1617

1718
import java.io.IOException;
@@ -36,7 +37,15 @@ public MlTestStateCleaner(Logger logger, RestHighLevelClient client) {
3637
public void clearMlMetadata() throws IOException {
3738
deleteAllTrainedModelIngestPipelines();
3839
// This resets all features, not just ML, but they should have been getting reset between tests anyway so it shouldn't matter
39-
client.features().resetFeatures(new ResetFeaturesRequest(), RequestOptions.DEFAULT);
40+
ResetFeaturesResponse response = client.features().resetFeatures(new ResetFeaturesRequest(), RequestOptions.DEFAULT);
41+
if (response.getFeatureResetStatuses().stream().anyMatch(status -> "FAILURE".equals(status.getStatus()))) {
42+
logger.warn("Not all feature states could be reset while clearing ML Metadata:");
43+
for (ResetFeaturesResponse.ResetFeatureStateStatus status : response.getFeatureResetStatuses()) {
44+
if (status.getStatus().equals("FAILURE")) {
45+
logger.warn("Feature {} failed with response: {}", status.getFeatureName(), status.getException());
46+
}
47+
}
48+
}
4049
}
4150

4251
@SuppressWarnings("unchecked")

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
package org.elasticsearch.cluster.metadata;
1010

11+
import org.apache.lucene.util.automaton.Automaton;
1112
import org.elasticsearch.ElasticsearchParseException;
1213
import org.elasticsearch.Version;
1314
import org.elasticsearch.action.IndicesRequest;
@@ -786,6 +787,10 @@ public Predicate<String> getSystemIndexAccessPredicate() {
786787
return systemIndexAccessLevelPredicate;
787788
}
788789

790+
public Automaton getSystemNameAutomaton() {
791+
return systemIndices.getSystemNameAutomaton();
792+
}
793+
789794
public Predicate<String> getNetNewSystemIndexPredicate() {
790795
return systemIndices::isNetNewSystemIndex;
791796
}

server/src/main/java/org/elasticsearch/indices/SystemIndices.java

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
import java.util.Map;
4343
import java.util.Map.Entry;
4444
import java.util.Optional;
45-
import java.util.Set;
4645
import java.util.function.Predicate;
4746
import java.util.stream.Collectors;
4847
import java.util.stream.Stream;
@@ -66,10 +65,12 @@ public class SystemIndices {
6665
TASKS_FEATURE_NAME, new Feature(TASKS_FEATURE_NAME, "Manages task results", List.of(TASKS_DESCRIPTOR))
6766
);
6867

69-
private final CharacterRunAutomaton systemIndexAutomaton;
70-
private final CharacterRunAutomaton systemDataStreamIndicesAutomaton;
68+
private final Automaton systemNameAutomaton;
7169
private final CharacterRunAutomaton netNewSystemIndexAutomaton;
72-
private final Predicate<String> systemDataStreamAutomaton;
70+
private final CharacterRunAutomaton systemNameRunAutomaton;
71+
private final CharacterRunAutomaton systemIndexRunAutomaton;
72+
private final CharacterRunAutomaton systemDataStreamIndicesRunAutomaton;
73+
private final Predicate<String> systemDataStreamPredicate;
7374
private final Map<String, Feature> featureDescriptors;
7475
private final Map<String, CharacterRunAutomaton> productToSystemIndicesMatcher;
7576
private final ExecutorSelector executorSelector;
@@ -83,12 +84,19 @@ public SystemIndices(Map<String, Feature> pluginAndModulesDescriptors) {
8384
featureDescriptors = buildSystemIndexDescriptorMap(pluginAndModulesDescriptors);
8485
checkForOverlappingPatterns(featureDescriptors);
8586
checkForDuplicateAliases(this.getSystemIndexDescriptors());
86-
this.systemIndexAutomaton = buildIndexCharacterRunAutomaton(featureDescriptors);
87+
Automaton systemIndexAutomata = buildIndexAutomaton(featureDescriptors);
88+
this.systemIndexRunAutomaton = new CharacterRunAutomaton(systemIndexAutomata);
89+
Automaton systemDataStreamIndicesAutomata = buildDataStreamBackingIndicesAutomaton(featureDescriptors);
90+
this.systemDataStreamIndicesRunAutomaton = new CharacterRunAutomaton(systemDataStreamIndicesAutomata);
91+
this.systemDataStreamPredicate = buildDataStreamNamePredicate(featureDescriptors);
8792
this.netNewSystemIndexAutomaton = buildNetNewIndexCharacterRunAutomaton(featureDescriptors);
88-
this.systemDataStreamIndicesAutomaton = buildDataStreamBackingIndicesAutomaton(featureDescriptors);
89-
this.systemDataStreamAutomaton = buildDataStreamNamePredicate(featureDescriptors);
9093
this.productToSystemIndicesMatcher = getProductToSystemIndicesMap(featureDescriptors);
9194
this.executorSelector = new ExecutorSelector(this);
95+
this.systemNameAutomaton = MinimizationOperations.minimize(
96+
Operations.union(List.of(systemIndexAutomata, systemDataStreamIndicesAutomata, buildDataStreamAutomaton(featureDescriptors))),
97+
Integer.MAX_VALUE
98+
);
99+
this.systemNameRunAutomaton = new CharacterRunAutomaton(systemNameAutomaton);
92100
}
93101

94102
private static void checkForDuplicateAliases(Collection<SystemIndexDescriptor> descriptors) {
@@ -150,7 +158,7 @@ private static Map<String, CharacterRunAutomaton> getProductToSystemIndicesMap(M
150158
* is checked against index names, aliases, data stream names, and the names of indices that back a system data stream.
151159
*/
152160
public boolean isSystemName(String name) {
153-
return isSystemIndex(name) || isSystemDataStream(name) || isSystemIndexBackingDataStream(name);
161+
return systemNameRunAutomaton.run(name);
154162
}
155163

156164
/**
@@ -169,22 +177,30 @@ public boolean isSystemIndex(Index index) {
169177
* @return true if the index name matches a pattern from a {@link SystemIndexDescriptor}
170178
*/
171179
public boolean isSystemIndex(String indexName) {
172-
return systemIndexAutomaton.run(indexName);
180+
return systemIndexRunAutomaton.run(indexName);
173181
}
174182

175183
/**
176184
* Determines whether the provided name matches that of a system data stream that has been defined by a
177185
* {@link SystemDataStreamDescriptor}
178186
*/
179187
public boolean isSystemDataStream(String name) {
180-
return systemDataStreamAutomaton.test(name);
188+
return systemDataStreamPredicate.test(name);
181189
}
182190

183191
/**
184192
* Determines whether the provided name matches that of an index that backs a system data stream.
185193
*/
186194
public boolean isSystemIndexBackingDataStream(String name) {
187-
return systemDataStreamIndicesAutomaton.run(name);
195+
return systemDataStreamIndicesRunAutomaton.run(name);
196+
}
197+
198+
/**
199+
* @return An {@link Automaton} that tests whether strings are names of system indices, aliases, or
200+
* data streams.
201+
*/
202+
public Automaton getSystemNameAutomaton() {
203+
return systemNameAutomaton;
188204
}
189205

190206
public boolean isNetNewSystemIndex(String indexName) {
@@ -293,11 +309,11 @@ public Map<String, Feature> getFeatures() {
293309
return featureDescriptors;
294310
}
295311

296-
private static CharacterRunAutomaton buildIndexCharacterRunAutomaton(Map<String, Feature> descriptors) {
312+
private static Automaton buildIndexAutomaton(Map<String, Feature> descriptors) {
297313
Optional<Automaton> automaton = descriptors.values().stream()
298314
.map(SystemIndices::featureToIndexAutomaton)
299315
.reduce(Operations::union);
300-
return new CharacterRunAutomaton(MinimizationOperations.minimize(automaton.orElse(EMPTY), Integer.MAX_VALUE));
316+
return MinimizationOperations.minimize(automaton.orElse(EMPTY), Integer.MAX_VALUE);
301317
}
302318

303319
private static CharacterRunAutomaton buildNetNewIndexCharacterRunAutomaton(Map<String, Feature> featureDescriptors) {
@@ -317,19 +333,26 @@ private static Automaton featureToIndexAutomaton(Feature feature) {
317333
return systemIndexAutomaton.orElse(EMPTY);
318334
}
319335

320-
private static Predicate<String> buildDataStreamNamePredicate(Map<String, Feature> descriptors) {
321-
Set<String> systemDataStreamNames = descriptors.values().stream()
336+
private static Automaton buildDataStreamAutomaton(Map<String, Feature> descriptors) {
337+
Optional<Automaton> automaton = descriptors.values().stream()
322338
.flatMap(feature -> feature.getDataStreamDescriptors().stream())
323339
.map(SystemDataStreamDescriptor::getDataStreamName)
324-
.collect(Collectors.toUnmodifiableSet());
325-
return systemDataStreamNames::contains;
340+
.map(dsName -> SystemIndexDescriptor.buildAutomaton(dsName, null))
341+
.reduce(Operations::union);
342+
343+
return automaton.isPresent() ? MinimizationOperations.minimize(automaton.get(), Integer.MAX_VALUE) : EMPTY;
344+
}
345+
346+
private static Predicate<String> buildDataStreamNamePredicate(Map<String, Feature> descriptors) {
347+
CharacterRunAutomaton characterRunAutomaton = new CharacterRunAutomaton(buildDataStreamAutomaton(descriptors));
348+
return characterRunAutomaton::run;
326349
}
327350

328-
private static CharacterRunAutomaton buildDataStreamBackingIndicesAutomaton(Map<String, Feature> descriptors) {
351+
private static Automaton buildDataStreamBackingIndicesAutomaton(Map<String, Feature> descriptors) {
329352
Optional<Automaton> automaton = descriptors.values().stream()
330353
.map(SystemIndices::featureToDataStreamBackingIndicesAutomaton)
331354
.reduce(Operations::union);
332-
return new CharacterRunAutomaton(automaton.orElse(EMPTY));
355+
return MinimizationOperations.minimize(automaton.orElse(EMPTY), Integer.MAX_VALUE);
333356
}
334357

335358
private static Automaton featureToDataStreamBackingIndicesAutomaton(Feature feature) {
@@ -343,7 +366,7 @@ private static Automaton featureToDataStreamBackingIndicesAutomaton(Feature feat
343366
}
344367

345368
public SystemDataStreamDescriptor validateDataStreamAccess(String dataStreamName, ThreadContext threadContext) {
346-
if (systemDataStreamAutomaton.test(dataStreamName)) {
369+
if (systemDataStreamPredicate.test(dataStreamName)) {
347370
SystemDataStreamDescriptor dataStreamDescriptor = featureDescriptors.values().stream()
348371
.flatMap(feature -> feature.getDataStreamDescriptors().stream())
349372
.filter(descriptor -> descriptor.getDataStreamName().equals(dataStreamName))
@@ -405,6 +428,7 @@ IllegalArgumentException dataStreamAccessException(@Nullable String product, Str
405428
/**
406429
* Determines what level of system index access should be allowed in the current context.
407430
*
431+
* @param threadContext the current thread context that has headers associated with the current request
408432
* @return {@link SystemIndexAccessLevel#ALL} if unrestricted system index access should be allowed,
409433
* {@link SystemIndexAccessLevel#RESTRICTED} if a subset of system index access should be allowed, or
410434
* {@link SystemIndexAccessLevel#NONE} if no system index access should be allowed.

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/async/AsyncTaskIndexService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ private static XContentBuilder mappings() {
142142

143143
public static SystemIndexDescriptor getSystemIndexDescriptor() {
144144
return SystemIndexDescriptor.builder()
145-
.setIndexPattern(XPackPlugin.ASYNC_RESULTS_INDEX)
145+
.setIndexPattern(XPackPlugin.ASYNC_RESULTS_INDEX + "*")
146146
.setDescription("Async search results")
147147
.setPrimaryIndex(XPackPlugin.ASYNC_RESULTS_INDEX)
148148
.setMappings(mappings())

0 commit comments

Comments
 (0)