Skip to content

Commit

Permalink
Added test to AsyncSearchSecurityIT to ensure status permissions are …
Browse files Browse the repository at this point in the history
…enforced while search is running
  • Loading branch information
quux00 committed Mar 25, 2024
1 parent d8d83cc commit e34046b
Showing 1 changed file with 104 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -170,74 +170,117 @@ public void testWithUsers() throws Exception {
testCase("user2", "user1");
}

/**
* This test uses a 10-second delay in the search completion so that all actions against that user are done
* while the search is still running (which has different code paths from when the search is finished, which
* the testWithUsers test is generally testing).
* @throws IOException
*/
public void testStatusWithUsersWhileSearchIsRunning() throws IOException {
String user = randomFrom("user1", "user2");
String other = user.equals("user1") ? "user2" : "user1";
String indexName = "index-" + user;
String query = """
{
"query": {
"error_query": {
"indices": [
{
"name": "*",
"error_type": "none",
"stall_time_seconds": 10
}
]
}
}
}""";

Response submitResp = submitAsyncSearchWithJsonBody(indexName, query, TimeValue.timeValueMillis(10), user);
assertOK(submitResp);
String id = extractResponseId(submitResp);

userBasedPermissionsAsserts(user, other, indexName, id);

ResponseException exc = expectThrows(
ResponseException.class,
() -> submitAsyncSearch("index-" + other, "*", TimeValue.timeValueSeconds(10), user)
);
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(403));
assertThat(exc.getMessage(), containsString("unauthorized"));
}

private void testCase(String user, String other) throws Exception {
for (String indexName : new String[] { "index", "index-" + user }) {
Response submitResp = submitAsyncSearch(indexName, "foo:bar", TimeValue.timeValueSeconds(10), user);
assertOK(submitResp);
String id = extractResponseId(submitResp);

Response statusResp = getAsyncStatus(id, user);
assertOK(statusResp);
userBasedPermissionsAsserts(user, other, indexName, id);
}
ResponseException exc = expectThrows(
ResponseException.class,
() -> submitAsyncSearch("index-" + other, "*", TimeValue.timeValueSeconds(10), user)
);
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(403));
assertThat(exc.getMessage(), containsString("unauthorized"));
}

Response getResp = getAsyncSearch(id, user);
assertOK(getResp);
private static void userBasedPermissionsAsserts(String user, String other, String indexName, String id) throws IOException {
Response statusResp = getAsyncStatus(id, user);
assertOK(statusResp);

// other (user) cannot access the status
ResponseException exc = expectThrows(ResponseException.class, () -> getAsyncStatus(id, other));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));
Response getResp = getAsyncSearch(id, user);
assertOK(getResp);

// other (user) cannot access the result
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, other));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));
// other (user) cannot access the status
ResponseException exc = expectThrows(ResponseException.class, () -> getAsyncStatus(id, other));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));

// user-cancel cannot access the result
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, "user-cancel"));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));
// other (user) cannot access the result
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, other));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));

// user-monitor can access the status
assertOK(getAsyncStatus(id, "user-monitor"));
// user-cancel cannot access the result
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, "user-cancel"));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));

// user-monitor cannot access the result
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, "user-monitor"));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));
// user-monitor can access the status
assertOK(getAsyncStatus(id, "user-monitor"));

// other cannot delete the result
exc = expectThrows(ResponseException.class, () -> deleteAsyncSearch(id, other));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));
// user-monitor cannot access the result
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, "user-monitor"));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));

// user-monitor cannot delete the result
exc = expectThrows(ResponseException.class, () -> deleteAsyncSearch(id, "user-monitor"));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));
// other cannot delete the result
exc = expectThrows(ResponseException.class, () -> deleteAsyncSearch(id, other));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));

// none of the users can access the result from direct get calls on the index
AsyncExecutionId searchId = AsyncExecutionId.decode(id);
for (String runAs : new String[] { user, other, "user-monitor", "user-cancel" }) {
exc = expectThrows(ResponseException.class, () -> get(ASYNC_RESULTS_INDEX, searchId.getDocId(), runAs));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(403));
assertThat(exc.getMessage(), containsString("unauthorized"));
}
// user-monitor cannot delete the result
exc = expectThrows(ResponseException.class, () -> deleteAsyncSearch(id, "user-monitor"));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(404));

Response delResp = deleteAsyncSearch(id, user);
assertOK(delResp);
// none of the users can access the result from direct get calls on the index
AsyncExecutionId searchId = AsyncExecutionId.decode(id);
for (String runAs : new String[] { user, other, "user-monitor", "user-cancel" }) {
exc = expectThrows(ResponseException.class, () -> get(ASYNC_RESULTS_INDEX, searchId.getDocId(), runAs));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(403));
assertThat(exc.getMessage(), containsString("unauthorized"));
}

// check that users with the 'cancel_task' privilege can delete an async
// search submitted by a different user.
for (String runAs : new String[] { "user-cancel", "test_kibana_user" }) {
Response newResp = submitAsyncSearch(indexName, "foo:bar", TimeValue.timeValueSeconds(10), user);
assertOK(newResp);
String newId = extractResponseId(newResp);
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, runAs));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), greaterThan(400));
delResp = deleteAsyncSearch(newId, runAs);
assertOK(delResp);
}
Response delResp = deleteAsyncSearch(id, user);
assertOK(delResp);

// check that users with the 'cancel_task' privilege can delete an async
// search submitted by a different user.
for (String runAs : new String[] { "user-cancel", "test_kibana_user" }) {
Response newResp = submitAsyncSearch(indexName, "foo:bar", TimeValue.timeValueSeconds(10), user);
assertOK(newResp);
String newId = extractResponseId(newResp);
exc = expectThrows(ResponseException.class, () -> getAsyncSearch(id, runAs));
assertThat(exc.getResponse().getStatusLine().getStatusCode(), greaterThan(400));
delResp = deleteAsyncSearch(newId, runAs);
assertOK(delResp);
}
ResponseException exc = expectThrows(
ResponseException.class,
() -> submitAsyncSearch("index-" + other, "*", TimeValue.timeValueSeconds(10), user)
);
assertThat(exc.getResponse().getStatusLine().getStatusCode(), equalTo(403));
assertThat(exc.getMessage(), containsString("unauthorized"));
}

private SearchHit[] getSearchHits(String asyncId, String user) throws IOException {
Expand Down Expand Up @@ -412,6 +455,17 @@ static Response get(String index, String id, String user) throws IOException {
return client().performRequest(request);
}

static Response submitAsyncSearchWithJsonBody(String indexName, String jsonBody, TimeValue waitForCompletion, String user)
throws IOException {
final Request request = new Request("POST", indexName + "/_async_search");
setRunAsHeader(request, user);
request.setJsonEntity(jsonBody);
request.addParameter("wait_for_completion_timeout", waitForCompletion.toString());
// we do the cleanup explicitly
request.addParameter("keep_on_completion", "true");
return client().performRequest(request);
}

static Response submitAsyncSearch(String indexName, String query, TimeValue waitForCompletion, String user) throws IOException {
final Request request = new Request("POST", indexName + "/_async_search");
setRunAsHeader(request, user);
Expand Down

0 comments on commit e34046b

Please sign in to comment.