From 80a5bf11ed146051c36687b243b9a25764e453ee Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 23 Jul 2026 16:51:01 -0700 Subject: [PATCH 1/2] Register detector and correlation-rule indices as system indices Onboards .opensearch-sap-detectors-config and .opensearch-sap- correlation-rules-config as OpenSearch system indices. This is a prerequisite for onboarding security-analytics to the centralized resource-sharing framework, which requires resource indices to be declared as system indices. Registering these as system indices means all internal access must run under a stashed (system) thread context when the security plugin is enabled. Fixes the access sites that were not already stashing: - TransportIndexCorrelationRuleAction: stash at start of async flow (covers create-index, mapping-update, and write) - TransportDeleteCorrelationRuleAction: stash around delete-by-query - TransportDeleteRuleAction: stash around the detectors index search - DetectorUtils.getAllDetectorInputs: stash around the detectors search Reuses the existing StashedThreadContext.run() helper. Detector CRUD transport actions already stashed context, so they are unaffected. Signed-off-by: Darshit Chanpura --- .../securityanalytics/SecurityAnalyticsPlugin.java | 5 ++++- .../transport/TransportDeleteCorrelationRuleAction.java | 8 ++++++-- .../transport/TransportDeleteRuleAction.java | 7 +++++-- .../transport/TransportIndexCorrelationRuleAction.java | 3 +++ .../opensearch/securityanalytics/util/DetectorUtils.java | 7 +++++-- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java b/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java index c818beb0a..946e66c90 100644 --- a/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java +++ b/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java @@ -92,6 +92,7 @@ import org.opensearch.securityanalytics.mapper.IndexTemplateManager; import org.opensearch.securityanalytics.mapper.MapperService; import org.opensearch.securityanalytics.model.CustomLogType; +import org.opensearch.securityanalytics.model.CorrelationRule; import org.opensearch.securityanalytics.model.Detector; import org.opensearch.securityanalytics.model.DetectorInput; import org.opensearch.securityanalytics.model.Rule; @@ -293,7 +294,9 @@ public class SecurityAnalyticsPlugin extends Plugin implements ActionPlugin, Map public Collection getSystemIndexDescriptors(Settings settings) { List descriptors = List.of( new SystemIndexDescriptor(THREAT_INTEL_DATA_INDEX_NAME_PREFIX, "System index used for threat intel data"), - new SystemIndexDescriptor(CORRELATION_ALERT_INDEX, "System index used for Correlation Alerts") + new SystemIndexDescriptor(CORRELATION_ALERT_INDEX, "System index used for Correlation Alerts"), + new SystemIndexDescriptor(Detector.DETECTORS_INDEX, "System index used for detectors"), + new SystemIndexDescriptor(CorrelationRule.CORRELATION_RULE_INDEX, "System index used for correlation rules") ); return descriptors; } diff --git a/src/main/java/org/opensearch/securityanalytics/transport/TransportDeleteCorrelationRuleAction.java b/src/main/java/org/opensearch/securityanalytics/transport/TransportDeleteCorrelationRuleAction.java index ed3bbd04e..7f675ff3c 100644 --- a/src/main/java/org/opensearch/securityanalytics/transport/TransportDeleteCorrelationRuleAction.java +++ b/src/main/java/org/opensearch/securityanalytics/transport/TransportDeleteCorrelationRuleAction.java @@ -28,6 +28,7 @@ import org.opensearch.securityanalytics.action.DeleteCorrelationRuleRequest; import org.opensearch.securityanalytics.correlation.alert.CorrelationAlertService; import org.opensearch.securityanalytics.model.CorrelationRule; +import org.opensearch.securityanalytics.threatIntel.common.StashedThreadContext; import org.opensearch.securityanalytics.util.SecurityAnalyticsException; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; @@ -60,7 +61,10 @@ protected void doExecute(Task task, DeleteCorrelationRuleRequest request, Action WriteRequest.RefreshPolicy refreshPolicy = request.getRefreshPolicy(); log.debug("Deleting Correlation Rule with id: " + correlationRuleId); - new DeleteByQueryRequestBuilder(client, DeleteByQueryAction.INSTANCE) + // Correlation rule index is a system index; stash the thread context so the plugin + // can delete from it when the security plugin is enabled. + StashedThreadContext.run(client, () -> + new DeleteByQueryRequestBuilder(client, DeleteByQueryAction.INSTANCE) .source(CorrelationRule.CORRELATION_RULE_INDEX) .filter(QueryBuilders.matchQuery("_id", correlationRuleId)) .refresh(true) @@ -89,6 +93,6 @@ public void onResponse(BulkByScrollResponse response) { public void onFailure(Exception e) { listener.onFailure(SecurityAnalyticsException.wrap(e)); } - }); + })); } } diff --git a/src/main/java/org/opensearch/securityanalytics/transport/TransportDeleteRuleAction.java b/src/main/java/org/opensearch/securityanalytics/transport/TransportDeleteRuleAction.java index be54b7e4b..1ed9f2bdd 100644 --- a/src/main/java/org/opensearch/securityanalytics/transport/TransportDeleteRuleAction.java +++ b/src/main/java/org/opensearch/securityanalytics/transport/TransportDeleteRuleAction.java @@ -42,6 +42,7 @@ import org.opensearch.securityanalytics.model.DetectorRule; import org.opensearch.securityanalytics.model.Rule; import org.opensearch.securityanalytics.util.DetectorIndices; +import org.opensearch.securityanalytics.threatIntel.common.StashedThreadContext; import org.opensearch.securityanalytics.util.SecurityAnalyticsException; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; @@ -148,7 +149,9 @@ private void onGetResponse(Rule rule) { .size(10000)) .preference(Preference.PRIMARY_FIRST.type()); - client.search(searchRequest, new ActionListener<>() { + // Detectors index is a system index; stash the thread context so the plugin + // can search it when the security plugin is enabled. + StashedThreadContext.run(client, () -> client.search(searchRequest, new ActionListener<>() { @Override public void onResponse(SearchResponse response) { if (response.isTimedOut()) { @@ -190,7 +193,7 @@ public void onResponse(SearchResponse response) { public void onFailure(Exception e) { onFailures(e); } - }); + })); } else { deleteRule(rule.getId()); } diff --git a/src/main/java/org/opensearch/securityanalytics/transport/TransportIndexCorrelationRuleAction.java b/src/main/java/org/opensearch/securityanalytics/transport/TransportIndexCorrelationRuleAction.java index a2e57e497..73f993be1 100644 --- a/src/main/java/org/opensearch/securityanalytics/transport/TransportIndexCorrelationRuleAction.java +++ b/src/main/java/org/opensearch/securityanalytics/transport/TransportIndexCorrelationRuleAction.java @@ -80,6 +80,9 @@ class AsyncIndexCorrelationRuleAction { } void start() { + // Correlation rule index is a system index; stash the thread context so the + // plugin can create/update/write to it when the security plugin is enabled. + client.threadPool().getThreadContext().stashContext(); try { if (!correlationRuleIndices.correlationRuleIndexExists()) { try { diff --git a/src/main/java/org/opensearch/securityanalytics/util/DetectorUtils.java b/src/main/java/org/opensearch/securityanalytics/util/DetectorUtils.java index 3ea3db2d8..a47f7d597 100644 --- a/src/main/java/org/opensearch/securityanalytics/util/DetectorUtils.java +++ b/src/main/java/org/opensearch/securityanalytics/util/DetectorUtils.java @@ -28,6 +28,7 @@ import org.opensearch.securityanalytics.model.Detector; import org.opensearch.securityanalytics.model.DetectorInput; import org.opensearch.securityanalytics.model.Rule; +import org.opensearch.securityanalytics.threatIntel.common.StashedThreadContext; import org.opensearch.transport.client.Client; import java.io.IOException; @@ -80,7 +81,9 @@ public static void getAllDetectorInputs(Client client, NamedXContentRegistry xCo searchRequest.indices(Detector.DETECTORS_INDEX); searchRequest.preference(Preference.PRIMARY_FIRST.type()); - client.search(searchRequest, new ActionListener<>() { + // Detectors index is a system index; stash the thread context so the plugin + // can search it when the security plugin is enabled. + StashedThreadContext.run(client, () -> client.search(searchRequest, new ActionListener<>() { @Override public void onResponse(SearchResponse response) { Set allDetectorIndices = new HashSet<>(); @@ -101,7 +104,7 @@ public void onResponse(SearchResponse response) { public void onFailure(Exception e) { actionListener.onFailure(e); } - }); + })); } public static List getBucketLevelMonitorIds( From 8b15a7865688853e690b14c48fb0836f5397f2b8 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Thu, 23 Jul 2026 18:22:09 -0700 Subject: [PATCH 2/2] Route system-index searches in tests through the super-admin client Now that the detector and correlation-rule config indices are system indices, searching them by concrete name requires the super-admin (adminDN cert) client. The test framework's client() is a regular admin/admin user, whose system-index search results are scoped out (returns empty rather than erroring), causing ArrayIndexOutOfBounds in tests that read the detectors index directly. - executeSearch now uses adminClient() when security is enabled (covers 15 test files that search the detector/correlation indices) - Replace 5 hard-coded ".opensearch-sap-detectors-config/_search" calls in DetectorRestApiIT with adminClient() Signed-off-by: Darshit Chanpura --- .../SecurityAnalyticsRestTestCase.java | 6 +++++- .../resthandler/DetectorRestApiIT.java | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java b/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java index a83c6a094..898415dd4 100644 --- a/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java +++ b/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java @@ -454,7 +454,11 @@ protected List executeSearch(String index, String request, Boolean re refreshIndex(index); } - Response response = makeRequest(client(), "GET", String.format(Locale.getDefault(), "%s/_search", index), Map.of("preference", "_primary"), new StringEntity(request), new BasicHeader("Content-Type", "application/json")); + // Use the super-admin (adminDN cert) client so searches resolve against system indices + // such as the detector and correlation-rule config indices. A regular user client is + // scoped out of system-index search results. + RestClient searchClient = securityEnabled() ? adminClient() : client(); + Response response = makeRequest(searchClient, "GET", String.format(Locale.getDefault(), "%s/_search", index), Map.of("preference", "_primary"), new StringEntity(request), new BasicHeader("Content-Type", "application/json")); Assert.assertEquals("Search failed", RestStatus.OK, restStatus(response)); SearchResponse searchResponse = SearchResponse.fromXContent(createParser(JsonXContent.jsonXContent, response.getEntity().getContent())); diff --git a/src/test/java/org/opensearch/securityanalytics/resthandler/DetectorRestApiIT.java b/src/test/java/org/opensearch/securityanalytics/resthandler/DetectorRestApiIT.java index 8f9f48c38..f72cb0cfe 100644 --- a/src/test/java/org/opensearch/securityanalytics/resthandler/DetectorRestApiIT.java +++ b/src/test/java/org/opensearch/securityanalytics/resthandler/DetectorRestApiIT.java @@ -1292,7 +1292,7 @@ public void testDeletingADetector_oneDetectorType_multiple_ruleTopicIndex() thro List.of(index1) ); String detectorId1 = createDetector(detector1); - Response response = makeRequest(client(), "POST", ".opensearch-sap-detectors-config/_search", Map.of(), + Response response = makeRequest(adminClient(), "POST", ".opensearch-sap-detectors-config/_search", Map.of(), new StringEntity("{\"query\": {\"match\": {\"_id\": \"" + detectorId1 + "\"}}}"), new BasicHeader("Content-Type", "application/json")); String ruleTopicIndex1 = ((Map) ((Map) ((List>) ((Map) responseAsMap(response).get("hits")) .get("hits")).get(0).get("_source")).get("detector")).get("rule_topic_index").toString() + "-000001"; @@ -1305,7 +1305,7 @@ public void testDeletingADetector_oneDetectorType_multiple_ruleTopicIndex() thro ); String detectorId2 = createDetector(detector2); - response = makeRequest(client(), "POST", ".opensearch-sap-detectors-config/_search", Map.of(), + response = makeRequest(adminClient(), "POST", ".opensearch-sap-detectors-config/_search", Map.of(), new StringEntity("{\"query\": {\"match\": {\"_id\": \"" + detectorId2 + "\"}}}"), new BasicHeader("Content-Type", "application/json")); String ruleTopicIndex2 = ((Map) ((Map) ((List>) ((Map) responseAsMap(response).get("hits")) .get("hits")).get(0).get("_source")).get("detector")).get("rule_topic_index").toString() + "-000001"; @@ -1820,7 +1820,7 @@ public void testCreatingDetectorWithDynamicQueryIndexDisabledAndThenEnabledToUpd noOfSigmaRuleMatches = ((List>) ((Map) executeResults.get("input_results")).get("results")).get(0).size(); Assert.assertEquals(5, noOfSigmaRuleMatches); - response = makeRequest(client(), "POST", ".opensearch-sap-detectors-config/_search", Map.of(), + response = makeRequest(adminClient(), "POST", ".opensearch-sap-detectors-config/_search", Map.of(), new StringEntity("{\"query\": {\"match\": {\"_id\": \"" + detectorId1 + "\"}}}"), new BasicHeader("Content-Type", "application/json")); String ruleTopicIndex1 = ((Map) ((Map) ((List>) ((Map) responseAsMap(response).get("hits")) .get("hits")).get(0).get("_source")).get("detector")).get("rule_topic_index").toString() + "-000001"; @@ -1853,7 +1853,7 @@ public void testCreatingDetectorWithDynamicQueryIndexEnabledAndThenDisabled() th String detectorId1 = responseBody.get("_id").toString(); - response = makeRequest(client(), "POST", ".opensearch-sap-detectors-config/_search", Map.of(), + response = makeRequest(adminClient(), "POST", ".opensearch-sap-detectors-config/_search", Map.of(), new StringEntity("{\"query\": {\"match\": {\"_id\": \"" + detectorId1 + "\"}}}"), new BasicHeader("Content-Type", "application/json")); String ruleTopicIndex1 = ((Map) ((Map) ((List>) ((Map) responseAsMap(response).get("hits")) .get("hits")).get(0).get("_source")).get("detector")).get("rule_topic_index").toString() + "-000001"; @@ -1865,7 +1865,7 @@ public void testCreatingDetectorWithDynamicQueryIndexEnabledAndThenDisabled() th Response updateResponse = makeRequest(client(), "PUT", SecurityAnalyticsPlugin.DETECTOR_BASE_URI + "/" + detectorId1, Collections.emptyMap(), toHttpEntity(detector)); Assert.assertEquals("Update detector failed", RestStatus.OK, restStatus(updateResponse)); - response = makeRequest(client(), "POST", ".opensearch-sap-detectors-config/_search", Map.of(), + response = makeRequest(adminClient(), "POST", ".opensearch-sap-detectors-config/_search", Map.of(), new StringEntity("{\"query\": {\"match\": {\"_id\": \"" + detectorId1 + "\"}}}"), new BasicHeader("Content-Type", "application/json")); ruleTopicIndex1 = ((Map) ((Map) ((List>) ((Map) responseAsMap(response).get("hits")) .get("hits")).get(0).get("_source")).get("detector")).get("rule_topic_index").toString() + "-000001";