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 92ba69f..27495dd 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 @@ -8,7 +8,7 @@ package com.wazuh.commandmanager.index; import com.wazuh.commandmanager.CommandManagerPlugin; -import com.wazuh.commandmanager.model.Command; +import com.wazuh.commandmanager.model.Document; import com.wazuh.commandmanager.utils.IndexTemplateUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -53,10 +53,10 @@ public CommandIndex(Client client, ClusterService clusterService, ThreadPool thr } /** - * @param command: A Command model object + * @param document: A Command model object * @return A CompletableFuture with the RestStatus response from the operation */ - public CompletableFuture asyncCreate(Command command) { + public CompletableFuture asyncCreate(Document document) { CompletableFuture future = new CompletableFuture<>(); ExecutorService executor = this.threadPool.executor(ThreadPool.Names.WRITE); @@ -70,12 +70,12 @@ public CompletableFuture asyncCreate(Command command) { ); } - logger.debug("Indexing command {}", command); + logger.debug("Indexing command {}", document); try { IndexRequest request = new IndexRequest() .index(CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME) - .source(command.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) - .id(command.getId()) + .source(document.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)) + .id(document.getId()) .create(true); executor.submit( () -> { @@ -89,12 +89,14 @@ public CompletableFuture asyncCreate(Command command) { ); } catch (IOException e) { logger.error( - "Failed to index command with ID {}: {}", command.getId(), e); + "Failed to index command with ID {}: {}", document.getId(), e); } return future; } /** + * + * @param template_name * @return */ public boolean indexTemplateExists(String template_name) { 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 97c1d86..71cd038 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 @@ -18,23 +18,23 @@ * Command's action fields. */ public class Action implements ToXContentObject { - - public static final String TYPE = "type"; + public static final String ACTION = "action"; + public static final String NAME = "name"; public static final String ARGS = "args"; public static final String VERSION = "version"; - private final String type; + private final String name; private final List args; private final String version; /** * Default constructor. * - * @param type action type to be executed on the target, + * @param name action to be executed on the target, * @param args actual command. * @param version version of the action. */ - public Action(String type, List args, String version) { - this.type = type; + public Action(String name, List args, String version) { + this.name = name; this.args = args; this.version = version; } @@ -45,7 +45,7 @@ public Action(String type, List args, String version) { * @throws IOException */ public static Action parse(XContentParser parser) throws IOException { - String type = ""; + String name = ""; List args = List.of(); String version = ""; @@ -53,8 +53,8 @@ public static Action parse(XContentParser parser) throws IOException { String fieldName = parser.currentName(); parser.nextToken(); switch (fieldName) { - case TYPE: - type = parser.text(); + case NAME: + name = parser.text(); break; case ARGS: args = parser.list(); @@ -70,40 +70,13 @@ public static Action parse(XContentParser parser) throws IOException { // Cast args field Object list to String list List convertedArgsFields = (List) (List) (args); - return new Action(type, convertedArgsFields, version); - } - - /** - * Return action's type field. - * - * @return type - */ - public String getType() { - return this.type; - } - - /** - * Returns action's args field. - * - * @return args - */ - public List getArgs() { - return this.args; - } - - /** - * Returns action's version field. - * - * @return version - */ - public String getVersion() { - return this.version; + return new Action(name, convertedArgsFields, version); } @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject("action"); - builder.field(TYPE, this.type); + builder.startObject(ACTION); + builder.field(NAME, this.name); builder.field(ARGS, this.args); builder.field(VERSION, this.version); return builder.endObject(); @@ -112,7 +85,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws @Override public String toString() { return "Action{" + - "type='" + type + '\'' + + "name='" + name + '\'' + ", args=" + args + ", version='" + version + '\'' + '}'; diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Agent.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Agent.java new file mode 100644 index 0000000..77971cf --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Agent.java @@ -0,0 +1,70 @@ +/* + * 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.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +import java.io.IOException; +import java.util.List; + +/** + * Command's agent fields. + */ +public class Agent implements ToXContentObject { + public static final String AGENT = "agent"; + public static final String GROUPS = "groups"; + private final List groups; + + /** + * Default constructor. + * + * @param groups Agent's groups + */ + public Agent(List groups) { + this.groups = groups; + } + + /** + * @param parser + * @return + * @throws IOException + */ + public static Agent parse(XContentParser parser) throws IOException { + List groups = List.of(); + + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + String fieldName = parser.currentName(); + parser.nextToken(); + if (fieldName.equals(GROUPS)) { + groups = parser.list(); + } else { + parser.skipChildren(); + } + } + + // Cast args field Object list to String list + List convertedGroupFields = (List) (List) (groups); + return new Agent(convertedGroupFields); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(AGENT); + builder.field(GROUPS, this.groups); + return builder.endObject(); + } + + @Override + public String toString() { + return "Agent{" + + "groups=" + groups + + '}'; + } +} 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 be11d7d..93d39e9 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,8 +7,6 @@ */ 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; @@ -18,26 +16,19 @@ import java.io.IOException; -import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken; - public class Command implements ToXContentObject { - public static final String NAME = "command"; + public static final String COMMAND = "command"; 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 Target target; private final Integer timeout; - private final String type; private final String user; private final Status status; private final Action action; @@ -45,28 +36,24 @@ public class Command implements ToXContentObject { /** * Default constructor * - * @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 source origin of the request. + * @param target {@link Target} + * @param timeout time window in which the command has to be sent to its target. * @param user the user that originated the request - * @param action target action type and additional parameters + * @param action {@link Action} */ public Command( @NonNull String source, - @NonNull String target, + @NonNull Target target, @NonNull Integer timeout, - @NonNull String type, @NonNull String user, @NonNull Action action ) { - this.id = UUIDs.base64UUID(); this.requestId = UUIDs.base64UUID(); this.orderId = UUIDs.base64UUID(); this.source = source; this.target = target; this.timeout = timeout; - this.type = type; this.user = user; this.action = action; this.status = Status.PENDING; @@ -81,15 +68,11 @@ public Command( */ public static Command parse(XContentParser parser) throws IOException { String source = null; - String target = null; + Target target = null; Integer timeout = null; - String type = null; String user = null; Action action = null; - // 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(); @@ -98,19 +81,16 @@ public static Command parse(XContentParser parser) throws IOException { case SOURCE: source = parser.text(); break; - case TARGET: - target = parser.text(); + case Target.TARGET: + target = Target.parse(parser); break; case TIMEOUT: timeout = parser.intValue(); break; - case TYPE: - type = parser.text(); - break; case USER: user = parser.text(); break; - case ACTION: + case Action.ACTION: action = Action.parse(parser); break; default: @@ -119,14 +99,11 @@ public static Command parse(XContentParser parser) throws IOException { } } - assert source != null; - assert target != null; - assert timeout != null; + // TODO add proper validation return new Command( source, target, timeout, - type, user, action ); @@ -134,40 +111,27 @@ public static Command parse(XContentParser parser) throws IOException { @Override public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { - builder.startObject(); - - builder.startObject(NAME); + builder.startObject(COMMAND); builder.field(SOURCE, this.source); builder.field(USER, this.user); - builder.field(TARGET, this.target); - builder.field(TYPE, this.type); + this.target.toXContent(builder, ToXContent.EMPTY_PARAMS); 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); - builder.endObject(); return builder.endObject(); } - /** - * @return Document's ID - */ - public String getId() { - return this.id; - } - @Override public String toString() { return "Command{" + - "ID='" + id + '\'' + - ", orderID='" + orderId + '\'' + - ", requestID='" + requestId + '\'' + + "orderId='" + orderId + '\'' + + ", requestId='" + requestId + '\'' + ", source='" + source + '\'' + - ", target='" + target + '\'' + + ", 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/Document.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Document.java new file mode 100644 index 0000000..28ec263 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Document.java @@ -0,0 +1,82 @@ +/* + * 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.common.UUIDs; +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.List; + +/** + * Command's target fields. + */ +public class Document implements ToXContentObject { + private final Agent agent; + private final Command command; + private final String id; + + /** + * Default constructor + * + * @param agent + * @param command + */ + public Document(Agent agent, Command command) { + this.agent = agent; + this.command = command; + this.id = UUIDs.base64UUID(); + } + + /** + * @param parser + * @return + * @throws IOException + */ + public static Document parse(XContentParser parser) throws IOException { + Agent agent = new Agent(List.of("groups000")); // TODO read agent from .agents index + Command command = null; + + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + String fieldName = parser.currentName(); + parser.nextToken(); + if (fieldName.equals(Command.COMMAND)) { + command = Command.parse(parser); + } else { + parser.skipChildren(); // TODO raise error as command values are required + } + } + + return new Document(agent, command); + } + + /** + * @return Document's ID + */ + public String getId() { + return this.id; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + this.agent.toXContent(builder, ToXContentObject.EMPTY_PARAMS); + this.command.toXContent(builder, ToXContentObject.EMPTY_PARAMS); + return builder.endObject(); + } + + @Override + public String toString() { + return "Document{" + + "agent=" + agent + + ", command=" + command + + '}'; + } +} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Target.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Target.java new file mode 100644 index 0000000..3a7bb82 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/model/Target.java @@ -0,0 +1,80 @@ +/* + * 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.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.core.xcontent.XContentParser; + +import java.io.IOException; + +/** + * Command's target fields. + */ +public class Target implements ToXContentObject { + public static final String TARGET = "target"; + public static final String TYPE = "type"; + public static final String ID = "id"; + private final String type; + private final String id; + + /** + * Default constructor. + * + * @param type The destination type. One of [`group`, `agent`, `server`] + * @param id Unique identifier of the destination to send the command to. + */ + public Target(String type, String id) { + this.type = type; + this.id = id; + } + + /** + * @param parser + * @return + * @throws IOException + */ + public static Target parse(XContentParser parser) throws IOException { + String type = ""; + String id = ""; + + while (parser.nextToken() != XContentParser.Token.END_OBJECT) { + String fieldName = parser.currentName(); + parser.nextToken(); + switch (fieldName) { + case TYPE: + type = parser.text(); + break; + case ID: + id = parser.text(); + break; + default: + parser.skipChildren(); + break; + } + } + + return new Target(type, id); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(TARGET); + builder.field(TYPE, this.type); + builder.field(ID, this.id); + return builder.endObject(); + } + + @Override + public String toString() { + return "Target{" + + "type='" + type + '\'' + + ", id='" + 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 d42a948..c27257d 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 @@ -9,7 +9,7 @@ import com.wazuh.commandmanager.CommandManagerPlugin; import com.wazuh.commandmanager.index.CommandIndex; -import com.wazuh.commandmanager.model.Command; +import com.wazuh.commandmanager.model.Document; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.opensearch.client.node.NodeClient; @@ -76,16 +76,16 @@ protected RestChannelConsumer prepareRequest( XContentParser parser = restRequest.contentParser(); ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser); - Command command = Command.parse(parser); + Document document = Document.parse(parser); // Send response return channel -> { - this.commandIndex.asyncCreate(command) + this.commandIndex.asyncCreate(document) .thenAccept(restStatus -> { try (XContentBuilder builder = channel.newBuilder()) { builder.startObject(); builder.field("_index", CommandManagerPlugin.COMMAND_MANAGER_INDEX_NAME); - builder.field("_id", command.getId()); + builder.field("_id", document.getId()); builder.field("result", restStatus.name()); builder.endObject(); channel.sendResponse(new BytesRestResponse(restStatus, builder)); diff --git a/plugins/command-manager/src/main/resources/index-template-commands.json b/plugins/command-manager/src/main/resources/index-template-commands.json index 5170315..3614c17 100644 --- a/plugins/command-manager/src/main/resources/index-template-commands.json +++ b/plugins/command-manager/src/main/resources/index-template-commands.json @@ -4,7 +4,16 @@ ], "mappings": { "date_detection": false, + "dynamic": "strict", "properties": { + "agent": { + "properties": { + "groups": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, "command": { "properties": { "action": { @@ -13,7 +22,7 @@ "ignore_above": 1024, "type": "keyword" }, - "type": { + "name": { "ignore_above": 1024, "type": "keyword" }, @@ -55,16 +64,20 @@ "type": "keyword" }, "target": { - "ignore_above": 1024, - "type": "keyword" + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } }, "timeout": { "type": "short" }, - "type": { - "ignore_above": 1024, - "type": "keyword" - }, "user": { "ignore_above": 1024, "type": "keyword" @@ -81,11 +94,11 @@ "number_of_shards": "1", "query.default_field": [ "command.source", - "command.target", + "command.target.type", "command.status", - "command.type" + "command.action.name" ], "refresh_interval": "5s" } } -} \ 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 index c667872..c621fdc 100644 --- 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 @@ -6,10 +6,12 @@ command: source: "Users/Services" user: "user13" - target: "WazuhServerCluster5" - type: "agent_group" + target: { + id: "target4", + type: "agent" + } action: { - type: "Server cluster", + name: "change_group", args: [ "/path/to/executable/arg8" ], version: "v4" } @@ -24,11 +26,11 @@ id: $document_id - match: { _source.command.source: "Users/Services" } - match: { _source.command.user: "user13" } - - match: { _source.command.target: "WazuhServerCluster5" } - - match: { _source.command.type: "agent_group" } + - match: { _source.command.target.type: "agent" } + - match: { _source.command.target.id: "target4" } - match: { _source.command.action: { - type: "Server cluster", + name: "change_group", args: [ "/path/to/executable/arg8" ], version: "v4" }