Skip to content

Commit 9f5958a

Browse files
[Failure Store] Add more test coverage (#126891) (#127887)
This PR adds more test coverage for: - adding/removing a failure store backing index - data stream APIs - watcher
1 parent 3cfa691 commit 9f5958a

File tree

1 file changed

+332
-0
lines changed

1 file changed

+332
-0
lines changed

x-pack/plugin/security/qa/security-trial/src/javaRestTest/java/org/elasticsearch/xpack/security/failurestore/FailureStoreSecurityRestIT.java

+332
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,12 @@
5353

5454
import static org.hamcrest.Matchers.containsInAnyOrder;
5555
import static org.hamcrest.Matchers.containsString;
56+
import static org.hamcrest.Matchers.empty;
5657
import static org.hamcrest.Matchers.equalTo;
5758
import static org.hamcrest.Matchers.hasItem;
59+
import static org.hamcrest.Matchers.in;
5860
import static org.hamcrest.Matchers.is;
61+
import static org.hamcrest.Matchers.iterableWithSize;
5962
import static org.hamcrest.Matchers.notNullValue;
6063

6164
public class FailureStoreSecurityRestIT extends ESRestTestCase {
@@ -1921,6 +1924,335 @@ public void testFailureStoreAccess() throws Exception {
19211924
}
19221925
}
19231926

1927+
public void testModifyingFailureStoreBackingIndices() throws Exception {
1928+
setupDataStream();
1929+
Tuple<String, String> backingIndices = getSingleDataAndFailureIndices("test1");
1930+
String dataIndexName = backingIndices.v1();
1931+
String failureIndexName = backingIndices.v2();
1932+
1933+
createUser(MANAGE_ACCESS, PASSWORD, MANAGE_ACCESS);
1934+
createOrUpdateRoleAndApiKey(MANAGE_ACCESS, MANAGE_ACCESS, """
1935+
{
1936+
"cluster": ["all"],
1937+
"indices": [{"names": ["test*"], "privileges": ["manage"]}]
1938+
}""");
1939+
assertOK(addFailureStoreBackingIndex(MANAGE_ACCESS, "test1", failureIndexName));
1940+
assertDataStreamHasDataAndFailureIndices("test1", dataIndexName, failureIndexName);
1941+
1942+
// remove the failure index
1943+
assertOK(removeFailureStoreBackingIndex(MANAGE_ACCESS, "test1", failureIndexName));
1944+
assertDataStreamHasNoFailureIndices("test1", dataIndexName);
1945+
1946+
// adding it will fail because the user has no direct access to the backing failure index (.fs*)
1947+
expectThrows(() -> addFailureStoreBackingIndex(MANAGE_ACCESS, "test1", failureIndexName), 403);
1948+
1949+
// let's change that
1950+
createOrUpdateRoleAndApiKey(MANAGE_ACCESS, MANAGE_ACCESS, """
1951+
{
1952+
"cluster": ["all"],
1953+
"indices": [{"names": ["test*", ".fs*"], "privileges": ["manage"]}]
1954+
}""");
1955+
1956+
// adding should succeed now
1957+
assertOK(addFailureStoreBackingIndex(MANAGE_ACCESS, "test1", failureIndexName));
1958+
assertDataStreamHasDataAndFailureIndices("test1", dataIndexName, failureIndexName);
1959+
1960+
createUser(MANAGE_FAILURE_STORE_ACCESS, PASSWORD, MANAGE_FAILURE_STORE_ACCESS);
1961+
createOrUpdateRoleAndApiKey(MANAGE_FAILURE_STORE_ACCESS, MANAGE_FAILURE_STORE_ACCESS, """
1962+
{
1963+
"cluster": ["all"],
1964+
"indices": [{"names": ["test*"], "privileges": ["manage_failure_store"]}]
1965+
}""");
1966+
1967+
// manage_failure_store can only remove the failure backing index, but not add it
1968+
assertOK(removeFailureStoreBackingIndex(MANAGE_FAILURE_STORE_ACCESS, "test1", failureIndexName));
1969+
assertDataStreamHasNoFailureIndices("test1", dataIndexName);
1970+
expectThrows(() -> addFailureStoreBackingIndex(MANAGE_FAILURE_STORE_ACCESS, "test1", failureIndexName), 403);
1971+
1972+
// not even with access to .fs*
1973+
createOrUpdateRoleAndApiKey(MANAGE_FAILURE_STORE_ACCESS, MANAGE_FAILURE_STORE_ACCESS, """
1974+
{
1975+
"cluster": ["all"],
1976+
"indices": [{"names": ["test*", ".fs*"], "privileges": ["manage_failure_store"]}]
1977+
}""");
1978+
expectThrows(() -> addFailureStoreBackingIndex(MANAGE_FAILURE_STORE_ACCESS, "test1", failureIndexName), 403);
1979+
}
1980+
1981+
private void assertDataStreamHasDataAndFailureIndices(String dataStreamName, String dataIndexName, String failureIndexName)
1982+
throws IOException {
1983+
Tuple<List<String>, List<String>> indices = getDataAndFailureIndices(dataStreamName);
1984+
assertThat(indices.v1(), containsInAnyOrder(dataIndexName));
1985+
assertThat(indices.v2(), containsInAnyOrder(failureIndexName));
1986+
}
1987+
1988+
private void assertDataStreamHasNoFailureIndices(String dataStreamName, String dataIndexName) throws IOException {
1989+
Tuple<List<String>, List<String>> indices = getDataAndFailureIndices(dataStreamName);
1990+
assertThat(indices.v1(), containsInAnyOrder(dataIndexName));
1991+
assertThat(indices.v2(), is(empty()));
1992+
}
1993+
1994+
private Response addFailureStoreBackingIndex(String user, String dataStreamName, String failureIndexName) throws IOException {
1995+
return modifyFailureStoreBackingIndex(user, "add_backing_index", dataStreamName, failureIndexName);
1996+
}
1997+
1998+
private Response removeFailureStoreBackingIndex(String user, String dataStreamName, String failureIndexName) throws IOException {
1999+
return modifyFailureStoreBackingIndex(user, "remove_backing_index", dataStreamName, failureIndexName);
2000+
}
2001+
2002+
private Response modifyFailureStoreBackingIndex(String user, String action, String dataStreamName, String failureIndexName)
2003+
throws IOException {
2004+
Request request = new Request("POST", "/_data_stream/_modify");
2005+
request.setJsonEntity(Strings.format("""
2006+
{
2007+
"actions": [
2008+
{
2009+
"%s": {
2010+
"data_stream": "%s",
2011+
"index": "%s",
2012+
"failure_store" : true
2013+
}
2014+
}
2015+
]
2016+
}
2017+
""", action, dataStreamName, failureIndexName));
2018+
return performRequest(user, request);
2019+
}
2020+
2021+
public void testDataStreamApis() throws Exception {
2022+
setupDataStream();
2023+
setupOtherDataStream();
2024+
2025+
final String username = "user";
2026+
final String roleName = "role";
2027+
createUser(username, PASSWORD, roleName);
2028+
{
2029+
// manage_failure_store does not grant access to _data_stream APIs
2030+
createOrUpdateRoleAndApiKey(username, roleName, """
2031+
{
2032+
"cluster": ["all"],
2033+
"indices": [
2034+
{
2035+
"names": ["test1", "other1"],
2036+
"privileges": ["manage_failure_store"]
2037+
}
2038+
]
2039+
}
2040+
""");
2041+
2042+
expectThrows(() -> performRequest(username, new Request("GET", "/_data_stream/test1")), 403);
2043+
expectThrows(() -> performRequest(username, new Request("GET", "/_data_stream/test1/_stats")), 403);
2044+
expectThrows(() -> performRequest(username, new Request("GET", "/_data_stream/test1/_options")), 403);
2045+
expectThrows(() -> performRequest(username, new Request("GET", "/_data_stream/test1/_lifecycle")), 403);
2046+
expectThrows(() -> putDataStreamLifecycle(username, "test1", """
2047+
{
2048+
"data_retention": "7d"
2049+
}"""), 403);
2050+
expectEmptyDataStreamStats(username, new Request("GET", "/_data_stream/_stats"));
2051+
expectEmptyDataStreamStats(username, new Request("GET", "/_data_stream/" + randomFrom("test*", "*") + "/_stats"));
2052+
}
2053+
{
2054+
createOrUpdateRoleAndApiKey(username, roleName, """
2055+
{
2056+
"cluster": ["all"],
2057+
"indices": [
2058+
{
2059+
"names": ["test1"],
2060+
"privileges": ["manage"]
2061+
}
2062+
]
2063+
}
2064+
""");
2065+
2066+
expectDataStreamStats(username, new Request("GET", "/_data_stream/_stats"), "test1", 2);
2067+
expectDataStreamStats(
2068+
username,
2069+
new Request("GET", "/_data_stream/" + randomFrom("test1", "test*", "*") + "/_stats"),
2070+
"test1",
2071+
2
2072+
);
2073+
expectDataStreams(username, new Request("GET", "/_data_stream/" + randomFrom("test1", "test*", "*")), "test1");
2074+
putDataStreamLifecycle(username, "test1", """
2075+
{
2076+
"data_retention": "7d"
2077+
}""");
2078+
2079+
var lifecycleResponse = assertOKAndCreateObjectPath(
2080+
performRequest(username, new Request("GET", "/_data_stream/" + randomFrom("test1", "test*", "*") + "/_lifecycle"))
2081+
);
2082+
assertThat(lifecycleResponse.evaluate("data_streams"), iterableWithSize(1));
2083+
assertThat(lifecycleResponse.evaluate("data_streams.0.name"), equalTo("test1"));
2084+
2085+
var optionsResponse = assertOKAndCreateObjectPath(
2086+
performRequest(username, new Request("GET", "/_data_stream/" + randomFrom("test1", "test*", "*") + "/_options"))
2087+
);
2088+
assertThat(optionsResponse.evaluate("data_streams"), iterableWithSize(1));
2089+
assertThat(optionsResponse.evaluate("data_streams.0.name"), equalTo("test1"));
2090+
}
2091+
}
2092+
2093+
private void putDataStreamLifecycle(String user, String dataStreamName, String lifecyclePolicy) throws IOException {
2094+
Request request = new Request("PUT", "/_data_stream/" + dataStreamName + "/_lifecycle");
2095+
request.setJsonEntity(lifecyclePolicy);
2096+
assertOK(performRequest(user, request));
2097+
}
2098+
2099+
private void expectDataStreams(String user, Request dataStreamRequest, String dataStreamName) throws IOException {
2100+
Response response = performRequest(user, dataStreamRequest);
2101+
ObjectPath path = assertOKAndCreateObjectPath(response);
2102+
List<?> dataStreams = path.evaluate("data_streams");
2103+
assertThat(dataStreams.size(), equalTo(1));
2104+
assertThat(path.evaluate("data_streams.0.name"), equalTo(dataStreamName));
2105+
}
2106+
2107+
private void expectDataStreamStats(String user, Request statsRequest, String dataStreamName, int backingIndices) throws IOException {
2108+
Response response = performRequest(user, statsRequest);
2109+
ObjectPath path = assertOKAndCreateObjectPath(response);
2110+
assertThat(path.evaluate("data_stream_count"), equalTo(1));
2111+
assertThat(path.evaluate("backing_indices"), equalTo(backingIndices));
2112+
assertThat(path.evaluate("data_streams.0.data_stream"), equalTo(dataStreamName));
2113+
}
2114+
2115+
private void expectEmptyDataStreamStats(String user, Request request) throws IOException {
2116+
Response response = performRequest(user, request);
2117+
ObjectPath path = assertOKAndCreateObjectPath(response);
2118+
assertThat(path.evaluate("data_stream_count"), equalTo(0));
2119+
assertThat(path.evaluate("backing_indices"), equalTo(0));
2120+
}
2121+
2122+
public void testWatcher() throws Exception {
2123+
setupDataStream();
2124+
setupOtherDataStream();
2125+
2126+
final Tuple<String, String> backingIndices = getSingleDataAndFailureIndices("test1");
2127+
final String dataIndexName = backingIndices.v1();
2128+
final String failureIndexName = backingIndices.v2();
2129+
2130+
final String watchId = "failures-watch";
2131+
final String username = "user";
2132+
final String roleName = "role";
2133+
createUser(username, PASSWORD, roleName);
2134+
2135+
{
2136+
// grant access to the failure store
2137+
createOrUpdateRoleAndApiKey(username, roleName, """
2138+
{
2139+
"cluster": ["manage_security", "manage_watcher"],
2140+
"indices": [
2141+
{
2142+
"names": ["test1"],
2143+
"privileges": ["read_failure_store"]
2144+
}
2145+
]
2146+
}
2147+
""");
2148+
2149+
// searching the failure store should return only test1 failure indices
2150+
createOrUpdateWatcher(username, watchId, Strings.format("""
2151+
{
2152+
"trigger": { "schedule": { "interval": "60m"}},
2153+
"input": {
2154+
"search": {
2155+
"request": {
2156+
"indices": [ "%s" ],
2157+
"body": {"query": {"match_all": {}}}
2158+
}
2159+
}
2160+
}
2161+
}""", randomFrom("test1::failures", "test*::failures", "*::failures", failureIndexName)));
2162+
executeWatchAndAssertResults(username, watchId, failureIndexName);
2163+
2164+
// searching the data should return empty results
2165+
createOrUpdateWatcher(username, watchId, Strings.format("""
2166+
{
2167+
"trigger": { "schedule": { "interval": "60m"}},
2168+
"input": {
2169+
"search": {
2170+
"request": {
2171+
"indices": [ "%s" ],
2172+
"body": {"query": {"match_all": {}}}
2173+
}
2174+
}
2175+
}
2176+
}""", randomFrom(dataIndexName, "*", "test*", "test1", "test1::data")));
2177+
executeWatchAndAssertEmptyResults(username, watchId);
2178+
}
2179+
2180+
{
2181+
// remove read_failure_store and add read
2182+
createOrUpdateRoleAndApiKey(username, roleName, """
2183+
{
2184+
"cluster": ["manage_security", "manage_watcher"],
2185+
"indices": [
2186+
{
2187+
"names": ["test1"],
2188+
"privileges": ["read"]
2189+
}
2190+
]
2191+
}
2192+
""");
2193+
2194+
// searching the failure store should return empty results
2195+
createOrUpdateWatcher(username, watchId, Strings.format("""
2196+
{
2197+
"trigger": { "schedule": { "interval": "60m"}},
2198+
"input": {
2199+
"search": {
2200+
"request": {
2201+
"indices": [ "%s" ],
2202+
"body": {"query": {"match_all": {}}}
2203+
}
2204+
}
2205+
}
2206+
}""", randomFrom("test1::failures", "test*::failures", "*::failures", failureIndexName)));
2207+
executeWatchAndAssertEmptyResults(username, watchId);
2208+
2209+
// searching the data should return single result
2210+
createOrUpdateWatcher(username, watchId, Strings.format("""
2211+
{
2212+
"trigger": { "schedule": { "interval": "60m"}},
2213+
"input": {
2214+
"search": {
2215+
"request": {
2216+
"indices": [ "%s" ],
2217+
"body": {"query": {"match_all": {}}}
2218+
}
2219+
}
2220+
}
2221+
}""", randomFrom("*", "test*", "test1", "test1::data", dataIndexName)));
2222+
executeWatchAndAssertResults(username, watchId, dataIndexName);
2223+
}
2224+
}
2225+
2226+
private void executeWatchAndAssertResults(String user, String watchId, final String... expectedIndices) throws IOException {
2227+
Request request = new Request("POST", "_watcher/watch/" + watchId + "/_execute");
2228+
Response response = performRequest(user, request);
2229+
ObjectPath path = assertOKAndCreateObjectPath(response);
2230+
assertThat(path.evaluate("watch_record.user"), equalTo(user));
2231+
assertThat(path.evaluate("watch_record.state"), equalTo("executed"));
2232+
assertThat(path.evaluate("watch_record.result.input.status"), equalTo("success"));
2233+
assertThat(path.evaluate("watch_record.result.input.payload.hits.total"), equalTo(expectedIndices.length));
2234+
List<Map<String, ?>> hits = path.evaluate("watch_record.result.input.payload.hits.hits");
2235+
hits.stream().map(hit -> hit.get("_index")).forEach(index -> { assertThat(index, is(in(expectedIndices))); });
2236+
}
2237+
2238+
private void executeWatchAndAssertEmptyResults(String user, String watchId) throws IOException {
2239+
Request request = new Request("POST", "_watcher/watch/" + watchId + "/_execute");
2240+
Response response = performRequest(user, request);
2241+
ObjectPath path = assertOKAndCreateObjectPath(response);
2242+
assertThat(path.evaluate("watch_record.user"), equalTo(user));
2243+
assertThat(path.evaluate("watch_record.state"), equalTo("executed"));
2244+
assertThat(path.evaluate("watch_record.result.input.status"), equalTo("success"));
2245+
assertThat(path.evaluate("watch_record.result.input.payload.hits.total"), equalTo(0));
2246+
List<Map<String, ?>> hits = path.evaluate("watch_record.result.input.payload.hits.hits");
2247+
assertThat(hits.size(), equalTo(0));
2248+
}
2249+
2250+
private void createOrUpdateWatcher(String user, String watchId, String watch) throws IOException {
2251+
Request request = new Request("PUT", "/_watcher/watch/" + watchId);
2252+
request.setJsonEntity(watch);
2253+
assertOK(performRequest(user, request));
2254+
}
2255+
19242256
public void testAliasBasedAccess() throws Exception {
19252257
List<String> docIds = setupDataStream();
19262258
assertThat(docIds.size(), equalTo(2));

0 commit comments

Comments
 (0)