From 38175a5f83e81712b7c59f873136f51c244eb947 Mon Sep 17 00:00:00 2001 From: Petar Dzepina Date: Tue, 21 Feb 2023 01:15:07 +0100 Subject: [PATCH] GetAllRuleCategories API (#327) Signed-off-by: Petar Dzepina --- .../SecurityAnalyticsPlugin.java | 9 +- .../action/GetAllRuleCategoriesAction.java | 17 ++++ .../action/GetAllRuleCategoriesRequest.java | 32 +++++++ .../action/GetAllRuleCategoriesResponse.java | 46 +++++++++ .../securityanalytics/model/RuleCategory.java | 94 +++++++++++++++++++ .../RestGetAllRuleCategoriesAction.java | 41 ++++++++ .../TransportGetAllRuleCategoriesAction.java | 41 ++++++++ src/main/resources/rules/rule_categories.json | 36 +++++++ .../resthandler/RuleRestApiIT.java | 13 +++ 9 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesAction.java create mode 100644 src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesRequest.java create mode 100644 src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesResponse.java create mode 100644 src/main/java/org/opensearch/securityanalytics/model/RuleCategory.java create mode 100644 src/main/java/org/opensearch/securityanalytics/resthandler/RestGetAllRuleCategoriesAction.java create mode 100644 src/main/java/org/opensearch/securityanalytics/transport/TransportGetAllRuleCategoriesAction.java create mode 100644 src/main/resources/rules/rule_categories.json diff --git a/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java b/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java index 23aaea746..ca7115a7f 100644 --- a/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java +++ b/src/main/java/org/opensearch/securityanalytics/SecurityAnalyticsPlugin.java @@ -34,6 +34,7 @@ import org.opensearch.securityanalytics.action.CreateIndexMappingsAction; import org.opensearch.securityanalytics.action.DeleteDetectorAction; import org.opensearch.securityanalytics.action.GetAlertsAction; +import org.opensearch.securityanalytics.action.GetAllRuleCategoriesAction; import org.opensearch.securityanalytics.action.GetDetectorAction; import org.opensearch.securityanalytics.action.GetFindingsAction; import org.opensearch.securityanalytics.action.GetIndexMappingsAction; @@ -45,10 +46,12 @@ import org.opensearch.securityanalytics.action.ValidateRulesAction; import org.opensearch.securityanalytics.mapper.MapperService; import org.opensearch.securityanalytics.resthandler.RestAcknowledgeAlertsAction; +import org.opensearch.securityanalytics.resthandler.RestGetAllRuleCategoriesAction; import org.opensearch.securityanalytics.resthandler.RestGetFindingsAction; import org.opensearch.securityanalytics.resthandler.RestValidateRulesAction; import org.opensearch.securityanalytics.transport.TransportAcknowledgeAlertsAction; import org.opensearch.securityanalytics.transport.TransportCreateIndexMappingsAction; +import org.opensearch.securityanalytics.transport.TransportGetAllRuleCategoriesAction; import org.opensearch.securityanalytics.transport.TransportGetFindingsAction; import org.opensearch.securityanalytics.action.DeleteRuleAction; import org.opensearch.securityanalytics.action.IndexRuleAction; @@ -154,7 +157,8 @@ public List getRestHandlers(Settings settings, new RestIndexRuleAction(), new RestSearchRuleAction(), new RestDeleteRuleAction(), - new RestValidateRulesAction() + new RestValidateRulesAction(), + new RestGetAllRuleCategoriesAction() ); } @@ -204,7 +208,8 @@ public List> getSettings() { new ActionPlugin.ActionHandler<>(IndexRuleAction.INSTANCE, TransportIndexRuleAction.class), new ActionPlugin.ActionHandler<>(SearchRuleAction.INSTANCE, TransportSearchRuleAction.class), new ActionPlugin.ActionHandler<>(DeleteRuleAction.INSTANCE, TransportDeleteRuleAction.class), - new ActionPlugin.ActionHandler<>(ValidateRulesAction.INSTANCE, TransportValidateRulesAction.class) + new ActionPlugin.ActionHandler<>(ValidateRulesAction.INSTANCE, TransportValidateRulesAction.class), + new ActionPlugin.ActionHandler<>(GetAllRuleCategoriesAction.INSTANCE, TransportGetAllRuleCategoriesAction.class) ); } } \ No newline at end of file diff --git a/src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesAction.java b/src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesAction.java new file mode 100644 index 000000000..3eae5ac31 --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesAction.java @@ -0,0 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.action; + +import org.opensearch.action.ActionType; + +public class GetAllRuleCategoriesAction extends ActionType { + + public static final GetAllRuleCategoriesAction INSTANCE = new GetAllRuleCategoriesAction(); + public static final String NAME = "cluster:admin/opensearch/securityanalytics/rules/categories"; + + public GetAllRuleCategoriesAction() { + super(NAME, GetAllRuleCategoriesResponse::new); + } +} diff --git a/src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesRequest.java b/src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesRequest.java new file mode 100644 index 000000000..241d7a066 --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesRequest.java @@ -0,0 +1,32 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.action; + +import java.io.IOException; +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; + +public class GetAllRuleCategoriesRequest extends ActionRequest { + + public GetAllRuleCategoriesRequest() { + super(); + } + public GetAllRuleCategoriesRequest(StreamInput sin) throws IOException { + this(); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + + } + +} \ No newline at end of file diff --git a/src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesResponse.java b/src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesResponse.java new file mode 100644 index 000000000..b06144554 --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/action/GetAllRuleCategoriesResponse.java @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.action; + +import java.io.IOException; +import java.util.List; +import org.opensearch.action.ActionResponse; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.securityanalytics.model.RuleCategory; + +public class GetAllRuleCategoriesResponse extends ActionResponse implements ToXContentObject { + + private static final String RULE_CATEGORIES = "rule_categories"; + + private List ruleCategories; + + public GetAllRuleCategoriesResponse(List ruleCategories) { + super(); + this.ruleCategories = ruleCategories; + } + + public GetAllRuleCategoriesResponse(StreamInput sin) throws IOException { + this(sin.readList(RuleCategory::new)); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeList(ruleCategories); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.startArray(RULE_CATEGORIES); + for (RuleCategory c : ruleCategories) { + c.toXContent(builder, null); + } + builder.endArray(); + return builder.endObject(); + } +} \ No newline at end of file diff --git a/src/main/java/org/opensearch/securityanalytics/model/RuleCategory.java b/src/main/java/org/opensearch/securityanalytics/model/RuleCategory.java new file mode 100644 index 000000000..2a1bd2ec4 --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/model/RuleCategory.java @@ -0,0 +1,94 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.securityanalytics.model; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.opensearch.OpenSearchParseException; +import org.opensearch.common.io.stream.StreamInput; +import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.io.stream.Writeable; +import org.opensearch.common.settings.SettingsException; +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.common.xcontent.XContentHelper; +import org.opensearch.common.xcontent.json.JsonXContent; + +public class RuleCategory implements Writeable, ToXContentObject { + + public static final String KEY = "key"; + public static final String DISPLAY_NAME = "display_name"; + + private String name; + private String displayName; + + public RuleCategory(StreamInput sin) throws IOException { + this(sin.readString(), sin.readString()); + } + + public RuleCategory(String name, String displayName) { + this.name = name; + this.displayName = displayName; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(name); + out.writeString(displayName); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject() + .field(KEY, name) + .field(DISPLAY_NAME, displayName) + .endObject(); + } + + public String getName() { + return name; + } + + + private static final String RULE_CATEGORIES_CONFIG_FILE = "rules/rule_categories.json"; + + // Rule category is the same as detector type + public static final List ALL_RULE_CATEGORIES; + + static { + List ruleCategories = new ArrayList<>(); + String ruleCategoriesJson; + try ( + InputStream is = RuleCategory.class.getClassLoader().getResourceAsStream(RULE_CATEGORIES_CONFIG_FILE) + ) { + ruleCategoriesJson = new String(Objects.requireNonNull(is).readAllBytes(), StandardCharsets.UTF_8); + + if (ruleCategoriesJson != null) { + Map configMap = + XContentHelper.convertToMap(JsonXContent.jsonXContent, ruleCategoriesJson, false); + List> categories = (List>) configMap.get("rule_categories"); + for (Map c : categories) { + ruleCategories.add(new RuleCategory( + (String) c.get(KEY), + (String) c.get(DISPLAY_NAME) + )); + } + } + } catch (OpenSearchParseException e) { + throw e; + } catch (Exception e) { + throw new SettingsException("Failed to load settings from [" + RULE_CATEGORIES_CONFIG_FILE + "]", e); + } + ALL_RULE_CATEGORIES = Collections.unmodifiableList(ruleCategories); + } +} + diff --git a/src/main/java/org/opensearch/securityanalytics/resthandler/RestGetAllRuleCategoriesAction.java b/src/main/java/org/opensearch/securityanalytics/resthandler/RestGetAllRuleCategoriesAction.java new file mode 100644 index 000000000..682337371 --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/resthandler/RestGetAllRuleCategoriesAction.java @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.resthandler; + +import java.io.IOException; +import java.util.List; +import org.opensearch.client.node.NodeClient; +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.GetAllRuleCategoriesAction; +import org.opensearch.securityanalytics.action.GetAllRuleCategoriesRequest; + + +import static org.opensearch.rest.RestRequest.Method.GET; + +public class RestGetAllRuleCategoriesAction extends BaseRestHandler { + + @Override + public String getName() { + return "get_all_rule_categories_action"; + } + + @Override + public List routes() { + return List.of(new Route(GET, SecurityAnalyticsPlugin.RULE_BASE_URI + "/categories")); + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + + return channel -> client.execute( + GetAllRuleCategoriesAction.INSTANCE, + new GetAllRuleCategoriesRequest(), + new RestToXContentListener<>(channel) + ); + } +} diff --git a/src/main/java/org/opensearch/securityanalytics/transport/TransportGetAllRuleCategoriesAction.java b/src/main/java/org/opensearch/securityanalytics/transport/TransportGetAllRuleCategoriesAction.java new file mode 100644 index 000000000..34ff899bb --- /dev/null +++ b/src/main/java/org/opensearch/securityanalytics/transport/TransportGetAllRuleCategoriesAction.java @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +package org.opensearch.securityanalytics.transport; + +import org.opensearch.action.ActionListener; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.inject.Inject; +import org.opensearch.securityanalytics.action.GetAllRuleCategoriesAction; +import org.opensearch.securityanalytics.action.GetAllRuleCategoriesRequest; +import org.opensearch.securityanalytics.action.GetAllRuleCategoriesResponse; +import org.opensearch.securityanalytics.model.RuleCategory; +import org.opensearch.tasks.Task; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +public class TransportGetAllRuleCategoriesAction extends HandledTransportAction { + + private final ThreadPool threadPool; + + @Inject + public TransportGetAllRuleCategoriesAction( + TransportService transportService, + ActionFilters actionFilters, + GetAllRuleCategoriesAction getAllRuleCategoriesAction, + ClusterService clusterService, + ThreadPool threadPool + ) { + super(getAllRuleCategoriesAction.NAME, transportService, actionFilters, GetAllRuleCategoriesRequest::new); + this.threadPool = threadPool; + } + + @Override + protected void doExecute(Task task, GetAllRuleCategoriesRequest request, ActionListener actionListener) { + this.threadPool.getThreadContext().stashContext(); + actionListener.onResponse(new GetAllRuleCategoriesResponse(RuleCategory.ALL_RULE_CATEGORIES)); + } +} diff --git a/src/main/resources/rules/rule_categories.json b/src/main/resources/rules/rule_categories.json new file mode 100644 index 000000000..788b51758 --- /dev/null +++ b/src/main/resources/rules/rule_categories.json @@ -0,0 +1,36 @@ +{ + "rule_categories": [ + { + "key": "ad_ldap", + "display_name": "AD/LDAP" + }, + { + "key": "dns", + "display_name": "DNS logs" + }, + { + "key": "network", + "display_name": "Network" + }, + { + "key": "apache_access", + "display_name": "Apache access logs" + }, + { + "key": "cloudtrail", + "display_name": "Cloud Trail logs" + }, + { + "key": "s3", + "display_name": "S3 access logs" + }, + { + "key": "windows", + "display_name": "Windows logs" + }, + { + "key": "linux", + "display_name": "System logs" + } + ] +} \ No newline at end of file diff --git a/src/test/java/org/opensearch/securityanalytics/resthandler/RuleRestApiIT.java b/src/test/java/org/opensearch/securityanalytics/resthandler/RuleRestApiIT.java index ea4936a4a..5920427d5 100644 --- a/src/test/java/org/opensearch/securityanalytics/resthandler/RuleRestApiIT.java +++ b/src/test/java/org/opensearch/securityanalytics/resthandler/RuleRestApiIT.java @@ -765,4 +765,17 @@ public void testCustomRuleValidation() throws IOException { assertEquals(rule2createdId, ((List)responseBody.get("nonapplicable_fields")).get(0)); } + public void testGetAllRuleCategories() throws IOException { + Response response = makeRequest(client(), "GET", SecurityAnalyticsPlugin.RULE_BASE_URI + "/categories", Collections.emptyMap(), null); + List categories = (List) asMap(response).get("rule_categories"); + assertEquals(8, categories.size()); + assertTrue(((Map)categories.get(0)).get("key").equals("ad_ldap")); + assertTrue(((Map)categories.get(1)).get("key").equals("dns")); + assertTrue(((Map)categories.get(2)).get("key").equals("network")); + assertTrue(((Map)categories.get(3)).get("key").equals("apache_access")); + assertTrue(((Map)categories.get(4)).get("key").equals("cloudtrail")); + assertTrue(((Map)categories.get(5)).get("key").equals("s3")); + assertTrue(((Map)categories.get(6)).get("key").equals("windows")); + assertTrue(((Map)categories.get(7)).get("key").equals("linux")); + } } \ No newline at end of file