Skip to content

Commit de81400

Browse files
committed
TODOs and REST IT
1 parent 90aca93 commit de81400

File tree

4 files changed

+173
-2
lines changed

4 files changed

+173
-2
lines changed

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ && isIndexVisible(
103103
resolveSelectorsAndCombine(indexAbstraction, selectorString, indicesOptions, resolvedIndices, metadata);
104104
if (minus) {
105105
finalIndices.removeAll(resolvedIndices);
106-
// TODO if selector is *, we need to run two authz checks, one for data and one for failures
107106
} else if (indicesOptions.ignoreUnavailable() == false || isAuthorized.test(indexAbstraction, selectorString)) {
108107
// Unauthorized names are considered unavailable, so if `ignoreUnavailable` is `true` they should be silently
109108
// discarded from the `finalIndices` list. Other "ways of unavailable" must be handled by the action
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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.security;
9+
10+
import org.elasticsearch.action.search.SearchResponse;
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.SecureString;
16+
import org.elasticsearch.core.Strings;
17+
import org.elasticsearch.search.SearchHit;
18+
import org.elasticsearch.search.SearchResponseUtils;
19+
20+
import java.io.IOException;
21+
import java.util.ArrayList;
22+
import java.util.Arrays;
23+
import java.util.List;
24+
import java.util.Map;
25+
26+
import static org.hamcrest.Matchers.containsInAnyOrder;
27+
import static org.hamcrest.Matchers.equalTo;
28+
import static org.hamcrest.Matchers.hasItem;
29+
30+
public class FailureStoreSecurityRestIT extends SecurityOnTrialLicenseRestTestCase {
31+
32+
private static final String USER = "user";
33+
private static final SecureString PASSWORD = new SecureString("elastic-password");
34+
35+
public void testFailureStoreAccess() throws IOException {
36+
String failureStoreAccessRole = "failure_store_access";
37+
createUser(USER, PASSWORD, List.of(failureStoreAccessRole));
38+
39+
upsertRole(Strings.format("""
40+
{
41+
"description": "Role with failure store access",
42+
"cluster": ["all"],
43+
"indices": [{"names": ["test*::failures"], "privileges": ["read"]}]
44+
}"""), failureStoreAccessRole);
45+
46+
createTemplates();
47+
List<String> ids = populateDataStreamWithBulkRequest();
48+
assertThat(ids.size(), equalTo(2));
49+
assertThat(ids, hasItem("1"));
50+
String successDocId = "1";
51+
String failedDocId = ids.stream().filter(id -> false == id.equals(successDocId)).findFirst().get();
52+
53+
// user with access to failures index
54+
assertContainsDocIds(performRequestAsUser1(new Request("GET", "/test1::failures/_search")), failedDocId);
55+
assertContainsDocIds(performRequestAsUser1(new Request("GET", "/test*::failures/_search")), failedDocId);
56+
assertContainsDocIds(performRequestAsUser1(new Request("GET", "/*1::failures/_search")), failedDocId);
57+
assertContainsDocIds(performRequestAsUser1(new Request("GET", "/*::failures/_search")), failedDocId);
58+
59+
expectThrows404(() -> performRequestAsUser1(new Request("GET", "/test12::failures/_search")));
60+
expectThrows404(() -> performRequestAsUser1(new Request("GET", "/test2::failures/_search")));
61+
62+
// user with access to everything
63+
assertContainsDocIds(adminClient().performRequest(new Request("GET", "/test1::failures/_search")), failedDocId);
64+
assertContainsDocIds(adminClient().performRequest(new Request("GET", "/test*::failures/_search")), failedDocId);
65+
assertContainsDocIds(adminClient().performRequest(new Request("GET", "/*1::failures/_search")), failedDocId);
66+
assertContainsDocIds(adminClient().performRequest(new Request("GET", "/*::failures/_search")), failedDocId);
67+
68+
expectThrows404(() -> adminClient().performRequest(new Request("GET", "/test2::failures/_search")));
69+
expectThrows404(() -> adminClient().performRequest(new Request("GET", "/test12::failures/_search")));
70+
}
71+
72+
private static void expectThrows404(ThrowingRunnable get) {
73+
var ex = expectThrows(ResponseException.class, get);
74+
assertThat(ex.getResponse().getStatusLine().getStatusCode(), equalTo(404));
75+
}
76+
77+
@SuppressWarnings("unchecked")
78+
private static void assertContainsDocIds(Response response, String... docIds) throws IOException {
79+
assertOK(response);
80+
final SearchResponse searchResponse = SearchResponseUtils.parseSearchResponse(responseAsParser(response));
81+
try {
82+
SearchHit[] hits = searchResponse.getHits().getHits();
83+
assertThat(hits.length, equalTo(docIds.length));
84+
List<String> actualDocIds = Arrays.stream(hits).map(SearchHit::getId).toList();
85+
assertThat(actualDocIds, containsInAnyOrder(docIds));
86+
} finally {
87+
searchResponse.decRef();
88+
}
89+
}
90+
91+
private static void assert404(Response response) {
92+
assertThat(response.getStatusLine().getStatusCode(), equalTo(404));
93+
}
94+
95+
private static void assert403(Response response) {
96+
assertThat(response.getStatusLine().getStatusCode(), equalTo(403));
97+
}
98+
99+
private void createTemplates() throws IOException {
100+
var componentTemplateRequest = new Request("PUT", "/_component_template/component1");
101+
componentTemplateRequest.setJsonEntity("""
102+
{
103+
"template": {
104+
"mappings": {
105+
"properties": {
106+
"@timestamp": {
107+
"type": "date"
108+
},
109+
"age": {
110+
"type": "integer"
111+
},
112+
"email": {
113+
"type": "keyword"
114+
},
115+
"name": {
116+
"type": "text"
117+
}
118+
}
119+
},
120+
"data_stream_options": {
121+
"failure_store": {
122+
"enabled": true
123+
}
124+
}
125+
}
126+
}
127+
""");
128+
assertOK(adminClient().performRequest(componentTemplateRequest));
129+
130+
var indexTemplateRequest = new Request("PUT", "/_index_template/template1");
131+
indexTemplateRequest.setJsonEntity("""
132+
{
133+
"index_patterns": ["test*"],
134+
"data_stream": {},
135+
"priority": 500,
136+
"composed_of": ["component1"]
137+
}
138+
""");
139+
assertOK(adminClient().performRequest(indexTemplateRequest));
140+
}
141+
142+
@SuppressWarnings("unchecked")
143+
private List<String> populateDataStreamWithBulkRequest() throws IOException {
144+
var bulkRequest = new Request("POST", "/_bulk?refresh=true");
145+
bulkRequest.setJsonEntity("""
146+
{ "create" : { "_index" : "test1", "_id" : "1" } }
147+
{ "@timestamp": 1, "age" : 1, "name" : "jack", "email" : "[email protected]" }
148+
{ "create" : { "_index" : "test1", "_id" : "2" } }
149+
{ "@timestamp": 2, "age" : "this should be an int", "name" : "jack", "email" : "[email protected]" }
150+
""");
151+
Response response = adminClient().performRequest(bulkRequest);
152+
assertOK(response);
153+
// we need this dance because the ID for the failed document is random, **not** 2
154+
Map<String, Object> stringObjectMap = responseAsMap(response);
155+
List<Object> items = (List<Object>) stringObjectMap.get("items");
156+
List<String> ids = new ArrayList<>();
157+
for (Object item : items) {
158+
Map<String, Object> itemMap = (Map<String, Object>) item;
159+
Map<String, Object> create = (Map<String, Object>) itemMap.get("create");
160+
assertThat(create.get("status"), equalTo(201));
161+
ids.add((String) create.get("_id"));
162+
}
163+
return ids;
164+
}
165+
166+
private Response performRequestAsUser1(Request request) throws IOException {
167+
request.setOptions(RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", basicAuthHeaderValue(USER, PASSWORD)).build());
168+
var response = client().performRequest(request);
169+
return response;
170+
}
171+
172+
}

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,6 @@ static AuthorizedIndices resolveAuthorizedIndicesFromRole(
884884
for (Index index : indexAbstraction.getIndices()) {
885885
indicesAndAliases.add(index.getName());
886886
}
887-
// TODO: We need to limit if a data stream's failure indices should return here.
888887
for (Index index : ((DataStream) indexAbstraction).getFailureIndices()) {
889888
indicesAndAliases.add(index.getName());
890889
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ public static void buildRoleFromDescriptors(
539539
final Role.Builder builder = Role.builder(restrictedIndices, roleNames.toArray(Strings.EMPTY_ARRAY))
540540
.cluster(clusterPrivileges, configurableClusterPrivileges)
541541
.runAs(runAsPrivilege);
542+
// TODO need to handle wildcard to grant access to both failure and non-failure indices
542543
indicesPrivilegesMap.forEach((key, privilege) -> {
543544
// TODO double-check if this is always the case
544545
assert privilege.indices.isEmpty() == false : "indices must not be empty";

0 commit comments

Comments
 (0)