Skip to content

Commit

Permalink
Add CommangManagerSettings and the respective tests (#120)
Browse files Browse the repository at this point in the history
* Add CommangManagerSettings and the respective tests

* Apply spotless

* Apply spotless

---------

Co-authored-by: Álex Ruiz <[email protected]>
  • Loading branch information
mcasas993 and AlexRuiz7 authored Oct 25, 2024
1 parent 077a574 commit 99ae457
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 26 deletions.
17 changes: 15 additions & 2 deletions plugins/command-manager/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import org.opensearch.gradle.test.RestIntegTestTask

buildscript {
Expand Down Expand Up @@ -75,7 +76,9 @@ def versions = [
httpcore5: "5.3.1",
slf4j: "2.0.16",
log4j: "2.23.1",
conscrypt: "2.5.2"
conscrypt: "2.5.2",
mockito: "5.12.0",
junit:"4.13.2"
]

dependencies {
Expand All @@ -85,6 +88,9 @@ dependencies {
api "org.apache.logging.log4j:log4j-slf4j-impl:${versions.log4j}"
api "org.slf4j:slf4j-api:${versions.slf4j}"
api "org.conscrypt:conscrypt-openjdk-uber:${versions.conscrypt}"

testImplementation "org.mockito:mockito-core:${versions.mockito}"
testImplementation "junit:junit:${versions.junit}"
}

// This requires an additional Jar not published as part of build-tools
Expand All @@ -108,6 +114,7 @@ repositories {

test {
include '**/*Tests.class'
jvmArgs "-Djava.security.policy=./plugins/command-manager/src/main/plugin-metadata/plugin-security.policy/plugin-security.policy"
}

task integTest(type: RestIntegTestTask) {
Expand All @@ -117,6 +124,7 @@ task integTest(type: RestIntegTestTask) {
}
tasks.named("check").configure { dependsOn(integTest) }


integTest {
// The --debug-jvm command-line option makes the cluster debuggable; this makes the tests debuggable
if (System.getProperty("test.debug") != null) {
Expand All @@ -126,9 +134,14 @@ integTest {

testClusters.integTest {
testDistribution = "INTEG_TEST"

//testDistribution = "ARCHIVE"
// This installs our plugin into the testClusters
plugin(project.tasks.bundlePlugin.archiveFile)

// add customized keystore
keystore 'm_api.auth.username', 'admin'
keystore 'm_api.auth.password', 'test'
keystore 'm_api.uri', 'https://httpbin.org/post'
}

run {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@
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.common.settings.*;
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.plugins.ReloadablePlugin;
import org.opensearch.repositories.RepositoriesService;
import org.opensearch.rest.RestController;
import org.opensearch.rest.RestHandler;
Expand All @@ -30,28 +28,30 @@
import org.opensearch.watcher.ResourceWatcherService;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;

import com.wazuh.commandmanager.index.CommandIndex;
import com.wazuh.commandmanager.rest.RestPostCommandAction;
import com.wazuh.commandmanager.settings.CommandManagerSettings;
import com.wazuh.commandmanager.utils.httpclient.HttpRestClient;
import com.wazuh.commandmanager.utils.httpclient.HttpRestClientDemo;

/**
* 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 class CommandManagerPlugin extends Plugin implements ActionPlugin, ReloadablePlugin {
public static final String COMMAND_MANAGER_BASE_URI = "/_plugins/_command_manager";
public static final String COMMANDS_URI = COMMAND_MANAGER_BASE_URI + "/commands";
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 CommandManagerSettings commandManagerSettings;

@Override
public Collection<Object> createComponents(
Expand All @@ -68,10 +68,8 @@ public Collection<Object> createComponents(
Supplier<RepositoriesService> repositoriesServiceSupplier) {
this.commandIndex = new CommandIndex(client, clusterService, threadPool);

// HttpRestClient stuff
String uri = "https://httpbin.org/post";
String payload = "{\"message\": \"Hello world!\"}";
HttpRestClientDemo.run(uri, payload);
this.commandManagerSettings = CommandManagerSettings.getInstance(environment);

return Collections.emptyList();
}

Expand All @@ -83,7 +81,26 @@ public List<RestHandler> getRestHandlers(
SettingsFilter settingsFilter,
IndexNameExpressionResolver indexNameExpressionResolver,
Supplier<DiscoveryNodes> nodesInCluster) {
return Collections.singletonList(new RestPostCommandAction(this.commandIndex));
return Collections.singletonList(
new RestPostCommandAction(this.commandIndex, this.commandManagerSettings));
}

@Override
public List<Setting<?>> getSettings() {
return Arrays.asList(
// Register API settings
CommandManagerSettings.M_API_AUTH_USERNAME,
CommandManagerSettings.M_API_AUTH_PASSWORD,
CommandManagerSettings.M_API_URI);
}

@Override
public void reload(Settings settings) {
// secure settings should be readable
// final CommandManagerSettings commandManagerSettings =
// CommandManagerSettings.getClientSettings(secureSettingsPassword);
// I don't know what I have to do when we want to reload the settings already
// xxxService.refreshAndClearCache(commandManagerSettings);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
package com.wazuh.commandmanager.rest;

import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.client.node.NodeClient;
Expand All @@ -23,8 +22,6 @@
import org.opensearch.rest.RestRequest;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
Expand All @@ -34,7 +31,8 @@
import com.wazuh.commandmanager.model.Agent;
import com.wazuh.commandmanager.model.Command;
import com.wazuh.commandmanager.model.Document;
import com.wazuh.commandmanager.utils.httpclient.HttpRestClient;
import com.wazuh.commandmanager.settings.CommandManagerSettings;
import com.wazuh.commandmanager.utils.httpclient.HttpRestClientDemo;

import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken;
import static org.opensearch.rest.RestRequest.Method.POST;
Expand All @@ -49,14 +47,16 @@ public class RestPostCommandAction extends BaseRestHandler {
"post_command_action_request_details";
private static final Logger log = LogManager.getLogger(RestPostCommandAction.class);
private final CommandIndex commandIndex;
private final CommandManagerSettings settings;

/**
* Default constructor
*
* @param commandIndex persistence layer
*/
public RestPostCommandAction(CommandIndex commandIndex) {
public RestPostCommandAction(CommandIndex commandIndex, CommandManagerSettings settings) {
this.commandIndex = commandIndex;
this.settings = settings;
}

public String getName() {
Expand Down Expand Up @@ -108,19 +108,15 @@ private RestChannelConsumer handlePost(RestRequest request) throws IOException {

// Commands delivery to the Management API.
// Note: needs to be decoupled from the Rest handler (job scheduler task).
HttpRestClient httpClient = HttpRestClient.getInstance();
try {
String uri = "https://httpbin.org/post";
// String uri = "https://127.0.0.1:5000";
URI receiverURI = new URIBuilder(uri).build();
String receiverURI = this.settings.getUri();
String payload =
document.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS)
.toString();
SimpleHttpResponse response = httpClient.post(receiverURI, payload, document.getId());
SimpleHttpResponse response =
HttpRestClientDemo.runWithResponse(receiverURI, payload, document.getId());
log.info("Received response to POST request with code [{}]", response.getCode());
log.info("Raw response:\n{}", response.getBodyText());
} catch (URISyntaxException e) {
log.error("Bad URI: {}", e.getMessage());
} catch (Exception e) {
log.error("Error reading response: {}", e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright OpenSearch Contributors
* 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.settings;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.common.settings.SecureSetting;
import org.opensearch.common.settings.Setting;
import org.opensearch.common.settings.Settings;
import org.opensearch.core.common.settings.SecureString;
import org.opensearch.env.Environment;

/** This class manages the configuration of the plugin. */
public class CommandManagerSettings {

/** The access key (ie login username) for connecting to api. */
public static final Setting<SecureString> M_API_AUTH_USERNAME =
SecureSetting.secureString("m_api.auth.username", null);

/** The secret key (ie password) for connecting to api. */
public static final Setting<SecureString> M_API_AUTH_PASSWORD =
SecureSetting.secureString("m_api.auth.password", null);

/** The uri for connecting to api. */
public static final Setting<SecureString> M_API_URI =
SecureSetting.secureString("m_api.uri", null);

/** The access key (ie login username) for connecting to api. */
private final String authUsername;

/** The password for connecting to api. */
private final String authPassword;

/** The uri for connecting to api. */
private final String uri;

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

/** Private default constructor */
private CommandManagerSettings(String authUsername, String authPassword, String uri) {
this.authUsername = authUsername;
this.authPassword = authPassword;
this.uri = uri;
log.info("CommandManagerSettings created ");
}

/**
* Singleton instance accessor
*
* @return {@link CommandManagerSettings#instance}
*/
public static CommandManagerSettings getInstance(Environment environment) {
if (CommandManagerSettings.instance == null) {
instance = CommandManagerSettings.getSettings(environment);
}
return CommandManagerSettings.instance;
}

/** Parse settings for a single client. */
public static CommandManagerSettings getSettings(Environment environment) {

final Settings settings = environment.settings();
if (settings != null) {
log.info("Settings created with the keystore information.");
try (SecureString authUsername = M_API_AUTH_USERNAME.get(settings);
SecureString authPassword = M_API_AUTH_PASSWORD.get(settings);
SecureString uri = M_API_URI.get(settings); ) {
return new CommandManagerSettings(
authUsername.toString(), authPassword.toString(), uri.toString());
}
} else {
return null;
}
}

public String getAuthPassword() {
return authPassword;
}

public String getAuthUsername() {
return authUsername;
}

public String getUri() {
return uri;
}

@Override
public String toString() {
return "CommandManagerSettings{"
+ " authUsername='"
+ authUsername
+ '\''
+ ", authPassword='"
+ authPassword
+ '\''
+ ", uri='"
+ uri
+ '\''
+ '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright OpenSearch Contributors
* 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.settings;

public class CommandManagerSettingsException extends Exception {

// Constructor that accepts a message
public CommandManagerSettingsException(String message) {
super(message);
}

// Exception for the case when load keystore failed
public static CommandManagerSettingsException loadSettingsFailed(
String keyStorePath, String errorMessage) {
return new CommandManagerSettingsException(
"Load settings from: " + keyStorePath + " failed. Error: " + errorMessage);
}

// Exception for the case when reload plugin with the keystore failed
public static CommandManagerSettingsException reloadPluginFailed(String pluginName) {
return new CommandManagerSettingsException("Reload failed for plugin: " + pluginName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,30 @@ public static void run(String endpoint, String body) {
return null;
});
}

/**
* Demo method to test the {@link HttpRestClient} class.
*
* @param endpoint POST's requests endpoint as a well-formed URI
* @param body POST's request body as a JSON string.
* @return
*/
public static SimpleHttpResponse runWithResponse(String endpoint, String body, String docId) {
log.info("Executing POST request");
SimpleHttpResponse response;
response =
AccessController.doPrivileged(
(PrivilegedAction<SimpleHttpResponse>)
() -> {
HttpRestClient httpClient = HttpRestClient.getInstance();
try {
URI host = new URIBuilder(endpoint).build();
return httpClient.post(host, body, docId);
} catch (URISyntaxException e) {
log.error("Bad URI:{}", e.getMessage());
}
return null;
});
return response;
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
grant {
permission java.net.SocketPermission "*", "connect,resolve";
};
};
Loading

0 comments on commit 99ae457

Please sign in to comment.