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 874069e..073216c 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,10 @@ */ package com.wazuh.commandmanager; -import com.wazuh.commandmanager.config.reader.ConfigReader; import com.wazuh.commandmanager.index.CommandIndex; import com.wazuh.commandmanager.rest.action.RestPostCommandAction; -import com.wazuh.commandmanager.scheduler.JobScheduler; +import com.wazuh.commandmanager.utils.httpclient.HttpRestClient; +import com.wazuh.commandmanager.utils.httpclient.HttpRestClientDemo; import org.opensearch.client.Client; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.node.DiscoveryNodes; @@ -32,6 +32,7 @@ import org.opensearch.threadpool.ThreadPool; import org.opensearch.watcher.ResourceWatcherService; +import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -65,8 +66,11 @@ public Collection createComponents( Supplier repositoriesServiceSupplier ) { this.commandIndex = new CommandIndex(client, clusterService, threadPool); - ConfigReader configReader = new ConfigReader("httpbin.org", 80, "/post", "admin", "admin"); - JobScheduler jobScheduler = new JobScheduler(threadPool, configReader); + + // HttpRestClient stuff + String uri = "https://httpbin.org/post"; + String payload = "{\"message\": \"Hello world!\"}"; + HttpRestClientDemo.run(uri, payload); return Collections.emptyList(); } @@ -81,4 +85,15 @@ public List getRestHandlers( ) { return Collections.singletonList(new RestPostCommandAction(this.commandIndex)); } + + /** + * Close the resources opened by this plugin. + * + * @throws IOException if the plugin failed to close its resources + */ + @Override + public void close() throws IOException { + super.close(); + HttpRestClient.getInstance().stopHttpAsyncClient(); + } } diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/config/reader/ConfigReader.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/config/reader/ConfigReader.java deleted file mode 100644 index 32245bb..0000000 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/config/reader/ConfigReader.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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.config.reader; - -public class ConfigReader { - - String hostName; - int port; - String path; - String username; - String password; - - public ConfigReader() { - this.hostName = "jsonplaceholder.typicode.com"; - this.port = 80; - this.path = "/posts/1"; - this.username = "admin"; - this.password = "admin"; - } - - public ConfigReader(String hostName, int port, String path, String username, String password) { - this.hostName = hostName; - this.port = port; - this.path = path; - this.username = username; - this.password = password; - } - - public String getHostName() { - return hostName; - } - - public int getPort() { - return port; - } - - public String getPath() { - return path; - } - - public String getUsername() { - return username; - } - - public String getPassword() { - return password; - } -} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/http/client/AsyncRequestRepository.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/http/client/AsyncRequestRepository.java deleted file mode 100644 index 349169d..0000000 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/http/client/AsyncRequestRepository.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - */ -package com.wazuh.commandmanager.http.client; - -import com.wazuh.commandmanager.config.reader.ConfigReader; -import org.apache.hc.client5.http.async.methods.*; -import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; -import org.apache.hc.client5.http.impl.async.HttpAsyncClients; -import org.apache.hc.core5.concurrent.FutureCallback; -import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HttpHost; -import org.apache.hc.core5.http.message.StatusLine; -import org.apache.hc.core5.io.CloseMode; -import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.Closeable; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -public class AsyncRequestRepository implements Closeable { - private static AsyncRequestRepository instance; - private static final Logger logger = LogManager.getLogger(AsyncRequestRepository.class); - private final HttpHost target; - private final String requestUri; - private CloseableHttpAsyncClient client; - - private AsyncRequestRepository(ConfigReader configReader) throws Exception { - this.target = new HttpHost(configReader.getHostName(), configReader.getPort()); - this.requestUri = configReader.getPath(); - } - - public static AsyncRequestRepository getInstance(ConfigReader configReader) throws Exception { - if (instance == null) { - instance = new AsyncRequestRepository(configReader); - } - return instance; - } - - public Future performAsyncRequest() throws Exception { - logger.info("Preparing Async Request"); - IOReactorConfig ioReactorConfig = IOReactorConfig.custom() - .setSoTimeout(5, TimeUnit.SECONDS) - .build(); - - this.client = HttpAsyncClients.custom() - .setIOReactorConfig(ioReactorConfig) - .build(); - this.client.start(); - - final SimpleHttpRequest request = SimpleRequestBuilder.post() - .setHttpHost(target) - .setPath(requestUri) - .setBody("{\"field\":\"value\"}", ContentType.APPLICATION_JSON) - .build(); - - logger.info("Executing {} request", request); - - - return this.client.execute( - SimpleRequestProducer.create(request), - SimpleResponseConsumer.create(), - new FutureCallback<>() { - @Override - public void completed(final SimpleHttpResponse response) { - logger.info("{}->{}", request, new StatusLine(response)); - } - - @Override - public void failed(final Exception ex) { - logger.error("Could not process {} request: {}", request, ex.getMessage()); - } - - @Override - public void cancelled() { - logger.error("{} cancelled", request); - } - } - ); - } - - public void close() { - logger.info("HTTP requester shutting down"); - if (this.client == null) { - return; - } - this.client.close(CloseMode.GRACEFUL); - } -} - 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..f4238a9 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 @@ -31,6 +31,9 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; +/** + * Class to manage the Command Manager index and index template. + */ public class CommandIndex implements IndexingOperationListener { private static final Logger logger = LogManager.getLogger(CommandIndex.class); @@ -53,7 +56,7 @@ public CommandIndex(Client client, ClusterService clusterService, ThreadPool thr } /** - * @param command: A Command model object + * @param command A Command model object * @return A CompletableFuture with the RestStatus response from the operation */ public CompletableFuture asyncCreate(Command command) { @@ -95,7 +98,10 @@ public CompletableFuture asyncCreate(Command command) { } /** - * @return + * Checks for the existence of the given index template in the cluster. + * + * @param template_name index template name within the resources folder + * @return whether the index template exists. */ public boolean indexTemplateExists(String template_name) { Map templates = this.clusterService diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/scheduler/JobScheduler.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/scheduler/JobScheduler.java deleted file mode 100644 index a33c182..0000000 --- a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/scheduler/JobScheduler.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.scheduler; - -import com.wazuh.commandmanager.config.reader.ConfigReader; -import com.wazuh.commandmanager.http.client.AsyncRequestRepository; -import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.opensearch.threadpool.ThreadPool; - -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; - -public class JobScheduler { - - private static final Logger logger = LogManager.getLogger(JobScheduler.class); - private final ConfigReader configReader; - - public JobScheduler(ThreadPool threadPool, ConfigReader configReader) { - this.configReader = configReader; - start(threadPool); - } - - private void start(ThreadPool threadPool) { - ExecutorService executorService = threadPool.executor(ThreadPool.Names.GENERIC); - Future future = AccessController.doPrivileged( - (PrivilegedAction>) () -> { - try (AsyncRequestRepository asyncRequestRepository = AsyncRequestRepository.getInstance(configReader)){ - return asyncRequestRepository.performAsyncRequest(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - ); - executorService.submit( - () -> { - while(!Thread.currentThread().isInterrupted()) { - try { - Thread.sleep(5000); - logger.info("Running HTTP Request"); - logger.info(future.get().getBodyText()); - - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.info("Exiting scheduler"); - break; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - ); - } -} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/httpclient/HttpResponseCallback.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/httpclient/HttpResponseCallback.java new file mode 100644 index 0000000..eaf357d --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/httpclient/HttpResponseCallback.java @@ -0,0 +1,53 @@ +/* + * 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.httpclient; + +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.message.StatusLine; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class HttpResponseCallback implements FutureCallback { + + private static final Logger log = LogManager.getLogger(HttpResponseCallback.class); + + /** + * The Http get request. + */ + SimpleHttpRequest httpRequest; + + /** + * The Error message. + */ + String errorMessage; + + public HttpResponseCallback(SimpleHttpRequest httpRequest, + String errorMessage) { + this.httpRequest = httpRequest; + this.errorMessage = errorMessage; + } + + @Override + public void completed(SimpleHttpResponse response) { + log.debug("{}->{}", httpRequest, new StatusLine(response)); + log.debug("Got response: {}", response.getBody()); + } + + @Override + public void failed(Exception ex) { + log.error("{}->{}", httpRequest, ex); +// throw new HttpException(errorMessage, ex); + } + + @Override + public void cancelled() { + log.debug(httpRequest + " cancelled"); + } +} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/httpclient/HttpRestClient.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/httpclient/HttpRestClient.java new file mode 100644 index 0000000..f21cd58 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/httpclient/HttpRestClient.java @@ -0,0 +1,131 @@ +/* + * 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.httpclient; + +import org.apache.hc.client5.http.async.methods.*; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.reactor.IOReactorConfig; +import org.apache.hc.core5.util.Timeout; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.common.Randomness; + +import java.net.URI; +import java.util.concurrent.Future; + +/** + * HTTP Rest client. Currently used to perform + * POST requests against the Wazuh Server. + */ +public class HttpRestClient { + + private static final Logger log = LogManager.getLogger(HttpRestClient.class); + private static HttpRestClient instance; + private CloseableHttpAsyncClient httpClient; + + /** + * Private default constructor + */ + private HttpRestClient() { + startHttpAsyncClient(); + } + + /** + * Singleton instance accessor + * + * @return {@link HttpRestClient#instance} + */ + public static HttpRestClient getInstance() { + if (HttpRestClient.instance == null) { + instance = new HttpRestClient(); + } + return HttpRestClient.instance; + } + + /** + * Starts http async client. + */ + private void startHttpAsyncClient() { + if (this.httpClient == null) { + try { + PoolingAsyncClientConnectionManager cm = + PoolingAsyncClientConnectionManagerBuilder.create().build(); + + IOReactorConfig ioReactorConfig = IOReactorConfig.custom() + .setSoTimeout(Timeout.ofSeconds(5)) + .build(); + + httpClient = HttpAsyncClients.custom() + .setIOReactorConfig(ioReactorConfig) + .setConnectionManager(cm) + .build(); + + httpClient.start(); + } catch (Exception e) { + // handle exception + log.error("Error starting async Http client {}", e.getMessage()); + } + } + } + + /** + * Stop http async client. + */ + public void stopHttpAsyncClient() { + if (this.httpClient != null) { + log.info("Shutting down."); + httpClient.close(CloseMode.GRACEFUL); + httpClient = null; + } + } + + /** + * Sends a POST request. + * + * @param uri Well-formed URI + * @param payload data to send + * @return HTTP response + */ + public SimpleHttpResponse post(URI uri, String payload) { + Long id = Randomness.get().nextLong(); + + try { + // Create request + HttpHost httpHost = HttpHost.create(uri.getHost()); + + SimpleHttpRequest httpPostRequest = SimpleRequestBuilder + .post() + .setHttpHost(httpHost) + .setPath(uri.getPath()) + .setBody(payload, ContentType.APPLICATION_JSON) + .build(); + + // log request + Future future = + this.httpClient.execute( + SimpleRequestProducer.create(httpPostRequest), + SimpleResponseConsumer.create(), + new HttpResponseCallback( + httpPostRequest, + "Failed to send data for ID: " + id + ) + ); + + return future.get(); + } catch (Exception e) { + log.error("Failed to send data for ID: {}", id); + } + return null; + } +} diff --git a/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/httpclient/HttpRestClientDemo.java b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/httpclient/HttpRestClientDemo.java new file mode 100644 index 0000000..448ece4 --- /dev/null +++ b/plugins/command-manager/src/main/java/com/wazuh/commandmanager/utils/httpclient/HttpRestClientDemo.java @@ -0,0 +1,50 @@ +/* + * 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.httpclient; + +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 java.net.URI; +import java.net.URISyntaxException; +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * Demo class to test the {@link HttpRestClient} class. + */ +public class HttpRestClientDemo { + + private static final Logger log = LogManager.getLogger(HttpRestClientDemo.class); + + /** + * 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. + */ + public static void run(String endpoint, String body) { + log.info("Executing POST request"); + AccessController.doPrivileged( + (PrivilegedAction) () -> { + HttpRestClient httpClient = HttpRestClient.getInstance(); + URI host; + try { + host = new URIBuilder(endpoint).build(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + SimpleHttpResponse postResponse = httpClient.post(host, body); + log.info(postResponse.getBodyText()); + return postResponse; + } + ); + } +} diff --git a/plugins/command-manager/src/main/plugin-metadata/plugin-security.policy b/plugins/command-manager/src/main/plugin-metadata/plugin-security.policy index a1754fc..6e4d716 100644 --- a/plugins/command-manager/src/main/plugin-metadata/plugin-security.policy +++ b/plugins/command-manager/src/main/plugin-metadata/plugin-security.policy @@ -1,3 +1,3 @@ grant { -permission java.net.SocketPermission "*", "connect,resolve"; + permission java.net.SocketPermission "*", "connect,resolve"; }; \ No newline at end of file diff --git a/plugins/command-manager/src/test/java/com/wazuh/commandmanager/CommandManagerTests.java b/plugins/command-manager/src/test/java/com/wazuh/commandmanager/CommandManagerTests.java index cb4da7e..eacd0b1 100644 --- a/plugins/command-manager/src/test/java/com/wazuh/commandmanager/CommandManagerTests.java +++ b/plugins/command-manager/src/test/java/com/wazuh/commandmanager/CommandManagerTests.java @@ -7,8 +7,54 @@ */ package com.wazuh.commandmanager; +import com.wazuh.commandmanager.utils.httpclient.HttpRestClient; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.junit.Assert; import org.opensearch.test.OpenSearchTestCase; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.AccessController; +import java.security.PrivilegedAction; + public class CommandManagerTests extends OpenSearchTestCase { // Add unit tests for your plugin + + private HttpRestClient httpClient; + + public void testPost_success() { + try { + AccessController.doPrivileged( + (PrivilegedAction) () -> { + this.httpClient = HttpRestClient.getInstance(); + URI uri = null; + try { + uri = new URI("https://httpbin.org/post"); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + String payload = "{\"message\": \"Hello world!\"}"; + SimpleHttpResponse postResponse = this.httpClient.post(uri, payload); + + String responseText = postResponse.getBodyText(); + assertNotEquals(null, postResponse); + assertNotEquals(null, responseText); + assertEquals(200, postResponse.getCode()); + assertNotEquals(0, responseText.length()); + assertTrue(responseText.contains("Hello world!")); + return postResponse; + } + ); + } catch (Exception e) { + Assert.fail("Failed to execute HTTP request: " + e); + } finally { + this.httpClient.stopHttpAsyncClient(); + } + } + + public void testPost_badUri() { + } + + public void testPost_badPayload() { + } }