Skip to content

Commit

Permalink
Service Returns Unhandled Error Response (opensearch-project#248)
Browse files Browse the repository at this point in the history
Signed-off-by: Subhobrata Dey <[email protected]>
  • Loading branch information
sbcd90 authored and eirsep committed Apr 3, 2023
1 parent 3a0b8e6 commit 811b140
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public void onResponse(GetResponse response) {

@Override
public void onFailure(Exception t) {
onFailures(t);
onFailures(new OpenSearchStatusException(String.format(Locale.getDefault(), "Detector with %s is not found", detectorId), RestStatus.NOT_FOUND));
}
});
}
Expand Down Expand Up @@ -240,6 +240,9 @@ private void onFailures(Exception t) {
private void finishHim(String detectorId, Exception t) {
threadPool.executor(ThreadPool.Names.GENERIC).execute(ActionRunnable.supply(listener, () -> {
if (t != null) {
if (t instanceof OpenSearchStatusException) {
throw t;
}
throw SecurityAnalyticsException.wrap(t);
} else {
return new DeleteDetectorResponse(detectorId, NO_VERSION, RestStatus.NO_CONTENT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public void onResponse(GetResponse response) {

@Override
public void onFailure(Exception e) {
onFailures(e);
onFailures(new OpenSearchStatusException(String.format(Locale.getDefault(), "Rule with %s is not found", ruleId), RestStatus.NOT_FOUND));
}
});
}
Expand Down Expand Up @@ -273,6 +273,9 @@ private void onFailures(Exception t) {
private void finishHim(String ruleId, Exception t) {
threadPool.executor(ThreadPool.Names.GENERIC).execute(ActionRunnable.supply(listener, () -> {
if (t != null) {
if (t instanceof OpenSearchStatusException) {
throw t;
}
throw SecurityAnalyticsException.wrap(t);
} else {
return new DeleteRuleResponse(ruleId, NO_VERSION, RestStatus.NO_CONTENT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import org.opensearch.commons.alerting.model.SearchInput;
import org.opensearch.commons.alerting.model.action.Action;
import org.opensearch.commons.authuser.User;
import org.opensearch.index.IndexNotFoundException;
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.index.query.QueryBuilders;
import org.opensearch.index.reindex.BulkByScrollResponse;
Expand Down Expand Up @@ -197,8 +198,13 @@ public void onFailure(Exception e) {
listener.onFailure(SecurityAnalyticsException.wrap(
new OpenSearchStatusException(String.format(Locale.getDefault(), "User doesn't have read permissions for one or more configured index %s", detectorIndices), RestStatus.FORBIDDEN)
));
} else {
listener.onFailure(e);
} else if (e instanceof IndexNotFoundException) {
listener.onFailure(SecurityAnalyticsException.wrap(
new OpenSearchStatusException(String.format(Locale.getDefault(), "Indices not found %s", String.join(", ", detectorIndices)), RestStatus.NOT_FOUND)
));
}
else {
listener.onFailure(SecurityAnalyticsException.wrap(e));
}
}
});
Expand Down Expand Up @@ -991,7 +997,7 @@ public void onResponse(SearchResponse response) {
if (ruleIndices.ruleIndexExists(false)) {
importCustomRules(detector, detectorInput, queries, listener);
} else if (detectorInput.getCustomRules().size() > 0) {
onFailures(new OpenSearchStatusException("Custom Rule Index not found", RestStatus.BAD_REQUEST));
onFailures(new OpenSearchStatusException("Custom Rule Index not found", RestStatus.NOT_FOUND));
} else {
if (request.getMethod() == RestRequest.Method.POST) {
createMonitorFromQueries(logIndex, queries, detector, listener, request.getRefreshPolicy());
Expand Down Expand Up @@ -1109,6 +1115,9 @@ private void onFailures(Exception t) {
private void finishHim(Detector detector, Exception t) {
threadPool.executor(ThreadPool.Names.GENERIC).execute(ActionRunnable.supply(listener, () -> {
if (t != null) {
if (t instanceof OpenSearchStatusException) {
throw t;
}
throw SecurityAnalyticsException.wrap(t);
} else {
return new IndexDetectorResponse(detector.getId(), detector.getVersion(), request.getMethod() == RestRequest.Method.POST? RestStatus.CREATED: RestStatus.OK, detector);
Expand Down
123 changes: 123 additions & 0 deletions src/test/java/org/opensearch/securityanalytics/alerts/AlertsIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,129 @@ public void testAckAlerts_WithInvalidDetectorAlertsCombination() throws IOExcept
}
}

public void testAckAlertsWithInvalidDetector() throws IOException {
String index = createTestIndex(randomIndex(), windowsIndexMapping());

String rule = randomRule();

Response createResponse = makeRequest(client(), "POST", SecurityAnalyticsPlugin.RULE_BASE_URI, Collections.singletonMap("category", randomDetectorType()),
new StringEntity(rule), new BasicHeader("Content-Type", "application/json"));
Assert.assertEquals("Create rule failed", RestStatus.CREATED, restStatus(createResponse));

Map<String, Object> responseBody = asMap(createResponse);

String createdId = responseBody.get("_id").toString();

// Execute CreateMappingsAction to add alias mapping for index
Request createMappingRequest = new Request("POST", SecurityAnalyticsPlugin.MAPPER_BASE_URI);
// both req params and req body are supported
createMappingRequest.setJsonEntity(
"{ \"index_name\":\"" + index + "\"," +
" \"rule_topic\":\"" + randomDetectorType() + "\", " +
" \"partial\":true" +
"}"
);

Response response = client().performRequest(createMappingRequest);
assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());

createAlertingMonitorConfigIndex(null);
Action triggerAction = randomAction(createDestination());

Detector detector = randomDetectorWithInputsAndTriggers(List.of(new DetectorInput("windows detector for security analytics", List.of("windows"), List.of(new DetectorRule(createdId)),
getRandomPrePackagedRules().stream().map(DetectorRule::new).collect(Collectors.toList()))),
List.of(new DetectorTrigger(null, "test-trigger", "1", List.of(), List.of(createdId), List.of(), List.of("attack.defense_evasion"), List.of(triggerAction))));

createResponse = makeRequest(client(), "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector));
Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse));

responseBody = asMap(createResponse);

createdId = responseBody.get("_id").toString();

String request = "{\n" +
" \"query\" : {\n" +
" \"match\":{\n" +
" \"_id\": \"" + createdId + "\"\n" +
" }\n" +
" }\n" +
"}";
List<SearchHit> hits = executeSearch(Detector.DETECTORS_INDEX, request);
SearchHit hit = hits.get(0);

String monitorId = ((List<String>) ((Map<String, Object>) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0);

indexDoc(index, "1", randomDoc());

Response executeResponse = executeAlertingMonitor(monitorId, Collections.emptyMap());
Map<String, Object> executeResults = entityAsMap(executeResponse);

int noOfSigmaRuleMatches = ((List<Map<String, Object>>) ((Map<String, Object>) executeResults.get("input_results")).get("results")).get(0).size();
Assert.assertEquals(6, noOfSigmaRuleMatches);

Assert.assertEquals(1, ((Map<String, Object>) executeResults.get("trigger_results")).values().size());

for (Map.Entry<String, Map<String, Object>> triggerResult: ((Map<String, Map<String, Object>>) executeResults.get("trigger_results")).entrySet()) {
Assert.assertEquals(1, ((Map<String, Object>) triggerResult.getValue().get("action_results")).values().size());

for (Map.Entry<String, Map<String, Object>> alertActionResult: ((Map<String, Map<String, Object>>) triggerResult.getValue().get("action_results")).entrySet()) {
Map<String, Object> actionResults = alertActionResult.getValue();

for (Map.Entry<String, Object> actionResult: actionResults.entrySet()) {
Map<String, String> actionOutput = ((Map<String, Map<String, String>>) actionResult.getValue()).get("output");
String expectedMessage = triggerAction.getSubjectTemplate().getIdOrCode().replace("{{ctx.detector.name}}", detector.getName())
.replace("{{ctx.trigger.name}}", "test-trigger").replace("{{ctx.trigger.severity}}", "1");

Assert.assertEquals(expectedMessage, actionOutput.get("subject"));
Assert.assertEquals(expectedMessage, actionOutput.get("message"));
}
}
}

request = "{\n" +
" \"query\" : {\n" +
" \"match_all\":{\n" +
" }\n" +
" }\n" +
"}";
hits = new ArrayList<>();

while (hits.size() == 0) {
hits = executeSearch(DetectorMonitorConfig.getAlertsIndex(randomDetectorType()), request);
}

// Call GetAlerts API
Map<String, String> params = new HashMap<>();
params.put("detector_id", createdId);
Response getAlertsResponse = makeRequest(client(), "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null);
Map<String, Object> getAlertsBody = asMap(getAlertsResponse);
// TODO enable asserts here when able
Assert.assertEquals(1, getAlertsBody.get("total_alerts"));
String alertId = (String) ((ArrayList<HashMap<String, Object>>) getAlertsBody.get("alerts")).get(0).get("id");
String detectorId = (String) ((ArrayList<HashMap<String, Object>>) getAlertsBody.get("alerts")).get(0).get("detector_id");
String body = String.format(Locale.getDefault(), "{\"alerts\":[\"%s\"]}", alertId);
Request post = new Request("POST", String.format(
Locale.getDefault(),
"%s/%s/_acknowledge/alerts",
SecurityAnalyticsPlugin.DETECTOR_BASE_URI,
java.util.UUID.randomUUID()));
post.setJsonEntity(body);

try {
client().performRequest(post);
} catch (ResponseException ex) {
Assert.assertEquals(HttpStatus.SC_NOT_FOUND, ex.getResponse().getStatusLine().getStatusCode());
}

body = String.format(Locale.getDefault(), "{\"alerts\":[\"%s\"]}", java.util.UUID.randomUUID());
post = new Request("POST", String.format(
Locale.getDefault(),
"%s/%s/_acknowledge/alerts",
SecurityAnalyticsPlugin.DETECTOR_BASE_URI,
detectorId));
post.setJsonEntity(body);
}

public void testGetAlerts_byDetectorType_success() throws IOException, InterruptedException {
String index = createTestIndex(randomIndex(), windowsIndexMapping());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.opensearch.action.search.SearchResponse;
import org.opensearch.client.Request;
import org.opensearch.client.Response;
import org.opensearch.client.ResponseException;
import org.opensearch.commons.alerting.model.Monitor.MonitorType;
import org.opensearch.rest.RestStatus;
import org.opensearch.search.SearchHit;
Expand Down Expand Up @@ -109,6 +110,42 @@ public void testCreatingADetector() throws IOException {
Assert.assertEquals(5, noOfSigmaRuleMatches);
}

public void testCreatingADetectorWithIndexNotExists() throws IOException {
Detector detector = randomDetectorWithTriggers(getRandomPrePackagedRules(), List.of(new DetectorTrigger(null, "test-trigger", "1", List.of(randomDetectorType()), List.of(), List.of(), List.of(), List.of())));

try {
makeRequest(client(), "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector));
} catch (ResponseException ex) {
Assert.assertEquals(404, ex.getResponse().getStatusLine().getStatusCode());
}
}

public void testCreatingADetectorWithNonExistingCustomRule() throws IOException {
String index = createTestIndex(randomIndex(), windowsIndexMapping());

// Execute CreateMappingsAction to add alias mapping for index
Request createMappingRequest = new Request("POST", SecurityAnalyticsPlugin.MAPPER_BASE_URI);
// both req params and req body are supported
createMappingRequest.setJsonEntity(
"{ \"index_name\":\"" + index + "\"," +
" \"rule_topic\":\"" + randomDetectorType() + "\", " +
" \"partial\":true" +
"}"
);

Response response = client().performRequest(createMappingRequest);
assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
DetectorInput input = new DetectorInput("windows detector for security analytics", List.of("windows"), List.of(new DetectorRule(java.util.UUID.randomUUID().toString())),
getRandomPrePackagedRules().stream().map(DetectorRule::new).collect(Collectors.toList()));
Detector detector = randomDetectorWithInputs(List.of(input));

try {
makeRequest(client(), "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector));
} catch (ResponseException ex) {
Assert.assertEquals(404, ex.getResponse().getStatusLine().getStatusCode());
}
}

/**
* 1. Creates detector with no rules
* 2. Detector without rules and monitors created successfully
Expand Down Expand Up @@ -457,6 +494,42 @@ public void testUpdateADetector() throws IOException {
Assert.assertEquals(6, response.getHits().getTotalHits().value);
}

public void testUpdateANonExistingDetector() throws IOException {
String index = createTestIndex(randomIndex(), windowsIndexMapping());

// Execute CreateMappingsAction to add alias mapping for index
Request createMappingRequest = new Request("POST", SecurityAnalyticsPlugin.MAPPER_BASE_URI);
// both req params and req body are supported
createMappingRequest.setJsonEntity(
"{ \"index_name\":\"" + index + "\"," +
" \"rule_topic\":\"" + randomDetectorType() + "\", " +
" \"partial\":true" +
"}"
);

DetectorInput input = new DetectorInput("windows detector for security analytics", List.of("windows"), List.of(),
getRandomPrePackagedRules().stream().map(DetectorRule::new).collect(Collectors.toList()));
Detector updatedDetector = randomDetectorWithInputs(List.of(input));

try {
makeRequest(client(), "PUT", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + java.util.UUID.randomUUID(), Collections.emptyMap(), toHttpEntity(updatedDetector));
} catch (ResponseException ex) {
Assert.assertEquals(404, ex.getResponse().getStatusLine().getStatusCode());
}
}

public void testUpdateADetectorWithIndexNotExists() throws IOException {
DetectorInput input = new DetectorInput("windows detector for security analytics", List.of("windows"), List.of(),
getRandomPrePackagedRules().stream().map(DetectorRule::new).collect(Collectors.toList()));
Detector updatedDetector = randomDetectorWithInputs(List.of(input));

try {
makeRequest(client(), "PUT", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + java.util.UUID.randomUUID(), Collections.emptyMap(), toHttpEntity(updatedDetector));
} catch (ResponseException ex) {
Assert.assertEquals(404, ex.getResponse().getStatusLine().getStatusCode());
}
}

@SuppressWarnings("unchecked")
public void testDeletingADetector() throws IOException {
String index = createTestIndex(randomIndex(), windowsIndexMapping());
Expand Down Expand Up @@ -505,4 +578,12 @@ public void testDeletingADetector() throws IOException {
hits = executeSearch(Detector.DETECTORS_INDEX, request);
Assert.assertEquals(0, hits.size());
}

public void testDeletingANonExistingDetector() throws IOException {
try {
makeRequest(client(), "DELETE", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + java.util.UUID.randomUUID(), Collections.emptyMap(), null);
} catch (ResponseException ex) {
Assert.assertEquals(404, ex.getResponse().getStatusLine().getStatusCode());
}
}
}
Loading

0 comments on commit 811b140

Please sign in to comment.