From 3a0b8e61a6cc488b686baefb9dae92f3b6bf03de Mon Sep 17 00:00:00 2001 From: Stevan Buzejic <30922513+stevanbz@users.noreply.github.com> Date: Fri, 6 Jan 2023 00:53:30 +0100 Subject: [PATCH] Added dummy search when creating detector on the given indicies (#197) Signed-off-by: Stevan Buzejic --- .../TransportIndexDetectorAction.java | 46 +- .../SecurityAnalyticsRestTestCase.java | 98 ++++- .../alerts/SecureAlertsRestApiIT.java | 359 ++++++++-------- .../findings/SecureFindingRestApiIT.java | 403 +++++++++--------- .../resthandler/SecureDetectorRestApiIT.java | 367 ++++++++++++---- 5 files changed, 817 insertions(+), 456 deletions(-) diff --git a/src/main/java/org/opensearch/securityanalytics/transport/TransportIndexDetectorAction.java b/src/main/java/org/opensearch/securityanalytics/transport/TransportIndexDetectorAction.java index 310662477..9c6774ac5 100644 --- a/src/main/java/org/opensearch/securityanalytics/transport/TransportIndexDetectorAction.java +++ b/src/main/java/org/opensearch/securityanalytics/transport/TransportIndexDetectorAction.java @@ -173,8 +173,35 @@ protected void doExecute(Task task, IndexDetectorRequest request, ActionListener return; } - AsyncIndexDetectorsAction asyncAction = new AsyncIndexDetectorsAction(user, task, request, listener); - asyncAction.start(); + checkIndicesAndExecute(task, request, listener, user); + } + + private void checkIndicesAndExecute( + Task task, + IndexDetectorRequest request, + ActionListener listener, + User user + ) { + String [] detectorIndices = request.getDetector().getInputs().stream().flatMap(detectorInput -> detectorInput.getIndices().stream()).toArray(String[]::new); + SearchRequest searchRequest = new SearchRequest(detectorIndices).source(SearchSourceBuilder.searchSource().size(1).query(QueryBuilders.matchAllQuery()));; + client.search(searchRequest, new ActionListener<>() { + @Override + public void onResponse(SearchResponse searchResponse) { + AsyncIndexDetectorsAction asyncAction = new AsyncIndexDetectorsAction(user, task, request, listener); + asyncAction.start(); + } + + @Override + public void onFailure(Exception e) { + if (e instanceof OpenSearchStatusException) { + 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); + } + } + }); } private void createMonitorFromQueries(String index, List> rulesById, Detector detector, ActionListener> listener, WriteRequest.RefreshPolicy refreshPolicy) throws SigmaError, IOException { @@ -192,7 +219,7 @@ private void createMonitorFromQueries(String index, List> rul monitorRequests.addAll(buildBucketLevelMonitorRequests(Pair.of(index, bucketLevelRules), detector, refreshPolicy, Monitor.NO_ID, Method.POST)); } // Do nothing if detector doesn't have any monitor - if(monitorRequests.isEmpty()){ + if (monitorRequests.isEmpty()){ listener.onResponse(Collections.emptyList()); return; } @@ -206,7 +233,7 @@ private void createMonitorFromQueries(String index, List> rul addFirstMonitorStep.whenComplete(addedFirstMonitorResponse -> { monitorResponses.add(addedFirstMonitorResponse); int numberOfUnprocessedResponses = monitorRequests.size() - 1; - if(numberOfUnprocessedResponses == 0){ + if (numberOfUnprocessedResponses == 0){ listener.onResponse(monitorResponses); } else { GroupedActionListener monitorResponseListener = new GroupedActionListener( @@ -251,7 +278,7 @@ private void updateMonitorFromQueries(String index, List> rul for (Pair query: bucketLevelRules) { Rule rule = query.getRight(); - if(rule.getAggregationQueries() != null){ + if (rule.getAggregationQueries() != null){ // Detect if the monitor should be added or updated if (monitorPerRule.containsKey(rule.getId())) { String monitorId = monitorPerRule.get(rule.getId()); @@ -321,7 +348,7 @@ private void updateAlertingMonitors( executeMonitorActionRequest(monitorsToBeAdded, addNewMonitorsStep); // 1. Add new alerting monitors (for the rules that didn't exist previously) addNewMonitorsStep.whenComplete(addNewMonitorsResponse -> { - if(addNewMonitorsResponse != null && !addNewMonitorsResponse.isEmpty()) { + if (addNewMonitorsResponse != null && !addNewMonitorsResponse.isEmpty()) { updatedMonitors.addAll(addNewMonitorsResponse); } @@ -329,7 +356,7 @@ private void updateAlertingMonitors( executeMonitorActionRequest(monitorsToBeUpdated, updateMonitorsStep); // 2. Update existing alerting monitors (based on the common rules) updateMonitorsStep.whenComplete(updateMonitorResponse -> { - if(updateMonitorResponse!=null && !updateMonitorResponse.isEmpty()) { + if (updateMonitorResponse!=null && !updateMonitorResponse.isEmpty()) { updatedMonitors.addAll(updateMonitorResponse); } @@ -413,7 +440,7 @@ private List buildBucketLevelMonitorRequests(Pair> listener) { // In the case of not provided monitors, just return empty list - if(indexMonitors == null || indexMonitors.isEmpty()) { + if (indexMonitors == null || indexMonitors.isEmpty()) { listener.onResponse(new ArrayList<>()); return; } @@ -595,7 +622,6 @@ class AsyncIndexDetectorsAction { void start() { try { - TransportIndexDetectorAction.this.threadPool.getThreadContext().stashContext(); if (!detectorIndices.detectorIndexExists()) { diff --git a/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java b/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java index f62d7ade7..9855f0c94 100644 --- a/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java +++ b/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java @@ -72,7 +72,7 @@ import static org.opensearch.action.admin.indices.create.CreateIndexRequest.MAPPINGS; import static org.opensearch.securityanalytics.TestHelpers.sumAggregationTestRule; import static org.opensearch.securityanalytics.TestHelpers.productIndexAvgAggRule; -import static org.opensearch.securityanalytics.util.RuleTopicIndices.ruleTopicIndexMappings; +import static org.opensearch.securityanalytics.TestHelpers.windowsIndexMapping; import static org.opensearch.securityanalytics.util.RuleTopicIndices.ruleTopicIndexSettings; public class SecurityAnalyticsRestTestCase extends OpenSearchRestTestCase { @@ -108,6 +108,22 @@ protected void createRuleTopicIndex(String detectorType, String additionalMappin } } + protected final List clusterPermissions = List.of( + "cluster:admin/opensearch/securityanalytics/detector/*", + "cluster:admin/opendistro/alerting/alerts/*", + "cluster:admin/opendistro/alerting/findings/*", + "cluster:admin/opensearch/securityanalytics/mapping/*", + "cluster:admin/opensearch/securityanalytics/rule/*" + ); + + protected final List indexPermissions = List.of( + "indices:admin/mappings/get", + "indices:admin/mapping/put", + "indices:data/read/search" + ); + + protected static String TEST_HR_ROLE = "hr_role"; + protected String createTestIndex(String index, String mapping) throws IOException { createTestIndex(index, mapping, Settings.EMPTY); return index; @@ -375,6 +391,20 @@ public static SearchResponse executeSearchRequest(String indexName, String query return SearchResponse.fromXContent(parser); } + public static SearchResponse executeSearchRequest(RestClient client, String indexName, String queryJson) throws IOException { + + Request request = new Request("GET", indexName + "/_search"); + request.setJsonEntity(queryJson); + Response response = client.performRequest(request); + + XContentParser parser = JsonXContent.jsonXContent.createParser( + new NamedXContentRegistry(ClusterModule.getNamedXWriteables()), + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + response.getEntity().getContent() + ); + return SearchResponse.fromXContent(parser); + } + protected HttpEntity toHttpEntity(Detector detector) throws IOException { return new StringEntity(toJsonString(detector), ContentType.APPLICATION_JSON); } @@ -997,6 +1027,41 @@ protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOE } + protected void createIndexRole(String name, List clusterPermissions, List indexPermission, List indexPatterns) throws IOException { + Response response; + try { + response = client().performRequest(new Request("GET", String.format(Locale.getDefault(), "/_plugins/_security/api/roles/%s", name))); + } catch (ResponseException ex) { + response = ex.getResponse(); + } + // Role already exists + if(response.getStatusLine().getStatusCode() == RestStatus.OK.getStatus()) { + return; + } + + Request request = new Request("PUT", String.format(Locale.getDefault(), "/_plugins/_security/api/roles/%s", name)); + String clusterPermissionsStr = clusterPermissions.stream().map(p -> "\"" + p + "\"").collect(Collectors.joining(",")); + String indexPermissionsStr = indexPermission.stream().map(p -> "\"" + p + "\"").collect(Collectors.joining(",")); + String indexPatternsStr = indexPatterns.stream().map(p -> "\"" + p + "\"").collect(Collectors.joining(",")); + + String entity = "{\n" + + "\"cluster_permissions\": [\n" + + "" + clusterPermissionsStr + "\n" + + "], \n" + + "\"index_permissions\": [\n" + + "{" + + "\"fls\": [], " + + "\"masked_fields\": [], " + + "\"allowed_actions\": [" + indexPermissionsStr + "], " + + "\"index_patterns\": [" + indexPatternsStr + "]" + + "}" + + "], " + + "\"tenant_permissions\": []" + + "}"; + + request.setJsonEntity(entity); + client().performRequest(request); + } protected void createCustomRole(String name, String clusterPermissions) throws IOException { Request request = new Request("PUT", String.format(Locale.getDefault(), "/_plugins/_security/api/roles/%s", name)); @@ -1009,7 +1074,7 @@ protected void createCustomRole(String name, String clusterPermissions) throws I client().performRequest(request); } - protected void createUser(String name, String passwd, String[] backendRoles) throws IOException { + public void createUser(String name, String passwd, String[] backendRoles) throws IOException { Request request = new Request("PUT", String.format(Locale.getDefault(), "/_plugins/_security/api/internalusers/%s", name)); String broles = String.join(",", backendRoles); //String roles = String.join(",", customRoles); @@ -1048,19 +1113,46 @@ protected void createUserWithDataAndCustomRole(String userName, String userPass createUserRolesMapping(roleName, users); } - protected void createUserWithData(String userName, String userPasswd, String roleName, String[] backendRoles ) throws IOException { + protected void createUserWithDataAndCustomRole(String userName, String userPasswd, String roleName, String[] backendRoles, List clusterPermissions, List indexPermissions, List indexPatterns) throws IOException { String[] users = {userName}; createUser(userName, userPasswd, backendRoles); + createIndexRole(roleName, clusterPermissions, indexPermissions, indexPatterns); createUserRolesMapping(roleName, users); } + protected void createUserWithData(String userName, String userPasswd, String roleName, String[] backendRoles ) throws IOException { + String[] users = {userName}; + createUser(userName, userPasswd, backendRoles); + createUserRolesMapping(roleName, users); + } + public void createUserWithTestData(String user, String index, String role, String [] backendRoles, List indexPermissions) throws IOException{ + String[] users = {user}; + createUser(user, user, backendRoles); + createTestIndex(client(), index, windowsIndexMapping(), Settings.EMPTY); + createIndexRole(role, Collections.emptyList(), indexPermissions, List.of(index)); + createUserRolesMapping(role, users); + } protected void deleteUser(String name) throws IOException { Request request = new Request("DELETE", String.format(Locale.getDefault(), "/_plugins/_security/api/internalusers/%s", name)); client().performRequest(request); } + protected void tryDeletingRole(String name) throws IOException{ + Response response; + try { + response = client().performRequest(new Request("GET", String.format(Locale.getDefault(), "/_plugins/_security/api/roles/%s", name))); + } catch (ResponseException ex) { + response = ex.getResponse(); + } + // Role already exists + if(response.getStatusLine().getStatusCode() == RestStatus.OK.getStatus()) { + Request request = new Request("DELETE", String.format(Locale.getDefault(), "/_plugins/_security/api/roles/%s", name)); + client().performRequest(request); + } + } + @Override protected boolean preserveIndicesUponCompletion() { return true; diff --git a/src/test/java/org/opensearch/securityanalytics/alerts/SecureAlertsRestApiIT.java b/src/test/java/org/opensearch/securityanalytics/alerts/SecureAlertsRestApiIT.java index 6039f16ea..ff6cba8bb 100644 --- a/src/test/java/org/opensearch/securityanalytics/alerts/SecureAlertsRestApiIT.java +++ b/src/test/java/org/opensearch/securityanalytics/alerts/SecureAlertsRestApiIT.java @@ -22,7 +22,6 @@ import org.opensearch.search.SearchHit; import org.opensearch.securityanalytics.SecurityAnalyticsPlugin; import org.opensearch.securityanalytics.SecurityAnalyticsRestTestCase; -import org.opensearch.securityanalytics.action.AlertDto; import org.opensearch.securityanalytics.config.monitors.DetectorMonitorConfig; import org.opensearch.securityanalytics.model.Detector; import org.opensearch.securityanalytics.model.DetectorInput; @@ -34,7 +33,6 @@ import java.util.stream.Collectors; import static org.opensearch.securityanalytics.TestHelpers.*; -import static org.opensearch.securityanalytics.settings.SecurityAnalyticsSettings.*; public class SecureAlertsRestApiIT extends SecurityAnalyticsRestTestCase { @@ -63,252 +61,271 @@ public void cleanup() throws IOException { @SuppressWarnings("unchecked") public void testGetAlerts_byDetectorId_success() throws IOException { - String index = createTestIndex(randomIndex(), windowsIndexMapping()); + try { + String index = createTestIndex(randomIndex(), windowsIndexMapping()); + // Assign a role to the index + createIndexRole(TEST_HR_ROLE, Collections.emptyList(), indexPermissions, List.of(index)); + String[] users = {user}; + // Assign a role to existing user + createUserRolesMapping(TEST_HR_ROLE, users); - String rule = randomRule(); + String rule = randomRule(); - Response createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.RULE_BASE_URI, Collections.singletonMap("category", randomDetectorType()), + Response createResponse = makeRequest(userClient, "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)); + Assert.assertEquals("Create rule failed", RestStatus.CREATED, restStatus(createResponse)); - Map responseBody = asMap(createResponse); + Map responseBody = asMap(createResponse); - String createdId = responseBody.get("_id").toString(); + 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( + // 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" + - "}" - ); + " \"rule_topic\":\"" + randomDetectorType() + "\", " + + " \"partial\":true" + + "}" + ); - Response response = userClient.performRequest(createMappingRequest); - assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); + Response response = userClient.performRequest(createMappingRequest); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); - createAlertingMonitorConfigIndex(null); - Action triggerAction = randomAction(createDestination()); + 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()))), + 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(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); - Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); + createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); + Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); - responseBody = asMap(createResponse); + responseBody = asMap(createResponse); - createdId = responseBody.get("_id").toString(); + createdId = responseBody.get("_id").toString(); - String request = "{\n" + + String request = "{\n" + " \"query\" : {\n" + " \"match\":{\n" + " \"_id\": \"" + createdId + "\"\n" + " }\n" + " }\n" + "}"; - List hits = executeSearch(Detector.DETECTORS_INDEX, request); - SearchHit hit = hits.get(0); + List hits = executeSearch(Detector.DETECTORS_INDEX, request); + SearchHit hit = hits.get(0); - String monitorId = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); + String monitorId = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); - indexDoc(index, "1", randomDoc()); + indexDoc(index, "1", randomDoc()); - Response executeResponse = executeAlertingMonitor(monitorId, Collections.emptyMap()); - Map executeResults = entityAsMap(executeResponse); + Response executeResponse = executeAlertingMonitor(monitorId, Collections.emptyMap()); + Map executeResults = entityAsMap(executeResponse); - int noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); - Assert.assertEquals(6, noOfSigmaRuleMatches); + int noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); + Assert.assertEquals(6, noOfSigmaRuleMatches); - Assert.assertEquals(1, ((Map) executeResults.get("trigger_results")).values().size()); + Assert.assertEquals(1, ((Map) executeResults.get("trigger_results")).values().size()); - for (Map.Entry> triggerResult: ((Map>) executeResults.get("trigger_results")).entrySet()) { - Assert.assertEquals(1, ((Map) triggerResult.getValue().get("action_results")).values().size()); + for (Map.Entry> triggerResult: ((Map>) executeResults.get("trigger_results")).entrySet()) { + Assert.assertEquals(1, ((Map) triggerResult.getValue().get("action_results")).values().size()); - for (Map.Entry> alertActionResult: ((Map>) triggerResult.getValue().get("action_results")).entrySet()) { - Map actionResults = alertActionResult.getValue(); + for (Map.Entry> alertActionResult: ((Map>) triggerResult.getValue().get("action_results")).entrySet()) { + Map actionResults = alertActionResult.getValue(); - for (Map.Entry actionResult: actionResults.entrySet()) { - Map actionOutput = ((Map>) actionResult.getValue()).get("output"); - String expectedMessage = triggerAction.getSubjectTemplate().getIdOrCode().replace("{{ctx.detector.name}}", detector.getName()) + for (Map.Entry actionResult: actionResults.entrySet()) { + Map actionOutput = ((Map>) 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")); + Assert.assertEquals(expectedMessage, actionOutput.get("subject")); + Assert.assertEquals(expectedMessage, actionOutput.get("message")); + } } } - } - request = "{\n" + + request = "{\n" + " \"query\" : {\n" + " \"match_all\":{\n" + " }\n" + " }\n" + "}"; - hits = new ArrayList<>(); + hits = new ArrayList<>(); - while (hits.size() == 0) { - hits = executeSearch(DetectorMonitorConfig.getAlertsIndex(randomDetectorType()), request); - } + while (hits.size() == 0) { + hits = executeSearch(DetectorMonitorConfig.getAlertsIndex(randomDetectorType()), request); + } - // try to do get finding as a user with read access - String userRead = "userReadAlert"; - String[] backendRoles = { TEST_IT_BACKEND_ROLE }; - createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, backendRoles ); - RestClient userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - - // Call GetAlerts API - Map params = new HashMap<>(); - params.put("detector_id", createdId); - Response getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); - Map getAlertsBody = asMap(getAlertsResponse); - Assert.assertEquals(1, getAlertsBody.get("total_alerts")); - - // Enable backend filtering and try to read finding as a user with no backend roles matching the user who created the detector - enableOrDisableFilterBy("true"); - try { - getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); - } catch (ResponseException e) - { - assertEquals("Get alert failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); - } - finally { - userReadOnlyClient.close(); - deleteUser(userRead); - } + // try to do get finding as a user with read access + String userRead = "userReadAlert"; + String[] backendRoles = { TEST_IT_BACKEND_ROLE }; + createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, backendRoles ); + RestClient userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + + // Call GetAlerts API + Map params = new HashMap<>(); + params.put("detector_id", createdId); + Response getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); + Map getAlertsBody = asMap(getAlertsResponse); + Assert.assertEquals(1, getAlertsBody.get("total_alerts")); + + // Enable backend filtering and try to read finding as a user with no backend roles matching the user who created the detector + enableOrDisableFilterBy("true"); + try { + getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); + } catch (ResponseException e) + { + assertEquals("Get alert failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); + } + finally { + userReadOnlyClient.close(); + deleteUser(userRead); + } - // recreate user with matching backend roles and try again - String[] newBackendRoles = { TEST_HR_BACKEND_ROLE }; - createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, newBackendRoles ); - userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); - getAlertsBody = asMap(getAlertsResponse); - Assert.assertEquals(1, getAlertsBody.get("total_alerts")); - userReadOnlyClient.close(); - - // update user with no backend roles and try again - createUser(userRead, userRead, EMPTY_ARRAY); - userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - try { + // recreate user with matching backend roles and try again + String[] newBackendRoles = { TEST_HR_BACKEND_ROLE }; + createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, newBackendRoles ); + userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); - } catch (ResponseException e) - { - assertEquals("Get alert failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); - } - finally { + getAlertsBody = asMap(getAlertsResponse); + Assert.assertEquals(1, getAlertsBody.get("total_alerts")); userReadOnlyClient.close(); - deleteUser(userRead); + + // update user with no backend roles and try again + createUser(userRead, userRead, EMPTY_ARRAY); + userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + try { + getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); + } catch (ResponseException e) + { + assertEquals("Get alert failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); + } + finally { + userReadOnlyClient.close(); + deleteUser(userRead); + } + } finally { + tryDeletingRole(TEST_HR_ROLE); } + } public void testGetAlerts_byDetectorType_success() throws IOException, InterruptedException { - 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( + try { + String index = createTestIndex(randomIndex(), windowsIndexMapping()); + // Assign a role to the index + createIndexRole(TEST_HR_ROLE, Collections.emptyList(), indexPermissions, List.of(index)); + String[] users = {user}; + // Assign a role to existing user + createUserRolesMapping(TEST_HR_ROLE, users); + + // 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" + - "}" - ); + " \"rule_topic\":\"" + randomDetectorType() + "\", " + + " \"partial\":true" + + "}" + ); - Response response = userClient.performRequest(createMappingRequest); - assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); + Response response = userClient.performRequest(createMappingRequest); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); - Detector detector = randomDetectorWithTriggers(getRandomPrePackagedRules(), List.of(new DetectorTrigger(null, "test-trigger", "1", List.of(randomDetectorType()), List.of(), List.of(), List.of(), List.of()))); + Detector detector = randomDetectorWithTriggers(getRandomPrePackagedRules(), List.of(new DetectorTrigger(null, "test-trigger", "1", List.of(randomDetectorType()), List.of(), List.of(), List.of(), List.of()))); - Response createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); - Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); + Response createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); + Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); - Map responseBody = asMap(createResponse); + Map responseBody = asMap(createResponse); - String createdId = responseBody.get("_id").toString(); + String createdId = responseBody.get("_id").toString(); - String request = "{\n" + + String request = "{\n" + " \"query\" : {\n" + " \"match\":{\n" + " \"_id\": \"" + createdId + "\"\n" + " }\n" + " }\n" + "}"; - List hits = executeSearch(Detector.DETECTORS_INDEX, request); - SearchHit hit = hits.get(0); + List hits = executeSearch(Detector.DETECTORS_INDEX, request); + SearchHit hit = hits.get(0); - String monitorId = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); + String monitorId = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); - indexDoc(index, "1", randomDoc()); + indexDoc(index, "1", randomDoc()); - client().performRequest(new Request("POST", "_refresh")); + client().performRequest(new Request("POST", "_refresh")); - Response executeResponse = executeAlertingMonitor(monitorId, Collections.emptyMap()); - Map executeResults = entityAsMap(executeResponse); - int noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); - Assert.assertEquals(5, noOfSigmaRuleMatches); + Response executeResponse = executeAlertingMonitor(monitorId, Collections.emptyMap()); + Map executeResults = entityAsMap(executeResponse); + int noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); + Assert.assertEquals(5, noOfSigmaRuleMatches); - request = "{\n" + + request = "{\n" + " \"query\" : {\n" + " \"match_all\":{\n" + " }\n" + " }\n" + "}"; - hits = new ArrayList<>(); + hits = new ArrayList<>(); - while (hits.size() == 0) { - hits = executeSearch(DetectorMonitorConfig.getAlertsIndex(randomDetectorType()), request); - } + while (hits.size() == 0) { + hits = executeSearch(DetectorMonitorConfig.getAlertsIndex(randomDetectorType()), request); + } - // try to do get finding as a user with read access - String userRead = "userReadAlert"; - String[] backendRoles = { TEST_IT_BACKEND_ROLE }; - createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, backendRoles ); - RestClient userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - - // Call GetAlerts API - Map params = new HashMap<>(); - params.put("detectorType", randomDetectorType()); - Response getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); - Map getAlertsBody = asMap(getAlertsResponse); - Assert.assertEquals(1, getAlertsBody.get("total_alerts")); - - // Enable backend filtering and try to read finding as a user with no backend roles matching the user who created the detector - enableOrDisableFilterBy("true"); - try { - getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); - } catch (ResponseException e) - { - assertEquals("Get alert failed", RestStatus.NOT_FOUND, restStatus(e.getResponse())); - } - finally { - userReadOnlyClient.close(); - deleteUser(userRead); - } + // try to do get finding as a user with read access + String userRead = "userReadAlert"; + String[] backendRoles = { TEST_IT_BACKEND_ROLE }; + createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, backendRoles ); + RestClient userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + + // Call GetAlerts API + Map params = new HashMap<>(); + params.put("detectorType", randomDetectorType()); + Response getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); + Map getAlertsBody = asMap(getAlertsResponse); + Assert.assertEquals(1, getAlertsBody.get("total_alerts")); + + // Enable backend filtering and try to read finding as a user with no backend roles matching the user who created the detector + enableOrDisableFilterBy("true"); + try { + getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); + } catch (ResponseException e) + { + assertEquals("Get alert failed", RestStatus.NOT_FOUND, restStatus(e.getResponse())); + } + finally { + userReadOnlyClient.close(); + deleteUser(userRead); + } - // recreate user with matching backend roles and try again - String[] newBackendRoles = { TEST_HR_BACKEND_ROLE }; - createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, newBackendRoles ); - userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); - getAlertsBody = asMap(getAlertsResponse); - Assert.assertEquals(1, getAlertsBody.get("total_alerts")); - userReadOnlyClient.close(); - - // update user with no backend roles and try again - createUser(userRead, userRead, EMPTY_ARRAY); - userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - try { + // recreate user with matching backend roles and try again + String[] newBackendRoles = { TEST_HR_BACKEND_ROLE }; + createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, newBackendRoles ); + userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); - } catch (ResponseException e) - { - assertEquals("Get alert failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); - } - finally { + getAlertsBody = asMap(getAlertsResponse); + Assert.assertEquals(1, getAlertsBody.get("total_alerts")); userReadOnlyClient.close(); - deleteUser(userRead); + + // update user with no backend roles and try again + createUser(userRead, userRead, EMPTY_ARRAY); + userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + try { + getAlertsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.ALERTS_BASE_URI, params, null); + } catch (ResponseException e) + { + assertEquals("Get alert failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); + } + finally { + userReadOnlyClient.close(); + deleteUser(userRead); + } + } finally { + tryDeletingRole(TEST_HR_ROLE); } } diff --git a/src/test/java/org/opensearch/securityanalytics/findings/SecureFindingRestApiIT.java b/src/test/java/org/opensearch/securityanalytics/findings/SecureFindingRestApiIT.java index e95235bab..aaf26e97d 100644 --- a/src/test/java/org/opensearch/securityanalytics/findings/SecureFindingRestApiIT.java +++ b/src/test/java/org/opensearch/securityanalytics/findings/SecureFindingRestApiIT.java @@ -66,248 +66,269 @@ public void cleanup() throws IOException { @SuppressWarnings("unchecked") public void testGetFindings_byDetectorId_success() 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( + try { + String index = createTestIndex(randomIndex(), windowsIndexMapping()); + // Assign a role to the index + createIndexRole(TEST_HR_ROLE, Collections.emptyList(), indexPermissions, List.of(index)); + String[] users = {user}; + // Assign a role to existing user + createUserRolesMapping(TEST_HR_ROLE, users); + + // 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" + - "}" - ); + " \"rule_topic\":\"" + randomDetectorType() + "\", " + + " \"partial\":true" + + "}" + ); - Response response = userClient.performRequest(createMappingRequest); - assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); + Response response = userClient.performRequest(createMappingRequest); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); - Detector detector = randomDetectorWithTriggers(getRandomPrePackagedRules(), List.of(new DetectorTrigger(null, "test-trigger", "1", List.of(randomDetectorType()), List.of(), List.of(), List.of(), List.of()))); + Detector detector = randomDetectorWithTriggers(getRandomPrePackagedRules(), List.of(new DetectorTrigger(null, "test-trigger", "1", List.of(randomDetectorType()), List.of(), List.of(), List.of(), List.of()))); - Response createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); - Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); + Response createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); + Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); - Map responseBody = asMap(createResponse); + Map responseBody = asMap(createResponse); - String createdId = responseBody.get("_id").toString(); + String createdId = responseBody.get("_id").toString(); - String request = "{\n" + + String request = "{\n" + " \"query\" : {\n" + " \"match\":{\n" + " \"_id\": \"" + createdId + "\"\n" + " }\n" + " }\n" + "}"; - List hits = executeSearch(Detector.DETECTORS_INDEX, request); - SearchHit hit = hits.get(0); - - String monitorId = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); - - indexDoc(index, "1", randomDoc()); - - Response executeResponse = executeAlertingMonitor(monitorId, Collections.emptyMap()); - Map executeResults = entityAsMap(executeResponse); - - int noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); - Assert.assertEquals(5, noOfSigmaRuleMatches); - - // try to do get finding as a user with read access - String userRead = "userReadFinding"; - String[] backendRoles = { TEST_IT_BACKEND_ROLE }; - createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, backendRoles ); - RestClient userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - // Call GetFindings API - Map params = new HashMap<>(); - params.put("detector_id", createdId); - Response getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); - Map getFindingsBody = entityAsMap(getFindingsResponse); - Assert.assertEquals(1, getFindingsBody.get("total_findings")); - - // Enable backend filtering and try to read finding as a user with no backend roles matching the user who created the detector - enableOrDisableFilterBy("true"); - try { + List hits = executeSearch(Detector.DETECTORS_INDEX, request); + SearchHit hit = hits.get(0); + + String monitorId = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); + + indexDoc(index, "1", randomDoc()); + + Response executeResponse = executeAlertingMonitor(monitorId, Collections.emptyMap()); + Map executeResults = entityAsMap(executeResponse); + + int noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); + Assert.assertEquals(5, noOfSigmaRuleMatches); + + // try to do get finding as a user with read access + String userRead = "userReadFinding"; + String[] backendRoles = { TEST_IT_BACKEND_ROLE }; + createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, backendRoles ); + RestClient userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + // Call GetFindings API + Map params = new HashMap<>(); + params.put("detector_id", createdId); + Response getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); + Map getFindingsBody = entityAsMap(getFindingsResponse); + Assert.assertEquals(1, getFindingsBody.get("total_findings")); + + // Enable backend filtering and try to read finding as a user with no backend roles matching the user who created the detector + enableOrDisableFilterBy("true"); + try { + getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); + } catch (ResponseException e) + { + assertEquals("Get finding failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); + } + finally { + userReadOnlyClient.close(); + deleteUser(userRead); + } + + // recreate user with matching backend roles and try again + String[] newBackendRoles = { TEST_HR_BACKEND_ROLE }; + createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, newBackendRoles ); + userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); - } catch (ResponseException e) - { - assertEquals("Get finding failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); - } - finally { + getFindingsBody = entityAsMap(getFindingsResponse); + Assert.assertEquals(1, getFindingsBody.get("total_findings")); userReadOnlyClient.close(); - deleteUser(userRead); - } - // recreate user with matching backend roles and try again - String[] newBackendRoles = { TEST_HR_BACKEND_ROLE }; - createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, newBackendRoles ); - userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); - getFindingsBody = entityAsMap(getFindingsResponse); - Assert.assertEquals(1, getFindingsBody.get("total_findings")); - userReadOnlyClient.close(); - - // update user with no backend roles and try again - createUser(userRead, userRead, EMPTY_ARRAY); - userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - try { - getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); - } catch (ResponseException e) - { - assertEquals("Get finding failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); - } - finally { - userReadOnlyClient.close(); - deleteUser(userRead); + // update user with no backend roles and try again + createUser(userRead, userRead, EMPTY_ARRAY); + userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + try { + getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); + } catch (ResponseException e) + { + assertEquals("Get finding failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); + } + finally { + userReadOnlyClient.close(); + deleteUser(userRead); + } + + } finally { + tryDeletingRole(TEST_HR_ROLE); } - } - + public void testGetFindings_byDetectorType_success() throws IOException { - String index1 = createTestIndex(randomIndex(), windowsIndexMapping()); + try { + String index1 = 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( + // 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\":\"" + index1 + "\"," + - " \"rule_topic\":\"" + randomDetectorType() + "\", " + - " \"partial\":true" + - "}" - ); - // index 2 - String index2 = createTestIndex("netflow_test", netFlowMappings()); - - // Execute CreateMappingsAction to add alias mapping for index - createMappingRequest = new Request("POST", SecurityAnalyticsPlugin.MAPPER_BASE_URI); - // both req params and req body are supported - createMappingRequest.setJsonEntity( + " \"rule_topic\":\"" + randomDetectorType() + "\", " + + " \"partial\":true" + + "}" + ); + Response response = userClient.performRequest(createMappingRequest); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); + + // index 2 + String index2 = createTestIndex("netflow_test", netFlowMappings()); + + // Execute CreateMappingsAction to add alias mapping for index + createMappingRequest = new Request("POST", SecurityAnalyticsPlugin.MAPPER_BASE_URI); + // both req params and req body are supported + createMappingRequest.setJsonEntity( "{ \"index_name\":\"" + index2 + "\"," + - " \"rule_topic\":\"netflow\", " + - " \"partial\":true" + - "}" - ); + " \"rule_topic\":\"netflow\", " + + " \"partial\":true" + + "}" + ); + + response = userClient.performRequest(createMappingRequest); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); - Response response = userClient.performRequest(createMappingRequest); - assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); - // Detector 1 - WINDOWS - Detector detector1 = randomDetectorWithTriggers(getRandomPrePackagedRules(), List.of(new DetectorTrigger(null, "test-trigger", "1", List.of(randomDetectorType()), List.of(), List.of(), List.of(), List.of()))); - Response createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector1)); - Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); + createIndexRole(TEST_HR_ROLE, Collections.emptyList(), indexPermissions, List.of(index1, index2)); + String[] users = {user}; + createUserRolesMapping(TEST_HR_ROLE, users); - Map responseBody = asMap(createResponse); + // Detector 1 - WINDOWS + Detector detector1 = randomDetectorWithTriggers(getRandomPrePackagedRules(), List.of(new DetectorTrigger(null, "test-trigger", "1", List.of(randomDetectorType()), List.of(), List.of(), List.of(), List.of()))); + Response createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector1)); + Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); - String createdId = responseBody.get("_id").toString(); + Map responseBody = asMap(createResponse); - String request = "{\n" + + String createdId = responseBody.get("_id").toString(); + + String request = "{\n" + " \"query\" : {\n" + " \"match\":{\n" + " \"_id\": \"" + createdId + "\"\n" + " }\n" + " }\n" + "}"; - List hits = executeSearch(Detector.DETECTORS_INDEX, request); - SearchHit hit = hits.get(0); - String monitorId1 = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); - // Detector 2 - NETWORK - DetectorInput inputNetflow = new DetectorInput("windows detector for security analytics", List.of("netflow_test"), Collections.emptyList(), + List hits = executeSearch(Detector.DETECTORS_INDEX, request); + SearchHit hit = hits.get(0); + String monitorId1 = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); + // Detector 2 - NETWORK + DetectorInput inputNetflow = new DetectorInput("windows detector for security analytics", List.of("netflow_test"), Collections.emptyList(), getPrePackagedRules("network").stream().map(DetectorRule::new).collect(Collectors.toList())); - Detector detector2 = randomDetectorWithTriggers( + Detector detector2 = randomDetectorWithTriggers( getPrePackagedRules("network"), List.of(new DetectorTrigger(null, "test-trigger", "1", List.of("network"), List.of(), List.of(), List.of(), List.of())), Detector.DetectorType.NETWORK, inputNetflow - ); + ); - createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector2)); - Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); + createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector2)); + Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); - responseBody = asMap(createResponse); + responseBody = asMap(createResponse); - createdId = responseBody.get("_id").toString(); + createdId = responseBody.get("_id").toString(); - request = "{\n" + + request = "{\n" + " \"query\" : {\n" + " \"match\":{\n" + " \"_id\": \"" + createdId + "\"\n" + " }\n" + " }\n" + "}"; - hits = executeSearch(Detector.DETECTORS_INDEX, request); - hit = hits.get(0); - String monitorId2 = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); - - indexDoc(index1, "1", randomDoc()); - indexDoc(index2, "1", randomDoc()); - // execute monitor 1 - Response executeResponse = executeAlertingMonitor(monitorId1, Collections.emptyMap()); - Map executeResults = entityAsMap(executeResponse); - - int noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); - Assert.assertEquals(3, noOfSigmaRuleMatches); - - // execute monitor 2 - executeResponse = executeAlertingMonitor(monitorId2, Collections.emptyMap()); - executeResults = entityAsMap(executeResponse); - - noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); - Assert.assertEquals(1, noOfSigmaRuleMatches); - - client().performRequest(new Request("POST", "_refresh")); - - - // try to do get finding as a user with read access - String userRead = "userReadFinding"; - String[] backendRoles = { TEST_IT_BACKEND_ROLE }; - createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, backendRoles ); - RestClient userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - - - // Call GetFindings API for first detector - Map params = new HashMap<>(); - params.put("detectorType", detector1.getDetectorType().toUpperCase()); - Response getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); - Map getFindingsBody = entityAsMap(getFindingsResponse); - Assert.assertEquals(1, getFindingsBody.get("total_findings")); - // Call GetFindings API for second detector - params.clear(); - params.put("detectorType", detector2.getDetectorType().toUpperCase()); - getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); - getFindingsBody = entityAsMap(getFindingsResponse); - Assert.assertEquals(1, getFindingsBody.get("total_findings")); - - // Enable backend filtering and try to read finding as a user with no backend roles matching the user who created the detector - enableOrDisableFilterBy("true"); - try { - getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); - } catch (ResponseException e) - { - assertEquals("Get finding failed", RestStatus.NOT_FOUND, restStatus(e.getResponse())); - } - finally { - userReadOnlyClient.close(); - deleteUser(userRead); - } + hits = executeSearch(Detector.DETECTORS_INDEX, request); + hit = hits.get(0); + String monitorId2 = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); - // recreate user with matching backend roles and try again - String[] newBackendRoles = { TEST_HR_BACKEND_ROLE }; - createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, newBackendRoles ); - userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); - getFindingsBody = entityAsMap(getFindingsResponse); - Assert.assertEquals(1, getFindingsBody.get("total_findings")); - userReadOnlyClient.close(); + indexDoc(index1, "1", randomDoc()); + indexDoc(index2, "1", randomDoc()); + // execute monitor 1 + Response executeResponse = executeAlertingMonitor(monitorId1, Collections.emptyMap()); + Map executeResults = entityAsMap(executeResponse); + int noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); + Assert.assertEquals(5, noOfSigmaRuleMatches); - // update user with no backend roles and try again - createUser(userRead, userRead, EMPTY_ARRAY); - userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - try { - getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); - } catch (ResponseException e) - { - assertEquals("Get finding failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); - } - finally { + // execute monitor 2 + executeResponse = executeAlertingMonitor(monitorId2, Collections.emptyMap()); + executeResults = entityAsMap(executeResponse); + + noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); + Assert.assertEquals(1, noOfSigmaRuleMatches); + + client().performRequest(new Request("POST", "_refresh")); + + + // try to do get finding as a user with read access + String userRead = "userReadFinding"; + String[] backendRoles = { TEST_IT_BACKEND_ROLE }; + createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, backendRoles ); + RestClient userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + + + // Call GetFindings API for first detector + Map params = new HashMap<>(); + params.put("detectorType", detector1.getDetectorType().toUpperCase()); + Response getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); + Map getFindingsBody = entityAsMap(getFindingsResponse); + Assert.assertEquals(1, getFindingsBody.get("total_findings")); + // Call GetFindings API for second detector + params.clear(); + params.put("detectorType", detector2.getDetectorType().toUpperCase()); + getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); + getFindingsBody = entityAsMap(getFindingsResponse); + Assert.assertEquals(1, getFindingsBody.get("total_findings")); + + // Enable backend filtering and try to read finding as a user with no backend roles matching the user who created the detector + enableOrDisableFilterBy("true"); + try { + getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); + } catch (ResponseException e) + { + assertEquals("Get finding failed", RestStatus.NOT_FOUND, restStatus(e.getResponse())); + } + finally { + userReadOnlyClient.close(); + deleteUser(userRead); + } + + // recreate user with matching backend roles and try again + String[] newBackendRoles = { TEST_HR_BACKEND_ROLE }; + createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, newBackendRoles ); + userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); + getFindingsBody = entityAsMap(getFindingsResponse); + Assert.assertEquals(1, getFindingsBody.get("total_findings")); userReadOnlyClient.close(); - deleteUser(userRead); + + + // update user with no backend roles and try again + createUser(userRead, userRead, EMPTY_ARRAY); + userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + try { + getFindingsResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.FINDINGS_BASE_URI + "/_search", params, null); + } catch (ResponseException e) + { + assertEquals("Get finding failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); + } + finally { + userReadOnlyClient.close(); + deleteUser(userRead); + } + } finally { + tryDeletingRole(TEST_HR_ROLE); } } } diff --git a/src/test/java/org/opensearch/securityanalytics/resthandler/SecureDetectorRestApiIT.java b/src/test/java/org/opensearch/securityanalytics/resthandler/SecureDetectorRestApiIT.java index ed111b2ed..e9ce3b9d9 100644 --- a/src/test/java/org/opensearch/securityanalytics/resthandler/SecureDetectorRestApiIT.java +++ b/src/test/java/org/opensearch/securityanalytics/resthandler/SecureDetectorRestApiIT.java @@ -38,12 +38,9 @@ public class SecureDetectorRestApiIT extends SecurityAnalyticsRestTestCase { static String SECURITY_ANALYTICS_FULL_ACCESS_ROLE = "security_analytics_full_access"; static String SECURITY_ANALYTICS_READ_ACCESS_ROLE = "security_analytics_read_access"; static String TEST_HR_BACKEND_ROLE = "HR"; - static String CUSTOM_HR_ROLE = "HR"; static String TEST_IT_BACKEND_ROLE = "IT"; - - static Map roleToPermissionsMap = Map.ofEntries( Map.entry(SECURITY_ANALYTICS_FULL_ACCESS_ROLE, "cluster:admin/opendistro/securityanalytics/detector/*"), Map.entry(SECURITY_ANALYTICS_READ_ACCESS_ROLE, "cluster:admin/opendistro/securityanalytics/detector/read") @@ -70,102 +67,109 @@ public void cleanup() throws IOException { @SuppressWarnings("unchecked") public void testCreateDetectorWithFullAccess() throws IOException { - String[] users = {user}; - //createUserRolesMapping("alerting_full_access", users); - String index = createTestIndex(client(), randomIndex(), windowsIndexMapping(), Settings.EMPTY); - - // 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( + try { + String index = createTestIndex(randomIndex(), windowsIndexMapping()); + // Assign a role to the index + createIndexRole(TEST_HR_ROLE, Collections.emptyList(), indexPermissions, List.of(index)); + String[] users = {user}; + // Assign a role to existing user + createUserRolesMapping(TEST_HR_ROLE, users); + + // 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" + - "}" - ); + " \"rule_topic\":\"" + randomDetectorType() + "\", " + + " \"partial\":true" + + "}" + ); - Response response = userClient.performRequest(createMappingRequest); - assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); + Response response = userClient.performRequest(createMappingRequest); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); - Detector detector = randomDetector(getRandomPrePackagedRules()); + Detector detector = randomDetector(getRandomPrePackagedRules()); - Response createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); - Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); + Response createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); + Assert.assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); - Map responseBody = asMap(createResponse); + Map responseBody = asMap(createResponse); - String createdId = responseBody.get("_id").toString(); - int createdVersion = Integer.parseInt(responseBody.get("_version").toString()); - Assert.assertNotEquals("response is missing Id", Detector.NO_ID, createdId); - Assert.assertTrue("incorrect version", createdVersion > 0); - Assert.assertEquals("Incorrect Location header", String.format(Locale.getDefault(), "%s/%s", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, createdId), createResponse.getHeader("Location")); - Assert.assertFalse(((Map) responseBody.get("detector")).containsKey("rule_topic_index")); - Assert.assertFalse(((Map) responseBody.get("detector")).containsKey("findings_index")); - Assert.assertFalse(((Map) responseBody.get("detector")).containsKey("alert_index")); + String createdId = responseBody.get("_id").toString(); + int createdVersion = Integer.parseInt(responseBody.get("_version").toString()); + Assert.assertNotEquals("response is missing Id", Detector.NO_ID, createdId); + Assert.assertTrue("incorrect version", createdVersion > 0); + Assert.assertEquals("Incorrect Location header", String.format(Locale.getDefault(), "%s/%s", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, createdId), createResponse.getHeader("Location")); + Assert.assertFalse(((Map) responseBody.get("detector")).containsKey("rule_topic_index")); + Assert.assertFalse(((Map) responseBody.get("detector")).containsKey("findings_index")); + Assert.assertFalse(((Map) responseBody.get("detector")).containsKey("alert_index")); - String request = "{\n" + + String request = "{\n" + " \"query\" : {\n" + " \"match\":{\n" + " \"_id\": \"" + createdId + "\"\n" + " }\n" + " }\n" + "}"; - List hits = executeSearch(Detector.DETECTORS_INDEX, request); - SearchHit hit = hits.get(0); - - String monitorId = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); - - indexDoc(index, "1", randomDoc()); - - Response executeResponse = executeAlertingMonitor(monitorId, Collections.emptyMap()); - Map executeResults = entityAsMap(executeResponse); - - int noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); - Assert.assertEquals(5, noOfSigmaRuleMatches); - - // try to do get detector as a user with read access - String userRead = "userRead"; - String[] backendRoles = { TEST_IT_BACKEND_ROLE }; - createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, backendRoles ); - RestClient userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - Response getResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + createdId, Collections.emptyMap(), null); - Map getResponseBody = asMap(getResponse); - Assert.assertEquals(createdId, getResponseBody.get("_id")); + List hits = executeSearch(Detector.DETECTORS_INDEX, request); + SearchHit hit = hits.get(0); + + String monitorId = ((List) ((Map) hit.getSourceAsMap().get("detector")).get("monitor_id")).get(0); + + indexDoc(index, "1", randomDoc()); + + Response executeResponse = executeAlertingMonitor(monitorId, Collections.emptyMap()); + Map executeResults = entityAsMap(executeResponse); + + int noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); + Assert.assertEquals(5, noOfSigmaRuleMatches); + + // try to do get detector as a user with read access + String userRead = "userRead"; + String[] backendRoles = { TEST_IT_BACKEND_ROLE }; + createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, backendRoles ); + RestClient userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + Response getResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + createdId, Collections.emptyMap(), null); + Map getResponseBody = asMap(getResponse); + Assert.assertEquals(createdId, getResponseBody.get("_id")); + + + // Enable backend filtering and try to read detector as a user with no backend roles matching the user who created the detector + enableOrDisableFilterBy("true"); + try { + getResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + createdId, Collections.emptyMap(), null); + } catch (ResponseException e) + { + assertEquals("Get detector failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); + } + finally { + userReadOnlyClient.close(); + deleteUser(userRead); + } + + // recreate user with matching backend roles and try again + String[] newBackendRoles = { TEST_HR_BACKEND_ROLE }; + createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, newBackendRoles ); + userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); + getResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + createdId, Collections.emptyMap(), null); + getResponseBody = asMap(getResponse); + Assert.assertEquals(createdId, getResponseBody.get("_id")); + //Search on id should give one result + String queryJson = "{ \"query\": { \"match\": { \"_id\" : \"" + createdId + "\"} } }"; + HttpEntity requestEntity = new NStringEntity(queryJson, ContentType.APPLICATION_JSON); + Response searchResponse = makeRequest(userReadOnlyClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + "_search", Collections.emptyMap(), requestEntity); + Map searchResponseBody = asMap(searchResponse); + Assert.assertNotNull("response is not null", searchResponseBody); + Map searchResponseHits = (Map) searchResponseBody.get("hits"); + Map searchResponseTotal = (Map) searchResponseHits.get("total"); + Assert.assertEquals(1, searchResponseTotal.get("value")); - // Enable backend filtering and try to read detector as a user with no backend roles matching the user who created the detector - enableOrDisableFilterBy("true"); - try { - getResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + createdId, Collections.emptyMap(), null); - } catch (ResponseException e) - { - assertEquals("Get detector failed", RestStatus.FORBIDDEN, restStatus(e.getResponse())); - } - finally { userReadOnlyClient.close(); deleteUser(userRead); + } finally { + tryDeletingRole(TEST_HR_ROLE); } - - // recreate user with matching backend roles and try again - String[] newBackendRoles = { TEST_HR_BACKEND_ROLE }; - createUserWithData( userRead, userRead, SECURITY_ANALYTICS_READ_ACCESS_ROLE, newBackendRoles ); - userReadOnlyClient = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userRead, userRead).setSocketTimeout(60000).build(); - getResponse = makeRequest(userReadOnlyClient, "GET", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + createdId, Collections.emptyMap(), null); - getResponseBody = asMap(getResponse); - Assert.assertEquals(createdId, getResponseBody.get("_id")); - - //Search on id should give one result - String queryJson = "{ \"query\": { \"match\": { \"_id\" : \"" + createdId + "\"} } }"; - HttpEntity requestEntity = new NStringEntity(queryJson, ContentType.APPLICATION_JSON); - Response searchResponse = makeRequest(userReadOnlyClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + "_search", Collections.emptyMap(), requestEntity); - Map searchResponseBody = asMap(searchResponse); - Assert.assertNotNull("response is not null", searchResponseBody); - Map searchResponseHits = (Map) searchResponseBody.get("hits"); - Map searchResponseTotal = (Map) searchResponseHits.get("total"); - Assert.assertEquals(1, searchResponseTotal.get("value")); - - userReadOnlyClient.close(); - deleteUser(userRead); } public void testCreateDetectorWithNoBackendRoles() throws IOException { @@ -204,4 +208,205 @@ public void testCreateDetectorWithNoBackendRoles() throws IOException { deleteUser(userFull); } } + + public void testCreateDetector_userHasIndexAccess_success() throws IOException { + String[] backendRoles = { TEST_IT_BACKEND_ROLE }; + String userWithAccess = "user1"; + String roleNameWithIndexPatternAccess = "test-role-1"; + String windowsIndexPattern = "windows*"; + createUserWithDataAndCustomRole(userWithAccess, userWithAccess, roleNameWithIndexPatternAccess, backendRoles, clusterPermissions, indexPermissions, List.of(windowsIndexPattern)); + RestClient clientWithAccess = null; + + try { + clientWithAccess = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userWithAccess, userWithAccess).setSocketTimeout(60000).build(); + String index = createTestIndex(client(), randomIndex(), windowsIndexMapping(), Settings.EMPTY); + + Request createMappingRequest = new Request("POST", SecurityAnalyticsPlugin.MAPPER_BASE_URI); + createMappingRequest.setJsonEntity( + "{ \"index_name\":\"" + index + "\"," + + " \"rule_topic\":\"" + randomDetectorType() + "\", " + + " \"partial\":true" + + "}" + ); + Response response = clientWithAccess.performRequest(createMappingRequest); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); + + Detector detector = randomDetector(getRandomPrePackagedRules()); + + Response createResponse = makeRequest(clientWithAccess, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); + assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); + + Map responseBody = asMap(createResponse); + + String createdId = responseBody.get("_id").toString(); + int createdVersion = Integer.parseInt(responseBody.get("_version").toString()); + + assertNotEquals("response is missing Id", Detector.NO_ID, createdId); + assertTrue("incorrect version", createdVersion > 0); + assertEquals("Incorrect Location header", String.format(Locale.getDefault(), "%s/%s", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, createdId), createResponse.getHeader("Location")); + assertFalse(((Map) responseBody.get("detector")).containsKey("rule_topic_index")); + assertFalse(((Map) responseBody.get("detector")).containsKey("findings_index")); + assertFalse(((Map) responseBody.get("detector")).containsKey("alert_index")); + } finally { + if (clientWithAccess != null) clientWithAccess.close(); + deleteUser(userWithAccess); + tryDeletingRole(roleNameWithIndexPatternAccess); + } + } + + public void testCreateDetector_userDoesntHaveIndexAccess_failure() throws IOException { + String[] backendRoles = { TEST_IT_BACKEND_ROLE }; + + String userWithoutAccess = "user"; + String roleNameWithoutIndexPatternAccess = "test-role"; + String testIndexPattern = "test*"; + createUserWithDataAndCustomRole(userWithoutAccess, userWithoutAccess, roleNameWithoutIndexPatternAccess, backendRoles, clusterPermissions, indexPermissions, List.of(testIndexPattern)); + RestClient clientWithoutAccess = null; + + try { + clientWithoutAccess = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userWithoutAccess, userWithoutAccess).setSocketTimeout(60000).build(); + + String index = createTestIndex(client(), randomIndex(), windowsIndexMapping(), Settings.EMPTY); + + // 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 = userClient.performRequest(createMappingRequest); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); + + Detector detector = randomDetector(getRandomPrePackagedRules()); + + try { + makeRequest(clientWithoutAccess, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); + } catch (ResponseException e) { + assertEquals("Create detector error status", RestStatus.FORBIDDEN, restStatus(e.getResponse())); + } + } finally { + if (clientWithoutAccess!= null) clientWithoutAccess.close(); + deleteUser(userWithoutAccess); + tryDeletingRole(roleNameWithoutIndexPatternAccess); + } + } + + public void testUpdateDetector_userHasIndexAccess_success() throws IOException { + String[] backendRoles = { TEST_IT_BACKEND_ROLE }; + + String userWithAccess = "user1"; + String roleNameWithIndexPatternAccess = "test-role-1"; + String windowsIndexPattern = "windows*"; + createUserWithDataAndCustomRole(userWithAccess, userWithAccess, roleNameWithIndexPatternAccess, backendRoles, clusterPermissions, indexPermissions, List.of(windowsIndexPattern)); + RestClient clientWithAccess = null; + try { + clientWithAccess = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userWithAccess, userWithAccess).setSocketTimeout(60000).build(); + //createUserRolesMapping("alerting_full_access", users); + String index = createTestIndex(client(), randomIndex(), windowsIndexMapping(), Settings.EMPTY); + + // 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 = clientWithAccess.performRequest(createMappingRequest); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); + + Detector detector = randomDetector(getRandomPrePackagedRules()); + + Response createResponse = makeRequest(clientWithAccess, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); + assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); + + Map responseBody = asMap(createResponse); + + String createdId = responseBody.get("_id").toString(); + int createdVersion = Integer.parseInt(responseBody.get("_version").toString()); + + assertNotEquals("response is missing Id", Detector.NO_ID, createdId); + assertTrue("incorrect version", createdVersion > 0); + assertEquals("Incorrect Location header", String.format(Locale.getDefault(), "%s/%s", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, createdId), createResponse.getHeader("Location")); + assertFalse(((Map) responseBody.get("detector")).containsKey("rule_topic_index")); + assertFalse(((Map) responseBody.get("detector")).containsKey("findings_index")); + assertFalse(((Map) responseBody.get("detector")).containsKey("alert_index")); + + String detectorId = responseBody.get("_id").toString(); + Response updateResponse = makeRequest(clientWithAccess, "PUT", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + detectorId, Collections.emptyMap(), toHttpEntity(detector)); + assertEquals("Update detector failed", RestStatus.OK, restStatus(updateResponse)); + } finally { + if (clientWithAccess != null) clientWithAccess.close(); + deleteUser(userWithAccess); + tryDeletingRole(roleNameWithIndexPatternAccess); + } + } + + public void testUpdateDetector_userDoesntHaveIndexAccess_failure() throws IOException { + String[] backendRoles = { TEST_IT_BACKEND_ROLE }; + + String userWithoutAccess = "user"; + String roleNameWithoutIndexPatternAccess = "test-role"; + String testIndexPattern = "test*"; + createUserWithDataAndCustomRole(userWithoutAccess, userWithoutAccess, roleNameWithoutIndexPatternAccess, backendRoles, clusterPermissions, indexPermissions, List.of(testIndexPattern)); + RestClient clientWithoutAccess = null; + + try { + clientWithoutAccess = new SecureRestClientBuilder(getClusterHosts().toArray(new HttpHost[]{}), isHttps(), userWithoutAccess, userWithoutAccess).setSocketTimeout(60000).build(); + + //createUserRolesMapping("alerting_full_access", users); + String index = createTestIndex(client(), randomIndex(), windowsIndexMapping(), Settings.EMPTY); + // Assign a role to the index + createIndexRole(TEST_HR_ROLE, Collections.emptyList(), indexPermissions, List.of(index)); + String[] users = {user}; + // Assign a role to existing user + createUserRolesMapping(TEST_HR_ROLE, users); + + // 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 = userClient.performRequest(createMappingRequest); + assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); + + Detector detector = randomDetector(getRandomPrePackagedRules()); + + Response createResponse = makeRequest(userClient, "POST", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, Collections.emptyMap(), toHttpEntity(detector)); + assertEquals("Create detector failed", RestStatus.CREATED, restStatus(createResponse)); + + Map responseBody = asMap(createResponse); + + String createdId = responseBody.get("_id").toString(); + int createdVersion = Integer.parseInt(responseBody.get("_version").toString()); + + assertNotEquals("response is missing Id", Detector.NO_ID, createdId); + assertTrue("incorrect version", createdVersion > 0); + assertEquals("Incorrect Location header", String.format(Locale.getDefault(), "%s/%s", SecurityAnalyticsPlugin.DETECTOR_BASE_URI, createdId), createResponse.getHeader("Location")); + assertFalse(((Map) responseBody.get("detector")).containsKey("rule_topic_index")); + assertFalse(((Map) responseBody.get("detector")).containsKey("findings_index")); + assertFalse(((Map) responseBody.get("detector")).containsKey("alert_index")); + + String detectorId = responseBody.get("_id").toString(); + + try { + makeRequest(clientWithoutAccess, "PUT", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + detectorId, Collections.emptyMap(), toHttpEntity(detector)); + } catch (ResponseException e) { + assertEquals("Update detector error status", RestStatus.FORBIDDEN, restStatus(e.getResponse())); + } + } finally { + if (clientWithoutAccess != null) clientWithoutAccess.close(); + deleteUser(userWithoutAccess); + tryDeletingRole(roleNameWithoutIndexPatternAccess); + tryDeletingRole(TEST_HR_ROLE); + } + } } \ No newline at end of file