Skip to content

Commit

Permalink
Command Manager client prototype (#274)
Browse files Browse the repository at this point in the history
* Add imposter for cti API

* Add authorization to imposter API

* Fix authorization

* Add http client

* Add placeholder authorization headers

* Add CTI API reply to console

* Add CTI API reply to logs

* Fixing GET route

* Fix cti_api.uri SecureSetting

* Improve class structure

* Populate enum and model classes

* Fix HttpClient permissions

* Decouple privileged action execution

* Add a model for the cti api context consumer catalog endpoint

* Apply spotless

* Make endpoint reply with offset and snapshot link

* Iterate over consumers. Parse and return their information

* Improve multiple endpoints handling

* Move API logic from handler to action class

* Add parser for offset payload

* Fix parsing data array

* Make payload parseable to any depth

* Add javadocs

* Removing unused API mock

* Changing exception message

* Adding comments about provisional code

* Remove implementation of ClusterPlugin

* Spotless run

* Add URL as a setting

* Add action and model packages to command manager client scaffolding

* New HttpClient class proposal

* Implement new HttpCLient 'CommandManagerClient'

Add Command Manager related settings

Add ClusterInfoHelper utility class

* Implemente PostCommand action to generate new commands to the CM API

Add CommandHandler to handle new endpoint

* Add API Uri definition on HttpClient classes

* Remove unnecesary handler

* Implement Privileged util to handle privileged actions

Add basic Command model

Improve the specific HttpClients functions avoiding unnecesary parameters

* Remove references to previous HttpClient

* Apply spotless formatting

* Add query params to changes endpoint

* Add unit tests for HttpClient class

Make HttpClient non-abstract

Remove dependencies licenses validation

* Apply spotless to tests

* Move CTI and CommandManager clients to base client/ directory

Add docstrings to new classes

Remove unused functions

* Implement more Unit Tests scenarios for HttpClient

Add more docstrings

Remove legacy HttpClient

* Update command body with new defined fields

Add required headers to CommandManager command generation endpoint

* Replace Http related string values (methods, headers) with enum values

* Replace single use constant

---------

Co-authored-by: f-galland <[email protected]>
Co-authored-by: Fede Galland <[email protected]>
Co-authored-by: Alex Ruiz <[email protected]>
  • Loading branch information
4 people authored Feb 17, 2025
1 parent 3567734 commit cd71bf3
Show file tree
Hide file tree
Showing 14 changed files with 637 additions and 235 deletions.
6 changes: 6 additions & 0 deletions plugins/content-manager/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ loggerUsageCheck.enabled = false
// No need to validate pom, as we do not upload to maven/sonatype
validateNebulaPom.enabled = false

// Skip forbiddenAPIs check on dependencies
thirdPartyAudit.enabled = false

//Skip checking for third party licenses
dependencyLicenses.enabled = false

repositories {
mavenLocal()
maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@

public class ContentManagerPlugin extends Plugin implements ActionPlugin {

public static String CTI_VD_CONSUMER_URL;
public static String CTI_CHANGES_URL;

/** ClassConstructor * */
public ContentManagerPlugin() {}

Expand All @@ -64,13 +61,7 @@ public Collection<Object> createComponents(
NamedWriteableRegistry namedWriteableRegistry,
IndexNameExpressionResolver indexNameExpressionResolver,
Supplier<RepositoriesService> repositoriesServiceSupplier) {
PluginSettings.getInstance(environment.settings());
CTI_VD_CONSUMER_URL =
PluginSettings.getInstance().getCtiBaseUrl()
+ "/catalog/contexts/vd_1.0.0/consumers/vd_4.8.0";
CTI_CHANGES_URL =
PluginSettings.getInstance().getCtiBaseUrl()
+ "/catalog/contexts/vd_1.0.0/consumers/vd_4.8.0/changes";
PluginSettings.getInstance(environment.settings(), clusterService);
return Collections.emptyList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.wazuh.contentmanager.action.cti;

import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.core5.http.Header;
import org.opensearch.common.xcontent.XContentFactory;
import org.opensearch.common.xcontent.XContentType;
import org.opensearch.core.rest.RestStatus;
Expand All @@ -26,9 +25,9 @@

import java.io.IOException;

import com.wazuh.contentmanager.ContentManagerPlugin;
import com.wazuh.contentmanager.client.CTIClient;
import com.wazuh.contentmanager.model.ctiapi.ContextConsumerCatalog;
import com.wazuh.contentmanager.privileged.PrivilegedHttpAction;
import com.wazuh.contentmanager.util.Privileged;

/**
* Action class handling Catalog logic. This is mainly useful to get the last offset value, as well
Expand All @@ -50,8 +49,7 @@ public static BytesRestResponse run() throws IOException, IllegalArgumentExcepti
XContent xContent = XContentType.JSON.xContent();
XContentBuilder builder = XContentFactory.jsonBuilder();
SimpleHttpResponse response =
PrivilegedHttpAction.get(
ContentManagerPlugin.CTI_VD_CONSUMER_URL, null, null, (Header) null);
Privileged.doPrivilegedRequest(() -> CTIClient.getInstance().getCatalog());
ContextConsumerCatalog.parse(
xContent.createParser(
NamedXContentRegistry.EMPTY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
package com.wazuh.contentmanager.action.cti;

import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.core5.http.Header;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.common.xcontent.XContentFactory;
import org.opensearch.common.xcontent.XContentType;
import org.opensearch.core.rest.RestStatus;
Expand All @@ -28,15 +29,18 @@
import java.util.HashMap;
import java.util.Map;

import com.wazuh.contentmanager.ContentManagerPlugin;
import com.wazuh.contentmanager.client.CTIClient;
import com.wazuh.contentmanager.client.CommandManagerClient;
import com.wazuh.contentmanager.model.commandmanager.Command;
import com.wazuh.contentmanager.model.ctiapi.Offsets;
import com.wazuh.contentmanager.privileged.PrivilegedHttpAction;
import com.wazuh.contentmanager.util.Privileged;

/**
* Action class handling Offsets logic. This is used to get the json patches to the current
* vulnerability data
*/
public class GetChangesAction {
private static final Logger log = LogManager.getLogger(GetChangesAction.class);

private static String FROM_OFFSET_FIELD = "from_offset";
private static String TO_OFFSET_FIELD = "to_offset";
Expand All @@ -63,14 +67,18 @@ public BytesRestResponse run() throws IOException, IllegalArgumentException {
XContent xContent = XContentType.JSON.xContent();
XContentBuilder builder = XContentFactory.jsonBuilder();
SimpleHttpResponse response =
PrivilegedHttpAction.get(
ContentManagerPlugin.CTI_CHANGES_URL, null, buildQueryParametersMap(), (Header) null);
Privileged.doPrivilegedRequest(
() -> CTIClient.getInstance().getChanges(buildQueryParametersMap()));
Offsets.parse(
xContent.createParser(
NamedXContentRegistry.EMPTY,
DeprecationHandler.IGNORE_DEPRECATIONS,
response.getBodyBytes()))
.toXContent(builder, ToXContent.EMPTY_PARAMS);
// Post new command informing the new changes. (Call may be need to be moved elsewhere)
SimpleHttpResponse commandResponse =
CommandManagerClient.getInstance().postCommand(Command.generateCtiCommand());
log.info("Command Manager response: {}", commandResponse);
return new BytesRestResponse(RestStatus.fromCode(response.getCode()), builder.toString());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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.contentmanager.client;

import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.Method;

import java.net.URI;
import java.util.Map;

import com.wazuh.contentmanager.settings.PluginSettings;

/**
* CTIClient is a singleton class responsible for interacting with the CTI (Cyber Threat
* Intelligence) API. It extends HttpClient to handle HTTP requests.
*/
public class CTIClient extends HttpClient {
private static CTIClient instance;

private static final String apiUrl =
PluginSettings.getInstance().getCtiBaseUrl()
+ "/catalog/contexts/vd_1.0.0/consumers/vd_4.8.0";
private static final String CONTENT_CHANGES_ENDPOINT = "/changes";

/**
* Private constructor to enforce singleton pattern. Initializes the HTTP client with the CTI API
* base URL.
*/
protected CTIClient() {
super(URI.create(apiUrl));
}

/**
* Retrieves the singleton instance of CTIClient. Ensures thread-safe lazy initialization.
*
* @return The singleton instance of CTIClient.
*/
public static CTIClient getInstance() {
if (instance == null) {
synchronized (CTIClient.class) {
if (instance == null) {
instance = new CTIClient();
}
}
}
return instance;
}

/**
* Fetches content changes from the CTI API.
*
* @param queryParameters A map containing query parameters to filter the request.
* @return A SimpleHttpResponse containing the response from the API.
*/
public SimpleHttpResponse getChanges(Map<String, String> queryParameters) {
return sendRequest(Method.GET, CONTENT_CHANGES_ENDPOINT, null, queryParameters, (Header) null);
}

/**
* Fetches the entire CTI catalog from the API.
*
* @return A SimpleHttpResponse containing the response from the API.
*/
public SimpleHttpResponse getCatalog() {
return sendRequest(Method.GET, null, null, null, (Header) null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* 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.contentmanager.client;

import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.message.BasicHeader;

import java.net.URI;

import com.wazuh.contentmanager.settings.PluginSettings;

/**
* CommandManagerClient is a singleton class responsible for managing HTTP communication with the
* Command Manager API.
*/
public class CommandManagerClient extends HttpClient {
private static volatile CommandManagerClient instance;

/** Base Content Manager Plugin API endpoint. */
public static final String BASE_COMMAND_MANAGER_URI = "/_plugins/_command_manager";

/** Endpoint to post new commands. */
public static final String POST_COMMAND_ENDPOINT = "/commands";

/** Private constructor to initialize the CommandManagerClient with the base API URI. */
private CommandManagerClient() {
super(URI.create(PluginSettings.getInstance().getClusterBaseUrl() + BASE_COMMAND_MANAGER_URI));
}

/**
* Returns the singleton instance of CommandManagerClient. Uses double-checked locking to ensure
* thread safety.
*
* @return The singleton instance of CommandManagerClient.
*/
public static CommandManagerClient getInstance() {
if (instance == null) {
synchronized (CommandManagerClient.class) {
if (instance == null) {
instance = new CommandManagerClient();
}
}
}
return instance;
}

/**
* Sends a POST request to execute a command via the Command Manager API.
*
* @param requestBody The JSON request body containing the command details.
* @return A SimpleHttpResponse object containing the API response.
*/
public SimpleHttpResponse postCommand(String requestBody) {
Header header = new BasicHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON);
return sendRequest(Method.POST, POST_COMMAND_ENDPOINT, requestBody, null, header);
}
}
Loading

0 comments on commit cd71bf3

Please sign in to comment.