From 2a4a3fda69b1c9be0da80216a67ae18b319ff0f5 Mon Sep 17 00:00:00 2001 From: Subhobrata Dey Date: Mon, 17 Jun 2024 03:22:15 +0000 Subject: [PATCH] add search ioc findings api Signed-off-by: Subhobrata Dey --- .../SecurityAnalyticsPlugin.java | 7 +- .../{IocMatch.java => IocFinding.java} | 16 +- .../action/GetIocFindingsAction.java | 17 +++ .../action/GetIocFindingsRequest.java | 81 ++++++++++ .../action/GetIocFindingsResponse.java | 62 ++++++++ ...tchService.java => IocFindingService.java} | 73 +++++++-- .../iocscan/service/IoCScanService.java | 8 +- .../service/IoCScanServiceInterface.java | 3 - .../resthandler/RestGetIocFindingsAction.java | 97 ++++++++++++ .../TransportGetIocFindingsAction.java | 143 ++++++++++++++++++ .../SecurityAnalyticsRestTestCase.java | 10 ++ .../securityanalytics/TestHelpers.java | 8 +- ...ocMatchTests.java => IocFindingTests.java} | 58 +++---- ...erviceIT.java => IocFindingServiceIT.java} | 23 ++- .../dao/IocFindingServiceRestApiIT.java | 54 +++++++ 15 files changed, 585 insertions(+), 75 deletions(-) rename src/main/java/org/opensearch/securityanalytics/model/threatintel/{IocMatch.java => IocFinding.java} (92%) create mode 100644 src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsAction.java create mode 100644 src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsRequest.java create mode 100644 src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsResponse.java rename src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/{IocMatchService.java => IocFindingService.java} (66%) create mode 100644 src/main/java/org/opensearch/securityanalytics/threatIntel/resthandler/RestGetIocFindingsAction.java create mode 100644 src/main/java/org/opensearch/securityanalytics/threatIntel/transport/TransportGetIocFindingsAction.java rename src/test/java/org/opensearch/securityanalytics/model/{IocMatchTests.java => IocFindingTests.java} (50%) rename src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/{IocMatchServiceIT.java => IocFindingServiceIT.java} (71%) create mode 100644 src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocFindingServiceRestApiIT.java diff --git a/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java b/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java index 6aa798bb4..35a446b28 100644 --- a/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java +++ b/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java @@ -115,6 +115,7 @@ import org.opensearch.securityanalytics.resthandler.RestValidateRulesAction; import org.opensearch.securityanalytics.services.STIX2IOCFetchService; import org.opensearch.securityanalytics.settings.SecurityAnalyticsSettings; +import org.opensearch.securityanalytics.threatIntel.action.GetIocFindingsAction; import org.opensearch.securityanalytics.threatIntel.action.PutTIFJobAction; import org.opensearch.securityanalytics.threatIntel.action.SADeleteTIFSourceConfigAction; import org.opensearch.securityanalytics.threatIntel.action.SAGetTIFSourceConfigAction; @@ -133,6 +134,7 @@ import org.opensearch.securityanalytics.threatIntel.model.monitor.ThreatIntelMonitorRunner; import org.opensearch.securityanalytics.threatIntel.model.monitor.TransportThreatIntelMonitorFanOutAction; import org.opensearch.securityanalytics.threatIntel.resthandler.RestDeleteTIFSourceConfigAction; +import org.opensearch.securityanalytics.threatIntel.resthandler.RestGetIocFindingsAction; import org.opensearch.securityanalytics.threatIntel.resthandler.RestGetTIFSourceConfigAction; import org.opensearch.securityanalytics.threatIntel.resthandler.RestIndexTIFSourceConfigAction; import org.opensearch.securityanalytics.threatIntel.resthandler.RestRefreshTIFSourceConfigAction; @@ -147,6 +149,7 @@ import org.opensearch.securityanalytics.threatIntel.service.TIFJobUpdateService; import org.opensearch.securityanalytics.threatIntel.service.ThreatIntelFeedDataService; import org.opensearch.securityanalytics.threatIntel.transport.TransportDeleteTIFSourceConfigAction; +import org.opensearch.securityanalytics.threatIntel.transport.TransportGetIocFindingsAction; import org.opensearch.securityanalytics.threatIntel.transport.TransportGetTIFSourceConfigAction; import org.opensearch.securityanalytics.threatIntel.transport.TransportIndexTIFSourceConfigAction; import org.opensearch.securityanalytics.threatIntel.transport.TransportPutTIFJobAction; @@ -346,6 +349,7 @@ public List getRestHandlers(Settings settings, new RestDeleteThreatIntelMonitorAction(), new RestSearchThreatIntelMonitorAction(), new RestRefreshTIFSourceConfigAction(), + new RestGetIocFindingsAction(), new RestListIOCsAction() ); } @@ -491,7 +495,8 @@ public List> getSettings() { new ActionHandler<>(SASearchTIFSourceConfigsAction.INSTANCE, TransportSearchTIFSourceConfigsAction.class), new ActionHandler<>(SARefreshTIFSourceConfigAction.INSTANCE, TransportRefreshTIFSourceConfigAction.class), new ActionHandler<>(ThreatIntelMonitorRunner.REMOTE_DOC_LEVEL_MONITOR_ACTION_INSTANCE, TransportThreatIntelMonitorFanOutAction.class), - new ActionHandler<>(ListIOCsAction.INSTANCE, TransportListIOCsAction.class) + new ActionHandler<>(ListIOCsAction.INSTANCE, TransportListIOCsAction.class), + new ActionHandler<>(GetIocFindingsAction.INSTANCE, TransportGetIocFindingsAction.class) ); } diff --git a/src/main/java/org/opensearch/securityanalytics/model/threatintel/IocMatch.java b/src/main/java/org/opensearch/securityanalytics/model/threatintel/IocFinding.java similarity index 92% rename from src/main/java/org/opensearch/securityanalytics/model/threatintel/IocMatch.java rename to src/main/java/org/opensearch/securityanalytics/model/threatintel/IocFinding.java index 037541741..43ecd53ad 100644 --- a/src/main/java/org/opensearch/securityanalytics/model/threatintel/IocMatch.java +++ b/src/main/java/org/opensearch/securityanalytics/model/threatintel/IocFinding.java @@ -20,7 +20,7 @@ * IoC Match provides mapping of the IoC Value to the list of docs that contain the ioc in a given execution of IoC_Scan_job * It's the inverse of an IoC finding which maps a document to list of IoC's */ -public class IocMatch implements Writeable, ToXContent { +public class IocFinding implements Writeable, ToXContent { //TODO implement IoC_Match interface from security-analytics-commons public static final String ID_FIELD = "id"; public static final String RELATED_DOC_IDS_FIELD = "related_doc_ids"; @@ -42,8 +42,8 @@ public class IocMatch implements Writeable, ToXContent { private final Instant timestamp; private final String executionId; - public IocMatch(String id, List relatedDocIds, List feedIds, String iocScanJobId, - String iocScanJobName, String iocValue, String iocType, Instant timestamp, String executionId) { + public IocFinding(String id, List relatedDocIds, List feedIds, String iocScanJobId, + String iocScanJobName, String iocValue, String iocType, Instant timestamp, String executionId) { validateIoCMatch(id, iocScanJobId, iocScanJobName, iocValue, timestamp, executionId, relatedDocIds); this.id = id; this.relatedDocIds = relatedDocIds; @@ -56,7 +56,7 @@ public IocMatch(String id, List relatedDocIds, List feedIds, Str this.executionId = executionId; } - public IocMatch(StreamInput in) throws IOException { + public IocFinding(StreamInput in) throws IOException { id = in.readString(); relatedDocIds = in.readStringList(); feedIds = in.readStringList(); @@ -133,7 +133,7 @@ public String getExecutionId() { return executionId; } - public static IocMatch parse(XContentParser xcp) throws IOException { + public static IocFinding parse(XContentParser xcp) throws IOException { String id = null; List relatedDocIds = new ArrayList<>(); List feedIds = new ArrayList<>(); @@ -197,11 +197,11 @@ public static IocMatch parse(XContentParser xcp) throws IOException { } } - return new IocMatch(id, relatedDocIds, feedIds, iocScanJobId, iocScanName, iocValue, iocType, timestamp, executionId); + return new IocFinding(id, relatedDocIds, feedIds, iocScanJobId, iocScanName, iocValue, iocType, timestamp, executionId); } - public static IocMatch readFrom(StreamInput in) throws IOException { - return new IocMatch(in); + public static IocFinding readFrom(StreamInput in) throws IOException { + return new IocFinding(in); } diff --git a/src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsAction.java b/src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsAction.java new file mode 100644 index 000000000..f92f3350e --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsAction.java @@ -0,0 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.threatIntel.action; + +import org.opensearch.action.ActionType; + +public class GetIocFindingsAction extends ActionType { + + public static final GetIocFindingsAction INSTANCE = new GetIocFindingsAction(); + public static final String NAME = "cluster:admin/opensearch/securityanalytics/ioc/findings/get"; + + public GetIocFindingsAction() { + super(NAME, GetIocFindingsResponse::new); + } +} \ No newline at end of file diff --git a/src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsRequest.java b/src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsRequest.java new file mode 100644 index 000000000..a33b69401 --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsRequest.java @@ -0,0 +1,81 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.threatIntel.action; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.action.ValidateActions; +import org.opensearch.commons.alerting.model.Table; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; + +import java.io.IOException; +import java.time.Instant; +import java.util.List; +import java.util.Locale; + +public class GetIocFindingsRequest extends ActionRequest { + + private List findingIds; + + private Instant startTime; + + private Instant endTime; + + private Table table; + + public GetIocFindingsRequest(StreamInput sin) throws IOException { + this( + sin.readOptionalStringList(), + sin.readOptionalInstant(), + sin.readOptionalInstant(), + Table.readFrom(sin) + ); + } + + public GetIocFindingsRequest(List findingIds, + Instant startTime, + Instant endTime, + Table table) { + this.findingIds = findingIds; + this.startTime = startTime; + this.endTime = endTime; + this.table = table; + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = null; + if (startTime != null && endTime != null && startTime.isAfter(endTime)) { + validationException = ValidateActions.addValidationError(String.format(Locale.getDefault(), + "startTime should be less than endTime"), validationException); + } + return validationException; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeOptionalStringCollection(findingIds); + out.writeOptionalInstant(startTime); + out.writeOptionalInstant(endTime); + table.writeTo(out); + } + + public List getFindingIds() { + return findingIds; + } + + public Instant getStartTime() { + return startTime; + } + + public Instant getEndTime() { + return endTime; + } + + public Table getTable() { + return table; + } +} \ No newline at end of file diff --git a/src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsResponse.java b/src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsResponse.java new file mode 100644 index 000000000..50ae08dd4 --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/threatIntel/action/GetIocFindingsResponse.java @@ -0,0 +1,62 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.threatIntel.action; + +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.securityanalytics.model.threatintel.IocFinding; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +public class GetIocFindingsResponse extends ActionResponse implements ToXContentObject { + + private static final String TOTAL_IOC_FINDINGS_FIELD = "total_findings"; + + private static final String IOC_FINDINGS_FIELD = "ioc_findings"; + + private Integer totalFindings; + + private List iocFindings; + + public GetIocFindingsResponse(Integer totalFindings, List iocFindings) { + super(); + this.totalFindings = totalFindings; + this.iocFindings = iocFindings; + } + + public GetIocFindingsResponse(StreamInput sin) throws IOException { + this( + sin.readInt(), + Collections.unmodifiableList(sin.readList(IocFinding::new)) + ); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeInt(totalFindings); + out.writeCollection(iocFindings); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject() + .field(TOTAL_IOC_FINDINGS_FIELD, totalFindings) + .field(IOC_FINDINGS_FIELD, iocFindings); + return builder.endObject(); + } + + public Integer getTotalFindings() { + return totalFindings; + } + + public List getIocFindings() { + return iocFindings; + } +} \ No newline at end of file diff --git a/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocMatchService.java b/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocFindingService.java similarity index 66% rename from src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocMatchService.java rename to src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocFindingService.java index 9bf92a985..94f2ef540 100644 --- a/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocMatchService.java +++ b/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocFindingService.java @@ -7,21 +7,31 @@ import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.bulk.BulkRequest; import org.opensearch.action.bulk.BulkResponse; +import org.opensearch.action.get.MultiGetRequest; import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.search.SearchRequest; +import org.opensearch.action.search.SearchResponse; import org.opensearch.action.support.GroupedActionListener; import org.opensearch.action.support.WriteRequest; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.xcontent.LoggingDeprecationHandler; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.action.ActionListener; import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.core.xcontent.XContentParserUtils; +import org.opensearch.index.IndexNotFoundException; +import org.opensearch.search.SearchHit; +import org.opensearch.search.builder.SearchSourceBuilder; import org.opensearch.securityanalytics.SecurityAnalyticsPlugin; -import org.opensearch.securityanalytics.model.threatintel.IocMatch; +import org.opensearch.securityanalytics.model.threatintel.IocFinding; import org.opensearch.securityanalytics.settings.SecurityAnalyticsSettings; -import org.opensearch.securityanalytics.threatIntel.common.StashedThreadContext; +import org.opensearch.securityanalytics.threatIntel.action.GetIocFindingsResponse; import org.opensearch.securityanalytics.util.SecurityAnalyticsException; -import org.opensearch.threadpool.ThreadPool; import java.io.BufferedReader; import java.io.IOException; @@ -35,19 +45,22 @@ /** * Data layer to perform CRUD operations for threat intel ioc match : store in system index. */ -public class IocMatchService { +public class IocFindingService { //TODO manage index rollover public static final String INDEX_NAME = ".opensearch-sap-iocmatch"; - private static final Logger log = LogManager.getLogger(IocMatchService.class); + private static final Logger log = LogManager.getLogger(IocFindingService.class); private final Client client; private final ClusterService clusterService; - public IocMatchService(final Client client, final ClusterService clusterService) { + private final NamedXContentRegistry xContentRegistry; + + public IocFindingService(final Client client, final ClusterService clusterService, final NamedXContentRegistry xContentRegistry) { this.client = client; this.clusterService = clusterService; + this.xContentRegistry = xContentRegistry; } - public void indexIocMatches(List iocMatches, + public void indexIocMatches(List iocFindings, final ActionListener actionListener) { try { Integer batchSize = this.clusterService.getClusterSettings().get(SecurityAnalyticsSettings.BATCH_SIZE); @@ -55,16 +68,16 @@ public void indexIocMatches(List iocMatches, r -> { List bulkRequestList = new ArrayList<>(); BulkRequest bulkRequest = new BulkRequest(INDEX_NAME); - for (int i = 0; i < iocMatches.size(); i++) { - IocMatch iocMatch = iocMatches.get(i); + for (int i = 0; i < iocFindings.size(); i++) { + IocFinding iocFinding = iocFindings.get(i); try { IndexRequest indexRequest = new IndexRequest(INDEX_NAME) - .source(iocMatch.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .source(iocFinding.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) .opType(DocWriteRequest.OpType.CREATE); bulkRequest.add(indexRequest); if ( bulkRequest.requests().size() == batchSize - && i != iocMatches.size() - 1 // final bulk request will be added outside for loop with refresh policy none + && i != iocFindings.size() - 1 // final bulk request will be added outside for loop with refresh policy none ) { bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.NONE); bulkRequestList.add(bulkRequest); @@ -107,7 +120,7 @@ public void indexIocMatches(List iocMatches, private String getIndexMapping() { try { - try (InputStream is = IocMatchService.class.getResourceAsStream("/mappings/ioc_match_mapping.json")) { + try (InputStream is = IocFindingService.class.getResourceAsStream("/mappings/ioc_match_mapping.json")) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { return reader.lines().map(String::trim).collect(Collectors.joining()); } @@ -152,4 +165,40 @@ public void createIndexIfNotExists(final ActionListener listener) { listener.onFailure(e); } } + + public void searchIocMatches(SearchSourceBuilder searchSourceBuilder, final ActionListener actionListener) { + SearchRequest searchRequest = new SearchRequest() + .source(searchSourceBuilder) + .indices(INDEX_NAME); + + client.search(searchRequest, new ActionListener<>() { + @Override + public void onResponse(SearchResponse searchResponse) { + try { + long totalIocFindingsCount = searchResponse.getHits().getTotalHits().value; + List iocFindings = new ArrayList<>(); + + for (SearchHit hit: searchResponse.getHits()) { + XContentParser xcp = XContentType.JSON.xContent() + .createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, hit.getSourceAsString()); + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.nextToken(), xcp); + IocFinding iocFinding = IocFinding.parse(xcp); + iocFindings.add(iocFinding); + } + actionListener.onResponse(new GetIocFindingsResponse((int) totalIocFindingsCount, iocFindings)); + } catch (Exception ex) { + this.onFailure(ex); + } + } + + @Override + public void onFailure(Exception e) { + if (e instanceof IndexNotFoundException) { + actionListener.onResponse(new GetIocFindingsResponse(0, List.of())); + return; + } + actionListener.onFailure(e); + } + }); + } } \ No newline at end of file diff --git a/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/service/IoCScanService.java b/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/service/IoCScanService.java index 1c9d9bb4e..ce62fb92a 100644 --- a/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/service/IoCScanService.java +++ b/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/service/IoCScanService.java @@ -5,7 +5,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage; import org.opensearch.commons.alerting.model.Monitor; import org.opensearch.securityanalytics.model.STIX2IOC; -import org.opensearch.securityanalytics.model.threatintel.IocMatch; +import org.opensearch.securityanalytics.model.threatintel.IocFinding; import org.opensearch.securityanalytics.threatIntel.iocscan.dto.IocScanContext; import org.opensearch.securityanalytics.threatIntel.model.monitor.PerIocTypeScanInput; @@ -124,7 +124,7 @@ private void createIoCMatches(List iocs, .add(ioc.getFeedId()); } - List iocMatches = new ArrayList<>(); + List iocFindings = new ArrayList<>(); for (Map.Entry> entry : iocValueToFeedIds.entrySet()) { String iocValue = entry.getKey(); @@ -133,7 +133,7 @@ private void createIoCMatches(List iocs, List relatedDocIds = new ArrayList<>(iocValueToDocIdMap.getOrDefault(iocValue, new HashSet<>())); List feedIdsList = new ArrayList<>(feedIds); try { - IocMatch iocMatch = new IocMatch( + IocFinding iocFinding = new IocFinding( UUID.randomUUID().toString(), // Generating a unique ID relatedDocIds, feedIdsList, @@ -144,7 +144,7 @@ private void createIoCMatches(List iocs, timestamp, UUID.randomUUID().toString() // TODO execution ID ); - iocMatches.add(iocMatch); + iocFindings.add(iocFinding); } catch (Exception e) { log.error(String.format("skipping creating ioc match for %s due to unexpected failure.", entry.getKey()), e); } diff --git a/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/service/IoCScanServiceInterface.java b/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/service/IoCScanServiceInterface.java index 0746eddf4..1826824d3 100644 --- a/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/service/IoCScanServiceInterface.java +++ b/src/main/java/org/opensearch/securityanalytics/threatIntel/iocscan/service/IoCScanServiceInterface.java @@ -1,10 +1,7 @@ package org.opensearch.securityanalytics.threatIntel.iocscan.service; -import org.opensearch.commons.alerting.model.Finding; -import org.opensearch.securityanalytics.model.threatintel.IocMatch; import org.opensearch.securityanalytics.threatIntel.iocscan.dto.IocScanContext; -import java.util.List; import java.util.function.BiConsumer; public interface IoCScanServiceInterface { diff --git a/src/main/java/org/opensearch/securityanalytics/threatIntel/resthandler/RestGetIocFindingsAction.java b/src/main/java/org/opensearch/securityanalytics/threatIntel/resthandler/RestGetIocFindingsAction.java new file mode 100644 index 000000000..a6077679c --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/threatIntel/resthandler/RestGetIocFindingsAction.java @@ -0,0 +1,97 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.threatIntel.resthandler; + +import org.opensearch.client.node.NodeClient; +import org.opensearch.commons.alerting.model.Table; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; +import org.opensearch.securityanalytics.SecurityAnalyticsPlugin; +import org.opensearch.securityanalytics.action.GetFindingsAction; +import org.opensearch.securityanalytics.threatIntel.action.GetIocFindingsAction; +import org.opensearch.securityanalytics.threatIntel.action.GetIocFindingsRequest; + +import java.io.IOException; +import java.time.DateTimeException; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.GET; + +public class RestGetIocFindingsAction extends BaseRestHandler { + + @Override + public String getName() { + return "get_ioc_findings_action_sa"; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + String sortString = request.param("sortString", "timestamp"); + String sortOrder = request.param("sortOrder", "asc"); + String missing = request.param("missing"); + int size = request.paramAsInt("size", 20); + int startIndex = request.paramAsInt("startIndex", 0); + String searchString = request.param("searchString", ""); + + List findingIds = null; + if (request.param("findingIds") != null) { + findingIds = Arrays.asList(request.param("findingIds").split(",")); + } + Instant startTime = null; + String startTimeParam = request.param("startTime"); + if (startTimeParam != null && !startTimeParam.isEmpty()) { + try { + startTime = Instant.ofEpochMilli(Long.parseLong(startTimeParam)); + } catch (NumberFormatException | NullPointerException | DateTimeException e) { + // Handle the parsing error + // For example, log the error or provide a default value + startTime = Instant.now(); // Default value or fallback + } + } + + Instant endTime = null; + String endTimeParam = request.param("endTime"); + if (endTimeParam != null && !endTimeParam.isEmpty()) { + try { + endTime = Instant.ofEpochMilli(Long.parseLong(endTimeParam)); + } catch (NumberFormatException | NullPointerException | DateTimeException e) { + // Handle the parsing error + // For example, log the error or provide a default value + endTime = Instant.now(); // Default value or fallback + } + } + + Table table = new Table( + sortOrder, + sortString, + missing, + size, + startIndex, + searchString + ); + + GetIocFindingsRequest getIocFindingsRequest = new GetIocFindingsRequest( + findingIds, + startTime, + endTime, + table + ); + return channel -> client.execute( + GetIocFindingsAction.INSTANCE, + getIocFindingsRequest, + new RestToXContentListener<>(channel) + ); + } + + @Override + public List routes() { + return singletonList(new Route(GET, SecurityAnalyticsPlugin.THREAT_INTEL_BASE_URI + "/findings" + "/_search")); + } +} + diff --git a/src/main/java/org/opensearch/securityanalytics/threatIntel/transport/TransportGetIocFindingsAction.java b/src/main/java/org/opensearch/securityanalytics/threatIntel/transport/TransportGetIocFindingsAction.java new file mode 100644 index 000000000..debde4655 --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/threatIntel/transport/TransportGetIocFindingsAction.java @@ -0,0 +1,143 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.threatIntel.transport; + +import org.apache.lucene.search.join.ScoreMode; +import org.opensearch.OpenSearchStatusException; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.client.Client; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.settings.Settings; +import org.opensearch.commons.alerting.model.Table; +import org.opensearch.commons.authuser.User; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.common.Strings; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.Operator; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.search.builder.SearchSourceBuilder; +import org.opensearch.search.fetch.subphase.FetchSourceContext; +import org.opensearch.search.sort.FieldSortBuilder; +import org.opensearch.search.sort.SortBuilders; +import org.opensearch.search.sort.SortOrder; +import org.opensearch.securityanalytics.settings.SecurityAnalyticsSettings; +import org.opensearch.securityanalytics.threatIntel.action.GetIocFindingsAction; +import org.opensearch.securityanalytics.threatIntel.action.GetIocFindingsRequest; +import org.opensearch.securityanalytics.threatIntel.action.GetIocFindingsResponse; +import org.opensearch.securityanalytics.threatIntel.iocscan.dao.IocFindingService; +import org.opensearch.securityanalytics.transport.SecureTransportAction; +import org.opensearch.tasks.Task; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +import java.time.Instant; +import java.util.List; + +public class TransportGetIocFindingsAction extends HandledTransportAction implements SecureTransportAction { + + private final IocFindingService iocFindingService; + + private final ClusterService clusterService; + + private final Settings settings; + + private final ThreadPool threadPool; + + private volatile Boolean filterByEnabled; + + @Inject + public TransportGetIocFindingsAction( + TransportService transportService, + ActionFilters actionFilters, + ClusterService clusterService, + Settings settings, + NamedXContentRegistry xContentRegistry, + Client client + ) { + super(GetIocFindingsAction.NAME, transportService, actionFilters, GetIocFindingsRequest::new); + this.settings = settings; + this.clusterService = clusterService; + this.threadPool = client.threadPool(); + this.iocFindingService = new IocFindingService(client, this.clusterService, xContentRegistry); + this.filterByEnabled = SecurityAnalyticsSettings.FILTER_BY_BACKEND_ROLES.get(this.settings); + this.clusterService.getClusterSettings().addSettingsUpdateConsumer(SecurityAnalyticsSettings.FILTER_BY_BACKEND_ROLES, this::setFilterByEnabled); + } + + @Override + protected void doExecute(Task task, GetIocFindingsRequest request, ActionListener actionListener) { + User user = readUserFromThreadContext(this.threadPool); + + String validateBackendRoleMessage = validateUserBackendRoles(user, this.filterByEnabled); + if (!"".equals(validateBackendRoleMessage)) { + actionListener.onFailure(new OpenSearchStatusException("Do not have permissions to resource", RestStatus.FORBIDDEN)); + return; + } + Table tableProp = request.getTable(); + FieldSortBuilder sortBuilder = SortBuilders + .fieldSort(tableProp.getSortString()) + .order(SortOrder.fromString(tableProp.getSortOrder())); + if (tableProp.getMissing() != null && !tableProp.getMissing().isBlank()) { + sortBuilder.missing(tableProp.getMissing()); + } + + SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder() + .sort(sortBuilder) + .size(tableProp.getSize()) + .from(tableProp.getStartIndex()) + .fetchSource(new FetchSourceContext(true, Strings.EMPTY_ARRAY, Strings.EMPTY_ARRAY)) + .seqNoAndPrimaryTerm(true) + .version(true); + + BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); + List findingIds = request.getFindingIds(); + + if (findingIds != null && !findingIds.isEmpty()) { + queryBuilder.filter(QueryBuilders.termsQuery("id", findingIds)); + } + + Instant startTime = request.getStartTime(); + Instant endTime = request.getEndTime(); + if (startTime != null && endTime != null) { + long startTimeMillis = startTime.toEpochMilli(); + long endTimeMillis = endTime.toEpochMilli(); + QueryBuilder timeRangeQuery = QueryBuilders.rangeQuery("timestamp") + .from(startTimeMillis) // Greater than or equal to start time + .to(endTimeMillis); // Less than or equal to end time + queryBuilder.filter(timeRangeQuery); + } + + if (tableProp.getSearchString() != null && !tableProp.getSearchString().isBlank()) { + queryBuilder.should(QueryBuilders + .queryStringQuery(tableProp.getSearchString()) + ).should( + QueryBuilders.nestedQuery( + "queries", + QueryBuilders.boolQuery() + .must( + QueryBuilders + .queryStringQuery(tableProp.getSearchString()) + .defaultOperator(Operator.AND) + .field("queries.tags") + .field("queries.name") + ), + ScoreMode.Avg + ) + ); + } + searchSourceBuilder.query(queryBuilder).trackTotalHits(true); + + this.threadPool.getThreadContext().stashContext(); + iocFindingService.searchIocMatches(searchSourceBuilder, actionListener); + } + + private void setFilterByEnabled(boolean filterByEnabled) { + this.filterByEnabled = filterByEnabled; + } +} \ No newline at end of file diff --git a/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java b/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java index 15ec3cd9d..1af50dd83 100644 --- a/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java +++ b/src/test/java/org/opensearch/securityanalytics/SecurityAnalyticsRestTestCase.java @@ -60,6 +60,7 @@ import org.opensearch.securityanalytics.model.Detector; import org.opensearch.securityanalytics.model.Rule; import org.opensearch.securityanalytics.model.ThreatIntelFeedData; +import org.opensearch.securityanalytics.model.threatintel.IocFinding; import org.opensearch.securityanalytics.threatIntel.model.SATIFSourceConfigDto; import org.opensearch.securityanalytics.threatIntel.sacommons.monitor.ThreatIntelMonitorDto; import org.opensearch.securityanalytics.util.CorrelationIndices; @@ -666,6 +667,10 @@ protected HttpEntity toHttpEntity(SATIFSourceConfigDto saTifSourceConfigDto) thr return new StringEntity(toJsonString(saTifSourceConfigDto), ContentType.APPLICATION_JSON); } + protected HttpEntity toHttpEntity(IocFinding iocFinding) throws IOException { + return new StringEntity(toJsonString(iocFinding), ContentType.APPLICATION_JSON); + } + protected HttpEntity toHttpEntity(ThreatIntelMonitorDto threatIntelMonitorDto) throws IOException { return new StringEntity(toJsonString(threatIntelMonitorDto), ContentType.APPLICATION_JSON); } @@ -723,6 +728,11 @@ private String toJsonString(ThreatIntelMonitorDto threatIntelMonitorDto) throws return IndexUtilsKt.string(shuffleXContent(threatIntelMonitorDto.toXContent(builder, ToXContent.EMPTY_PARAMS))); } + private String toJsonString(IocFinding iocFinding) throws IOException { + XContentBuilder builder = XContentFactory.jsonBuilder(); + return IndexUtilsKt.string(shuffleXContent(iocFinding.toXContent(builder, ToXContent.EMPTY_PARAMS))); + } + private String alertingScheduledJobMappings() { return " \"_meta\" : {\n" + " \"schema_version\": 5\n" + diff --git a/src/test/java/org/opensearch/securityanalytics/TestHelpers.java b/src/test/java/org/opensearch/securityanalytics/TestHelpers.java index 7539fdd8f..cd9a68201 100644 --- a/src/test/java/org/opensearch/securityanalytics/TestHelpers.java +++ b/src/test/java/org/opensearch/securityanalytics/TestHelpers.java @@ -28,12 +28,10 @@ import org.opensearch.securityanalytics.model.DetectorInput; import org.opensearch.securityanalytics.model.DetectorRule; import org.opensearch.securityanalytics.model.DetectorTrigger; -import org.opensearch.securityanalytics.model.IoCMatch; import org.opensearch.securityanalytics.model.ThreatIntelFeedData; -import org.opensearch.securityanalytics.threatIntel.common.SourceConfigType; +import org.opensearch.securityanalytics.model.threatintel.IocFinding; import org.opensearch.securityanalytics.threatIntel.common.RefreshType; import org.opensearch.securityanalytics.threatIntel.common.TIFJobState; -import org.opensearch.securityanalytics.threatIntel.model.DefaultIocStoreConfig; import org.opensearch.securityanalytics.threatIntel.model.S3Source; import org.opensearch.securityanalytics.threatIntel.model.SATIFSourceConfigDto; import org.opensearch.securityanalytics.threatIntel.model.Source; @@ -807,9 +805,9 @@ public static String toJsonStringWithUser(Detector detector) throws IOException return BytesReference.bytes(builder).utf8ToString(); } - public static String toJsonString(IocMatch iocMatch) throws IOException { + public static String toJsonString(IocFinding iocFinding) throws IOException { XContentBuilder builder = XContentFactory.jsonBuilder(); - builder = iocMatch.toXContent(builder, ToXContent.EMPTY_PARAMS); + builder = iocFinding.toXContent(builder, ToXContent.EMPTY_PARAMS); return BytesReference.bytes(builder).utf8ToString(); } diff --git a/src/test/java/org/opensearch/securityanalytics/model/IocMatchTests.java b/src/test/java/org/opensearch/securityanalytics/model/IocFindingTests.java similarity index 50% rename from src/test/java/org/opensearch/securityanalytics/model/IocMatchTests.java rename to src/test/java/org/opensearch/securityanalytics/model/IocFindingTests.java index 298d3f7e4..cbad9f5d2 100644 --- a/src/test/java/org/opensearch/securityanalytics/model/IocMatchTests.java +++ b/src/test/java/org/opensearch/securityanalytics/model/IocFindingTests.java @@ -5,7 +5,7 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.securityanalytics.model.threatintel.IocMatch; +import org.opensearch.securityanalytics.model.threatintel.IocFinding; import org.opensearch.test.OpenSearchTestCase; import java.io.IOException; @@ -14,24 +14,24 @@ import static org.opensearch.securityanalytics.TestHelpers.toJsonString; -public class IocMatchTests extends OpenSearchTestCase { +public class IocFindingTests extends OpenSearchTestCase { public void testIoCMatchAsAStream() throws IOException { - IocMatch iocMatch = getRandomIoCMatch(); - String jsonString = toJsonString(iocMatch); + IocFinding iocFinding = getRandomIoCMatch(); + String jsonString = toJsonString(iocFinding); BytesStreamOutput out = new BytesStreamOutput(); - iocMatch.writeTo(out); + iocFinding.writeTo(out); StreamInput sin = StreamInput.wrap(out.bytes().toBytesRef().bytes); - IocMatch newIocMatch = new IocMatch(sin); - assertEquals(iocMatch.getId(), newIocMatch.getId()); - assertEquals(iocMatch.getIocScanJobId(), newIocMatch.getIocScanJobId()); - assertEquals(iocMatch.getIocScanJobName(), newIocMatch.getIocScanJobName()); - assertEquals(iocMatch.getIocValue(), newIocMatch.getIocValue()); - assertEquals(iocMatch.getIocType(), newIocMatch.getIocType()); - assertEquals(iocMatch.getTimestamp(), newIocMatch.getTimestamp()); - assertEquals(iocMatch.getExecutionId(), newIocMatch.getExecutionId()); - assertTrue(iocMatch.getFeedIds().containsAll(newIocMatch.getFeedIds())); - assertTrue(iocMatch.getRelatedDocIds().containsAll(newIocMatch.getRelatedDocIds())); + IocFinding newIocFinding = new IocFinding(sin); + assertEquals(iocFinding.getId(), newIocFinding.getId()); + assertEquals(iocFinding.getIocScanJobId(), newIocFinding.getIocScanJobId()); + assertEquals(iocFinding.getIocScanJobName(), newIocFinding.getIocScanJobName()); + assertEquals(iocFinding.getIocValue(), newIocFinding.getIocValue()); + assertEquals(iocFinding.getIocType(), newIocFinding.getIocType()); + assertEquals(iocFinding.getTimestamp(), newIocFinding.getTimestamp()); + assertEquals(iocFinding.getExecutionId(), newIocFinding.getExecutionId()); + assertTrue(iocFinding.getFeedIds().containsAll(newIocFinding.getFeedIds())); + assertTrue(iocFinding.getRelatedDocIds().containsAll(newIocFinding.getRelatedDocIds())); } public void testIoCMatchParse() throws IOException { @@ -39,20 +39,20 @@ public void testIoCMatchParse() throws IOException { "\"relatedDocId2\"], \"feed_ids\": [\"feedId1\", \"feedId2\"], \"ioc_scan_job_id\":" + " \"scanJob123\", \"ioc_scan_job_name\": \"Example Scan Job\", \"ioc_value\": \"exampleIocValue\", " + "\"ioc_type\": \"exampleIocType\", \"timestamp\": 1620912896000, \"execution_id\": \"execution123\" }"; - IocMatch iocMatch = IocMatch.parse((getParser(iocMatchString))); + IocFinding iocFinding = IocFinding.parse((getParser(iocMatchString))); BytesStreamOutput out = new BytesStreamOutput(); - iocMatch.writeTo(out); + iocFinding.writeTo(out); StreamInput sin = StreamInput.wrap(out.bytes().toBytesRef().bytes); - IocMatch newIocMatch = new IocMatch(sin); - assertEquals(iocMatch.getId(), newIocMatch.getId()); - assertEquals(iocMatch.getIocScanJobId(), newIocMatch.getIocScanJobId()); - assertEquals(iocMatch.getIocScanJobName(), newIocMatch.getIocScanJobName()); - assertEquals(iocMatch.getIocValue(), newIocMatch.getIocValue()); - assertEquals(iocMatch.getIocType(), newIocMatch.getIocType()); - assertEquals(iocMatch.getTimestamp(), newIocMatch.getTimestamp()); - assertEquals(iocMatch.getExecutionId(), newIocMatch.getExecutionId()); - assertTrue(iocMatch.getFeedIds().containsAll(newIocMatch.getFeedIds())); - assertTrue(iocMatch.getRelatedDocIds().containsAll(newIocMatch.getRelatedDocIds())); + IocFinding newIocFinding = new IocFinding(sin); + assertEquals(iocFinding.getId(), newIocFinding.getId()); + assertEquals(iocFinding.getIocScanJobId(), newIocFinding.getIocScanJobId()); + assertEquals(iocFinding.getIocScanJobName(), newIocFinding.getIocScanJobName()); + assertEquals(iocFinding.getIocValue(), newIocFinding.getIocValue()); + assertEquals(iocFinding.getIocType(), newIocFinding.getIocType()); + assertEquals(iocFinding.getTimestamp(), newIocFinding.getTimestamp()); + assertEquals(iocFinding.getExecutionId(), newIocFinding.getExecutionId()); + assertTrue(iocFinding.getFeedIds().containsAll(newIocFinding.getFeedIds())); + assertTrue(iocFinding.getRelatedDocIds().containsAll(newIocFinding.getRelatedDocIds())); } public XContentParser getParser(String xc) throws IOException { @@ -62,8 +62,8 @@ public XContentParser getParser(String xc) throws IOException { } - private static IocMatch getRandomIoCMatch() { - return new IocMatch( + private static IocFinding getRandomIoCMatch() { + return new IocFinding( randomAlphaOfLength(10), List.of(randomAlphaOfLength(10), randomAlphaOfLength(10)), List.of(randomAlphaOfLength(10), randomAlphaOfLength(10)), diff --git a/src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocMatchServiceIT.java b/src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocFindingServiceIT.java similarity index 71% rename from src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocMatchServiceIT.java rename to src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocFindingServiceIT.java index fc24b5b76..7f3dfcba5 100644 --- a/src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocMatchServiceIT.java +++ b/src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocFindingServiceIT.java @@ -1,33 +1,30 @@ package org.opensearch.securityanalytics.threatIntel.iocscan.dao; -import org.opensearch.action.LatchedActionListener; -import org.opensearch.action.StepListener; import org.opensearch.action.search.SearchRequest; import org.opensearch.common.util.concurrent.CountDown; import org.opensearch.core.action.ActionListener; import org.opensearch.securityanalytics.SecurityAnalyticsIntegTestCase; -import org.opensearch.securityanalytics.model.threatintel.IocMatch; +import org.opensearch.securityanalytics.model.threatintel.IocFinding; import java.time.Instant; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CountDownLatch; -public class IocMatchServiceIT extends SecurityAnalyticsIntegTestCase { +public class IocFindingServiceIT extends SecurityAnalyticsIntegTestCase { public void test_indexIocMatches() throws InterruptedException { - IocMatchService service = new IocMatchService(client(), clusterService()); - List iocMatches = generateIocMatches(10); + IocFindingService service = new IocFindingService(client(), clusterService(), xContentRegistry()); + List iocFindings = generateIocMatches(10); CountDown countdown = new CountDown(1); - service.indexIocMatches(iocMatches, ActionListener.wrap(r -> { + service.indexIocMatches(iocFindings, ActionListener.wrap(r -> { countdown.countDown(); }, e -> { logger.error("failed to index ioc matches", e); fail(); countdown.countDown(); })); - SearchRequest request = new SearchRequest(IocMatchService.INDEX_NAME); + SearchRequest request = new SearchRequest(IocFindingService.INDEX_NAME); request.source().size(10); CountDown countDownLatch1 = new CountDown(1); client().search(request, ActionListener.wrap( @@ -45,12 +42,12 @@ public void test_indexIocMatches() throws InterruptedException { countDownLatch1.isCountedDown(); } - private List generateIocMatches(int i) { - List iocMatches = new ArrayList<>(); + private List generateIocMatches(int i) { + List iocFindings = new ArrayList<>(); String monitorId = randomAlphaOfLength(10); String monitorName = randomAlphaOfLength(10); for (int i1 = 0; i1 < i; i1++) { - iocMatches.add(new IocMatch( + iocFindings.add(new IocFinding( randomAlphaOfLength(10), randomList(1, 10, () -> randomAlphaOfLength(10)),//docids randomList(1, 10, () -> randomAlphaOfLength(10)), //feedids @@ -62,6 +59,6 @@ private List generateIocMatches(int i) { randomAlphaOfLength(10) )); } - return iocMatches; + return iocFindings; } } \ No newline at end of file diff --git a/src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocFindingServiceRestApiIT.java b/src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocFindingServiceRestApiIT.java new file mode 100644 index 000000000..22bd9536f --- /dev/null +++ b/src/test/java/org/opensearch/securityanalytics/threatIntel/iocscan/dao/IocFindingServiceRestApiIT.java @@ -0,0 +1,54 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.threatIntel.iocscan.dao; + +import org.junit.Assert; +import org.opensearch.client.Response; +import org.opensearch.securityanalytics.SecurityAnalyticsPlugin; +import org.opensearch.securityanalytics.SecurityAnalyticsRestTestCase; +import org.opensearch.securityanalytics.model.threatintel.IocFinding; + +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class IocFindingServiceRestApiIT extends SecurityAnalyticsRestTestCase { + + @SuppressWarnings("unchecked") + public void testGetIocFindings() throws IOException { + List iocFindings = generateIocMatches(10); + for (IocFinding iocFinding: iocFindings) { + makeRequest(client(), "POST", IocFindingService.INDEX_NAME + "/_doc?refresh", Map.of(), + toHttpEntity(iocFinding)); + } + + Response response = makeRequest(client(), "GET", SecurityAnalyticsPlugin.THREAT_INTEL_BASE_URI + "/findings/_search?startIndex=1&size=5", + Map.of(), null); + Map responseAsMap = responseAsMap(response); + Assert.assertEquals(5, ((List>) responseAsMap.get("ioc_findings")).size()); + } + + private List generateIocMatches(int i) { + List iocFindings = new ArrayList<>(); + String monitorId = randomAlphaOfLength(10); + String monitorName = randomAlphaOfLength(10); + for (int i1 = 0; i1 < i; i1++) { + iocFindings.add(new IocFinding( + randomAlphaOfLength(10), + randomList(1, 10, () -> randomAlphaOfLength(10)),//docids + randomList(1, 10, () -> randomAlphaOfLength(10)), //feedids + monitorId, + monitorName, + randomAlphaOfLength(10), + "IP", + Instant.now(), + randomAlphaOfLength(10) + )); + } + return iocFindings; + } +} \ No newline at end of file