Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement an Order data model #189

Merged
merged 21 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions imposter/orders/response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
console.log(
JSON.stringify(
JSON.parse(context.request.body),
undefined,
2
)
)
5 changes: 4 additions & 1 deletion imposter/wazuh-server-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@ resources:

# Orders
- method: POST
path: /orders
path: /orders
response:
scriptFile:
orders/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
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.ToXContent;
import org.opensearch.index.query.QueryBuilders;
import org.opensearch.index.query.TermQueryBuilder;
import org.opensearch.search.SearchHit;
Expand All @@ -51,9 +51,7 @@
import java.util.concurrent.ExecutionException;

import com.wazuh.commandmanager.CommandManagerPlugin;
import com.wazuh.commandmanager.model.Command;
import com.wazuh.commandmanager.model.Document;
import com.wazuh.commandmanager.model.Status;
import com.wazuh.commandmanager.model.*;
import com.wazuh.commandmanager.settings.PluginSettings;
import com.wazuh.commandmanager.utils.httpclient.AuthHttpRestClient;

Expand Down Expand Up @@ -115,38 +113,31 @@ public static <T> T getNestedObject(Map<String, Object> map, String key, Class<T
* @param searchResponse The search results page
* @throws IllegalStateException Rethrown from setSentStatus()
*/
@SuppressWarnings("unchecked")
public void handlePage(SearchResponse searchResponse) throws IllegalStateException {
SearchHits searchHits = searchResponse.getHits();
ArrayList<Object> orders = new ArrayList<>();
for (SearchHit hit : searchHits) {
final Map<String, Object> orderMap =
getNestedObject(hit.getSourceAsMap(), Command.COMMAND, Map.class);
if (orderMap != null) {
orderMap.put("document_id", hit.getId());
orders.add(orderMap);
}
}
try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
final String payload =
builder.map(Collections.singletonMap("orders", orders)).toString();

final SimpleHttpResponse response = deliverOrders(payload);
if (response == null) {
log.error("No reply from server.");
return;
}
log.info("Server replied with {}. Updating orders' status.", response.getCode());
Status status = Status.FAILURE;
if (List.of(RestStatus.CREATED, RestStatus.ACCEPTED, RestStatus.OK)
.contains(RestStatus.fromCode(response.getCode()))) {
status = Status.SENT;
}
for (SearchHit hit : searchHits) {
this.setSentStatus(hit, status);
}
String payload;
try {
payload =
Orders.fromSearchHits(searchHits)
.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)
.toString();
} catch (IOException e) {
log.error("Error parsing hit contents: {}", e.getMessage());
log.error("Error parsing orders from search hits, due to {}", e.getMessage());
return;
}
final SimpleHttpResponse response = this.deliverOrders(payload);
if (response == null) {
log.error("No reply from server.");
return;
}
log.info("Server replied with {}. Updating orders' status.", response.getCode());
Status status = Status.FAILURE;
if (List.of(RestStatus.CREATED, RestStatus.ACCEPTED, RestStatus.OK)
.contains(RestStatus.fromCode(response.getCode()))) {
status = Status.SENT;
}
for (SearchHit hit : searchHits) {
this.setSentStatus(hit, status);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ public Command(
this.orderId = UUIDs.base64UUID();
this.source = source;
this.target = target;
this.timeout = timeout;
this.user = user;
this.action = action;
this.timeout = timeout;
this.status = Status.PENDING;
}

Expand Down Expand Up @@ -97,28 +97,30 @@ public static Command parse(XContentParser parser)
Action action = null;

while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
String fieldName = parser.currentName();

parser.nextToken();
switch (fieldName) {
case SOURCE:
source = parser.text();
break;
case Target.TARGET:
target = Target.parse(parser);
break;
case TIMEOUT:
timeout = parser.intValue();
break;
case USER:
user = parser.text();
break;
case Action.ACTION:
action = Action.parse(parser);
break;
default:
parser.skipChildren();
break;
if (parser.currentToken().equals(XContentParser.Token.FIELD_NAME)) {
String fieldName = parser.currentName();

parser.nextToken();
switch (fieldName) {
case SOURCE:
source = parser.text();
break;
case Target.TARGET:
target = Target.parse(parser);
break;
case TIMEOUT:
timeout = parser.intValue();
break;
case USER:
user = parser.text();
break;
case Action.ACTION:
action = Action.parse(parser);
break;
default:
parser.skipChildren();
break;
}
}
}

Expand Down Expand Up @@ -179,6 +181,42 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
return builder.endObject();
}

/**
* Returns the nested Action fields.
*
* @return Action fields.
*/
public Action getAction() {
return this.action;
}

/**
* Returns the nested Source fields.
*
* @return source fields.
*/
public String getSource() {
return this.source;
}

/**
* Returns the nested Target fields.
*
* @return Target fields.
*/
public Target getTarget() {
return this.target;
}

/**
* Returns the user that requested this command.
*
* @return the user that requested this command.
*/
public String getUser() {
return this.user;
}

@Override
public String toString() {
return "Command{"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (C) 2024, Wazuh Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.wazuh.commandmanager.model;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.common.xcontent.XContentHelper;
import org.opensearch.common.xcontent.XContentType;
import org.opensearch.core.xcontent.*;
import org.opensearch.search.SearchHit;

import java.io.IOException;
import java.util.Objects;

import reactor.util.annotation.NonNull;

/** Order model class. */
public class Order implements ToXContent {
AlexRuiz7 marked this conversation as resolved.
Show resolved Hide resolved
public static final String SOURCE = "source";
public static final String USER = "user";
public static final String DOCUMENT_ID = "document_id";
private final String source;
private final Target target;
private final String user;
private final Action action;
private final String documentId;

private static final Logger log = LogManager.getLogger(Order.class);

/**
* Default constructor
*
* @param source String field representing the origin of the command order
* @param target Object containing the destination's type and id. It is handled by its own model
* class
* @param user The requester of the command
* @param action An object containing the actual executable plus arguments and version. Handled
* by its own model class
* @param documentId The document ID from the index that holds commands. Used by the agent to
* report back the results of the action
*/
public Order(
@NonNull String source,
@NonNull Target target,
@NonNull String user,
@NonNull Action action,
@NonNull String documentId) {
this.source = source;
this.target = target;
this.user = user;
this.action = action;
this.documentId = documentId;
}

/**
* Parses a SearchHit into an order as expected by a Wazuh Agent
*
* @param hit The SearchHit result of a search
* @return An Order Object in accordance with the data model
*/
public static Order fromSearchHit(SearchHit hit) {
try {
XContentParser parser =
XContentHelper.createParser(
NamedXContentRegistry.EMPTY,
DeprecationHandler.IGNORE_DEPRECATIONS,
hit.getSourceRef(),
XContentType.JSON);
Command command = null;
// Iterate over the JsonXContentParser's JsonToken until we hit null,
// which corresponds to end of data
while (parser.nextToken() != null) {
// Look for FIELD_NAME JsonToken s
if (parser.currentToken().equals(XContentParser.Token.FIELD_NAME)) {
String fieldName = parser.currentName();
if (fieldName.equals(Command.COMMAND)) {
// Parse Command
command = Command.parse(parser);
} else {
parser.skipChildren();
}
}
}
// Create a new Order object with the Command's fields
return new Order(
Objects.requireNonNull(command).getSource(),
Objects.requireNonNull(command).getTarget(),
Objects.requireNonNull(command).getUser(),
Objects.requireNonNull(command).getAction(),
Objects.requireNonNull(hit).getId());
} catch (IOException e) {
log.error("Order could not be parsed: {}", e.getMessage());
} catch (NullPointerException e) {
log.error(
"Could not create Order object. One or more of the constructor's arguments was null: {}",
e.getMessage());
}
return null;
}

/**
* Used to serialize the Order's contents.
*
* @param builder The builder object we will add our Json to
* @param params Not used. Required by the interface.
* @return XContentBuilder with a Json object including this Order's fields
* @throws IOException Rethrown from IOException's XContentBuilder methods
*/
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(SOURCE, this.source);
builder.field(USER, this.user);
this.target.toXContent(builder, ToXContent.EMPTY_PARAMS);
this.action.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.field(DOCUMENT_ID, this.documentId);

return builder.endObject();
}

@Override
public String toString() {
return "Order{"
+ "action="
+ action
+ ", source='"
+ source
+ '\''
+ ", target="
+ target
+ ", user='"
+ user
+ '\''
+ ", document_id='"
+ documentId
+ '\''
+ '}';
}
}
Loading