Skip to content

Commit dc5eaf5

Browse files
committed
initial black box test (copied from Nikolaj's PoC)
1 parent 81f32a9 commit dc5eaf5

File tree

1 file changed

+175
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)