Skip to content

Commit 5ca8591

Browse files
jaymodegwbrown
andauthored
Handle the existence of system data streams in Get Aliases API (elastic#73254)
This commit adjusts the behavior of the Get Aliases API to more thoroughly prevent errors and warnings from being emitted unnecessarily from the Get Aliases API by retrieving all indices including system ones and only warning in the post processing of the action. Additionally, the IndexAbstractionResolver has been updated to properly handle system data streams when evaluating visibility. Closes elastic#73218 Co-authored-by: Gordon Brown <gordon.brown@elastic.co>
1 parent 7358bb8 commit 5ca8591

File tree

4 files changed

+299
-13
lines changed

4 files changed

+299
-13
lines changed

server/src/main/java/org/elasticsearch/action/admin/indices/alias/get/TransportGetAliasesAction.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,8 @@ protected ClusterBlockException checkBlock(GetAliasesRequest request, ClusterSta
5858

5959
@Override
6060
protected void masterOperation(GetAliasesRequest request, ClusterState state, ActionListener<GetAliasesResponse> listener) {
61-
String[] concreteIndices;
62-
// Switch to a context which will drop any deprecation warnings, because there may be indices resolved here which are not
63-
// returned in the final response. We'll add warnings back later if necessary in checkSystemIndexAccess.
64-
try (ThreadContext.StoredContext ignore = threadPool.getThreadContext().newStoredContext(false)) {
65-
concreteIndices = indexNameExpressionResolver.concreteIndexNames(state, request);
66-
}
61+
// resolve all concrete indices upfront and warn/error later
62+
final String[] concreteIndices = indexNameExpressionResolver.concreteIndexNamesWithSystemIndexAccess(state, request);
6763
final SystemIndexAccessLevel systemIndexAccessLevel = indexNameExpressionResolver.getSystemIndexAccessLevel();
6864
ImmutableOpenMap<String, List<AliasMetadata>> aliases = state.metadata().findAliases(request, concreteIndices);
6965
listener.onResponse(new GetAliasesResponse(postProcess(request, concreteIndices, aliases, state,

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

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.action.support.IndicesOptions;
1212
import org.elasticsearch.common.regex.Regex;
1313
import org.elasticsearch.index.IndexNotFoundException;
14+
import org.elasticsearch.indices.SystemIndices.SystemIndexAccessLevel;
1415

1516
import java.util.ArrayList;
1617
import java.util.Arrays;
@@ -63,7 +64,8 @@ public List<String> resolveIndexAbstractions(Iterable<String> indices, IndicesOp
6364
// continue
6465
indexAbstraction = dateMathName;
6566
} else if (availableIndexAbstractions.contains(dateMathName) &&
66-
isIndexVisible(indexAbstraction, dateMathName, indicesOptions, metadata, includeDataStreams, true)) {
67+
isIndexVisible(indexAbstraction, dateMathName, indicesOptions, metadata, indexNameExpressionResolver,
68+
includeDataStreams, true)) {
6769
if (minus) {
6870
finalIndices.remove(dateMathName);
6971
} else {
@@ -81,7 +83,8 @@ public List<String> resolveIndexAbstractions(Iterable<String> indices, IndicesOp
8183
Set<String> resolvedIndices = new HashSet<>();
8284
for (String authorizedIndex : availableIndexAbstractions) {
8385
if (Regex.simpleMatch(indexAbstraction, authorizedIndex) &&
84-
isIndexVisible(indexAbstraction, authorizedIndex, indicesOptions, metadata, includeDataStreams)) {
86+
isIndexVisible(indexAbstraction, authorizedIndex, indicesOptions, metadata, indexNameExpressionResolver,
87+
includeDataStreams)) {
8588
resolvedIndices.add(authorizedIndex);
8689
}
8790
}
@@ -109,12 +112,12 @@ public List<String> resolveIndexAbstractions(Iterable<String> indices, IndicesOp
109112
}
110113

111114
public static boolean isIndexVisible(String expression, String index, IndicesOptions indicesOptions, Metadata metadata,
112-
boolean includeDataStreams) {
113-
return isIndexVisible(expression, index, indicesOptions, metadata, includeDataStreams, false);
115+
IndexNameExpressionResolver resolver, boolean includeDataStreams) {
116+
return isIndexVisible(expression, index, indicesOptions, metadata, resolver, includeDataStreams, false);
114117
}
115118

116119
public static boolean isIndexVisible(String expression, String index, IndicesOptions indicesOptions, Metadata metadata,
117-
boolean includeDataStreams, boolean dateMathExpression) {
120+
IndexNameExpressionResolver resolver, boolean includeDataStreams, boolean dateMathExpression) {
118121
IndexAbstraction indexAbstraction = metadata.getIndicesLookup().get(index);
119122
if (indexAbstraction == null) {
120123
throw new IllegalStateException("could not resolve index abstraction [" + index + "]");
@@ -127,7 +130,22 @@ public static boolean isIndexVisible(String expression, String index, IndicesOpt
127130
return isVisible && indicesOptions.ignoreAliases() == false;
128131
}
129132
if (indexAbstraction.getType() == IndexAbstraction.Type.DATA_STREAM) {
130-
return isVisible && includeDataStreams;
133+
if (includeDataStreams == false) {
134+
return false;
135+
}
136+
137+
if (indexAbstraction.isSystem()) {
138+
final SystemIndexAccessLevel level = resolver.getSystemIndexAccessLevel();
139+
if (level == SystemIndexAccessLevel.ALL) {
140+
return true;
141+
} else if (level == SystemIndexAccessLevel.NONE) {
142+
return false;
143+
} else if (level == SystemIndexAccessLevel.RESTRICTED) {
144+
return resolver.getSystemIndexAccessPredicate().test(indexAbstraction.getName());
145+
}
146+
} else {
147+
return isVisible;
148+
}
131149
}
132150
assert indexAbstraction.getIndices().size() == 1 : "concrete index must point to a single index";
133151
// since it is a date math expression, we consider the index visible regardless of open/closed/hidden as the user is using
@@ -139,6 +157,22 @@ public static boolean isIndexVisible(String expression, String index, IndicesOpt
139157
if (isVisible == false) {
140158
return false;
141159
}
160+
if (indexAbstraction.isSystem()) {
161+
// system index that backs system data stream
162+
if (indexAbstraction.getParentDataStream() != null) {
163+
if (indexAbstraction.getParentDataStream().isSystem() == false) {
164+
throw new IllegalStateException("system index is part of a data stream that is not a system data stream");
165+
}
166+
final SystemIndexAccessLevel level = resolver.getSystemIndexAccessLevel();
167+
if (level == SystemIndexAccessLevel.ALL) {
168+
return true;
169+
} else if (level == SystemIndexAccessLevel.NONE) {
170+
return false;
171+
} else if (level == SystemIndexAccessLevel.RESTRICTED) {
172+
return resolver.getSystemIndexAccessPredicate().test(indexAbstraction.getName());
173+
}
174+
}
175+
}
142176

143177
IndexMetadata indexMetadata = indexAbstraction.getIndices().get(0);
144178
if (indexMetadata.getState() == IndexMetadata.State.CLOSE && indicesOptions.expandWildcardsClosed()) {
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.fleet;
9+
10+
import org.apache.http.util.EntityUtils;
11+
import org.elasticsearch.client.Request;
12+
import org.elasticsearch.client.RequestOptions;
13+
import org.elasticsearch.client.Response;
14+
import org.elasticsearch.client.ResponseException;
15+
import org.elasticsearch.common.settings.Settings;
16+
import org.elasticsearch.common.util.concurrent.ThreadContext;
17+
import org.elasticsearch.test.SecuritySettingsSourceField;
18+
import org.elasticsearch.test.rest.ESRestTestCase;
19+
20+
import java.util.Collections;
21+
import java.util.List;
22+
23+
import static java.util.Collections.emptyList;
24+
import static java.util.Collections.singletonList;
25+
import static org.hamcrest.Matchers.allOf;
26+
import static org.hamcrest.Matchers.containsString;
27+
import static org.hamcrest.Matchers.is;
28+
import static org.hamcrest.Matchers.not;
29+
30+
public class FleetDataStreamIT extends ESRestTestCase {
31+
32+
static final String BASIC_AUTH_VALUE = basicAuthHeaderValue(
33+
"x_pack_rest_user",
34+
SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING
35+
);
36+
37+
@Override
38+
protected Settings restClientSettings() {
39+
// Note that we are superuser here but DO NOT provide a product origin
40+
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", BASIC_AUTH_VALUE).build();
41+
}
42+
43+
@Override
44+
protected Settings restAdminSettings() {
45+
// Note that we are both superuser here and provide a product origin
46+
return Settings.builder()
47+
.put(ThreadContext.PREFIX + ".Authorization", BASIC_AUTH_VALUE)
48+
.put(ThreadContext.PREFIX + ".X-elastic-product-origin", "fleet")
49+
.build();
50+
}
51+
52+
public void testAliasWithSystemDataStream() throws Exception {
53+
// Create a system data stream
54+
Request initialDocResponse = new Request("POST", ".fleet-actions-results/_doc");
55+
initialDocResponse.setJsonEntity("{\"@timestamp\": 0}");
56+
assertOK(adminClient().performRequest(initialDocResponse));
57+
58+
// Create a system index - this one has an alias
59+
Request sysIdxRequest = new Request("PUT", ".fleet-artifacts");
60+
assertOK(adminClient().performRequest(sysIdxRequest));
61+
62+
// Create a regular index
63+
String regularIndex = "regular-idx";
64+
String regularAlias = "regular-alias";
65+
Request regularIdxRequest = new Request("PUT", regularIndex);
66+
regularIdxRequest.setJsonEntity("{\"aliases\": {\"" + regularAlias + "\": {}}}");
67+
assertOK(client().performRequest(regularIdxRequest));
68+
69+
assertGetAliasAPIBehavesAsExpected(regularIndex, regularAlias);
70+
}
71+
72+
public void testAliasWithSystemIndices() throws Exception {
73+
// Create a system index - this one has an alias
74+
Request sysIdxRequest = new Request("PUT", ".fleet-artifacts");
75+
assertOK(adminClient().performRequest(sysIdxRequest));
76+
77+
// Create a regular index
78+
String regularIndex = "regular-idx";
79+
String regularAlias = "regular-alias";
80+
Request regularIdxRequest = new Request("PUT", regularIndex);
81+
regularIdxRequest.setJsonEntity("{\"aliases\": {\"" + regularAlias + "\": {}}}");
82+
assertOK(client().performRequest(regularIdxRequest));
83+
84+
assertGetAliasAPIBehavesAsExpected(regularIndex, regularAlias);
85+
}
86+
87+
private void assertGetAliasAPIBehavesAsExpected(String regularIndex, String regularAlias) throws Exception {
88+
// Get a non-system alias, should not warn or error
89+
{
90+
Request request = new Request("GET", "_alias/" + regularAlias);
91+
Response response = client().performRequest(request);
92+
assertOK(response);
93+
assertThat(
94+
EntityUtils.toString(response.getEntity()),
95+
allOf(containsString(regularAlias), containsString(regularIndex), not(containsString(".fleet-artifacts")))
96+
);
97+
}
98+
99+
// Fully specify a regular index and alias, should not warn or error
100+
{
101+
Request request = new Request("GET", regularIndex + "/_alias/" + regularAlias);
102+
Response response = client().performRequest(request);
103+
assertOK(response);
104+
assertThat(
105+
EntityUtils.toString(response.getEntity()),
106+
allOf(containsString(regularAlias), containsString(regularIndex), not(containsString(".fleet-artifacts")))
107+
);
108+
}
109+
110+
// The rest of these produce a warning
111+
RequestOptions consumeWarningsOptions = RequestOptions.DEFAULT.toBuilder()
112+
.setWarningsHandler(
113+
warnings -> Collections.singletonList(
114+
"this request accesses system indices: [.fleet-artifacts-7], but "
115+
+ "in a future major version, direct access to system indices will be prevented by default"
116+
).equals(warnings) == false
117+
)
118+
.build();
119+
120+
// The base _alias route warns because there is a system index in the response
121+
{
122+
Request request = new Request("GET", "_alias");
123+
request.setOptions(consumeWarningsOptions); // The result includes system indices, so we warn
124+
Response response = client().performRequest(request);
125+
assertOK(response);
126+
assertThat(
127+
EntityUtils.toString(response.getEntity()),
128+
allOf(containsString(regularAlias), containsString(regularIndex), not(containsString(".fleet-actions-results")))
129+
);
130+
}
131+
132+
// Specify a system alias, should warn
133+
{
134+
Request request = new Request("GET", "_alias/.fleet-artifacts");
135+
request.setOptions(consumeWarningsOptions);
136+
Response response = client().performRequest(request);
137+
assertOK(response);
138+
assertThat(
139+
EntityUtils.toString(response.getEntity()),
140+
allOf(
141+
containsString(".fleet-artifacts"),
142+
containsString(".fleet-artifacts-7"),
143+
not(containsString(regularAlias)),
144+
not(containsString(regularIndex))
145+
)
146+
);
147+
}
148+
149+
// Fully specify a system index and alias, should warn
150+
{
151+
Request request = new Request("GET", ".fleet-artifacts-7/_alias/.fleet-artifacts");
152+
request.setOptions(consumeWarningsOptions);
153+
Response response = client().performRequest(request);
154+
assertOK(response);
155+
assertThat(
156+
EntityUtils.toString(response.getEntity()),
157+
allOf(
158+
containsString(".fleet-artifacts"),
159+
containsString(".fleet-artifacts-7"),
160+
not(containsString(regularAlias)),
161+
not(containsString(regularIndex))
162+
)
163+
);
164+
}
165+
166+
// Check an alias that doesn't exist
167+
{
168+
Request getAliasRequest = new Request("GET", "_alias/auditbeat-7.13.0");
169+
try {
170+
client().performRequest(getAliasRequest);
171+
fail("this request should not succeed, as it is looking for an alias that does not exist");
172+
} catch (ResponseException e) {
173+
assertThat(e.getResponse().getStatusLine().getStatusCode(), is(404));
174+
assertThat(
175+
EntityUtils.toString(e.getResponse().getEntity()),
176+
not(containsString("use and access is reserved for system operations"))
177+
);
178+
}
179+
}
180+
181+
// Specify a system data stream as an alias - should 404
182+
{
183+
Request getAliasRequest = new Request("GET", "_alias/.fleet-actions-results");
184+
try {
185+
client().performRequest(getAliasRequest);
186+
fail("this request should not succeed, as it is looking for an alias that does not exist");
187+
} catch (ResponseException e) {
188+
assertThat(e.getResponse().getStatusLine().getStatusCode(), is(404));
189+
assertThat(
190+
EntityUtils.toString(e.getResponse().getEntity()),
191+
not(containsString("use and access is reserved for system operations"))
192+
);
193+
}
194+
}
195+
}
196+
197+
public void testCountWithSystemDataStream() throws Exception {
198+
assertThatAPIWildcardResolutionWorks();
199+
200+
// Create a system data stream
201+
Request initialDocResponse = new Request("POST", ".fleet-actions-results/_doc");
202+
initialDocResponse.setJsonEntity("{\"@timestamp\": 0}");
203+
assertOK(adminClient().performRequest(initialDocResponse));
204+
assertThatAPIWildcardResolutionWorks();
205+
206+
// Create a system index - this one has an alias
207+
Request sysIdxRequest = new Request("PUT", ".fleet-artifacts");
208+
assertOK(adminClient().performRequest(sysIdxRequest));
209+
assertThatAPIWildcardResolutionWorks(
210+
singletonList(
211+
"this request accesses system indices: [.fleet-artifacts-7], but in a future major version, direct access to system"
212+
+ " indices will be prevented by default"
213+
)
214+
);
215+
assertThatAPIWildcardResolutionWorks(
216+
singletonList(
217+
"this request accesses system indices: [.fleet-artifacts-7], but in a future major version, direct access to system"
218+
+ " indices will be prevented by default"
219+
),
220+
".f*"
221+
);
222+
223+
// Create a regular index
224+
String regularIndex = "regular-idx";
225+
String regularAlias = "regular-alias";
226+
Request regularIdxRequest = new Request("PUT", regularIndex);
227+
regularIdxRequest.setJsonEntity("{\"aliases\": {\"" + regularAlias + "\": {}}}");
228+
assertOK(client().performRequest(regularIdxRequest));
229+
assertThatAPIWildcardResolutionWorks(
230+
singletonList(
231+
"this request accesses system indices: [.fleet-artifacts-7], but in a future major version, direct access to system"
232+
+ " indices will be prevented by default"
233+
)
234+
);
235+
assertThatAPIWildcardResolutionWorks(emptyList(), "r*");
236+
}
237+
238+
private void assertThatAPIWildcardResolutionWorks() throws Exception {
239+
assertThatAPIWildcardResolutionWorks(emptyList(), null);
240+
}
241+
242+
private void assertThatAPIWildcardResolutionWorks(List<String> warningsExpected) throws Exception {
243+
assertThatAPIWildcardResolutionWorks(warningsExpected, null);
244+
}
245+
246+
private void assertThatAPIWildcardResolutionWorks(List<String> warningsExpected, String indexPattern) throws Exception {
247+
String path = indexPattern == null || indexPattern.isEmpty() ? "/_count" : "/" + indexPattern + "/_count";
248+
Request countRequest = new Request("GET", path);
249+
if (warningsExpected.isEmpty() == false) {
250+
countRequest.setOptions(
251+
countRequest.getOptions().toBuilder().setWarningsHandler(warnings -> warningsExpected.equals(warnings) == false)
252+
);
253+
}
254+
assertOK(client().performRequest(countRequest));
255+
}
256+
}

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/IndicesAndAliasesResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ ResolvedIndices resolveIndicesAndAliases(IndicesRequest indicesRequest, Metadata
141141
if (IndexNameExpressionResolver.isAllIndices(indicesList(indicesRequest.indices()))) {
142142
if (replaceWildcards) {
143143
for (String authorizedIndex : authorizedIndices) {
144-
if (IndexAbstractionResolver.isIndexVisible("*", authorizedIndex, indicesOptions, metadata,
144+
if (IndexAbstractionResolver.isIndexVisible("*", authorizedIndex, indicesOptions, metadata, nameExpressionResolver,
145145
indicesRequest.includeDataStreams())) {
146146
resolvedIndicesBuilder.addLocal(authorizedIndex);
147147
}

0 commit comments

Comments
 (0)