From f8ff61d6f5b69d7a886660aa255975b592cd1fff Mon Sep 17 00:00:00 2001 From: f-galland Date: Wed, 18 Sep 2024 18:31:06 -0300 Subject: [PATCH 01/27] Adding POST endpoint --- plugins/command-manager/build.gradle | 4 + .../commandmanager/CommandManagerPlugin.java | 35 +++++- .../rest/action/RestPostCommandAction.java | 113 ++++++++++++++++++ 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java diff --git a/plugins/command-manager/build.gradle b/plugins/command-manager/build.gradle index 7defcd1..2b5f23b 100644 --- a/plugins/command-manager/build.gradle +++ b/plugins/command-manager/build.gradle @@ -83,6 +83,10 @@ repositories { maven { url "https://plugins.gradle.org/m2/" } } +dependencies { + implementation("com.google.guava:guava:33.3.0-jre") +} + test { include '**/*Tests.class' } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java index b7aa839..6ea3809 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java @@ -7,9 +7,40 @@ */ package com.wazuh.commandmanager; +import com.google.common.collect.ImmutableList; +import com.wazuh.commandmanager.rest.action.RestPostCommandAction; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.IndexScopedSettings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.SettingsFilter; +import org.opensearch.plugins.ActionPlugin; import org.opensearch.plugins.Plugin; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestHandler; +import java.util.List; +import java.util.function.Supplier; -public class CommandManagerPlugin extends Plugin { - // Implement the relevant Plugin Interfaces here + +public class CommandManagerPlugin extends Plugin implements ActionPlugin { + public static final String COMMAND_MANAGER_BASE_URI = "/_plugins/_commandmanager"; + public static final String COMMAND_MANAGER_INDEX_NAME = "command-manager"; + private static final Logger log = LogManager.getLogger(CommandManagerPlugin.class); + + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { + RestPostCommandAction restPostCommandAction = new RestPostCommandAction(); + return ImmutableList.of(restPostCommandAction); + } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java new file mode 100644 index 0000000..78f824a --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -0,0 +1,113 @@ +package com.wazuh.commandmanager.rest.action; + +import com.wazuh.commandmanager.CommandManagerPlugin; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.action.DocWriteRequest; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.ActiveShardCount; +import org.opensearch.client.node.NodeClient; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.index.VersionType; +import org.opensearch.rest.BaseRestHandler; +import com.google.common.collect.ImmutableList; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestActions; +import org.opensearch.rest.action.RestStatusToXContentListener; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.opensearch.rest.RestRequest.Method.POST; + +public class RestPostCommandAction extends BaseRestHandler { + + public static final String POST_COMMAND_ACTION_REQUEST_DETAILS = "post_command_action_request_details"; + + private final Logger logger = LogManager.getLogger(RestPostCommandAction.class); + + public String getName() { + return POST_COMMAND_ACTION_REQUEST_DETAILS; + } + + @Override + public List routes() { + return ImmutableList.of( + new Route( + POST, + String.format(Locale.ROOT, "%s/%s/{%s}", + CommandManagerPlugin.COMMAND_MANAGER_BASE_URI, + "create", + "id" + ) + ) + ); + } + + @Override + public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + IndexRequest indexRequest = new IndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); + indexRequest.id(request.param("id")); + indexRequest.routing(request.param("routing")); + indexRequest.setPipeline(request.param("pipeline")); + indexRequest.source(request.requiredContent(), request.getMediaType()); + indexRequest.timeout(request.paramAsTime("timeout", IndexRequest.DEFAULT_TIMEOUT)); + indexRequest.setRefreshPolicy(request.param("refresh")); + indexRequest.version(RestActions.parseVersion(request)); + indexRequest.versionType(VersionType.fromString(request.param("version_type"), indexRequest.versionType())); + indexRequest.setIfSeqNo(request.paramAsLong("if_seq_no", indexRequest.ifSeqNo())); + indexRequest.setIfPrimaryTerm(request.paramAsLong("if_primary_term", indexRequest.ifPrimaryTerm())); + indexRequest.setRequireAlias(request.paramAsBoolean(DocWriteRequest.REQUIRE_ALIAS, indexRequest.isRequireAlias())); + + // Source. One of [Users/Services (via Management API), Engine (via Management API), Content manager (directly)] + request.params().putIfAbsent("source", "engine"); + // User. The user that originated the request. This user may represent a Management API or Indexer API user depending on the source. + request.params().putIfAbsent("user", "admin"); + // Target. Cluster name destination. + request.params().putIfAbsent("target", "wazuh-cluster"); + // Type. One of [Agent groups, Agent, Server cluster] + request.params().putIfAbsent("type", "agent"); + // Timeout. Number of seconds to wait for the command to be executed. + request.params().putIfAbsent("timeout", "120"); + // Action + Map actionField = new HashMap<>(); + // Type. One of One of [Agent groups, Agent, Server cluster] + actionField.put("type", "agent"); + // Params. Additional parameters for the action. + actionField.put("params", "--help"); + // Version. Version of the action. + actionField.put("version", "1.0"); + + // Get the action object into a json string + try (XContentBuilder builder = XContentFactory.jsonBuilder() ) { + builder.startObject(); + for (Map.Entry entry : actionField.entrySet()) { + builder.field(entry.getKey(), entry.getValue()); + } + builder.endObject(); + String actionFieldJson = builder.toString(); + request.params().put("action", actionFieldJson); + } catch (IOException e) { + logger.error(e); + } + + String sOpType = request.param("op_type"); + String waitForActiveShards = request.param("wait_for_active_shards"); + if (waitForActiveShards != null) { + indexRequest.waitForActiveShards(ActiveShardCount.parseString(waitForActiveShards)); + } + if (sOpType != null) { + indexRequest.opType(sOpType); + } + + return channel -> client.index( + indexRequest, + new RestStatusToXContentListener<>(channel, r -> r.getLocation(indexRequest.routing())) + ); + } +} From cb47c418c4f9bf092dd79db569299062cb016a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Ruiz?= Date: Thu, 19 Sep 2024 13:25:26 +0200 Subject: [PATCH 02/27] Revert guava dependency --- plugins/command-manager/build.gradle | 4 - .../commandmanager/CommandManagerPlugin.java | 25 ++- .../rest/action/RestPostCommandAction.java | 159 +++++++++--------- 3 files changed, 90 insertions(+), 98 deletions(-) diff --git a/plugins/command-manager/build.gradle b/plugins/command-manager/build.gradle index 2b5f23b..7defcd1 100644 --- a/plugins/command-manager/build.gradle +++ b/plugins/command-manager/build.gradle @@ -83,10 +83,6 @@ repositories { maven { url "https://plugins.gradle.org/m2/" } } -dependencies { - implementation("com.google.guava:guava:33.3.0-jre") -} - test { include '**/*Tests.class' } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java index 6ea3809..ba79045 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java @@ -7,7 +7,6 @@ */ package com.wazuh.commandmanager; -import com.google.common.collect.ImmutableList; import com.wazuh.commandmanager.rest.action.RestPostCommandAction; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -22,6 +21,7 @@ import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; +import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -31,16 +31,15 @@ public class CommandManagerPlugin extends Plugin implements ActionPlugin { public static final String COMMAND_MANAGER_INDEX_NAME = "command-manager"; private static final Logger log = LogManager.getLogger(CommandManagerPlugin.class); - public List getRestHandlers( - Settings settings, - RestController restController, - ClusterSettings clusterSettings, - IndexScopedSettings indexScopedSettings, - SettingsFilter settingsFilter, - IndexNameExpressionResolver indexNameExpressionResolver, - Supplier nodesInCluster - ) { - RestPostCommandAction restPostCommandAction = new RestPostCommandAction(); - return ImmutableList.of(restPostCommandAction); - } + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { + return Collections.singletonList(new RestPostCommandAction()); + } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 78f824a..78bc820 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -8,106 +8,103 @@ import org.opensearch.action.support.ActiveShardCount; import org.opensearch.client.node.NodeClient; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.core.xcontent.XContent; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.index.VersionType; import org.opensearch.rest.BaseRestHandler; -import com.google.common.collect.ImmutableList; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestActions; import org.opensearch.rest.action.RestStatusToXContentListener; import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import java.util.*; import static org.opensearch.rest.RestRequest.Method.POST; public class RestPostCommandAction extends BaseRestHandler { - public static final String POST_COMMAND_ACTION_REQUEST_DETAILS = "post_command_action_request_details"; + public static final String POST_COMMAND_ACTION_REQUEST_DETAILS = "post_command_action_request_details"; - private final Logger logger = LogManager.getLogger(RestPostCommandAction.class); + private final Logger logger = LogManager.getLogger(RestPostCommandAction.class); - public String getName() { - return POST_COMMAND_ACTION_REQUEST_DETAILS; - } + public String getName() { + return POST_COMMAND_ACTION_REQUEST_DETAILS; + } - @Override - public List routes() { - return ImmutableList.of( - new Route( - POST, - String.format(Locale.ROOT, "%s/%s/{%s}", - CommandManagerPlugin.COMMAND_MANAGER_BASE_URI, - "create", - "id" - ) - ) - ); - } + @Override + public List routes() { + return Collections.singletonList( + new Route( + POST, + String.format( + Locale.ROOT, + "%s/%s/{%s}", + CommandManagerPlugin.COMMAND_MANAGER_BASE_URI, + "create", + "id" + ) + ) + ); + } - @Override - public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { - IndexRequest indexRequest = new IndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); - indexRequest.id(request.param("id")); - indexRequest.routing(request.param("routing")); - indexRequest.setPipeline(request.param("pipeline")); - indexRequest.source(request.requiredContent(), request.getMediaType()); - indexRequest.timeout(request.paramAsTime("timeout", IndexRequest.DEFAULT_TIMEOUT)); - indexRequest.setRefreshPolicy(request.param("refresh")); - indexRequest.version(RestActions.parseVersion(request)); - indexRequest.versionType(VersionType.fromString(request.param("version_type"), indexRequest.versionType())); - indexRequest.setIfSeqNo(request.paramAsLong("if_seq_no", indexRequest.ifSeqNo())); - indexRequest.setIfPrimaryTerm(request.paramAsLong("if_primary_term", indexRequest.ifPrimaryTerm())); - indexRequest.setRequireAlias(request.paramAsBoolean(DocWriteRequest.REQUIRE_ALIAS, indexRequest.isRequireAlias())); + @Override + protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) { + IndexRequest indexRequest = new IndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); + indexRequest.id(request.param("id")); + indexRequest.routing(request.param("routing")); + indexRequest.setPipeline(request.param("pipeline")); + indexRequest.source(request.requiredContent(), request.getMediaType()); + indexRequest.timeout(request.paramAsTime("timeout", IndexRequest.DEFAULT_TIMEOUT)); + indexRequest.setRefreshPolicy(request.param("refresh")); + indexRequest.version(RestActions.parseVersion(request)); + indexRequest.versionType(VersionType.fromString(request.param("version_type"), indexRequest.versionType())); + indexRequest.setIfSeqNo(request.paramAsLong("if_seq_no", indexRequest.ifSeqNo())); + indexRequest.setIfPrimaryTerm(request.paramAsLong("if_primary_term", indexRequest.ifPrimaryTerm())); + indexRequest.setRequireAlias(request.paramAsBoolean(DocWriteRequest.REQUIRE_ALIAS, indexRequest.isRequireAlias())); - // Source. One of [Users/Services (via Management API), Engine (via Management API), Content manager (directly)] - request.params().putIfAbsent("source", "engine"); - // User. The user that originated the request. This user may represent a Management API or Indexer API user depending on the source. - request.params().putIfAbsent("user", "admin"); - // Target. Cluster name destination. - request.params().putIfAbsent("target", "wazuh-cluster"); - // Type. One of [Agent groups, Agent, Server cluster] - request.params().putIfAbsent("type", "agent"); - // Timeout. Number of seconds to wait for the command to be executed. - request.params().putIfAbsent("timeout", "120"); - // Action - Map actionField = new HashMap<>(); - // Type. One of One of [Agent groups, Agent, Server cluster] - actionField.put("type", "agent"); - // Params. Additional parameters for the action. - actionField.put("params", "--help"); - // Version. Version of the action. - actionField.put("version", "1.0"); + // Source. One of [Users/Services (via Management API), Engine (via Management API), Content manager (directly)] + request.params().putIfAbsent("source", "engine"); + // User. The user that originated the request. This user may represent a Management API or Indexer API user depending on the source. + request.params().putIfAbsent("user", "admin"); + // Target. Cluster name destination. + request.params().putIfAbsent("target", "wazuh-cluster"); + // Type. One of [Agent groups, Agent, Server cluster] + request.params().putIfAbsent("type", "agent"); + // Timeout. Number of seconds to wait for the command to be executed. + request.params().putIfAbsent("timeout", "120"); + // Action + Map actionField = new HashMap<>(); + // Type. One of [Agent groups, Agent, Server cluster] + actionField.put("type", "agent"); + // Params. Additional parameters for the action. + actionField.put("params", "--help"); + // Version. Version of the action. + actionField.put("version", "1.0"); - // Get the action object into a json string - try (XContentBuilder builder = XContentFactory.jsonBuilder() ) { - builder.startObject(); - for (Map.Entry entry : actionField.entrySet()) { - builder.field(entry.getKey(), entry.getValue()); - } - builder.endObject(); - String actionFieldJson = builder.toString(); - request.params().put("action", actionFieldJson); - } catch (IOException e) { - logger.error(e); - } + // Get the action object into a json string + try (XContentBuilder builder = XContentFactory.jsonBuilder()) { + builder.startObject(); + for (Map.Entry entry : actionField.entrySet()) { + builder.field(entry.getKey(), entry.getValue()); + } + builder.endObject(); + String actionFieldJson = builder.toString(); + request.params().put("action", actionFieldJson); + } catch (IOException e) { + logger.error(e); + } - String sOpType = request.param("op_type"); - String waitForActiveShards = request.param("wait_for_active_shards"); - if (waitForActiveShards != null) { - indexRequest.waitForActiveShards(ActiveShardCount.parseString(waitForActiveShards)); - } - if (sOpType != null) { - indexRequest.opType(sOpType); - } + String sOpType = request.param("op_type"); + String waitForActiveShards = request.param("wait_for_active_shards"); + if (waitForActiveShards != null) { + indexRequest.waitForActiveShards(ActiveShardCount.parseString(waitForActiveShards)); + } + if (sOpType != null) { + indexRequest.opType(sOpType); + } - return channel -> client.index( - indexRequest, - new RestStatusToXContentListener<>(channel, r -> r.getLocation(indexRequest.routing())) - ); - } + return channel -> client.index( + indexRequest, + new RestStatusToXContentListener<>(channel, r -> r.getLocation(indexRequest.routing())) + ); + } } From 789fae11de829042b26842019749f8b080792db2 Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 19 Sep 2024 10:41:46 -0300 Subject: [PATCH 03/27] Add required fields if not present in the request --- .../rest/action/RestPostCommandAction.java | 60 +++++++++++++++---- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 78bc820..c0f10b7 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -8,7 +8,9 @@ import org.opensearch.action.support.ActiveShardCount; import org.opensearch.client.node.NodeClient; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.xcontent.XContent; import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.VersionType; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestRequest; @@ -47,12 +49,12 @@ public List routes() { } @Override - protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) { + protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { IndexRequest indexRequest = new IndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); + // ID. Document ID. Generated combining the Order ID and the Command Request ID. indexRequest.id(request.param("id")); indexRequest.routing(request.param("routing")); indexRequest.setPipeline(request.param("pipeline")); - indexRequest.source(request.requiredContent(), request.getMediaType()); indexRequest.timeout(request.paramAsTime("timeout", IndexRequest.DEFAULT_TIMEOUT)); indexRequest.setRefreshPolicy(request.param("refresh")); indexRequest.version(RestActions.parseVersion(request)); @@ -61,17 +63,31 @@ protected RestChannelConsumer prepareRequest(final RestRequest request, final No indexRequest.setIfPrimaryTerm(request.paramAsLong("if_primary_term", indexRequest.ifPrimaryTerm())); indexRequest.setRequireAlias(request.paramAsBoolean(DocWriteRequest.REQUIRE_ALIAS, indexRequest.isRequireAlias())); + Map requestBodyMap; + + try ( XContentParser requestBodyXContent = request.contentParser() ) { + requestBodyMap = requestBodyXContent.map(); + } catch (IOException e) { + throw new RuntimeException(e); + } + // Source. One of [Users/Services (via Management API), Engine (via Management API), Content manager (directly)] - request.params().putIfAbsent("source", "engine"); + requestBodyMap.putIfAbsent("source", "engine"); // User. The user that originated the request. This user may represent a Management API or Indexer API user depending on the source. - request.params().putIfAbsent("user", "admin"); + requestBodyMap.putIfAbsent("user", "admin"); // Target. Cluster name destination. - request.params().putIfAbsent("target", "wazuh-cluster"); + requestBodyMap.putIfAbsent("target", "wazuh-cluster"); // Type. One of [Agent groups, Agent, Server cluster] - request.params().putIfAbsent("type", "agent"); + requestBodyMap.putIfAbsent("type", "agent"); // Timeout. Number of seconds to wait for the command to be executed. - request.params().putIfAbsent("timeout", "120"); - // Action + requestBodyMap.putIfAbsent("timeout", "120"); + // Command Request ID. Unique identifier generated by the Command Manager. Auto-incremental. + //assert requestBodyMap.containsKey("request_id"): "No request_id provided"; + // Order ID. Unique identifier generated by the Command Manager. Auto-incremental within the same Command Request ID. + //assert requestBodyMap.containsKey("order_id"): "No order_id provided"; + + + // Action object Map actionField = new HashMap<>(); // Type. One of [Agent groups, Agent, Server cluster] actionField.put("type", "agent"); @@ -80,7 +96,7 @@ protected RestChannelConsumer prepareRequest(final RestRequest request, final No // Version. Version of the action. actionField.put("version", "1.0"); - // Get the action object into a json string + // Get the Action object into a json string try (XContentBuilder builder = XContentFactory.jsonBuilder()) { builder.startObject(); for (Map.Entry entry : actionField.entrySet()) { @@ -88,11 +104,35 @@ protected RestChannelConsumer prepareRequest(final RestRequest request, final No } builder.endObject(); String actionFieldJson = builder.toString(); - request.params().put("action", actionFieldJson); + requestBodyMap.putIfAbsent("action", actionFieldJson); } catch (IOException e) { logger.error(e); } + // Result object + Map resultField = new HashMap<>(); + // Code. Result code + resultField.put("code", ""); + // Message. Description of the result + resultField.put("message", ""); + // Data. Additional data + resultField.put("data", ""); + + // Get the Result object into a json string + try (XContentBuilder builder = XContentFactory.jsonBuilder()) { + builder.startObject(); + for (Map.Entry entry : resultField.entrySet()) { + builder.field(entry.getKey(), entry.getValue()); + } + builder.endObject(); + String resultFieldJson = builder.toString(); + requestBodyMap.putIfAbsent("result", resultFieldJson); + } catch (IOException e) { + logger.error(e); + } + + + indexRequest.source(requestBodyMap, request.getMediaType()); String sOpType = request.param("op_type"); String waitForActiveShards = request.param("wait_for_active_shards"); if (waitForActiveShards != null) { From 05e1a0be3540d60b5cba878e4aa987a46b6290a3 Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 19 Sep 2024 12:47:12 -0300 Subject: [PATCH 04/27] Adding PostCommandRequest class to validate input --- .../rest/action/RestPostCommandAction.java | 1 - .../rest/request/PostCommandRequest.java | 224 ++++++++++++++++++ 2 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/request/PostCommandRequest.java diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index c0f10b7..d4f7e78 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -8,7 +8,6 @@ import org.opensearch.action.support.ActiveShardCount; import org.opensearch.client.node.NodeClient; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.core.xcontent.XContent; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.VersionType; diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/request/PostCommandRequest.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/request/PostCommandRequest.java new file mode 100644 index 0000000..fa6ad0c --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/request/PostCommandRequest.java @@ -0,0 +1,224 @@ +package com.wazuh.commandmanager.rest.request; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.core.xcontent.XContentParserUtils; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +public class PostCommandRequest extends ActionRequest { + + private static String documentId; + private static String commandOrderId; + private static String commandRequestId; + private static String commandSource; + private static String commandTarget; + private static String commandTimeout; + private static String commandType; + private static String commandUser; + private static Map commandAction; + private static Map commandResult; + + public static final String DOCUMENT_ID = "document_id"; + public static final String COMMAND_ORDER_ID = "command_order_id"; + public static final String COMMAND_REQUEST_ID = "command_request_id"; + public static final String COMMAND_SOURCE = "command_source"; + public static final String COMMAND_TARGET = "command_target"; + public static final String COMMAND_TIMEOUT = "command_timeout"; + public static final String COMMAND_TYPE = "command_type"; + public static final String COMMAND_USER = "command_user"; + public static final String COMMAND_ACTION = "command_action"; + public static final String COMMAND_RESULT = "command_result"; + + public PostCommandRequest(StreamInput in) throws IOException { + super(in); + documentId = in.readString(); + commandOrderId = in.readString(); + commandRequestId = in.readString(); + commandSource = in.readOptionalString(); + commandTarget = in.readString(); + commandTimeout = in.readString(); + commandType = in.readString(); + commandUser = in.readString(); + commandAction = in.readMap(); + commandResult = in.readMap(); + } + + public PostCommandRequest( + String documentId, + String commandOrderId, + String commandRequestId, + String commandSource, + String commandTarget, + String commandTimeout, + String commandType, + String commandUser, + Map commandAction, + Map commandResult + ) { + super(); + this.documentId = documentId; + this.commandOrderId = Objects.requireNonNull(commandOrderId); + this.commandRequestId = Objects.requireNonNull(commandRequestId); + this.commandSource = Objects.requireNonNull(commandSource); + this.commandTarget = Objects.requireNonNull(commandTarget); + this.commandTimeout = Objects.requireNonNull(commandTimeout); + this.commandType = Objects.requireNonNull(commandType); + this.commandUser = Objects.requireNonNull(commandUser); + this.commandAction = Objects.requireNonNull(commandAction); + this.commandResult = Objects.requireNonNull(commandResult); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeOptionalString(documentId); + out.writeString(commandOrderId); + out.writeString(commandRequestId); + out.writeString(commandSource); + out.writeString(commandTarget); + out.writeString(commandTimeout); + out.writeString(commandType); + out.writeString(commandUser); + out.writeMap(commandAction); + out.writeMap(commandResult); + } + + public String getDocumentId() { + return documentId; + } + public void setDocumentId(String documentId) { + this.documentId = documentId; + } + + public String getCommandOrderId() { + return commandOrderId; + } + public void setCommandOrderId(String commandOrderId) { + this.commandOrderId = commandOrderId; + } + + public String getCommandRequestId() { + return commandRequestId; + } + public void setCommandRequestId(String commandRequestId) { + this.commandRequestId = commandRequestId; + } + + public String getCommandSource() { + return commandSource; + } + public void setCommandSource(String commandSource) { + this.commandSource = commandSource; + } + + public String getCommandTarget() { + return commandTarget; + } + public void setCommandTarget(String commandTarget) { + this.commandTarget = commandTarget; + } + + public String getCommandTimeout() { + return commandTimeout; + } + public void setCommandTimeout(String commandTimeout) { + this.commandTimeout = commandTimeout; + } + + public String getCommandType() { + return commandType; + } + public void setCommandType(String commandType) { + this.commandType = commandType; + } + + public String getCommandUser() { + return commandUser; + } + public void setCommandUser(String commandUser) { + this.commandUser = commandUser; + } + + public Map getCommandAction() { + return commandAction; + } + public void setCommandAction(Map commandAction) { + this.commandAction = commandAction; + } + + public Map getCommandResult() { + return commandResult; + } + public void setCommandResult(Map commandResult) { + this.commandResult = commandResult; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public static PostCommandRequest parse(XContentParser parser) throws IOException { + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser); + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + String fieldName = parser.currentName(); + parser.nextToken(); + + switch (fieldName) { + case DOCUMENT_ID: + documentId = parser.textOrNull(); + break; + case COMMAND_ORDER_ID: + commandOrderId = parser.textOrNull(); + break; + case COMMAND_REQUEST_ID: + commandRequestId = parser.textOrNull(); + break; + case COMMAND_SOURCE: + commandSource = parser.textOrNull(); + break; + case COMMAND_TARGET: + commandTarget = parser.textOrNull(); + break; + case COMMAND_TIMEOUT: + commandTimeout = parser.textOrNull(); + break; + case COMMAND_TYPE: + commandType = parser.textOrNull(); + break; + case COMMAND_USER: + commandUser = parser.textOrNull(); + break; + case COMMAND_ACTION: + commandAction = parser.map(); + break; + case COMMAND_RESULT: + commandResult = parser.map(); + break; + default: + parser.skipChildren(); + break; + } + + } + return new PostCommandRequest( + documentId, + commandOrderId, + commandRequestId, + commandSource, + commandTarget, + commandTimeout, + commandType, + commandUser, + commandAction, + commandResult + ); + } +} From 117b5c0f6e9c99accf669bbf78890e66337571ee Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 19 Sep 2024 16:39:57 -0300 Subject: [PATCH 05/27] Adding CommandDetails model class --- .../commandmanager/model/CommandDetails.java | 307 ++++++++++++++++++ .../rest/action/RestPostCommandAction.java | 37 ++- 2 files changed, 335 insertions(+), 9 deletions(-) create mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/CommandDetails.java diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/CommandDetails.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/CommandDetails.java new file mode 100644 index 0000000..b7082f8 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/CommandDetails.java @@ -0,0 +1,307 @@ +package com.wazuh.commandmanager.model; + +import org.opensearch.common.Nullable; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; + +public class CommandDetails implements ToXContentObject { + + + private String commandOrderId; + private String commandRequestId; + private String commandSource; + private String commandTarget; + private String commandTimeout; + private String commandType; + private String commandUser; + private Map commandAction; + private Map commandResult; + + public static final String DOCUMENT_ID = "document_id"; + public static final String COMMAND_ORDER_ID = "command_order_id"; + public static final String COMMAND_REQUEST_ID = "command_request_id"; + public static final String COMMAND_SOURCE = "command_source"; + public static final String COMMAND_TARGET = "command_target"; + public static final String COMMAND_TIMEOUT = "command_timeout"; + public static final String COMMAND_TYPE = "command_type"; + public static final String COMMAND_USER = "command_user"; + public static final String COMMAND_ACTION = "command_action"; + public static final String COMMAND_RESULT = "command_result"; + + public CommandDetails() {} + + public CommandDetails( + String commandOrderId, + String commandRequestId, + String commandSource, + String commandTarget, + String commandTimeout, + String commandType, + String commandUser, + Map commandAction, + Map commandResult + ) { + this.commandOrderId = commandOrderId; + this.commandRequestId = commandRequestId; + this.commandSource = commandSource; + this.commandTarget = commandTarget; + this.commandTimeout = commandTimeout; + this.commandType = commandType; + this.commandUser = commandUser; + this.commandAction = commandAction; + this.commandResult = commandResult; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { + XContentBuilder xContentBuilder = builder.startObject(); + if (commandOrderId != null) { + xContentBuilder.field(COMMAND_ORDER_ID, commandOrderId); + } + if (commandRequestId != null) { + xContentBuilder.field(COMMAND_REQUEST_ID, commandRequestId); + } + if (commandSource != null) { + xContentBuilder.field(COMMAND_SOURCE, commandSource); + } + if (commandTarget != null) { + xContentBuilder.field(COMMAND_TARGET, commandTarget); + } + if (commandTimeout != null) { + xContentBuilder.field(COMMAND_TIMEOUT, commandTimeout); + } + if (commandType != null) { + xContentBuilder.field(COMMAND_TYPE, commandType); + } + if (commandUser != null) { + xContentBuilder.field(COMMAND_USER, commandUser); + } + if (commandAction != null) { + xContentBuilder.field(COMMAND_ACTION, commandAction); + } + if (commandResult != null) { + xContentBuilder.field(COMMAND_RESULT, commandResult); + } + return xContentBuilder.endObject(); + } + + public static CommandDetails parse(XContentParser parser) throws IOException { + String commandOrderId = null; + String commandRequestId = null; + String commandSource = null; + String commandTarget = null; + String commandTimeout = null; + String commandType = null; + String commandUser = null; + Map commandAction = null; + Map commandResult = null; + + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser); + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + String fieldName = parser.currentName(); + parser.nextToken(); + switch (fieldName) { + case COMMAND_ORDER_ID: + commandOrderId = parser.text(); + break; + case COMMAND_REQUEST_ID: + commandRequestId = parser.text(); + break; + case COMMAND_SOURCE: + commandSource = parser.text(); + break; + case COMMAND_TARGET: + commandTarget = parser.text(); + break; + case COMMAND_TIMEOUT: + commandTimeout = parser.text(); + break; + case COMMAND_TYPE: + commandType = parser.text(); + break; + case COMMAND_USER: + commandUser = parser.text(); + break; + case COMMAND_ACTION: + commandAction = parser.map(); + break; + case COMMAND_RESULT: + commandResult = parser.map(); + break; + default: + parser.skipChildren(); + break; + } + } + + return new CommandDetails( + commandOrderId, + commandRequestId, + commandSource, + commandTarget, + commandTimeout, + commandType, + commandUser, + commandAction, + commandResult + ); + } + + public CommandDetails(final CommandDetails copyCommandDetails) { + this( + copyCommandDetails.commandOrderId, + copyCommandDetails.commandRequestId, + copyCommandDetails.commandSource, + copyCommandDetails.commandTarget, + copyCommandDetails.commandTimeout, + copyCommandDetails.commandType, + copyCommandDetails.commandUser, + copyCommandDetails.commandAction, + copyCommandDetails.commandResult + ); + } + + @Nullable + public String getCommandOrderId() { + return commandOrderId; + } + public void setCommandOrderId(String commandOrderId) { + this.commandOrderId = commandOrderId; + } + + @Nullable + public String getCommandRequestId() { + return commandRequestId; + } + public void setCommandRequestId(String commandRequestId) { + this.commandRequestId = commandRequestId; + } + + @Nullable + public String getCommandSource() { + return commandSource; + } + public void setCommandSource(String commandSource) { + this.commandSource = commandSource; + } + + @Nullable + public String getCommandTarget() { + return commandTarget; + } + public void setCommandTarget(String commandTarget) { + this.commandTarget = commandTarget; + } + + @Nullable + public String getCommandTimeout() { + return commandTimeout; + } + public void setCommandTimeout(String commandTimeout) { + this.commandTimeout = commandTimeout; + } + + @Nullable + public String getCommandType() { + return commandType; + } + public void setCommandType(String commandType) { + this.commandType = commandType; + } + + @Nullable + public String getCommandUser() { + return commandUser; + } + public void setCommandUser(String commandUser) { + this.commandUser = commandUser; + } + + @Nullable + public Map getCommandAction() { + return commandAction; + } + public void setCommandAction(Map commandAction) { + this.commandAction = commandAction; + } + + @Nullable + public Map getCommandResult() { + return commandResult; + } + public void setCommandResult(Map commandResult) { + this.commandResult = commandResult; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CommandDetails that = (CommandDetails) o; + return Objects.equals(commandOrderId, that.commandOrderId) + && Objects.equals(commandRequestId, that.commandRequestId) + && Objects.equals(commandSource, that.commandSource) + && Objects.equals(commandTarget, that.commandTarget) + && Objects.equals(commandTimeout, that.commandTimeout) + && Objects.equals(commandType, that.commandType) + && Objects.equals(commandUser, that.commandUser) + && Objects.equals(commandAction, that.commandAction) + && Objects.equals(commandResult, that.commandResult); + } + + @Override + public int hashCode() { + return Objects.hash( + commandOrderId, + commandRequestId, + commandSource, + commandTarget, + commandTimeout, + commandType, + commandUser, + commandAction, + commandResult + ); + } + + @Override + public String toString() { + return "CommandDetails{" + + "commandOrderId='" + + commandOrderId + + '\'' + + ", commandRequestId='" + + commandRequestId + + '\'' + + ", commandSource='" + + commandSource + + '\'' + + ", commandTarget='" + + commandTarget + + '\'' + + ", commandTimeout='" + + commandTimeout + + '\'' + + ", commandType='" + + commandType + + '\'' + + ", commandUser='" + + commandUser + + '\'' + + ", commandAction='" + + commandAction + + '\'' + + ", commandResult='" + + commandResult + + '\'' + + '}'; + } +} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index d4f7e78..cf1781b 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -1,6 +1,7 @@ package com.wazuh.commandmanager.rest.action; import com.wazuh.commandmanager.CommandManagerPlugin; +import com.wazuh.commandmanager.rest.request.PostCommandRequest; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.action.DocWriteRequest; @@ -19,6 +20,7 @@ import java.io.IOException; import java.util.*; +import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; import static org.opensearch.rest.RestRequest.Method.POST; public class RestPostCommandAction extends BaseRestHandler { @@ -34,21 +36,22 @@ public String getName() { @Override public List routes() { return Collections.singletonList( - new Route( - POST, - String.format( - Locale.ROOT, - "%s/%s/{%s}", - CommandManagerPlugin.COMMAND_MANAGER_BASE_URI, - "create", - "id" - ) + new Route( + POST, + String.format( + Locale.ROOT, + "%s/%s/{%s}", + CommandManagerPlugin.COMMAND_MANAGER_BASE_URI, + "create", + "id" ) + ) ); } @Override protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { + IndexRequest indexRequest = new IndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); // ID. Document ID. Generated combining the Order ID and the Command Request ID. indexRequest.id(request.param("id")); @@ -62,6 +65,22 @@ protected RestChannelConsumer prepareRequest(final RestRequest request, final No indexRequest.setIfPrimaryTerm(request.paramAsLong("if_primary_term", indexRequest.ifPrimaryTerm())); indexRequest.setRequireAlias(request.paramAsBoolean(DocWriteRequest.REQUIRE_ALIAS, indexRequest.isRequireAlias())); + XContentParser parser = request.contentParser(); + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser); + + PostCommandRequest postCommandRequest = PostCommandRequest.parse(parser); + + String documentId = request.param(PostCommandRequest.DOCUMENT_ID); + String commandOrderId = postCommandRequest.getCommandOrderId(); + String commandRequestId = postCommandRequest.getCommandRequestId(); + String commandSource = postCommandRequest.getCommandSource(); + String commandTarget = postCommandRequest.getCommandTarget(); + String commandTimeout = postCommandRequest.getCommandTimeout(); + String commandType = postCommandRequest.getCommandType(); + String commandUser = postCommandRequest.getCommandUser(); + Map commandAction = postCommandRequest.getCommandAction(); + Map commandResult = postCommandRequest.getCommandResult(); + Map requestBodyMap; try ( XContentParser requestBodyXContent = request.contentParser() ) { From b7b28d39316402a6f4c41b300883c65f2f08d631 Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 19 Sep 2024 16:51:25 -0300 Subject: [PATCH 06/27] Adding CommandManagerService class to handle CRUD operations --- .../utils/CommandManagerService.java | 269 ++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java new file mode 100644 index 0000000..999b668 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java @@ -0,0 +1,269 @@ +package com.wazuh.commandmanager.utils; + +import com.wazuh.commandmanager.CommandManagerPlugin; +import com.wazuh.commandmanager.model.CommandDetails; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.ResourceAlreadyExistsException; +import org.opensearch.action.DocWriteResponse; +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.delete.DeleteRequest; +import org.opensearch.action.get.GetRequest; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.update.UpdateRequest; +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.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.index.IndexNotFoundException; +import org.opensearch.index.engine.DocumentMissingException; +import org.opensearch.index.engine.VersionConflictEngineException; +import org.opensearch.index.seqno.SequenceNumbers; +import org.opensearch.index.shard.IndexingOperationListener; + +import java.io.IOException; +import java.util.Map; + +public class CommandManagerService implements IndexingOperationListener { + + private static final Logger logger = LogManager.getLogger(CommandManagerService.class); + + private final Client client; + private final ClusterService clusterService; + + public CommandManagerService( + final Client client, + final ClusterService clusterService + ) { + this.client = client; + this.clusterService = clusterService; + } + + public boolean commandManagerIndexExists() { + return clusterService.state().routingTable().hasIndex(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); + } + + public void processCommand( + final String documentId, + final String commandOrderId, + final String commandRequestId, + final String commandSource, + final String commandTarget, + final String commandTimeout, + final String commandType, + final String commandUser, + final Map commandAction, + final Map commandResult, + ActionListener listener + ) { + // Validate command detail params + if ( + commandOrderId == null + || commandOrderId.isEmpty() + || commandRequestId == null + || commandRequestId.isEmpty() + || commandSource == null + || commandSource.isEmpty() + || commandTarget == null + || commandTarget.isEmpty() + || commandTimeout == null + || commandTimeout.isEmpty() + || commandType == null + || commandType.isEmpty() + || commandUser == null + || commandUser.isEmpty() + || commandAction == null + || commandAction.isEmpty() + || commandResult == null + || commandResult.isEmpty() + ) { + listener.onFailure( + new IllegalArgumentException( + "command_order_id, command_request_id, command_source, command_target, command_timeout, command_type, command_user, command_action, command_result: are mandatory fields" + ) + ); + } else { + // Ensure command details index has been created + createCommandManagerIndex(ActionListener.wrap(created -> { + if (created) { + try { + // Update entry request + if (documentId != null) { + // Recover entry via documentId + findCommandDetails(documentId, ActionListener.wrap(existingCommandDetails -> { + CommandDetails updateCommandDetails = new CommandDetails(existingCommandDetails); + + // Set updated fields + updateCommandDetails.setCommandOrderId(commandOrderId); + updateCommandDetails.setCommandRequestId(commandRequestId); + updateCommandDetails.setCommandSource(commandSource); + updateCommandDetails.setCommandTarget(commandTarget); + updateCommandDetails.setCommandTimeout(commandTimeout); + updateCommandDetails.setCommandType(commandType); + updateCommandDetails.setCommandUser(commandUser); + updateCommandDetails.setCommandAction(commandAction); + updateCommandDetails.setCommandResult(commandResult); + + // Send update Request + updateCommandDetails(documentId, updateCommandDetails, listener); + }, listener::onFailure)); + } else { + // Create CommandDetails from params + CommandDetails tempCommandDetails = new CommandDetails( + commandOrderId, + commandRequestId, + commandSource, + commandTarget, + commandTimeout, + commandType, + commandUser, + commandAction, + commandResult + ); + + // Index new command Details entry + logger.info( + "Creating command details" + " : " + tempCommandDetails.toString() + ); + createCommandDetails(tempCommandDetails, listener); + } + } catch (VersionConflictEngineException e) { + logger.debug("could not process command" + commandOrderId, e.getMessage()); + listener.onResponse(null); + } + } else { + listener.onResponse(null); + } + }, listener::onFailure)); + } + } + + private void createCommandDetails(final CommandDetails tempCommandDetails, ActionListener listener) { + try { + // Create index request, document Id will be randomly generated + final IndexRequest request = new IndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME).source( + tempCommandDetails.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) + ).setIfSeqNo(SequenceNumbers.UNASSIGNED_SEQ_NO).setIfPrimaryTerm(SequenceNumbers.UNASSIGNED_PRIMARY_TERM).create(true); + + client.index(request, ActionListener.wrap(response -> { listener.onResponse(response.getId()); }, exception -> { + if (exception instanceof IOException) { + logger.error("IOException occurred creating command details", exception); + } + listener.onResponse(null); + })); + } catch (IOException e) { + logger.error("IOException occurred creating command details", e); + listener.onResponse(null); + } + } + + /** + * Find command details for a particular document Id + * @param documentId unique id for command Details document + * @param listener an {@code ActionListener} that has onResponse and onFailure that is used to return the command details if it was found + * or else null. + */ + private void findCommandDetails(final String documentId, ActionListener listener) { + GetRequest getRequest = new GetRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME).id(documentId); + client.get(getRequest, ActionListener.wrap(response -> { + if (!response.isExists()) { + listener.onResponse(null); + } else { + try { + XContentParser parser = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getSourceAsString()); + parser.nextToken(); + listener.onResponse(CommandDetails.parse(parser)); + } catch (IOException e) { + logger.error("IOException occurred finding CommandDetails for documentId " + documentId, e); + listener.onResponse(null); + } + } + }, exception -> { + logger.error("Exception occurred finding command details for documentId " + documentId, exception); + listener.onFailure(exception); + })); + } + + /** + * Delete command details to a corresponding document Id + * @param documentId unique id to find and delete the command details document in the index + * @param listener an {@code ActionListener} that has onResponse and onFailure that is used to return the command details if it was deleted + * or else null. + */ + public void deleteCommandDetails(final String documentId, ActionListener listener) { + DeleteRequest deleteRequest = new DeleteRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME).id(documentId); + client.delete(deleteRequest, ActionListener.wrap(response -> { + listener.onResponse( + response.getResult() == DocWriteResponse.Result.DELETED || response.getResult() == DocWriteResponse.Result.NOT_FOUND + ); + }, exception -> { + if (exception instanceof IndexNotFoundException || exception.getCause() instanceof IndexNotFoundException) { + logger.debug("Index is not found to delete command details for document id. {} " + documentId, exception.getMessage()); + listener.onResponse(true); + } else { + listener.onFailure(exception); + } + })); + } + + /** + * Update command details to a corresponding documentId + * @param updateCommandDetails update command details object entry + * @param documentId unique id to find and update the corresponding document mapped to it + * @param listener an {@code ActionListener} that has onResponse and onFailure that is used to return the command details if it was updated + * or else null. + */ + private void updateCommandDetails(final String documentId, final CommandDetails updateCommandDetails, ActionListener listener) { + try { + UpdateRequest updateRequest = new UpdateRequest().index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) + .id(documentId) + .doc(updateCommandDetails.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .fetchSource(true); + + client.update(updateRequest, ActionListener.wrap(response -> listener.onResponse(response.getId()), exception -> { + if (exception instanceof VersionConflictEngineException) { + logger.debug("could not update command details for documentId " + documentId, exception.getMessage()); + } + if (exception instanceof DocumentMissingException) { + logger.debug("Document is deleted. This happens if the command details is already removed {}", exception.getMessage()); + } + if (exception instanceof IOException) { + logger.error("IOException occurred in updating command details.", exception); + } + listener.onResponse(null); + })); + } catch (IOException e) { + logger.error("IOException occurred updating command details for documentId " + documentId, e); + listener.onResponse(null); + } + } + /** + * + * @param listener an {@code ActionListener} that has onResponse and onFailure that is used to return the command details index if it was created + * or else null. + */ + void createCommandManagerIndex(ActionListener listener) { + if (commandManagerIndexExists()) { + listener.onResponse(true); + } else { + CreateIndexRequest request = new CreateIndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); + client.admin() + .indices() + .create(request, ActionListener.wrap(response -> listener.onResponse(response.isAcknowledged()), exception -> { + if (exception instanceof ResourceAlreadyExistsException + || exception.getCause() instanceof ResourceAlreadyExistsException) { + listener.onResponse(true); + } else { + listener.onFailure(exception); + } + })); + } + } + +} From 49db3b75d7e3ad7812d1787f8c663dcb7eb16f16 Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 19 Sep 2024 17:14:03 -0300 Subject: [PATCH 07/27] Rewrite prepareRequest following opensearch's common practices --- .../rest/action/RestPostCommandAction.java | 172 ++++++++---------- .../utils/CommandManagerService.java | 1 + 2 files changed, 78 insertions(+), 95 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index cf1781b..bb0d058 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -2,6 +2,7 @@ import com.wazuh.commandmanager.CommandManagerPlugin; import com.wazuh.commandmanager.rest.request.PostCommandRequest; +import com.wazuh.commandmanager.utils.CommandManagerService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.action.DocWriteRequest; @@ -9,16 +10,23 @@ import org.opensearch.action.support.ActiveShardCount; import org.opensearch.client.node.NodeClient; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.index.VersionType; import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestActions; import org.opensearch.rest.action.RestStatusToXContentListener; import java.io.IOException; import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; import static org.opensearch.rest.RestRequest.Method.POST; @@ -29,6 +37,8 @@ public class RestPostCommandAction extends BaseRestHandler { private final Logger logger = LogManager.getLogger(RestPostCommandAction.class); + public CommandManagerService commandManagerService; + public String getName() { return POST_COMMAND_ACTION_REQUEST_DETAILS; } @@ -50,27 +60,12 @@ public List routes() { } @Override - protected RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException { - - IndexRequest indexRequest = new IndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); - // ID. Document ID. Generated combining the Order ID and the Command Request ID. - indexRequest.id(request.param("id")); - indexRequest.routing(request.param("routing")); - indexRequest.setPipeline(request.param("pipeline")); - indexRequest.timeout(request.paramAsTime("timeout", IndexRequest.DEFAULT_TIMEOUT)); - indexRequest.setRefreshPolicy(request.param("refresh")); - indexRequest.version(RestActions.parseVersion(request)); - indexRequest.versionType(VersionType.fromString(request.param("version_type"), indexRequest.versionType())); - indexRequest.setIfSeqNo(request.paramAsLong("if_seq_no", indexRequest.ifSeqNo())); - indexRequest.setIfPrimaryTerm(request.paramAsLong("if_primary_term", indexRequest.ifPrimaryTerm())); - indexRequest.setRequireAlias(request.paramAsBoolean(DocWriteRequest.REQUIRE_ALIAS, indexRequest.isRequireAlias())); - - XContentParser parser = request.contentParser(); + protected RestChannelConsumer prepareRequest(final RestRequest restRequest, final NodeClient client) throws IOException { + XContentParser parser = restRequest.contentParser(); ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser); PostCommandRequest postCommandRequest = PostCommandRequest.parse(parser); - - String documentId = request.param(PostCommandRequest.DOCUMENT_ID); + String documentId = restRequest.param(PostCommandRequest.DOCUMENT_ID); String commandOrderId = postCommandRequest.getCommandOrderId(); String commandRequestId = postCommandRequest.getCommandRequestId(); String commandSource = postCommandRequest.getCommandSource(); @@ -81,88 +76,75 @@ protected RestChannelConsumer prepareRequest(final RestRequest request, final No Map commandAction = postCommandRequest.getCommandAction(); Map commandResult = postCommandRequest.getCommandResult(); - Map requestBodyMap; - - try ( XContentParser requestBodyXContent = request.contentParser() ) { - requestBodyMap = requestBodyXContent.map(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - // Source. One of [Users/Services (via Management API), Engine (via Management API), Content manager (directly)] - requestBodyMap.putIfAbsent("source", "engine"); - // User. The user that originated the request. This user may represent a Management API or Indexer API user depending on the source. - requestBodyMap.putIfAbsent("user", "admin"); - // Target. Cluster name destination. - requestBodyMap.putIfAbsent("target", "wazuh-cluster"); - // Type. One of [Agent groups, Agent, Server cluster] - requestBodyMap.putIfAbsent("type", "agent"); - // Timeout. Number of seconds to wait for the command to be executed. - requestBodyMap.putIfAbsent("timeout", "120"); - // Command Request ID. Unique identifier generated by the Command Manager. Auto-incremental. - //assert requestBodyMap.containsKey("request_id"): "No request_id provided"; - // Order ID. Unique identifier generated by the Command Manager. Auto-incremental within the same Command Request ID. - //assert requestBodyMap.containsKey("order_id"): "No order_id provided"; - - - // Action object - Map actionField = new HashMap<>(); - // Type. One of [Agent groups, Agent, Server cluster] - actionField.put("type", "agent"); - // Params. Additional parameters for the action. - actionField.put("params", "--help"); - // Version. Version of the action. - actionField.put("version", "1.0"); - - // Get the Action object into a json string - try (XContentBuilder builder = XContentFactory.jsonBuilder()) { - builder.startObject(); - for (Map.Entry entry : actionField.entrySet()) { - builder.field(entry.getKey(), entry.getValue()); + CompletableFuture inProgressFuture = new CompletableFuture<>(); + + commandManagerService.processCommand( + documentId, + commandOrderId, + commandRequestId, + commandSource, + commandTarget, + commandTimeout, + commandType, + commandUser, + commandAction, + commandResult, + new ActionListener<>() { + @Override + public void onResponse(String indexedDocumentId) { + // Set document Id + inProgressFuture.complete(indexedDocumentId); + } + + @Override + public void onFailure(Exception e) { + logger.info("could not process job index", e); + inProgressFuture.completeExceptionally(e); + } } - builder.endObject(); - String actionFieldJson = builder.toString(); - requestBodyMap.putIfAbsent("action", actionFieldJson); - } catch (IOException e) { - logger.error(e); - } + ); - // Result object - Map resultField = new HashMap<>(); - // Code. Result code - resultField.put("code", ""); - // Message. Description of the result - resultField.put("message", ""); - // Data. Additional data - resultField.put("data", ""); - - // Get the Result object into a json string - try (XContentBuilder builder = XContentFactory.jsonBuilder()) { - builder.startObject(); - for (Map.Entry entry : resultField.entrySet()) { - builder.field(entry.getKey(), entry.getValue()); + try { + inProgressFuture.orTimeout(CommandManagerService.TIME_OUT_FOR_REQUEST, TimeUnit.SECONDS); + } catch (CompletionException e) { + if (e.getCause() instanceof TimeoutException) { + logger.error("Get Job Details timed out ", e); + } + if (e.getCause() instanceof RuntimeException) { + throw (RuntimeException) e.getCause(); + } else if (e.getCause() instanceof Error) { + throw (Error) e.getCause(); + } else { + throw new RuntimeException(e.getCause()); } - builder.endObject(); - String resultFieldJson = builder.toString(); - requestBodyMap.putIfAbsent("result", resultFieldJson); - } catch (IOException e) { - logger.error(e); } + return channel -> { + String commandDetailsResponseHolder = null; + try { + commandDetailsResponseHolder = inProgressFuture.get(); + } catch (Exception e) { + logger.error("Exception occured in get command details ", e); + } + XContentBuilder builder = channel.newBuilder(); + RestStatus restStatus = RestStatus.OK; + String restResponseString = commandDetailsResponseHolder != null ? "success" : "failed"; + BytesRestResponse bytesRestResponse; + try { + builder.startObject(); + builder.field("response", restResponseString); + if (restResponseString.equals("success")) { + builder.field(PostCommandRequest.DOCUMENT_ID, commandDetailsResponseHolder); + } else { + restStatus = RestStatus.INTERNAL_SERVER_ERROR; + } + builder.endObject(); + bytesRestResponse = new BytesRestResponse(restStatus, builder); + } finally { + builder.close(); + } - indexRequest.source(requestBodyMap, request.getMediaType()); - String sOpType = request.param("op_type"); - String waitForActiveShards = request.param("wait_for_active_shards"); - if (waitForActiveShards != null) { - indexRequest.waitForActiveShards(ActiveShardCount.parseString(waitForActiveShards)); - } - if (sOpType != null) { - indexRequest.opType(sOpType); - } - - return channel -> client.index( - indexRequest, - new RestStatusToXContentListener<>(channel, r -> r.getLocation(indexRequest.routing())) - ); + channel.sendResponse(bytesRestResponse); + }; } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java index 999b668..34b0635 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java @@ -33,6 +33,7 @@ public class CommandManagerService implements IndexingOperationListener { private static final Logger logger = LogManager.getLogger(CommandManagerService.class); + public static Long TIME_OUT_FOR_REQUEST = 15L; private final Client client; private final ClusterService clusterService; From fbe7360d7267dfeff4461c794f9a900a17dc0181 Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 19 Sep 2024 17:16:35 -0300 Subject: [PATCH 08/27] Remove unused imports --- .../commandmanager/rest/action/RestPostCommandAction.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index bb0d058..b99a3c0 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -5,21 +5,14 @@ import com.wazuh.commandmanager.utils.CommandManagerService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.action.DocWriteRequest; -import org.opensearch.action.index.IndexRequest; -import org.opensearch.action.support.ActiveShardCount; import org.opensearch.client.node.NodeClient; -import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.action.ActionListener; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.index.VersionType; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestRequest; -import org.opensearch.rest.action.RestActions; -import org.opensearch.rest.action.RestStatusToXContentListener; import java.io.IOException; import java.util.*; From 3491748adf351181dd7b01de7a0898b1ad54e7f9 Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 19 Sep 2024 17:21:46 -0300 Subject: [PATCH 09/27] Change POST endpoint to /_plugins/_commandmanager --- .../commandmanager/rest/action/RestPostCommandAction.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index b99a3c0..75344f0 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -43,10 +43,8 @@ public List routes() { POST, String.format( Locale.ROOT, - "%s/%s/{%s}", - CommandManagerPlugin.COMMAND_MANAGER_BASE_URI, - "create", - "id" + "%s", + CommandManagerPlugin.COMMAND_MANAGER_BASE_URI ) ) ); From e5e97141b3bb62f89e28afc689b52c9fa02a1d8d Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 19 Sep 2024 17:41:27 -0300 Subject: [PATCH 10/27] Instantiating commandManagerService from CommandManagerPlugin class --- .../commandmanager/CommandManagerPlugin.java | 36 ++++++++++++++++++- .../rest/action/RestPostCommandAction.java | 17 +++++++-- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java index ba79045..3826aee 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java @@ -8,19 +8,31 @@ package com.wazuh.commandmanager; import com.wazuh.commandmanager.rest.action.RestPostCommandAction; +import com.wazuh.commandmanager.utils.CommandManagerService; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.IndexScopedSettings; import org.opensearch.common.settings.Settings; import org.opensearch.common.settings.SettingsFilter; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; import org.opensearch.plugins.ActionPlugin; import org.opensearch.plugins.Plugin; +import org.opensearch.repositories.RepositoriesService; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; +import org.opensearch.script.ScriptService; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -31,6 +43,27 @@ public class CommandManagerPlugin extends Plugin implements ActionPlugin { public static final String COMMAND_MANAGER_INDEX_NAME = "command-manager"; private static final Logger log = LogManager.getLogger(CommandManagerPlugin.class); + private CommandManagerService commandManagerService; + + @Override + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + this.commandManagerService = new CommandManagerService(client, clusterService); + + return Collections.emptyList(); + } + public List getRestHandlers( Settings settings, RestController restController, @@ -40,6 +73,7 @@ public List getRestHandlers( IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster ) { - return Collections.singletonList(new RestPostCommandAction()); + RestPostCommandAction restPostCommandAction = new RestPostCommandAction(commandManagerService); + return Collections.singletonList(restPostCommandAction); } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 75344f0..acfdf81 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -32,6 +32,10 @@ public class RestPostCommandAction extends BaseRestHandler { public CommandManagerService commandManagerService; + public RestPostCommandAction(final CommandManagerService commandManagerService) { + this.commandManagerService = commandManagerService; + } + public String getName() { return POST_COMMAND_ACTION_REQUEST_DETAILS; } @@ -39,12 +43,21 @@ public String getName() { @Override public List routes() { return Collections.singletonList( +// new Route( +// POST, +// String.format( +// Locale.ROOT, +// "%s", +// CommandManagerPlugin.COMMAND_MANAGER_BASE_URI +// ) +// ), new Route( POST, String.format( Locale.ROOT, - "%s", - CommandManagerPlugin.COMMAND_MANAGER_BASE_URI + "%s/{%s}", + CommandManagerPlugin.COMMAND_MANAGER_BASE_URI, + PostCommandRequest.DOCUMENT_ID ) ) ); From 475514195f284e352eb305892f29e8c88ccfd900 Mon Sep 17 00:00:00 2001 From: f-galland Date: Fri, 20 Sep 2024 10:34:43 -0300 Subject: [PATCH 11/27] Removing update functionality --- .../utils/CommandManagerService.java | 58 ++++++------------- 1 file changed, 19 insertions(+), 39 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java index 34b0635..17a0edb 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java @@ -94,45 +94,24 @@ public void processCommand( if (created) { try { // Update entry request - if (documentId != null) { - // Recover entry via documentId - findCommandDetails(documentId, ActionListener.wrap(existingCommandDetails -> { - CommandDetails updateCommandDetails = new CommandDetails(existingCommandDetails); - - // Set updated fields - updateCommandDetails.setCommandOrderId(commandOrderId); - updateCommandDetails.setCommandRequestId(commandRequestId); - updateCommandDetails.setCommandSource(commandSource); - updateCommandDetails.setCommandTarget(commandTarget); - updateCommandDetails.setCommandTimeout(commandTimeout); - updateCommandDetails.setCommandType(commandType); - updateCommandDetails.setCommandUser(commandUser); - updateCommandDetails.setCommandAction(commandAction); - updateCommandDetails.setCommandResult(commandResult); - - // Send update Request - updateCommandDetails(documentId, updateCommandDetails, listener); - }, listener::onFailure)); - } else { - // Create CommandDetails from params - CommandDetails tempCommandDetails = new CommandDetails( - commandOrderId, - commandRequestId, - commandSource, - commandTarget, - commandTimeout, - commandType, - commandUser, - commandAction, - commandResult - ); - - // Index new command Details entry - logger.info( - "Creating command details" + " : " + tempCommandDetails.toString() - ); - createCommandDetails(tempCommandDetails, listener); - } + // Create CommandDetails from params + CommandDetails tempCommandDetails = new CommandDetails( + commandOrderId, + commandRequestId, + commandSource, + commandTarget, + commandTimeout, + commandType, + commandUser, + commandAction, + commandResult + ); + + // Index new command Details entry + logger.info( + "Creating command details" + " : " + tempCommandDetails.toString() + ); + createCommandDetails(tempCommandDetails, listener); } catch (VersionConflictEngineException e) { logger.debug("could not process command" + commandOrderId, e.getMessage()); listener.onResponse(null); @@ -173,6 +152,7 @@ private void findCommandDetails(final String documentId, ActionListener { if (!response.isExists()) { + logger.info("Non-existent command: " + documentId); listener.onResponse(null); } else { try { From 2675b9ccc7984d947520db3065b3da0f975720bd Mon Sep 17 00:00:00 2001 From: f-galland Date: Fri, 20 Sep 2024 13:22:10 -0300 Subject: [PATCH 12/27] Generate order and request ids randomly --- .../rest/action/RestPostCommandAction.java | 12 +++---- .../utils/CommandManagerService.java | 34 +++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index acfdf81..076e4fe 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -55,9 +55,8 @@ public List routes() { POST, String.format( Locale.ROOT, - "%s/{%s}", - CommandManagerPlugin.COMMAND_MANAGER_BASE_URI, - PostCommandRequest.DOCUMENT_ID + "%s", + CommandManagerPlugin.COMMAND_MANAGER_BASE_URI ) ) ); @@ -69,9 +68,10 @@ protected RestChannelConsumer prepareRequest(final RestRequest restRequest, fina ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser); PostCommandRequest postCommandRequest = PostCommandRequest.parse(parser); - String documentId = restRequest.param(PostCommandRequest.DOCUMENT_ID); - String commandOrderId = postCommandRequest.getCommandOrderId(); - String commandRequestId = postCommandRequest.getCommandRequestId(); + String commandOrderId = commandManagerService.generateRandomString(4); + String commandRequestId = commandManagerService.generateRandomString(4); + // The document ID is a concatenation of the orderId and the requestId + String documentId = commandOrderId + commandRequestId; String commandSource = postCommandRequest.getCommandSource(); String commandTarget = postCommandRequest.getCommandTarget(); String commandTimeout = postCommandRequest.getCommandTimeout(); diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java index 17a0edb..db22623 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java @@ -27,7 +27,11 @@ import org.opensearch.index.shard.IndexingOperationListener; import java.io.IOException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Random; public class CommandManagerService implements IndexingOperationListener { @@ -247,4 +251,34 @@ void createCommandManagerIndex(ActionListener listener) { } } + public String generateRandomString(int stringLength) { + Random random = new Random(); + String randomString = ""; + // Build a list of ascii indices for Alphanumeric characters + // to be accessed by index + List asciiCharIndices = new ArrayList<>(); + // Decimal numbers + for(int i = 48; i <= 57 ; i++) + { + asciiCharIndices.add(i); + } + // Uppercase Latin Characters + for(int i = 65; i <= 90 ; i++) + { + asciiCharIndices.add(i); + } + // Lowercase Latin Characters + for(int i = 97; i <= 122 ; i++) + { + asciiCharIndices.add(i); + } + for(int i = 0; i <= stringLength; i++) + { + //randomString = randomString + (char) random.nextInt(asciiCharIndices.size()); + randomString = randomString + (char) (int) asciiCharIndices.get(random.nextInt(asciiCharIndices.size())); + logger.info((char) random.nextInt(asciiCharIndices.size())); + } + return randomString; + } + } From 7b81541a8bb16a0b066181563c6595b876613626 Mon Sep 17 00:00:00 2001 From: f-galland Date: Fri, 20 Sep 2024 14:10:51 -0300 Subject: [PATCH 13/27] Make the document _id be a concatenation of the orderId and requestId fields --- .../commandmanager/rest/action/RestPostCommandAction.java | 4 ++-- .../com/wazuh/commandmanager/utils/CommandManagerService.java | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 076e4fe..bfcfea6 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -102,7 +102,7 @@ public void onResponse(String indexedDocumentId) { @Override public void onFailure(Exception e) { - logger.info("could not process job index", e); + logger.info("could not process command", e); inProgressFuture.completeExceptionally(e); } } @@ -112,7 +112,7 @@ public void onFailure(Exception e) { inProgressFuture.orTimeout(CommandManagerService.TIME_OUT_FOR_REQUEST, TimeUnit.SECONDS); } catch (CompletionException e) { if (e.getCause() instanceof TimeoutException) { - logger.error("Get Job Details timed out ", e); + logger.error("Get Command Details timed out ", e); } if (e.getCause() instanceof RuntimeException) { throw (RuntimeException) e.getCause(); diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java index db22623..b6dc8fa 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java @@ -27,7 +27,6 @@ import org.opensearch.index.shard.IndexingOperationListener; import java.io.IOException; -import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -132,7 +131,7 @@ private void createCommandDetails(final CommandDetails tempCommandDetails, Actio // Create index request, document Id will be randomly generated final IndexRequest request = new IndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME).source( tempCommandDetails.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) - ).setIfSeqNo(SequenceNumbers.UNASSIGNED_SEQ_NO).setIfPrimaryTerm(SequenceNumbers.UNASSIGNED_PRIMARY_TERM).create(true); + ).setIfSeqNo(SequenceNumbers.UNASSIGNED_SEQ_NO).setIfPrimaryTerm(SequenceNumbers.UNASSIGNED_PRIMARY_TERM).id(tempCommandDetails.getCommandOrderId() + tempCommandDetails.getCommandRequestId()).create(true); client.index(request, ActionListener.wrap(response -> { listener.onResponse(response.getId()); }, exception -> { if (exception instanceof IOException) { From 73328d1dbb747a950fccd3f0c952627c1bb2d05b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Ruiz?= Date: Mon, 23 Sep 2024 18:59:22 +0200 Subject: [PATCH 14/27] Refactor --- .../commandmanager/CommandManagerPlugin.java | 11 +- .../commandmanager/index/CommandIndex.java | 101 ++++++ .../wazuh/commandmanager/model/Action.java | 96 ++++++ .../wazuh/commandmanager/model/Command.java | 236 ++++++++++++++ .../commandmanager/model/CommandDetails.java | 307 ------------------ .../wazuh/commandmanager/model/Status.java | 15 + .../rest/action/RestPostCommandAction.java | 163 ++++------ .../rest/request/PostCommandRequest.java | 224 ------------- .../utils/CommandManagerService.java | 283 ---------------- 9 files changed, 511 insertions(+), 925 deletions(-) create mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java create mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java create mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java delete mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/CommandDetails.java create mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Status.java delete mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/request/PostCommandRequest.java delete mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java index 3826aee..7229e98 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java @@ -8,7 +8,7 @@ package com.wazuh.commandmanager; import com.wazuh.commandmanager.rest.action.RestPostCommandAction; -import com.wazuh.commandmanager.utils.CommandManagerService; +import com.wazuh.commandmanager.index.CommandIndex; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.client.Client; @@ -41,9 +41,8 @@ public class CommandManagerPlugin extends Plugin implements ActionPlugin { public static final String COMMAND_MANAGER_BASE_URI = "/_plugins/_commandmanager"; public static final String COMMAND_MANAGER_INDEX_NAME = "command-manager"; - private static final Logger log = LogManager.getLogger(CommandManagerPlugin.class); - private CommandManagerService commandManagerService; + private CommandIndex commandIndex; @Override public Collection createComponents( @@ -59,8 +58,7 @@ public Collection createComponents( IndexNameExpressionResolver indexNameExpressionResolver, Supplier repositoriesServiceSupplier ) { - this.commandManagerService = new CommandManagerService(client, clusterService); - + this.commandIndex = new CommandIndex(client, clusterService); return Collections.emptyList(); } @@ -73,7 +71,6 @@ public List getRestHandlers( IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster ) { - RestPostCommandAction restPostCommandAction = new RestPostCommandAction(commandManagerService); - return Collections.singletonList(restPostCommandAction); + return Collections.singletonList(new RestPostCommandAction(this.commandIndex)); } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java new file mode 100644 index 0000000..63081e0 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -0,0 +1,101 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ +package com.wazuh.commandmanager.index; + +import com.wazuh.commandmanager.CommandManagerPlugin; +import com.wazuh.commandmanager.model.Command; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.index.IndexResponse; +import org.opensearch.client.Client; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.action.ActionFuture; +import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.index.shard.IndexingOperationListener; + +import java.io.IOException; + +public class CommandIndex implements IndexingOperationListener { + + private static final Logger logger = LogManager.getLogger(CommandIndex.class); + + public static Long TIME_OUT_FOR_REQUEST = 15L; + private final Client client; + private final ClusterService clusterService; + + /** + * @param client + * @param clusterService + */ + public CommandIndex( + final Client client, + final ClusterService clusterService + ) { + this.client = client; + this.clusterService = clusterService; + } + + /** + * Check if the CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME index exists. + * + * @return whether the index exists. + */ + public boolean indexExists() { + return clusterService + .state() + .routingTable() + .hasIndex(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); + } + + + public ActionFuture create(Command command) { + try { + IndexRequest request = new IndexRequest() + .index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) + .source(command.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .id(command.getId()) + .create(true); + return this.client.index(request); + } catch (IOException e) { + logger.error("IOException occurred creating command details", e); + } + return null; + } + + /** + * Persists the command into the commands index + * + * @param command command to persist in the index. + * @param listener + */ + protected void create(Command command, ActionListener listener) { + try { + // Create index request + final IndexRequest request = new IndexRequest() + .index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) + .source(command.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .id(command.getId()) + .create(true); + + client.index(request, ActionListener.wrap(response -> { + listener.onResponse(response.getId()); + }, exception -> { + if (exception instanceof IOException) { + logger.error("IOException occurred creating command details", exception); + } + listener.onResponse(null); + })); + } catch (IOException e) { + logger.error("IOException occurred creating command details", e); + listener.onResponse(null); + } + } +} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java new file mode 100644 index 0000000..441a2c6 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java @@ -0,0 +1,96 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ +package com.wazuh.commandmanager.model; + +import org.opensearch.core.xcontent.XContentParser; + +import java.io.IOException; + +/** + * Command's action fields. + */ +public class Action { + + public static final String TYPE = "type"; + public static final String ARGS = "args"; + public static final String VERSION = "version"; + private final String type; + private final String args; + private final String version; + + /** + * Default constructor. + * + * @param type action type to be executed on the target, + * @param args actual command. + * @param version version of the action. + */ + public Action(String type, String args, String version) { + this.type = type; + this.args = args; + this.version = version; + } + + /** + * @param parser + * @return + * @throws IOException + */ + public static Action parse(XContentParser parser) throws IOException { + String type = ""; + String args = ""; + String version = ""; + + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + String fieldName = parser.currentName(); + parser.nextToken(); + switch (fieldName) { + case TYPE: + type = parser.text(); + break; + case ARGS: + args = parser.text(); + break; + case VERSION: + version = parser.text(); + break; + default: + parser.skipChildren(); + break; + } + } + return new Action(type, args, version); + } + + /** + * Return action's type field. + * + * @return type + */ + public String getType() { + return this.type; + } + + /** + * Returns action's args field. + * + * @return args + */ + public String getArgs() { + return this.args; + } + + /** + * Returns action's version field. + * + * @return version + */ + public String getVersion() { + return this.version; + } +} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java new file mode 100644 index 0000000..166ac83 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java @@ -0,0 +1,236 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ +package com.wazuh.commandmanager.model; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +import java.io.IOException; + +import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; + +public class Command extends ActionRequest implements ToXContentObject { + + public static final String ORDER_ID = "order_id"; + public static final String REQUEST_ID = "request_id"; + public static final String SOURCE = "source"; + public static final String TARGET = "target"; + public static final String TIMEOUT = "timeout"; + public static final String TYPE = "type"; + public static final String USER = "user"; + public static final String STATUS = "status"; + public static final String ACTION = "action"; + private final String id; + private final String orderId; + private final String requestId; + private final String source; + private final String target; + private final Integer timeout; + private final String type; + private final String user; + private final Status status; + private final Action action; + + /** + * Default constructor + * + * @param requestID Unique identifier generated by the Command Manager. Auto-incremental. + * @param orderID Unique identifier generated by the Command Manager. Auto-incremental within the same Command Request ID. + * @param source origin of the request. One + * @param target Cluster name destination. + * @param timeout Number of seconds to wait for the command to be executed. + * @param type action type. One of agent_groups, agent, server. + * @param user the user that originated the request + * @param action target action type and additional parameters + */ + public Command( + String requestID, + String orderID, + String source, + String target, + Integer timeout, + String type, + String user, + Action action + ) { + this.id = orderID + requestID; + this.requestId = requestID; + this.orderId = orderID; + this.source = source; + this.target = target; + this.timeout = timeout; + this.type = type; + this.user = user; + this.action = action; + this.status = Status.PENDING; + } + + public static Command parse(String requestId, String orderId, XContentParser parser) throws IOException { + String source = null; + String target = null; + Integer timeout = null; + String type = null; + String user = null; + Action action = null; + + // @TODO check if this call is necessary as ensureExpectedToken is invoked previously + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser); + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + String fieldName = parser.currentName(); + parser.nextToken(); + switch (fieldName) { + case SOURCE: + source = parser.text(); + break; + case TARGET: + target = parser.text(); + break; + case TIMEOUT: + timeout = parser.intValue(); + break; + case TYPE: + type = parser.text(); + break; + case USER: + user = parser.text(); + break; + case ACTION: + action = Action.parse(parser); + break; + default: + parser.skipChildren(); + break; + } + } + + return new Command( + requestId, + orderId, + source, + target, + timeout, + type, + user, + action + ); + } + + /** + * @return + */ + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + XContentBuilder xContentBuilder = builder.startObject(); + + // @TODO review whether these IFs are necessary + if (this.source != null) { + xContentBuilder.field(SOURCE, this.source); + } + if (this.user != null) { + xContentBuilder.field(USER, this.user); + } + if (this.target != null) { + xContentBuilder.field(TARGET, this.target); + } + if (this.type != null) { + xContentBuilder.field(TYPE, this.type); + } + if (this.action != null) { + xContentBuilder.field(ACTION, this.action); + } + if (timeout != null) { + xContentBuilder.field(TIMEOUT, timeout); + } + + xContentBuilder.field(STATUS, this.status); + xContentBuilder.field(ORDER_ID, this.orderId); + xContentBuilder.field(REQUEST_ID, this.requestId); + + return xContentBuilder.endObject(); + } + + /** + * + * @return + */ + public String getOrderId() { + return this.orderId; + } + + /** + * + * @return + */ + public String getRequestId() { + return this.requestId; + } + + /** + * + * @return + */ + public String getId() { + return this.id; + } + +// @Override +// public boolean equals(Object o) { +// if (this == o) return true; +// if (o == null || getClass() != o.getClass()) return false; +// Command that = (Command) o; +// return Objects.equals(orderID, that.orderID) +// && Objects.equals(requestID, that.requestID) +// && Objects.equals(source, that.source) +// && Objects.equals(target, that.target) +// && Objects.equals(timeout, that.timeout) +// && Objects.equals(type, that.type) +// && Objects.equals(user, that.user) +// && Objects.equals(action, that.action) +// && Objects.equals(result, that.result); +// } + +// @Override +// public int hashCode() { +// return Objects.hash( +// orderID, +// requestID, +// source, +// target, +// timeout, +// type, +// user, +// action, +// result +// ); +// } + + + @Override + public String toString() { + return "Command{" + + "ID='" + id + '\'' + + ", orderID='" + orderId + '\'' + + ", requestID='" + requestId + '\'' + + ", source='" + source + '\'' + + ", target='" + target + '\'' + + ", timeout=" + timeout + + ", type='" + type + '\'' + + ", user='" + user + '\'' + + ", status=" + status + + ", action=" + action + + '}'; + } +} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/CommandDetails.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/CommandDetails.java deleted file mode 100644 index b7082f8..0000000 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/CommandDetails.java +++ /dev/null @@ -1,307 +0,0 @@ -package com.wazuh.commandmanager.model; - -import org.opensearch.common.Nullable; -import org.opensearch.core.xcontent.ToXContent; -import org.opensearch.core.xcontent.ToXContentObject; -import org.opensearch.core.xcontent.XContentBuilder; -import org.opensearch.core.xcontent.XContentParser; - -import java.io.IOException; -import java.util.Map; -import java.util.Objects; - -import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; - -public class CommandDetails implements ToXContentObject { - - - private String commandOrderId; - private String commandRequestId; - private String commandSource; - private String commandTarget; - private String commandTimeout; - private String commandType; - private String commandUser; - private Map commandAction; - private Map commandResult; - - public static final String DOCUMENT_ID = "document_id"; - public static final String COMMAND_ORDER_ID = "command_order_id"; - public static final String COMMAND_REQUEST_ID = "command_request_id"; - public static final String COMMAND_SOURCE = "command_source"; - public static final String COMMAND_TARGET = "command_target"; - public static final String COMMAND_TIMEOUT = "command_timeout"; - public static final String COMMAND_TYPE = "command_type"; - public static final String COMMAND_USER = "command_user"; - public static final String COMMAND_ACTION = "command_action"; - public static final String COMMAND_RESULT = "command_result"; - - public CommandDetails() {} - - public CommandDetails( - String commandOrderId, - String commandRequestId, - String commandSource, - String commandTarget, - String commandTimeout, - String commandType, - String commandUser, - Map commandAction, - Map commandResult - ) { - this.commandOrderId = commandOrderId; - this.commandRequestId = commandRequestId; - this.commandSource = commandSource; - this.commandTarget = commandTarget; - this.commandTimeout = commandTimeout; - this.commandType = commandType; - this.commandUser = commandUser; - this.commandAction = commandAction; - this.commandResult = commandResult; - } - - @Override - public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException { - XContentBuilder xContentBuilder = builder.startObject(); - if (commandOrderId != null) { - xContentBuilder.field(COMMAND_ORDER_ID, commandOrderId); - } - if (commandRequestId != null) { - xContentBuilder.field(COMMAND_REQUEST_ID, commandRequestId); - } - if (commandSource != null) { - xContentBuilder.field(COMMAND_SOURCE, commandSource); - } - if (commandTarget != null) { - xContentBuilder.field(COMMAND_TARGET, commandTarget); - } - if (commandTimeout != null) { - xContentBuilder.field(COMMAND_TIMEOUT, commandTimeout); - } - if (commandType != null) { - xContentBuilder.field(COMMAND_TYPE, commandType); - } - if (commandUser != null) { - xContentBuilder.field(COMMAND_USER, commandUser); - } - if (commandAction != null) { - xContentBuilder.field(COMMAND_ACTION, commandAction); - } - if (commandResult != null) { - xContentBuilder.field(COMMAND_RESULT, commandResult); - } - return xContentBuilder.endObject(); - } - - public static CommandDetails parse(XContentParser parser) throws IOException { - String commandOrderId = null; - String commandRequestId = null; - String commandSource = null; - String commandTarget = null; - String commandTimeout = null; - String commandType = null; - String commandUser = null; - Map commandAction = null; - Map commandResult = null; - - ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser); - while (parser.nextToken() != XContentParser.Token.END_OBJECT) { - String fieldName = parser.currentName(); - parser.nextToken(); - switch (fieldName) { - case COMMAND_ORDER_ID: - commandOrderId = parser.text(); - break; - case COMMAND_REQUEST_ID: - commandRequestId = parser.text(); - break; - case COMMAND_SOURCE: - commandSource = parser.text(); - break; - case COMMAND_TARGET: - commandTarget = parser.text(); - break; - case COMMAND_TIMEOUT: - commandTimeout = parser.text(); - break; - case COMMAND_TYPE: - commandType = parser.text(); - break; - case COMMAND_USER: - commandUser = parser.text(); - break; - case COMMAND_ACTION: - commandAction = parser.map(); - break; - case COMMAND_RESULT: - commandResult = parser.map(); - break; - default: - parser.skipChildren(); - break; - } - } - - return new CommandDetails( - commandOrderId, - commandRequestId, - commandSource, - commandTarget, - commandTimeout, - commandType, - commandUser, - commandAction, - commandResult - ); - } - - public CommandDetails(final CommandDetails copyCommandDetails) { - this( - copyCommandDetails.commandOrderId, - copyCommandDetails.commandRequestId, - copyCommandDetails.commandSource, - copyCommandDetails.commandTarget, - copyCommandDetails.commandTimeout, - copyCommandDetails.commandType, - copyCommandDetails.commandUser, - copyCommandDetails.commandAction, - copyCommandDetails.commandResult - ); - } - - @Nullable - public String getCommandOrderId() { - return commandOrderId; - } - public void setCommandOrderId(String commandOrderId) { - this.commandOrderId = commandOrderId; - } - - @Nullable - public String getCommandRequestId() { - return commandRequestId; - } - public void setCommandRequestId(String commandRequestId) { - this.commandRequestId = commandRequestId; - } - - @Nullable - public String getCommandSource() { - return commandSource; - } - public void setCommandSource(String commandSource) { - this.commandSource = commandSource; - } - - @Nullable - public String getCommandTarget() { - return commandTarget; - } - public void setCommandTarget(String commandTarget) { - this.commandTarget = commandTarget; - } - - @Nullable - public String getCommandTimeout() { - return commandTimeout; - } - public void setCommandTimeout(String commandTimeout) { - this.commandTimeout = commandTimeout; - } - - @Nullable - public String getCommandType() { - return commandType; - } - public void setCommandType(String commandType) { - this.commandType = commandType; - } - - @Nullable - public String getCommandUser() { - return commandUser; - } - public void setCommandUser(String commandUser) { - this.commandUser = commandUser; - } - - @Nullable - public Map getCommandAction() { - return commandAction; - } - public void setCommandAction(Map commandAction) { - this.commandAction = commandAction; - } - - @Nullable - public Map getCommandResult() { - return commandResult; - } - public void setCommandResult(Map commandResult) { - this.commandResult = commandResult; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - CommandDetails that = (CommandDetails) o; - return Objects.equals(commandOrderId, that.commandOrderId) - && Objects.equals(commandRequestId, that.commandRequestId) - && Objects.equals(commandSource, that.commandSource) - && Objects.equals(commandTarget, that.commandTarget) - && Objects.equals(commandTimeout, that.commandTimeout) - && Objects.equals(commandType, that.commandType) - && Objects.equals(commandUser, that.commandUser) - && Objects.equals(commandAction, that.commandAction) - && Objects.equals(commandResult, that.commandResult); - } - - @Override - public int hashCode() { - return Objects.hash( - commandOrderId, - commandRequestId, - commandSource, - commandTarget, - commandTimeout, - commandType, - commandUser, - commandAction, - commandResult - ); - } - - @Override - public String toString() { - return "CommandDetails{" - + "commandOrderId='" - + commandOrderId - + '\'' - + ", commandRequestId='" - + commandRequestId - + '\'' - + ", commandSource='" - + commandSource - + '\'' - + ", commandTarget='" - + commandTarget - + '\'' - + ", commandTimeout='" - + commandTimeout - + '\'' - + ", commandType='" - + commandType - + '\'' - + ", commandUser='" - + commandUser - + '\'' - + ", commandAction='" - + commandAction - + '\'' - + ", commandResult='" - + commandResult - + '\'' - + '}'; - } -} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Status.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Status.java new file mode 100644 index 0000000..65b0c38 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Status.java @@ -0,0 +1,15 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ +package com.wazuh.commandmanager.model; + +public enum Status { + PENDING, + SENT, + SUCCESS, + FAILURE +} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index bfcfea6..417f2b9 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -1,13 +1,20 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ package com.wazuh.commandmanager.rest.action; import com.wazuh.commandmanager.CommandManagerPlugin; -import com.wazuh.commandmanager.rest.request.PostCommandRequest; -import com.wazuh.commandmanager.utils.CommandManagerService; +import com.wazuh.commandmanager.index.CommandIndex; +import com.wazuh.commandmanager.model.Command; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.action.index.IndexResponse; import org.opensearch.client.node.NodeClient; -import org.opensearch.core.action.ActionListener; -import org.opensearch.core.rest.RestStatus; +import org.opensearch.common.Randomness; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.rest.BaseRestHandler; @@ -15,25 +22,33 @@ import org.opensearch.rest.RestRequest; import java.io.IOException; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.Collections; +import java.util.List; +import java.util.Locale; import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; import static org.opensearch.rest.RestRequest.Method.POST; +/** + * Handles HTTP requests to the POST + * {@value com.wazuh.commandmanager.CommandManagerPlugin#COMMAND_MANAGER_BASE_URI} + * endpoint. + */ public class RestPostCommandAction extends BaseRestHandler { public static final String POST_COMMAND_ACTION_REQUEST_DETAILS = "post_command_action_request_details"; private final Logger logger = LogManager.getLogger(RestPostCommandAction.class); - public CommandManagerService commandManagerService; + private final CommandIndex commandIndex; - public RestPostCommandAction(final CommandManagerService commandManagerService) { - this.commandManagerService = commandManagerService; + /** + * Default constructor + * + * @param commandIndex persistence layer + */ + public RestPostCommandAction(CommandIndex commandIndex) { + this.commandIndex = commandIndex; } public String getName() { @@ -43,112 +58,52 @@ public String getName() { @Override public List routes() { return Collections.singletonList( -// new Route( -// POST, -// String.format( -// Locale.ROOT, -// "%s", -// CommandManagerPlugin.COMMAND_MANAGER_BASE_URI -// ) -// ), - new Route( - POST, - String.format( - Locale.ROOT, - "%s", - CommandManagerPlugin.COMMAND_MANAGER_BASE_URI + new Route( + POST, + String.format( + Locale.ROOT, + "%s", + CommandManagerPlugin.COMMAND_MANAGER_BASE_URI + ) ) - ) ); } + /** + * Generates a random ID as string on range 0 to 10. + * + * @return random int as string + * TODO To be removed in future iterations + */ + private String getRandomId() { + return "" + Randomness.get().nextInt(); + } + @Override - protected RestChannelConsumer prepareRequest(final RestRequest restRequest, final NodeClient client) throws IOException { + protected RestChannelConsumer prepareRequest( + final RestRequest restRequest, + final NodeClient client + ) throws IOException { + // Get request details XContentParser parser = restRequest.contentParser(); ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser); - PostCommandRequest postCommandRequest = PostCommandRequest.parse(parser); - String commandOrderId = commandManagerService.generateRandomString(4); - String commandRequestId = commandManagerService.generateRandomString(4); - // The document ID is a concatenation of the orderId and the requestId - String documentId = commandOrderId + commandRequestId; - String commandSource = postCommandRequest.getCommandSource(); - String commandTarget = postCommandRequest.getCommandTarget(); - String commandTimeout = postCommandRequest.getCommandTimeout(); - String commandType = postCommandRequest.getCommandType(); - String commandUser = postCommandRequest.getCommandUser(); - Map commandAction = postCommandRequest.getCommandAction(); - Map commandResult = postCommandRequest.getCommandResult(); - - CompletableFuture inProgressFuture = new CompletableFuture<>(); + // Placeholders. These IDs will be generated properly on next iterations. + String requestId = getRandomId(); + String orderId = getRandomId(); + Command command = Command.parse(requestId, orderId, parser); - commandManagerService.processCommand( - documentId, - commandOrderId, - commandRequestId, - commandSource, - commandTarget, - commandTimeout, - commandType, - commandUser, - commandAction, - commandResult, - new ActionListener<>() { - @Override - public void onResponse(String indexedDocumentId) { - // Set document Id - inProgressFuture.complete(indexedDocumentId); - } - - @Override - public void onFailure(Exception e) { - logger.info("could not process command", e); - inProgressFuture.completeExceptionally(e); - } - } - ); - - try { - inProgressFuture.orTimeout(CommandManagerService.TIME_OUT_FOR_REQUEST, TimeUnit.SECONDS); - } catch (CompletionException e) { - if (e.getCause() instanceof TimeoutException) { - logger.error("Get Command Details timed out ", e); - } - if (e.getCause() instanceof RuntimeException) { - throw (RuntimeException) e.getCause(); - } else if (e.getCause() instanceof Error) { - throw (Error) e.getCause(); - } else { - throw new RuntimeException(e.getCause()); - } - } + // Persist command + IndexResponse indexResponse = this.commandIndex.create(command).actionGet(); + // Send response return channel -> { - String commandDetailsResponseHolder = null; - try { - commandDetailsResponseHolder = inProgressFuture.get(); - } catch (Exception e) { - logger.error("Exception occured in get command details ", e); - } - XContentBuilder builder = channel.newBuilder(); - RestStatus restStatus = RestStatus.OK; - String restResponseString = commandDetailsResponseHolder != null ? "success" : "failed"; - BytesRestResponse bytesRestResponse; - try { + try (XContentBuilder builder = channel.newBuilder()) { builder.startObject(); - builder.field("response", restResponseString); - if (restResponseString.equals("success")) { - builder.field(PostCommandRequest.DOCUMENT_ID, commandDetailsResponseHolder); - } else { - restStatus = RestStatus.INTERNAL_SERVER_ERROR; - } + builder.field("command", command.getId()); builder.endObject(); - bytesRestResponse = new BytesRestResponse(restStatus, builder); - } finally { - builder.close(); + channel.sendResponse(new BytesRestResponse(indexResponse.status(), builder)); } - - channel.sendResponse(bytesRestResponse); }; } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/request/PostCommandRequest.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/request/PostCommandRequest.java deleted file mode 100644 index fa6ad0c..0000000 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/request/PostCommandRequest.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.wazuh.commandmanager.rest.request; - -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; -import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.io.stream.StreamOutput; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.core.xcontent.XContentParserUtils; - -import java.io.IOException; -import java.util.Map; -import java.util.Objects; - -public class PostCommandRequest extends ActionRequest { - - private static String documentId; - private static String commandOrderId; - private static String commandRequestId; - private static String commandSource; - private static String commandTarget; - private static String commandTimeout; - private static String commandType; - private static String commandUser; - private static Map commandAction; - private static Map commandResult; - - public static final String DOCUMENT_ID = "document_id"; - public static final String COMMAND_ORDER_ID = "command_order_id"; - public static final String COMMAND_REQUEST_ID = "command_request_id"; - public static final String COMMAND_SOURCE = "command_source"; - public static final String COMMAND_TARGET = "command_target"; - public static final String COMMAND_TIMEOUT = "command_timeout"; - public static final String COMMAND_TYPE = "command_type"; - public static final String COMMAND_USER = "command_user"; - public static final String COMMAND_ACTION = "command_action"; - public static final String COMMAND_RESULT = "command_result"; - - public PostCommandRequest(StreamInput in) throws IOException { - super(in); - documentId = in.readString(); - commandOrderId = in.readString(); - commandRequestId = in.readString(); - commandSource = in.readOptionalString(); - commandTarget = in.readString(); - commandTimeout = in.readString(); - commandType = in.readString(); - commandUser = in.readString(); - commandAction = in.readMap(); - commandResult = in.readMap(); - } - - public PostCommandRequest( - String documentId, - String commandOrderId, - String commandRequestId, - String commandSource, - String commandTarget, - String commandTimeout, - String commandType, - String commandUser, - Map commandAction, - Map commandResult - ) { - super(); - this.documentId = documentId; - this.commandOrderId = Objects.requireNonNull(commandOrderId); - this.commandRequestId = Objects.requireNonNull(commandRequestId); - this.commandSource = Objects.requireNonNull(commandSource); - this.commandTarget = Objects.requireNonNull(commandTarget); - this.commandTimeout = Objects.requireNonNull(commandTimeout); - this.commandType = Objects.requireNonNull(commandType); - this.commandUser = Objects.requireNonNull(commandUser); - this.commandAction = Objects.requireNonNull(commandAction); - this.commandResult = Objects.requireNonNull(commandResult); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - super.writeTo(out); - out.writeOptionalString(documentId); - out.writeString(commandOrderId); - out.writeString(commandRequestId); - out.writeString(commandSource); - out.writeString(commandTarget); - out.writeString(commandTimeout); - out.writeString(commandType); - out.writeString(commandUser); - out.writeMap(commandAction); - out.writeMap(commandResult); - } - - public String getDocumentId() { - return documentId; - } - public void setDocumentId(String documentId) { - this.documentId = documentId; - } - - public String getCommandOrderId() { - return commandOrderId; - } - public void setCommandOrderId(String commandOrderId) { - this.commandOrderId = commandOrderId; - } - - public String getCommandRequestId() { - return commandRequestId; - } - public void setCommandRequestId(String commandRequestId) { - this.commandRequestId = commandRequestId; - } - - public String getCommandSource() { - return commandSource; - } - public void setCommandSource(String commandSource) { - this.commandSource = commandSource; - } - - public String getCommandTarget() { - return commandTarget; - } - public void setCommandTarget(String commandTarget) { - this.commandTarget = commandTarget; - } - - public String getCommandTimeout() { - return commandTimeout; - } - public void setCommandTimeout(String commandTimeout) { - this.commandTimeout = commandTimeout; - } - - public String getCommandType() { - return commandType; - } - public void setCommandType(String commandType) { - this.commandType = commandType; - } - - public String getCommandUser() { - return commandUser; - } - public void setCommandUser(String commandUser) { - this.commandUser = commandUser; - } - - public Map getCommandAction() { - return commandAction; - } - public void setCommandAction(Map commandAction) { - this.commandAction = commandAction; - } - - public Map getCommandResult() { - return commandResult; - } - public void setCommandResult(Map commandResult) { - this.commandResult = commandResult; - } - - @Override - public ActionRequestValidationException validate() { - return null; - } - - public static PostCommandRequest parse(XContentParser parser) throws IOException { - - XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser); - while (parser.nextToken() != XContentParser.Token.END_OBJECT) { - String fieldName = parser.currentName(); - parser.nextToken(); - - switch (fieldName) { - case DOCUMENT_ID: - documentId = parser.textOrNull(); - break; - case COMMAND_ORDER_ID: - commandOrderId = parser.textOrNull(); - break; - case COMMAND_REQUEST_ID: - commandRequestId = parser.textOrNull(); - break; - case COMMAND_SOURCE: - commandSource = parser.textOrNull(); - break; - case COMMAND_TARGET: - commandTarget = parser.textOrNull(); - break; - case COMMAND_TIMEOUT: - commandTimeout = parser.textOrNull(); - break; - case COMMAND_TYPE: - commandType = parser.textOrNull(); - break; - case COMMAND_USER: - commandUser = parser.textOrNull(); - break; - case COMMAND_ACTION: - commandAction = parser.map(); - break; - case COMMAND_RESULT: - commandResult = parser.map(); - break; - default: - parser.skipChildren(); - break; - } - - } - return new PostCommandRequest( - documentId, - commandOrderId, - commandRequestId, - commandSource, - commandTarget, - commandTimeout, - commandType, - commandUser, - commandAction, - commandResult - ); - } -} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java deleted file mode 100644 index b6dc8fa..0000000 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/CommandManagerService.java +++ /dev/null @@ -1,283 +0,0 @@ -package com.wazuh.commandmanager.utils; - -import com.wazuh.commandmanager.CommandManagerPlugin; -import com.wazuh.commandmanager.model.CommandDetails; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.opensearch.ResourceAlreadyExistsException; -import org.opensearch.action.DocWriteResponse; -import org.opensearch.action.admin.indices.create.CreateIndexRequest; -import org.opensearch.action.delete.DeleteRequest; -import org.opensearch.action.get.GetRequest; -import org.opensearch.action.index.IndexRequest; -import org.opensearch.action.update.UpdateRequest; -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.xcontent.NamedXContentRegistry; -import org.opensearch.core.xcontent.ToXContent; -import org.opensearch.core.xcontent.XContentParser; -import org.opensearch.index.IndexNotFoundException; -import org.opensearch.index.engine.DocumentMissingException; -import org.opensearch.index.engine.VersionConflictEngineException; -import org.opensearch.index.seqno.SequenceNumbers; -import org.opensearch.index.shard.IndexingOperationListener; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Random; - -public class CommandManagerService implements IndexingOperationListener { - - private static final Logger logger = LogManager.getLogger(CommandManagerService.class); - - public static Long TIME_OUT_FOR_REQUEST = 15L; - private final Client client; - private final ClusterService clusterService; - - public CommandManagerService( - final Client client, - final ClusterService clusterService - ) { - this.client = client; - this.clusterService = clusterService; - } - - public boolean commandManagerIndexExists() { - return clusterService.state().routingTable().hasIndex(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); - } - - public void processCommand( - final String documentId, - final String commandOrderId, - final String commandRequestId, - final String commandSource, - final String commandTarget, - final String commandTimeout, - final String commandType, - final String commandUser, - final Map commandAction, - final Map commandResult, - ActionListener listener - ) { - // Validate command detail params - if ( - commandOrderId == null - || commandOrderId.isEmpty() - || commandRequestId == null - || commandRequestId.isEmpty() - || commandSource == null - || commandSource.isEmpty() - || commandTarget == null - || commandTarget.isEmpty() - || commandTimeout == null - || commandTimeout.isEmpty() - || commandType == null - || commandType.isEmpty() - || commandUser == null - || commandUser.isEmpty() - || commandAction == null - || commandAction.isEmpty() - || commandResult == null - || commandResult.isEmpty() - ) { - listener.onFailure( - new IllegalArgumentException( - "command_order_id, command_request_id, command_source, command_target, command_timeout, command_type, command_user, command_action, command_result: are mandatory fields" - ) - ); - } else { - // Ensure command details index has been created - createCommandManagerIndex(ActionListener.wrap(created -> { - if (created) { - try { - // Update entry request - // Create CommandDetails from params - CommandDetails tempCommandDetails = new CommandDetails( - commandOrderId, - commandRequestId, - commandSource, - commandTarget, - commandTimeout, - commandType, - commandUser, - commandAction, - commandResult - ); - - // Index new command Details entry - logger.info( - "Creating command details" + " : " + tempCommandDetails.toString() - ); - createCommandDetails(tempCommandDetails, listener); - } catch (VersionConflictEngineException e) { - logger.debug("could not process command" + commandOrderId, e.getMessage()); - listener.onResponse(null); - } - } else { - listener.onResponse(null); - } - }, listener::onFailure)); - } - } - - private void createCommandDetails(final CommandDetails tempCommandDetails, ActionListener listener) { - try { - // Create index request, document Id will be randomly generated - final IndexRequest request = new IndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME).source( - tempCommandDetails.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS) - ).setIfSeqNo(SequenceNumbers.UNASSIGNED_SEQ_NO).setIfPrimaryTerm(SequenceNumbers.UNASSIGNED_PRIMARY_TERM).id(tempCommandDetails.getCommandOrderId() + tempCommandDetails.getCommandRequestId()).create(true); - - client.index(request, ActionListener.wrap(response -> { listener.onResponse(response.getId()); }, exception -> { - if (exception instanceof IOException) { - logger.error("IOException occurred creating command details", exception); - } - listener.onResponse(null); - })); - } catch (IOException e) { - logger.error("IOException occurred creating command details", e); - listener.onResponse(null); - } - } - - /** - * Find command details for a particular document Id - * @param documentId unique id for command Details document - * @param listener an {@code ActionListener} that has onResponse and onFailure that is used to return the command details if it was found - * or else null. - */ - private void findCommandDetails(final String documentId, ActionListener listener) { - GetRequest getRequest = new GetRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME).id(documentId); - client.get(getRequest, ActionListener.wrap(response -> { - if (!response.isExists()) { - logger.info("Non-existent command: " + documentId); - listener.onResponse(null); - } else { - try { - XContentParser parser = XContentType.JSON.xContent() - .createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, response.getSourceAsString()); - parser.nextToken(); - listener.onResponse(CommandDetails.parse(parser)); - } catch (IOException e) { - logger.error("IOException occurred finding CommandDetails for documentId " + documentId, e); - listener.onResponse(null); - } - } - }, exception -> { - logger.error("Exception occurred finding command details for documentId " + documentId, exception); - listener.onFailure(exception); - })); - } - - /** - * Delete command details to a corresponding document Id - * @param documentId unique id to find and delete the command details document in the index - * @param listener an {@code ActionListener} that has onResponse and onFailure that is used to return the command details if it was deleted - * or else null. - */ - public void deleteCommandDetails(final String documentId, ActionListener listener) { - DeleteRequest deleteRequest = new DeleteRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME).id(documentId); - client.delete(deleteRequest, ActionListener.wrap(response -> { - listener.onResponse( - response.getResult() == DocWriteResponse.Result.DELETED || response.getResult() == DocWriteResponse.Result.NOT_FOUND - ); - }, exception -> { - if (exception instanceof IndexNotFoundException || exception.getCause() instanceof IndexNotFoundException) { - logger.debug("Index is not found to delete command details for document id. {} " + documentId, exception.getMessage()); - listener.onResponse(true); - } else { - listener.onFailure(exception); - } - })); - } - - /** - * Update command details to a corresponding documentId - * @param updateCommandDetails update command details object entry - * @param documentId unique id to find and update the corresponding document mapped to it - * @param listener an {@code ActionListener} that has onResponse and onFailure that is used to return the command details if it was updated - * or else null. - */ - private void updateCommandDetails(final String documentId, final CommandDetails updateCommandDetails, ActionListener listener) { - try { - UpdateRequest updateRequest = new UpdateRequest().index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) - .id(documentId) - .doc(updateCommandDetails.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) - .fetchSource(true); - - client.update(updateRequest, ActionListener.wrap(response -> listener.onResponse(response.getId()), exception -> { - if (exception instanceof VersionConflictEngineException) { - logger.debug("could not update command details for documentId " + documentId, exception.getMessage()); - } - if (exception instanceof DocumentMissingException) { - logger.debug("Document is deleted. This happens if the command details is already removed {}", exception.getMessage()); - } - if (exception instanceof IOException) { - logger.error("IOException occurred in updating command details.", exception); - } - listener.onResponse(null); - })); - } catch (IOException e) { - logger.error("IOException occurred updating command details for documentId " + documentId, e); - listener.onResponse(null); - } - } - /** - * - * @param listener an {@code ActionListener} that has onResponse and onFailure that is used to return the command details index if it was created - * or else null. - */ - void createCommandManagerIndex(ActionListener listener) { - if (commandManagerIndexExists()) { - listener.onResponse(true); - } else { - CreateIndexRequest request = new CreateIndexRequest(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); - client.admin() - .indices() - .create(request, ActionListener.wrap(response -> listener.onResponse(response.isAcknowledged()), exception -> { - if (exception instanceof ResourceAlreadyExistsException - || exception.getCause() instanceof ResourceAlreadyExistsException) { - listener.onResponse(true); - } else { - listener.onFailure(exception); - } - })); - } - } - - public String generateRandomString(int stringLength) { - Random random = new Random(); - String randomString = ""; - // Build a list of ascii indices for Alphanumeric characters - // to be accessed by index - List asciiCharIndices = new ArrayList<>(); - // Decimal numbers - for(int i = 48; i <= 57 ; i++) - { - asciiCharIndices.add(i); - } - // Uppercase Latin Characters - for(int i = 65; i <= 90 ; i++) - { - asciiCharIndices.add(i); - } - // Lowercase Latin Characters - for(int i = 97; i <= 122 ; i++) - { - asciiCharIndices.add(i); - } - for(int i = 0; i <= stringLength; i++) - { - //randomString = randomString + (char) random.nextInt(asciiCharIndices.size()); - randomString = randomString + (char) (int) asciiCharIndices.get(random.nextInt(asciiCharIndices.size())); - logger.info((char) random.nextInt(asciiCharIndices.size())); - } - return randomString; - } - -} From 5e597e86d02bc4d911bb6514868ebcd0f0e4a987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Ruiz?= Date: Wed, 25 Sep 2024 16:33:28 +0200 Subject: [PATCH 15/27] More refactor --- .../commandmanager/CommandManagerPlugin.java | 2 +- .../commandmanager/index/CommandIndex.java | 57 +------- .../wazuh/commandmanager/model/Action.java | 13 +- .../wazuh/commandmanager/model/Command.java | 130 +++++------------- .../rest/action/RestPostCommandAction.java | 10 +- 5 files changed, 59 insertions(+), 153 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java index 7229e98..6feabb6 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java @@ -58,7 +58,7 @@ public Collection createComponents( IndexNameExpressionResolver indexNameExpressionResolver, Supplier repositoriesServiceSupplier ) { - this.commandIndex = new CommandIndex(client, clusterService); + this.commandIndex = new CommandIndex(client); return Collections.emptyList(); } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index 63081e0..d94bbdb 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -14,10 +14,9 @@ import org.opensearch.action.index.IndexRequest; import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; -import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.action.ActionFuture; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.action.ActionListener; +import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.index.shard.IndexingOperationListener; @@ -27,75 +26,33 @@ public class CommandIndex implements IndexingOperationListener { private static final Logger logger = LogManager.getLogger(CommandIndex.class); - public static Long TIME_OUT_FOR_REQUEST = 15L; private final Client client; - private final ClusterService clusterService; /** * @param client - * @param clusterService */ public CommandIndex( - final Client client, - final ClusterService clusterService + final Client client ) { this.client = client; - this.clusterService = clusterService; } /** - * Check if the CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME index exists. - * - * @return whether the index exists. + * @param command + * @return */ - public boolean indexExists() { - return clusterService - .state() - .routingTable() - .hasIndex(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); - } - - - public ActionFuture create(Command command) { + public RestStatus create(Command command) { try { IndexRequest request = new IndexRequest() .index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) .source(command.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) .id(command.getId()) .create(true); - return this.client.index(request); - } catch (IOException e) { - logger.error("IOException occurred creating command details", e); - } - return null; - } - - /** - * Persists the command into the commands index - * - * @param command command to persist in the index. - * @param listener - */ - protected void create(Command command, ActionListener listener) { - try { - // Create index request - final IndexRequest request = new IndexRequest() - .index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) - .source(command.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) - .id(command.getId()) - .create(true); - client.index(request, ActionListener.wrap(response -> { - listener.onResponse(response.getId()); - }, exception -> { - if (exception instanceof IOException) { - logger.error("IOException occurred creating command details", exception); - } - listener.onResponse(null); - })); + return this.client.index(request).actionGet().status(); } catch (IOException e) { logger.error("IOException occurred creating command details", e); - listener.onResponse(null); } + return RestStatus.INTERNAL_SERVER_ERROR; } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java index 441a2c6..ce574a8 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java @@ -7,6 +7,8 @@ */ package com.wazuh.commandmanager.model; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; import java.io.IOException; @@ -14,7 +16,7 @@ /** * Command's action fields. */ -public class Action { +public class Action implements ToXContentObject { public static final String TYPE = "type"; public static final String ARGS = "args"; @@ -93,4 +95,13 @@ public String getArgs() { public String getVersion() { return this.version; } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject("action"); + builder.field(TYPE, this.type); + builder.field(ARGS, this.args); + builder.field(VERSION, this.version); + return builder.endObject(); + } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java index 166ac83..8a85f9b 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java @@ -7,17 +7,17 @@ */ package com.wazuh.commandmanager.model; -import org.opensearch.action.ActionRequest; -import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; +import reactor.util.annotation.NonNull; import java.io.IOException; import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; -public class Command extends ActionRequest implements ToXContentObject { +public class Command implements ToXContentObject { public static final String ORDER_ID = "order_id"; public static final String REQUEST_ID = "request_id"; @@ -52,14 +52,14 @@ public class Command extends ActionRequest implements ToXContentObject { * @param action target action type and additional parameters */ public Command( - String requestID, - String orderID, - String source, - String target, - Integer timeout, - String type, - String user, - Action action + @NonNull String requestID, + @NonNull String orderID, + @NonNull String source, + @NonNull String target, + @NonNull Integer timeout, + @NonNull String type, + @NonNull String user, + @NonNull Action action ) { this.id = orderID + requestID; this.requestId = requestID; @@ -73,6 +73,14 @@ public Command( this.status = Status.PENDING; } + /** + * + * @param requestId + * @param orderId + * @param parser + * @return + * @throws IOException + */ public static Command parse(String requestId, String orderId, XContentParser parser) throws IOException { String source = null; String target = null; @@ -111,6 +119,9 @@ public static Command parse(String requestId, String orderId, XContentParser par } } + assert source != null; + assert target != null; + assert timeout != null; return new Command( requestId, orderId, @@ -123,101 +134,30 @@ public static Command parse(String requestId, String orderId, XContentParser par ); } - /** - * @return - */ - @Override - public ActionRequestValidationException validate() { - return null; - } - @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - XContentBuilder xContentBuilder = builder.startObject(); - - // @TODO review whether these IFs are necessary - if (this.source != null) { - xContentBuilder.field(SOURCE, this.source); - } - if (this.user != null) { - xContentBuilder.field(USER, this.user); - } - if (this.target != null) { - xContentBuilder.field(TARGET, this.target); - } - if (this.type != null) { - xContentBuilder.field(TYPE, this.type); - } - if (this.action != null) { - xContentBuilder.field(ACTION, this.action); - } - if (timeout != null) { - xContentBuilder.field(TIMEOUT, timeout); - } - - xContentBuilder.field(STATUS, this.status); - xContentBuilder.field(ORDER_ID, this.orderId); - xContentBuilder.field(REQUEST_ID, this.requestId); - - return xContentBuilder.endObject(); + builder.startObject(); + + builder.field(SOURCE, this.source); + builder.field(USER, this.user); + builder.field(TARGET, this.target); + builder.field(TYPE, this.type); + this.action.toXContent(builder, ToXContent.EMPTY_PARAMS); + builder.field(TIMEOUT, timeout); + builder.field(STATUS, this.status); + builder.field(ORDER_ID, this.orderId); + builder.field(REQUEST_ID, this.requestId); + + return builder.endObject(); } /** - * - * @return - */ - public String getOrderId() { - return this.orderId; - } - - /** - * - * @return - */ - public String getRequestId() { - return this.requestId; - } - - /** - * * @return */ public String getId() { return this.id; } -// @Override -// public boolean equals(Object o) { -// if (this == o) return true; -// if (o == null || getClass() != o.getClass()) return false; -// Command that = (Command) o; -// return Objects.equals(orderID, that.orderID) -// && Objects.equals(requestID, that.requestID) -// && Objects.equals(source, that.source) -// && Objects.equals(target, that.target) -// && Objects.equals(timeout, that.timeout) -// && Objects.equals(type, that.type) -// && Objects.equals(user, that.user) -// && Objects.equals(action, that.action) -// && Objects.equals(result, that.result); -// } - -// @Override -// public int hashCode() { -// return Objects.hash( -// orderID, -// requestID, -// source, -// target, -// timeout, -// type, -// user, -// action, -// result -// ); -// } - - @Override public String toString() { return "Command{" + diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 417f2b9..110afb1 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -10,17 +10,17 @@ import com.wazuh.commandmanager.CommandManagerPlugin; import com.wazuh.commandmanager.index.CommandIndex; import com.wazuh.commandmanager.model.Command; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.opensearch.action.index.IndexResponse; import org.opensearch.client.node.NodeClient; import org.opensearch.common.Randomness; +import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestRequest; +import java.awt.event.ActionListener; import java.io.IOException; import java.util.Collections; import java.util.List; @@ -38,8 +38,6 @@ public class RestPostCommandAction extends BaseRestHandler { public static final String POST_COMMAND_ACTION_REQUEST_DETAILS = "post_command_action_request_details"; - private final Logger logger = LogManager.getLogger(RestPostCommandAction.class); - private final CommandIndex commandIndex; /** @@ -94,7 +92,7 @@ protected RestChannelConsumer prepareRequest( Command command = Command.parse(requestId, orderId, parser); // Persist command - IndexResponse indexResponse = this.commandIndex.create(command).actionGet(); + RestStatus status = this.commandIndex.create(command); // Send response return channel -> { @@ -102,7 +100,7 @@ protected RestChannelConsumer prepareRequest( builder.startObject(); builder.field("command", command.getId()); builder.endObject(); - channel.sendResponse(new BytesRestResponse(indexResponse.status(), builder)); + channel.sendResponse(new BytesRestResponse(status, builder)); } }; } From 698bb7104fd475a7c1176d0887187288896cfc34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Ruiz?= Date: Wed, 25 Sep 2024 16:34:27 +0200 Subject: [PATCH 16/27] Remove unused imports --- .../main/java/com/wazuh/commandmanager/index/CommandIndex.java | 2 -- .../wazuh/commandmanager/rest/action/RestPostCommandAction.java | 2 -- 2 files changed, 4 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index d94bbdb..2dec733 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -12,10 +12,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.action.index.IndexRequest; -import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.core.action.ActionListener; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.index.shard.IndexingOperationListener; diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 110afb1..5c01e38 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -10,7 +10,6 @@ import com.wazuh.commandmanager.CommandManagerPlugin; import com.wazuh.commandmanager.index.CommandIndex; import com.wazuh.commandmanager.model.Command; -import org.opensearch.action.index.IndexResponse; import org.opensearch.client.node.NodeClient; import org.opensearch.common.Randomness; import org.opensearch.core.rest.RestStatus; @@ -20,7 +19,6 @@ import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestRequest; -import java.awt.event.ActionListener; import java.io.IOException; import java.util.Collections; import java.util.List; From e3dd5bbf88443da57faadc00a0c5fd63bd25c2ca Mon Sep 17 00:00:00 2001 From: f-galland Date: Wed, 25 Sep 2024 15:07:09 -0300 Subject: [PATCH 17/27] Go back to using an ActionListener to create a document. --- .../commandmanager/index/CommandIndex.java | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index 2dec733..135c92d 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -12,13 +12,16 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; import org.opensearch.common.xcontent.XContentFactory; +import org.opensearch.core.action.ActionListener; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.index.shard.IndexingOperationListener; import java.io.IOException; +import java.util.concurrent.CompletableFuture; public class CommandIndex implements IndexingOperationListener { @@ -36,7 +39,8 @@ public CommandIndex( } /** - * @param command + * + * @param command: Command to persist to an index * @return */ public RestStatus create(Command command) { @@ -47,7 +51,24 @@ public RestStatus create(Command command) { .id(command.getId()) .create(true); - return this.client.index(request).actionGet().status(); + //return this.client.index(request).actionGet().status(); + CompletableFuture inProgressFuture = new CompletableFuture<>(); + + client.index( + request, + new ActionListener() { + @Override + public void onResponse(IndexResponse indexResponse) { + inProgressFuture.complete(indexResponse); + } + + @Override + public void onFailure(Exception e) { + logger.info("Could not process command", e); + inProgressFuture.completeExceptionally(e); + } + } + ); } catch (IOException e) { logger.error("IOException occurred creating command details", e); } From 5505f70aa84b0a393221c77a95cd1da4df451e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Ruiz?= Date: Thu, 26 Sep 2024 13:52:09 +0200 Subject: [PATCH 18/27] Change Action.args to list of strings Improve endpoint response --- .../commandmanager/index/CommandIndex.java | 23 +++++++++---------- .../wazuh/commandmanager/model/Action.java | 16 ++++++++----- .../rest/action/RestPostCommandAction.java | 4 +++- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index 135c92d..674f7dd 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -39,7 +39,6 @@ public CommandIndex( } /** - * * @param command: Command to persist to an index * @return */ @@ -55,19 +54,19 @@ public RestStatus create(Command command) { CompletableFuture inProgressFuture = new CompletableFuture<>(); client.index( - request, - new ActionListener() { - @Override - public void onResponse(IndexResponse indexResponse) { - inProgressFuture.complete(indexResponse); - } + request, + new ActionListener<>() { + @Override + public void onResponse(IndexResponse indexResponse) { + inProgressFuture.complete(indexResponse); + } - @Override - public void onFailure(Exception e) { - logger.info("Could not process command", e); - inProgressFuture.completeExceptionally(e); + @Override + public void onFailure(Exception e) { + logger.info("Could not process command", e); + inProgressFuture.completeExceptionally(e); + } } - } ); } catch (IOException e) { logger.error("IOException occurred creating command details", e); diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java index ce574a8..7e53aeb 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java @@ -12,6 +12,7 @@ import org.opensearch.core.xcontent.XContentParser; import java.io.IOException; +import java.util.List; /** * Command's action fields. @@ -22,7 +23,7 @@ public class Action implements ToXContentObject { public static final String ARGS = "args"; public static final String VERSION = "version"; private final String type; - private final String args; + private final List args; private final String version; /** @@ -32,7 +33,7 @@ public class Action implements ToXContentObject { * @param args actual command. * @param version version of the action. */ - public Action(String type, String args, String version) { + public Action(String type, List args, String version) { this.type = type; this.args = args; this.version = version; @@ -45,7 +46,7 @@ public Action(String type, String args, String version) { */ public static Action parse(XContentParser parser) throws IOException { String type = ""; - String args = ""; + List args = List.of(); String version = ""; while (parser.nextToken() != XContentParser.Token.END_OBJECT) { @@ -56,7 +57,7 @@ public static Action parse(XContentParser parser) throws IOException { type = parser.text(); break; case ARGS: - args = parser.text(); + args = parser.list(); break; case VERSION: version = parser.text(); @@ -66,7 +67,10 @@ public static Action parse(XContentParser parser) throws IOException { break; } } - return new Action(type, args, version); + + // Cast args field Object list to String list + List convertedArgsFields = (List) (List) (args); + return new Action(type, convertedArgsFields, version); } /** @@ -83,7 +87,7 @@ public String getType() { * * @return args */ - public String getArgs() { + public List getArgs() { return this.args; } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 5c01e38..519aa3f 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -96,7 +96,9 @@ protected RestChannelConsumer prepareRequest( return channel -> { try (XContentBuilder builder = channel.newBuilder()) { builder.startObject(); - builder.field("command", command.getId()); + builder.field("_index", CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); + builder.field("_id", command.getId()); + builder.field("result", status.name()); builder.endObject(); channel.sendResponse(new BytesRestResponse(status, builder)); } From 0d0901a0e0452acc140a919ecb45a71d4a136382 Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 26 Sep 2024 16:50:36 -0300 Subject: [PATCH 19/27] Return the operation's RestStatus --- .../wazuh/commandmanager/index/CommandIndex.java | 14 +++++++++----- .../rest/action/RestPostCommandAction.java | 8 +++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index 674f7dd..51ba5dd 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; public class CommandIndex implements IndexingOperationListener { @@ -39,10 +40,14 @@ public CommandIndex( } /** - * @param command: Command to persist to an index - * @return + * + * @param command a Command class command + * @return Indexing operation RestStatus response + * @throws ExecutionException + * @throws InterruptedException */ - public RestStatus create(Command command) { + public RestStatus create(Command command) throws ExecutionException, InterruptedException { + CompletableFuture inProgressFuture = new CompletableFuture<>(); try { IndexRequest request = new IndexRequest() .index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) @@ -51,7 +56,6 @@ public RestStatus create(Command command) { .create(true); //return this.client.index(request).actionGet().status(); - CompletableFuture inProgressFuture = new CompletableFuture<>(); client.index( request, @@ -71,6 +75,6 @@ public void onFailure(Exception e) { } catch (IOException e) { logger.error("IOException occurred creating command details", e); } - return RestStatus.INTERNAL_SERVER_ERROR; + return inProgressFuture.get().status(); } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 519aa3f..142b9c8 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.concurrent.ExecutionException; import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; import static org.opensearch.rest.RestRequest.Method.POST; @@ -90,7 +91,12 @@ protected RestChannelConsumer prepareRequest( Command command = Command.parse(requestId, orderId, parser); // Persist command - RestStatus status = this.commandIndex.create(command); + RestStatus status; + try { + status = this.commandIndex.create(command); + } catch (ExecutionException | InterruptedException e) { + throw new RuntimeException(e); + } // Send response return channel -> { From 89bb6f5e235a70440b5b9c8bf2e7af2798ee0d14 Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 26 Sep 2024 16:58:14 -0300 Subject: [PATCH 20/27] Add logging --- .../java/com/wazuh/commandmanager/index/CommandIndex.java | 5 ++--- .../commandmanager/rest/action/RestPostCommandAction.java | 6 ++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index 51ba5dd..f6f345d 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -49,14 +49,13 @@ public CommandIndex( public RestStatus create(Command command) throws ExecutionException, InterruptedException { CompletableFuture inProgressFuture = new CompletableFuture<>(); try { + logger.info("Creating request for command: {}", command.getId()); IndexRequest request = new IndexRequest() .index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) .source(command.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) .id(command.getId()) .create(true); - //return this.client.index(request).actionGet().status(); - client.index( request, new ActionListener<>() { @@ -67,7 +66,7 @@ public void onResponse(IndexResponse indexResponse) { @Override public void onFailure(Exception e) { - logger.info("Could not process command", e); + logger.info("Could not process command: {}", command.getId(), e); inProgressFuture.completeExceptionally(e); } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 142b9c8..4da088f 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -10,6 +10,8 @@ import com.wazuh.commandmanager.CommandManagerPlugin; import com.wazuh.commandmanager.index.CommandIndex; import com.wazuh.commandmanager.model.Command; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.opensearch.client.node.NodeClient; import org.opensearch.common.Randomness; import org.opensearch.core.rest.RestStatus; @@ -39,6 +41,8 @@ public class RestPostCommandAction extends BaseRestHandler { private final CommandIndex commandIndex; + private static final Logger logger = LogManager.getLogger(RestPostCommandAction.class); + /** * Default constructor * @@ -93,8 +97,10 @@ protected RestChannelConsumer prepareRequest( // Persist command RestStatus status; try { + logger.info("Sending request to create command: {}", command.getId()); status = this.commandIndex.create(command); } catch (ExecutionException | InterruptedException e) { + logger.error("Could not send request to create command", e); throw new RuntimeException(e); } From 7b1988cb23400d58cbee18d9e893d93d945daf31 Mon Sep 17 00:00:00 2001 From: f-galland Date: Thu, 26 Sep 2024 17:38:45 -0300 Subject: [PATCH 21/27] Add yaml REST tests --- .../api/_plugins._commandmanager.json | 18 ++++++++++ .../rest-api-spec/test/20_create.yml | 35 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 plugins/command-manager/src/yamlRestTest/resources/rest-api-spec/api/_plugins._commandmanager.json create mode 100644 plugins/command-manager/src/yamlRestTest/resources/rest-api-spec/test/20_create.yml diff --git a/plugins/command-manager/src/yamlRestTest/resources/rest-api-spec/api/_plugins._commandmanager.json b/plugins/command-manager/src/yamlRestTest/resources/rest-api-spec/api/_plugins._commandmanager.json new file mode 100644 index 0000000..b04db29 --- /dev/null +++ b/plugins/command-manager/src/yamlRestTest/resources/rest-api-spec/api/_plugins._commandmanager.json @@ -0,0 +1,18 @@ +{ + "_plugins._commandmanager": { + "stability" : "stable", + "url": { + "paths": [ + { + "path": "/_plugins/_commandmanager", + "methods": [ + "POST" + ] + } + ] + }, + "body": { + "description": "The document" + } + } +} \ No newline at end of file diff --git a/plugins/command-manager/src/yamlRestTest/resources/rest-api-spec/test/20_create.yml b/plugins/command-manager/src/yamlRestTest/resources/rest-api-spec/test/20_create.yml new file mode 100644 index 0000000..4a7489d --- /dev/null +++ b/plugins/command-manager/src/yamlRestTest/resources/rest-api-spec/test/20_create.yml @@ -0,0 +1,35 @@ +--- +"Create command": + - do: + _plugins._commandmanager: + body: + source: "Users/Services" + user: "user13" + target: "WazuhServerCluster5" + type: "agent_group" + action: { + type: "Server cluster", + args: [ "/path/to/executable/arg8" ], + version: "v4" + } + timeout: 100 + + - set: { _id: document_id } + - match: { _index: command-manager } + + - do: + get: + index: command-manager + id: $document_id + - match: { _source.source: "Users/Services" } + - match: { _source.user: "user13" } + - match: { _source.target: "WazuhServerCluster5" } + - match: { _source.type: "agent_group" } + - match: { _source.action: + { + type: "Server cluster", + args: [ "/path/to/executable/arg8" ], + version: "v4" + } + } + - match: { _source.timeout: 100 } From 6379beeb1b1052088c5055b329d04983a12c6366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Ruiz?= Date: Fri, 27 Sep 2024 13:26:53 +0200 Subject: [PATCH 22/27] Replace document's ID with UUID --- .../commandmanager/CommandManagerPlugin.java | 44 +++++++++---------- .../commandmanager/index/CommandIndex.java | 6 +-- .../wazuh/commandmanager/model/Command.java | 36 +++++++-------- .../rest/action/RestPostCommandAction.java | 20 +-------- 4 files changed, 39 insertions(+), 67 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java index 6feabb6..61b1dd9 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java @@ -7,10 +7,8 @@ */ package com.wazuh.commandmanager; -import com.wazuh.commandmanager.rest.action.RestPostCommandAction; import com.wazuh.commandmanager.index.CommandIndex; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import com.wazuh.commandmanager.rest.action.RestPostCommandAction; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.node.DiscoveryNodes; @@ -39,28 +37,28 @@ public class CommandManagerPlugin extends Plugin implements ActionPlugin { - public static final String COMMAND_MANAGER_BASE_URI = "/_plugins/_commandmanager"; - public static final String COMMAND_MANAGER_INDEX_NAME = "command-manager"; + public static final String COMMAND_MANAGER_BASE_URI = "/_plugins/_commandmanager"; + public static final String COMMAND_MANAGER_INDEX_NAME = "command-manager"; - private CommandIndex commandIndex; + private CommandIndex commandIndex; - @Override - public Collection createComponents( - Client client, - ClusterService clusterService, - ThreadPool threadPool, - ResourceWatcherService resourceWatcherService, - ScriptService scriptService, - NamedXContentRegistry xContentRegistry, - Environment environment, - NodeEnvironment nodeEnvironment, - NamedWriteableRegistry namedWriteableRegistry, - IndexNameExpressionResolver indexNameExpressionResolver, - Supplier repositoriesServiceSupplier - ) { - this.commandIndex = new CommandIndex(client); - return Collections.emptyList(); - } + @Override + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + this.commandIndex = new CommandIndex(client); + return Collections.emptyList(); + } public List getRestHandlers( Settings settings, diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index f6f345d..f1694fd 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -33,18 +33,14 @@ public class CommandIndex implements IndexingOperationListener { /** * @param client */ - public CommandIndex( - final Client client - ) { + public CommandIndex(Client client) { this.client = client; } /** - * * @param command a Command class command * @return Indexing operation RestStatus response * @throws ExecutionException - * @throws InterruptedException */ public RestStatus create(Command command) throws ExecutionException, InterruptedException { CompletableFuture inProgressFuture = new CompletableFuture<>(); diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java index 8a85f9b..f4ddcd9 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java @@ -7,6 +7,7 @@ */ package com.wazuh.commandmanager.model; +import org.opensearch.common.UUIDs; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.ToXContentObject; import org.opensearch.core.xcontent.XContentBuilder; @@ -42,18 +43,14 @@ public class Command implements ToXContentObject { /** * Default constructor * - * @param requestID Unique identifier generated by the Command Manager. Auto-incremental. - * @param orderID Unique identifier generated by the Command Manager. Auto-incremental within the same Command Request ID. - * @param source origin of the request. One - * @param target Cluster name destination. - * @param timeout Number of seconds to wait for the command to be executed. - * @param type action type. One of agent_groups, agent, server. - * @param user the user that originated the request - * @param action target action type and additional parameters + * @param source origin of the request. One + * @param target Cluster name destination. + * @param timeout Number of seconds to wait for the command to be executed. + * @param type action type. One of agent_groups, agent, server. + * @param user the user that originated the request + * @param action target action type and additional parameters */ public Command( - @NonNull String requestID, - @NonNull String orderID, @NonNull String source, @NonNull String target, @NonNull Integer timeout, @@ -61,9 +58,9 @@ public Command( @NonNull String user, @NonNull Action action ) { - this.id = orderID + requestID; - this.requestId = requestID; - this.orderId = orderID; + this.id = UUIDs.base64UUID(); + this.requestId = UUIDs.base64UUID(); + this.orderId = UUIDs.base64UUID(); this.source = source; this.target = target; this.timeout = timeout; @@ -74,14 +71,13 @@ public Command( } /** + * Parses the request's payload into the Command model. * - * @param requestId - * @param orderId - * @param parser - * @return + * @param parser XContentParser from the Rest Request + * @return instance of Command * @throws IOException */ - public static Command parse(String requestId, String orderId, XContentParser parser) throws IOException { + public static Command parse(XContentParser parser) throws IOException { String source = null; String target = null; Integer timeout = null; @@ -123,8 +119,6 @@ public static Command parse(String requestId, String orderId, XContentParser par assert target != null; assert timeout != null; return new Command( - requestId, - orderId, source, target, timeout, @@ -152,7 +146,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws } /** - * @return + * @return Document's ID */ public String getId() { return this.id; diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 4da088f..7194eba 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -13,7 +13,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.client.node.NodeClient; -import org.opensearch.common.Randomness; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.XContentBuilder; import org.opensearch.core.xcontent.XContentParser; @@ -38,10 +37,8 @@ public class RestPostCommandAction extends BaseRestHandler { public static final String POST_COMMAND_ACTION_REQUEST_DETAILS = "post_command_action_request_details"; - - private final CommandIndex commandIndex; - private static final Logger logger = LogManager.getLogger(RestPostCommandAction.class); + private final CommandIndex commandIndex; /** * Default constructor @@ -70,16 +67,6 @@ public List routes() { ); } - /** - * Generates a random ID as string on range 0 to 10. - * - * @return random int as string - * TODO To be removed in future iterations - */ - private String getRandomId() { - return "" + Randomness.get().nextInt(); - } - @Override protected RestChannelConsumer prepareRequest( final RestRequest restRequest, @@ -89,10 +76,7 @@ protected RestChannelConsumer prepareRequest( XContentParser parser = restRequest.contentParser(); ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser); - // Placeholders. These IDs will be generated properly on next iterations. - String requestId = getRandomId(); - String orderId = getRandomId(); - Command command = Command.parse(requestId, orderId, parser); + Command command = Command.parse(parser); // Persist command RestStatus status; From 34081120047797fe4828066511be206502660f9b Mon Sep 17 00:00:00 2001 From: f-galland Date: Fri, 27 Sep 2024 11:52:23 -0300 Subject: [PATCH 23/27] Make threadpool accessible to RestPostCommandAction --- .../com/wazuh/commandmanager/CommandManagerPlugin.java | 4 +++- .../commandmanager/rest/action/RestPostCommandAction.java | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java index 61b1dd9..0bceb15 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java @@ -41,6 +41,7 @@ public class CommandManagerPlugin extends Plugin implements ActionPlugin { public static final String COMMAND_MANAGER_INDEX_NAME = "command-manager"; private CommandIndex commandIndex; + private ThreadPool threadPool; @Override public Collection createComponents( @@ -57,6 +58,7 @@ public Collection createComponents( Supplier repositoriesServiceSupplier ) { this.commandIndex = new CommandIndex(client); + this.threadPool = threadPool; return Collections.emptyList(); } @@ -69,6 +71,6 @@ public List getRestHandlers( IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster ) { - return Collections.singletonList(new RestPostCommandAction(this.commandIndex)); + return Collections.singletonList(new RestPostCommandAction(this.commandIndex, this.threadPool)); } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 7194eba..f8edbea 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -12,6 +12,7 @@ import com.wazuh.commandmanager.model.Command; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.ThreadContext; import org.opensearch.client.node.NodeClient; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.XContentBuilder; @@ -19,6 +20,7 @@ import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestRequest; +import org.opensearch.threadpool.ThreadPool; import java.io.IOException; import java.util.Collections; @@ -39,14 +41,18 @@ public class RestPostCommandAction extends BaseRestHandler { public static final String POST_COMMAND_ACTION_REQUEST_DETAILS = "post_command_action_request_details"; private static final Logger logger = LogManager.getLogger(RestPostCommandAction.class); private final CommandIndex commandIndex; + private final ThreadPool threadPool; /** * Default constructor * * @param commandIndex persistence layer + * @param threadPool */ - public RestPostCommandAction(CommandIndex commandIndex) { + public RestPostCommandAction(CommandIndex commandIndex, ThreadPool threadPool) { this.commandIndex = commandIndex; + this.threadPool = threadPool; + } public String getName() { From 48630ab1c76f9f3abc2df5bc79c50abafe333c96 Mon Sep 17 00:00:00 2001 From: f-galland Date: Fri, 27 Sep 2024 13:22:58 -0300 Subject: [PATCH 24/27] Make document creation threaded --- .../commandmanager/index/CommandIndex.java | 34 +++++++- .../rest/action/RestPostCommandAction.java | 78 ++++++++++++------- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index f1694fd..b4e9eac 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -14,15 +14,18 @@ import org.opensearch.action.index.IndexRequest; import org.opensearch.action.index.IndexResponse; import org.opensearch.client.Client; +import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.xcontent.XContentFactory; import org.opensearch.core.action.ActionListener; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.index.shard.IndexingOperationListener; +import org.opensearch.threadpool.ThreadPool; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; public class CommandIndex implements IndexingOperationListener { @@ -38,11 +41,12 @@ public CommandIndex(Client client) { } /** - * @param command a Command class command + * @param command a Command class command + * @param threadPool * @return Indexing operation RestStatus response * @throws ExecutionException */ - public RestStatus create(Command command) throws ExecutionException, InterruptedException { + public RestStatus create(Command command, ThreadPool threadPool) throws ExecutionException, InterruptedException { CompletableFuture inProgressFuture = new CompletableFuture<>(); try { logger.info("Creating request for command: {}", command.getId()); @@ -72,4 +76,30 @@ public void onFailure(Exception e) { } return inProgressFuture.get().status(); } + + public CompletableFuture performAsyncCreate(Command command, ThreadPool threadPool) { + CompletableFuture future = new CompletableFuture<>(); + ExecutorService executor = threadPool.executor(ThreadPool.Names.WRITE); + try { + IndexRequest request = new IndexRequest() + .index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) + .source(command.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .id(command.getId()) + .create(true); + executor.submit( + () -> { + try (ThreadContext.StoredContext ignored = threadPool.getThreadContext().stashContext()) { + RestStatus result = client.index(request).actionGet().status(); + future.complete(result); + } catch (Exception e) { + future.completeExceptionally(e); + } + } + ); + } catch (Exception e) { + logger.error(e); + } + return future; + } + } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index f8edbea..290068f 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -62,21 +62,55 @@ public String getName() { @Override public List routes() { return Collections.singletonList( - new Route( - POST, - String.format( - Locale.ROOT, - "%s", - CommandManagerPlugin.COMMAND_MANAGER_BASE_URI - ) + new Route( + POST, + String.format( + Locale.ROOT, + "%s", + CommandManagerPlugin.COMMAND_MANAGER_BASE_URI ) + ) ); } +// @Override +// protected RestChannelConsumer prepareRequest( +// final RestRequest restRequest, +// final NodeClient client +// ) throws IOException { +// // Get request details +// XContentParser parser = restRequest.contentParser(); +// ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser); +// +// Command command = Command.parse(parser); +// +// // Persist command +// RestStatus status; +// try { +// logger.info("Sending request to create command: {}", command.getId()); +// status = this.commandIndex.create(command,threadPool); +// } catch (ExecutionException | InterruptedException e) { +// logger.error("Could not send request to create command", e); +// throw new RuntimeException(e); +// } +// +// // Send response +// return channel -> { +// try (XContentBuilder builder = channel.newBuilder()) { +// builder.startObject(); +// builder.field("_index", CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); +// builder.field("_id", command.getId()); +// builder.field("result", status.name()); +// builder.endObject(); +// channel.sendResponse(new BytesRestResponse(status, builder)); +// } +// }; +// } + @Override protected RestChannelConsumer prepareRequest( - final RestRequest restRequest, - final NodeClient client + final RestRequest restRequest, + final NodeClient client ) throws IOException { // Get request details XContentParser parser = restRequest.contentParser(); @@ -84,26 +118,16 @@ protected RestChannelConsumer prepareRequest( Command command = Command.parse(parser); - // Persist command - RestStatus status; - try { - logger.info("Sending request to create command: {}", command.getId()); - status = this.commandIndex.create(command); - } catch (ExecutionException | InterruptedException e) { - logger.error("Could not send request to create command", e); - throw new RuntimeException(e); - } - // Send response return channel -> { - try (XContentBuilder builder = channel.newBuilder()) { - builder.startObject(); - builder.field("_index", CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); - builder.field("_id", command.getId()); - builder.field("result", status.name()); - builder.endObject(); - channel.sendResponse(new BytesRestResponse(status, builder)); - } + commandIndex.performAsyncCreate(command, this.threadPool) + .thenAccept(result -> { + channel.sendResponse(new BytesRestResponse(RestStatus.OK, result.toString())); + }).exceptionally( e -> { + channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage())); + return null; + }); }; + } } From 41ba7c3fc8ee7e51fd4dc2baf300e6e8d474a10c Mon Sep 17 00:00:00 2001 From: f-galland Date: Fri, 27 Sep 2024 13:44:26 -0300 Subject: [PATCH 25/27] Add a proper status reply --- .../commandmanager/index/CommandIndex.java | 13 +++- .../rest/action/RestPostCommandAction.java | 61 ++++++------------- 2 files changed, 27 insertions(+), 47 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index b4e9eac..4c54c7b 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -77,7 +77,14 @@ public void onFailure(Exception e) { return inProgressFuture.get().status(); } - public CompletableFuture performAsyncCreate(Command command, ThreadPool threadPool) { + /** + * + * @param command: A Command model object + * @param threadPool: An OpenSearch ThreadPool as passed to the createComponents() method + * @return A CompletableFuture with the RestStatus response from the operation + */ + + public CompletableFuture asyncCreate(Command command, ThreadPool threadPool) { CompletableFuture future = new CompletableFuture<>(); ExecutorService executor = threadPool.executor(ThreadPool.Names.WRITE); try { @@ -89,8 +96,8 @@ public CompletableFuture performAsyncCreate(Command command, ThreadP executor.submit( () -> { try (ThreadContext.StoredContext ignored = threadPool.getThreadContext().stashContext()) { - RestStatus result = client.index(request).actionGet().status(); - future.complete(result); + RestStatus restStatus = client.index(request).actionGet().status(); + future.complete(restStatus); } catch (Exception e) { future.completeExceptionally(e); } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 290068f..218a305 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -12,7 +12,6 @@ import com.wazuh.commandmanager.model.Command; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.ThreadContext; import org.opensearch.client.node.NodeClient; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.XContentBuilder; @@ -26,7 +25,6 @@ import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.concurrent.ExecutionException; import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; import static org.opensearch.rest.RestRequest.Method.POST; @@ -73,40 +71,6 @@ public List routes() { ); } -// @Override -// protected RestChannelConsumer prepareRequest( -// final RestRequest restRequest, -// final NodeClient client -// ) throws IOException { -// // Get request details -// XContentParser parser = restRequest.contentParser(); -// ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser); -// -// Command command = Command.parse(parser); -// -// // Persist command -// RestStatus status; -// try { -// logger.info("Sending request to create command: {}", command.getId()); -// status = this.commandIndex.create(command,threadPool); -// } catch (ExecutionException | InterruptedException e) { -// logger.error("Could not send request to create command", e); -// throw new RuntimeException(e); -// } -// -// // Send response -// return channel -> { -// try (XContentBuilder builder = channel.newBuilder()) { -// builder.startObject(); -// builder.field("_index", CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); -// builder.field("_id", command.getId()); -// builder.field("result", status.name()); -// builder.endObject(); -// channel.sendResponse(new BytesRestResponse(status, builder)); -// } -// }; -// } - @Override protected RestChannelConsumer prepareRequest( final RestRequest restRequest, @@ -120,14 +84,23 @@ protected RestChannelConsumer prepareRequest( // Send response return channel -> { - commandIndex.performAsyncCreate(command, this.threadPool) - .thenAccept(result -> { - channel.sendResponse(new BytesRestResponse(RestStatus.OK, result.toString())); - }).exceptionally( e -> { - channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage())); - return null; - }); - }; + commandIndex.asyncCreate(command, this.threadPool) + .thenAccept(restStatus -> { + try (XContentBuilder builder = channel.newBuilder()) { + builder.startObject(); + builder.field("_index", CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); + builder.field("_id", command.getId()); + builder.field("result", restStatus.name()); + builder.endObject(); + channel.sendResponse(new BytesRestResponse(restStatus, builder)); + } catch (Exception e) { + logger.error("Error indexing command: ",e); + } + }).exceptionally(e -> { + channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage())); + return null; + }); + }; } } From 84f4f55b855d32c34fe73cd2617145289451c988 Mon Sep 17 00:00:00 2001 From: f-galland Date: Fri, 27 Sep 2024 13:45:37 -0300 Subject: [PATCH 26/27] Rename old create() method --- .../main/java/com/wazuh/commandmanager/index/CommandIndex.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index 4c54c7b..483457f 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -42,11 +42,10 @@ public CommandIndex(Client client) { /** * @param command a Command class command - * @param threadPool * @return Indexing operation RestStatus response * @throws ExecutionException */ - public RestStatus create(Command command, ThreadPool threadPool) throws ExecutionException, InterruptedException { + public RestStatus create(Command command) throws ExecutionException, InterruptedException { CompletableFuture inProgressFuture = new CompletableFuture<>(); try { logger.info("Creating request for command: {}", command.getId()); From 1278e38265c8f859e1acc3e9f73cc656cccc9c05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lex=20Ruiz?= Date: Mon, 30 Sep 2024 17:39:51 +0200 Subject: [PATCH 27/27] Create index-template-commands on POST request --- .../commandmanager/CommandManagerPlugin.java | 16 ++- .../commandmanager/index/CommandIndex.java | 131 +++++++++++------- .../wazuh/commandmanager/model/Action.java | 9 ++ .../wazuh/commandmanager/model/Command.java | 12 +- .../rest/action/RestPostCommandAction.java | 28 ++-- .../utils/IndexTemplateUtils.java | 81 +++++++++++ .../resources/index-template-commands.json | 91 ++++++++++++ 7 files changed, 292 insertions(+), 76 deletions(-) create mode 100644 plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/IndexTemplateUtils.java create mode 100644 plugins/command-manager/src/main/resources/index-template-commands.json diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java index 0bceb15..267e0f4 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/CommandManagerPlugin.java @@ -35,13 +35,18 @@ import java.util.List; import java.util.function.Supplier; - +/** + * The Command Manager plugin exposes an HTTP API with a single endpoint to + * receive raw commands from the Wazuh Server. These commands are processed, + * indexed and sent back to the Server for its delivery to, in most cases, the + * Agents. + */ public class CommandManagerPlugin extends Plugin implements ActionPlugin { public static final String COMMAND_MANAGER_BASE_URI = "/_plugins/_commandmanager"; - public static final String COMMAND_MANAGER_INDEX_NAME = "command-manager"; + public static final String COMMAND_MANAGER_INDEX_NAME = ".commands"; + public static final String COMMAND_MANAGER_INDEX_TEMPLATE_NAME = "index-template-commands"; private CommandIndex commandIndex; - private ThreadPool threadPool; @Override public Collection createComponents( @@ -57,8 +62,7 @@ public Collection createComponents( IndexNameExpressionResolver indexNameExpressionResolver, Supplier repositoriesServiceSupplier ) { - this.commandIndex = new CommandIndex(client); - this.threadPool = threadPool; + this.commandIndex = new CommandIndex(client, clusterService, threadPool); return Collections.emptyList(); } @@ -71,6 +75,6 @@ public List getRestHandlers( IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster ) { - return Collections.singletonList(new RestPostCommandAction(this.commandIndex, this.threadPool)); + return Collections.singletonList(new RestPostCommandAction(this.commandIndex)); } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java index 483457f..853b41a 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/index/CommandIndex.java @@ -9,22 +9,26 @@ import com.wazuh.commandmanager.CommandManagerPlugin; import com.wazuh.commandmanager.model.Command; +import com.wazuh.commandmanager.utils.IndexTemplateUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.action.admin.indices.template.put.PutIndexTemplateRequest; import org.opensearch.action.index.IndexRequest; -import org.opensearch.action.index.IndexResponse; +import org.opensearch.action.support.master.AcknowledgedResponse; import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexTemplateMetadata; +import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.xcontent.XContentFactory; -import org.opensearch.core.action.ActionListener; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.index.shard.IndexingOperationListener; import org.opensearch.threadpool.ThreadPool; import java.io.IOException; +import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; public class CommandIndex implements IndexingOperationListener { @@ -32,80 +36,105 @@ public class CommandIndex implements IndexingOperationListener { private static final Logger logger = LogManager.getLogger(CommandIndex.class); private final Client client; + private final ClusterService clusterService; + private final ThreadPool threadPool; /** - * @param client + * Default constructor + * + * @param client OpenSearch client. + * @param clusterService OpenSearch cluster service. + * @param threadPool An OpenSearch ThreadPool. */ - public CommandIndex(Client client) { + public CommandIndex(Client client, ClusterService clusterService, ThreadPool threadPool) { this.client = client; + this.clusterService = clusterService; + this.threadPool = threadPool; } /** - * @param command a Command class command - * @return Indexing operation RestStatus response - * @throws ExecutionException + * @param command: A Command model object + * @return A CompletableFuture with the RestStatus response from the operation */ - public RestStatus create(Command command) throws ExecutionException, InterruptedException { - CompletableFuture inProgressFuture = new CompletableFuture<>(); + public CompletableFuture asyncCreate(Command command) { + CompletableFuture future = new CompletableFuture<>(); + ExecutorService executor = this.threadPool.executor(ThreadPool.Names.WRITE); + + // Create index template if it does not exist. + if (!indexTemplateExists(CommandManagerPlugin.COMMAND_MANAGER_INDEX_TEMPLATE_NAME)) { + putIndexTemplate(CommandManagerPlugin.COMMAND_MANAGER_INDEX_TEMPLATE_NAME); + } else { + logger.info(String.format( + "Index template %s already exists. Skipping creation.", + CommandManagerPlugin.COMMAND_MANAGER_INDEX_TEMPLATE_NAME + )); + } + + logger.debug("Indexing command {}", command); try { - logger.info("Creating request for command: {}", command.getId()); IndexRequest request = new IndexRequest() .index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) .source(command.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) .id(command.getId()) .create(true); - - client.index( - request, - new ActionListener<>() { - @Override - public void onResponse(IndexResponse indexResponse) { - inProgressFuture.complete(indexResponse); - } - - @Override - public void onFailure(Exception e) { - logger.info("Could not process command: {}", command.getId(), e); - inProgressFuture.completeExceptionally(e); + executor.submit( + () -> { + try (ThreadContext.StoredContext ignored = this.threadPool.getThreadContext().stashContext()) { + RestStatus restStatus = client.index(request).actionGet().status(); + future.complete(restStatus); + } catch (Exception e) { + future.completeExceptionally(e); } } ); } catch (IOException e) { - logger.error("IOException occurred creating command details", e); + logger.error(e); } - return inProgressFuture.get().status(); + return future; } /** - * - * @param command: A Command model object - * @param threadPool: An OpenSearch ThreadPool as passed to the createComponents() method - * @return A CompletableFuture with the RestStatus response from the operation + * @return */ + public boolean indexTemplateExists(String template_name) { + Map templates = this.clusterService + .state() + .metadata() + .templates(); + logger.debug("Existing index templates: {} ", templates); - public CompletableFuture asyncCreate(Command command, ThreadPool threadPool) { - CompletableFuture future = new CompletableFuture<>(); - ExecutorService executor = threadPool.executor(ThreadPool.Names.WRITE); + return templates.containsKey(template_name); + } + + /** + * Inserts an index template + * + * @param templateName : The name if the index template to load + */ + public void putIndexTemplate(String templateName) { + ExecutorService executor = this.threadPool.executor(ThreadPool.Names.WRITE); try { - IndexRequest request = new IndexRequest() - .index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) - .source(command.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) - .id(command.getId()) - .create(true); - executor.submit( - () -> { - try (ThreadContext.StoredContext ignored = threadPool.getThreadContext().stashContext()) { - RestStatus restStatus = client.index(request).actionGet().status(); - future.complete(restStatus); - } catch (Exception e) { - future.completeExceptionally(e); - } + // @throws IOException + Map template = IndexTemplateUtils.fromFile(templateName + ".json"); + + PutIndexTemplateRequest putIndexTemplateRequest = new PutIndexTemplateRequest() + .mapping(IndexTemplateUtils.get(template, "mappings")) + .settings(IndexTemplateUtils.get(template, "settings")) + .name(templateName) + .patterns((List) template.get("index_patterns")); + + executor.submit(() -> { + AcknowledgedResponse acknowledgedResponse = this.client.admin().indices().putTemplate(putIndexTemplateRequest).actionGet(); + if (acknowledgedResponse.isAcknowledged()) { + logger.info( + "Index template created successfully: {}", + templateName + ); } - ); - } catch (Exception e) { - logger.error(e); + }); + + } catch (IOException e) { + logger.error("Error reading index template from filesystem {}", templateName); } - return future; } - } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java index 7e53aeb..97c1d86 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Action.java @@ -108,4 +108,13 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.field(VERSION, this.version); return builder.endObject(); } + + @Override + public String toString() { + return "Action{" + + "type='" + type + '\'' + + ", args=" + args + + ", version='" + version + '\'' + + '}'; + } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java index f4ddcd9..be11d7d 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Command.java @@ -7,6 +7,8 @@ */ package com.wazuh.commandmanager.model; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.opensearch.common.UUIDs; import org.opensearch.core.xcontent.ToXContent; import org.opensearch.core.xcontent.ToXContentObject; @@ -19,7 +21,7 @@ import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; public class Command implements ToXContentObject { - + public static final String NAME = "command"; public static final String ORDER_ID = "order_id"; public static final String REQUEST_ID = "request_id"; public static final String SOURCE = "source"; @@ -85,10 +87,12 @@ public static Command parse(XContentParser parser) throws IOException { String user = null; Action action = null; - // @TODO check if this call is necessary as ensureExpectedToken is invoked previously - ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser); + // skips JSON's root level "command" + ensureExpectedToken(XContentParser.Token.FIELD_NAME, parser.nextToken(), parser); + ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser); while (parser.nextToken() != XContentParser.Token.END_OBJECT) { String fieldName = parser.currentName(); + parser.nextToken(); switch (fieldName) { case SOURCE: @@ -132,6 +136,7 @@ public static Command parse(XContentParser parser) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); + builder.startObject(NAME); builder.field(SOURCE, this.source); builder.field(USER, this.user); builder.field(TARGET, this.target); @@ -141,6 +146,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.field(STATUS, this.status); builder.field(ORDER_ID, this.orderId); builder.field(REQUEST_ID, this.requestId); + builder.endObject(); return builder.endObject(); } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java index 218a305..d42a948 100644 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/rest/action/RestPostCommandAction.java @@ -19,7 +19,6 @@ import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestRequest; -import org.opensearch.threadpool.ThreadPool; import java.io.IOException; import java.util.Collections; @@ -39,17 +38,14 @@ public class RestPostCommandAction extends BaseRestHandler { public static final String POST_COMMAND_ACTION_REQUEST_DETAILS = "post_command_action_request_details"; private static final Logger logger = LogManager.getLogger(RestPostCommandAction.class); private final CommandIndex commandIndex; - private final ThreadPool threadPool; /** * Default constructor * * @param commandIndex persistence layer - * @param threadPool */ - public RestPostCommandAction(CommandIndex commandIndex, ThreadPool threadPool) { + public RestPostCommandAction(CommandIndex commandIndex) { this.commandIndex = commandIndex; - this.threadPool = threadPool; } @@ -60,21 +56,21 @@ public String getName() { @Override public List routes() { return Collections.singletonList( - new Route( - POST, - String.format( - Locale.ROOT, - "%s", - CommandManagerPlugin.COMMAND_MANAGER_BASE_URI + new Route( + POST, + String.format( + Locale.ROOT, + "%s", + CommandManagerPlugin.COMMAND_MANAGER_BASE_URI + ) ) - ) ); } @Override protected RestChannelConsumer prepareRequest( - final RestRequest restRequest, - final NodeClient client + final RestRequest restRequest, + final NodeClient client ) throws IOException { // Get request details XContentParser parser = restRequest.contentParser(); @@ -84,7 +80,7 @@ protected RestChannelConsumer prepareRequest( // Send response return channel -> { - commandIndex.asyncCreate(command, this.threadPool) + this.commandIndex.asyncCreate(command) .thenAccept(restStatus -> { try (XContentBuilder builder = channel.newBuilder()) { builder.startObject(); @@ -94,7 +90,7 @@ protected RestChannelConsumer prepareRequest( builder.endObject(); channel.sendResponse(new BytesRestResponse(restStatus, builder)); } catch (Exception e) { - logger.error("Error indexing command: ",e); + logger.error("Error indexing command: ", e); } }).exceptionally(e -> { channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, e.getMessage())); diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/IndexTemplateUtils.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/IndexTemplateUtils.java new file mode 100644 index 0000000..bed3434 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/IndexTemplateUtils.java @@ -0,0 +1,81 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package com.wazuh.commandmanager.utils; + +import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; +import reactor.util.annotation.NonNull; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +/** + * Util functions to parse and manage index templates files. + */ +public class IndexTemplateUtils { + + /** + * Default constructor + */ + public IndexTemplateUtils() { + } + + /** + * Read index template file from the resources folder and returns its JSON + * content as a map. + * + * @param filename name of the index template to read from the resources folder + * @return the JSON index template as a map + * @throws IOException file not found or could not be read + */ + + public static Map fromFile(@NonNull String filename) throws IOException { + InputStream is = IndexTemplateUtils.class.getClassLoader().getResourceAsStream(filename); + return IndexTemplateUtils.toMap(is); + } + + /** + * Convert from a JSON InputStream into a String, Object map. + *

+ * Used to convert the JSON index templates to the required format. + *

+ * + * @param is: the JSON formatted InputStream + * @return a map with the json string contents. + * @throws IOException thrown by {@link JsonXContent#createParser(NamedXContentRegistry, DeprecationHandler, InputStream)} + */ + public static Map toMap(InputStream is) throws IOException { + XContentParser parser = JsonXContent.jsonXContent.createParser( + NamedXContentRegistry.EMPTY, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + is); + parser.nextToken(); + return parser.map(); + } + + /** + * Cast map's element to a String, Object map. + *

+ * Used to retrieve the settings and mappings from the index templates, + * which are a JSON object themselves. + *

+ * + * @param map the index template as a map. + * @param key the element's key to retrieve and cast. + * @return a String, Object map + */ + public static Map get(Map map, String key) { + return (Map) map.get(key); + } + +} + diff --git a/plugins/command-manager/src/main/resources/index-template-commands.json b/plugins/command-manager/src/main/resources/index-template-commands.json new file mode 100644 index 0000000..5170315 --- /dev/null +++ b/plugins/command-manager/src/main/resources/index-template-commands.json @@ -0,0 +1,91 @@ +{ + "index_patterns": [ + ".commands*" + ], + "mappings": { + "date_detection": false, + "properties": { + "command": { + "properties": { + "action": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "order_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "request_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "result": { + "properties": { + "code": { + "type": "short" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "ignore_above": 1024, + "type": "keyword" + }, + "status": { + "ignore_above": 1024, + "type": "keyword" + }, + "target": { + "ignore_above": 1024, + "type": "keyword" + }, + "timeout": { + "type": "short" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "order": 1, + "settings": { + "index": { + "hidden": true, + "number_of_replicas": "0", + "number_of_shards": "1", + "query.default_field": [ + "command.source", + "command.target", + "command.status", + "command.type" + ], + "refresh_interval": "5s" + } + } +} \ No newline at end of file