diff --git a/integration-tests-support/splunk/README.adoc b/integration-tests-support/splunk/README.adoc new file mode 100644 index 000000000000..08a2402e7e35 --- /dev/null +++ b/integration-tests-support/splunk/README.adoc @@ -0,0 +1,16 @@ +=== Development information + +* `Security` If no custom certificate is provided, splunk server creates its own one and the `SplunkTestResource` copies it from the container into `test-classes` +* `Security` Server certificate has to be signed (that is the reason of using keytool in the pom.xml) +* `Security` SSL connection can be verified by openssl tool +``` +openssl s_client -connect localhost:32825 -CAfile cacert.pem +``` +* `Security` Server certificate has to contain a private key, which could not be done via keytool itself. Proper way of achieving such certificate is to use Openssl tool. The `TestResource` is concatenating certificates and keys programmatically. +``` +openssl pkcs12 -export -out combined.p12 -inkey localhost-key.pem -in localhost.pem -certfile splunkca.pem +openssl pkcs12 -in combined.p12 -out combined.pem -nodes +``` +* Set log level to debug for `org.apache.camel.quarkus.test.support.splunk` (by adding `quarkus.log.category.\"org.apache.camel.quarkus.test.support.splunk\".level=DEBUG` into application.properties) to see more important information from runtime. +* `TestResource` is exporting configuration files, log and certificates to the `test-classes` folder. +* Splunk server takes a lot of time to start. It might be handy to start a test with a different process with some timeout (like several hours) and use FakeSplunkTestResource with hardcoded ports. \ No newline at end of file diff --git a/integration-tests-support/splunk/src/test/java/org/apache/camel/quarkus/test/support/splunk/FakeSplunkTestResource.java b/integration-tests-support/splunk/src/test/java/org/apache/camel/quarkus/test/support/splunk/FakeSplunkTestResource.java new file mode 100644 index 000000000000..c2094bee5fec --- /dev/null +++ b/integration-tests-support/splunk/src/test/java/org/apache/camel/quarkus/test/support/splunk/FakeSplunkTestResource.java @@ -0,0 +1,51 @@ +/* + * 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. + */ +package org.apache.camel.quarkus.test.support.splunk; + +import java.util.Map; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import org.apache.commons.lang3.StringUtils; + +/** + * Test Resource meant for development. Replace port numbers with the real ports of the container running in different + * process. + * See README.adoc for more hints. + */ +public class FakeSplunkTestResource implements QuarkusTestResourceLifecycleManager { + + @Override + public Map start() { + + String banner = StringUtils.repeat("*", 50); + + Map m = Map.of( + SplunkConstants.PARAM_REMOTE_HOST, "localhost", + SplunkConstants.PARAM_TCP_PORT, "328854", + SplunkConstants.PARAM_HEC_TOKEN, "TESTTEST-TEST-TEST-TEST-TESTTESTTEST", + SplunkConstants.PARAM_TEST_INDEX, SplunkTestResource.TEST_INDEX, + SplunkConstants.PARAM_REMOTE_PORT, "32885", + SplunkConstants.PARAM_HEC_PORT, "32886"); + + return m; + + } + + @Override + public void stop() { + } +} diff --git a/integration-tests-support/splunk/src/test/java/org/apache/camel/quarkus/test/support/splunk/SplunkTestResource.java b/integration-tests-support/splunk/src/test/java/org/apache/camel/quarkus/test/support/splunk/SplunkTestResource.java index e34ca450de5d..69a951a95065 100644 --- a/integration-tests-support/splunk/src/test/java/org/apache/camel/quarkus/test/support/splunk/SplunkTestResource.java +++ b/integration-tests-support/splunk/src/test/java/org/apache/camel/quarkus/test/support/splunk/SplunkTestResource.java @@ -16,16 +16,36 @@ */ package org.apache.camel.quarkus.test.support.splunk; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; import java.time.Duration; +import java.util.Base64; import java.util.Map; import java.util.TimeZone; +import java.util.stream.Collectors; +import io.quarkus.logging.Log; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import org.apache.commons.lang3.StringUtils; import org.eclipse.microprofile.config.ConfigProvider; -import org.jboss.logging.Logger; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.Transferable; +import org.testcontainers.utility.MountableFile; public class SplunkTestResource implements QuarkusTestResourceLifecycleManager { @@ -36,12 +56,28 @@ public class SplunkTestResource implements QuarkusTestResourceLifecycleManager { private static final int REMOTE_PORT = 8089; private static final int WEB_PORT = 8000; private static final int HEC_PORT = 8088; - private static final Logger LOG = Logger.getLogger(SplunkTestResource.class); + private static final Logger LOG = LoggerFactory.getLogger(SplunkTestResource.class); private GenericContainer container; + private String localhostCertPath; + private String localhostKeystorePath; + private String caCertPath; + private String keystorePassword; + + @Override + public void init(Map initArgs) { + localhostCertPath = initArgs.get("localhost_cert"); + caCertPath = initArgs.get("ca_cert"); + localhostKeystorePath = initArgs.get("localhost_keystore"); + keystorePassword = initArgs.get("keystore_password"); + } + @Override public Map start() { + + String banner = StringUtils.repeat("*", 50); + try { container = new GenericContainer<>(SPLUNK_IMAGE_NAME) .withExposedPorts(REMOTE_PORT, SplunkConstants.TCP_PORT, WEB_PORT, HEC_PORT) @@ -54,41 +90,137 @@ public Map start() { Wait.forLogMessage(".*Ansible playbook complete.*\\n", 1) .withStartupTimeout(Duration.ofMinutes(5))); + if (localhostCertPath != null && localhostKeystorePath != null && caCertPath != null && keystorePassword != null) { + //combine key + certificates into 1 pem - required for splunk + //extraction of private key can not be done by keytool (only openssl), but it can be done programmatically + byte[] concatenate = concatenateKeyAndCertificates(banner); + + container.withCopyToContainer(Transferable.of(concatenate), "/opt/splunk/etc/auth/mycerts/myServerCert.pem") + .withCopyToContainer(Transferable.of(Files.readAllBytes(Paths.get(caCertPath))), + "/opt/splunk/etc/auth/mycerts/cacert.pem"); + } else { + LOG.debug("Internal certificates are used for Splunk server."); + } + container.start(); - container.execInContainer("sudo", "sed", "-i", "s/allowRemoteLogin=requireSetPassword/allowRemoteLogin=always/", - "/opt/splunk/etc/system/default/server.conf"); - container.execInContainer("sudo", "sed", "-i", "s/enableSplunkdSSL = true/enableSplunkdSSL = false/", - "/opt/splunk/etc/system/default/server.conf"); + container.copyFileToContainer(MountableFile.forClasspathResource("local_server.conf"), + "/opt/splunk/etc/system/local/server.conf"); + container.copyFileToContainer(MountableFile.forClasspathResource("local_inputs.conf"), + "/opt/splunk/etc/system/local/inputs.conf"); + + container.copyFileToContainer(MountableFile.forClasspathResource("local_server.conf"), + "/opt/splunk/etc/system/local/server.conf"); + container.copyFileToContainer(MountableFile.forClasspathResource("local_inputs.conf"), + "/opt/splunk/etc/system/local/inputs.conf"); + container.execInContainer("sudo", "sed", "-i", "s/minFreeSpace = 5000/minFreeSpace = 100/", - "/opt/splunk/etc/system/default/server.conf"); + "/opt/splunk/etc/system/local/server.conf"); + + /* uncomment for troubleshooting purposes - copy configuration from container + container.copyFileFromContainer("/opt/splunk/etc/system/local/server.conf", + Path.of(getClass().getResource("/").getPath()).resolve("local_server_from_container.conf").toFile() + .getAbsolutePath());*/ - container.execInContainer("sudo", "microdnf", "--nodocs", "update", "tzdata");//install tzdata package so we can specify tz other than UTC + assertExecResult(container.execInContainer("sudo", "microdnf", "--nodocs", "update", "tzdata"), "tzdata install");//install tzdata package so we can specify tz other than UTC + + LOG.debug(banner); + LOG.debug("Restarting splunk server."); + LOG.debug(banner); + + assertExecResult(container.execInContainer("sudo", "./bin/splunk", "restart"), "splunk restart"); - container.execInContainer("sudo", "./bin/splunk", "restart"); container.execInContainer("sudo", "./bin/splunk", "add", "index", TEST_INDEX); container.execInContainer("sudo", "./bin/splunk", "add", "tcp", String.valueOf(SplunkConstants.TCP_PORT), "-sourcetype", "TCP"); - String splunkHost = container.getHost(); + /*uncomment for troubleshooting purposes - copy from container conf and log files + container.copyFileFromContainer("/opt/splunk/etc/system/local/server.conf", + Path.of(getClass().getResource("/").getPath()).resolve("local-server-from-container.conf").toFile() + .getAbsolutePath()); + container.copyFileFromContainer("/opt/splunk/etc/system/default/server.conf", + Path.of(getClass().getResource("/").getPath()).resolve("default-server-from-container.log").toFile() + .getAbsolutePath()); + if (localhostCertPath != null && localhostKeystorePath != null && caCertPath != null && keystorePassword != null) { + container.copyFileFromContainer("/opt/splunk/etc/auth/mycerts/myServerCert.pem", + Path.of(getClass().getResource("/").getPath()).resolve("myServerCert-from-container.pem").toFile() + .getAbsolutePath()); + container.copyFileFromContainer("/opt/splunk/etc/auth/mycerts/cacert.pem", + Path.of(getClass().getResource("/").getPath()).resolve("cacert-from-container.pem").toFile() + .getAbsolutePath()); + } else { + container.copyFileFromContainer("/opt/splunk/etc/auth/server.pem", + Path.of(getClass().getResource("/").getPath()).resolve("myServerCert-from-container.pem").toFile() + .getAbsolutePath()); + container.copyFileFromContainer("/opt/splunk/etc/auth/cacert.pem", + Path.of(getClass().getResource("/").getPath()).resolve("cacert-from-container.pem").toFile() + .getAbsolutePath()); + } + */ - String banner = StringUtils.repeat("*", 50); - LOG.info(banner); - LOG.infof("Splunk UI running on: http://%s:%d", splunkHost, container.getMappedPort(WEB_PORT)); - LOG.info(banner); + String splunkHost = container.getHost(); - return Map.of( + Map m = Map.of( SplunkConstants.PARAM_REMOTE_HOST, splunkHost, SplunkConstants.PARAM_TCP_PORT, container.getMappedPort(SplunkConstants.TCP_PORT).toString(), SplunkConstants.PARAM_HEC_TOKEN, HEC_TOKEN, SplunkConstants.PARAM_TEST_INDEX, TEST_INDEX, SplunkConstants.PARAM_REMOTE_PORT, container.getMappedPort(REMOTE_PORT).toString(), SplunkConstants.PARAM_HEC_PORT, container.getMappedPort(HEC_PORT).toString()); + + LOG.info(banner); + LOG.info(String.format("Splunk UI running on: http://%s:%d", splunkHost, container.getMappedPort(WEB_PORT))); + LOG.info(banner); + LOG.debug(m.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue()).sorted() + .collect(Collectors.joining("\n"))); + LOG.debug(banner); + + return m; + } catch (Exception e) { throw new RuntimeException(e); } } + private byte @NotNull [] concatenateKeyAndCertificates(String banner) + throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { + // Load the KeyStore + KeyStore keystore = KeyStore.getInstance("JKS"); + try (FileInputStream fis = new FileInputStream( + Paths.get(localhostKeystorePath).toFile())) { + keystore.load(fis, keystorePassword.toCharArray()); + } + // Get the private key + Key key = keystore.getKey(keystore.aliases().asIterator().next(), keystorePassword.toCharArray()); + + // Encode the private key to PEM format + String encodedKey = Base64.getEncoder().encodeToString(key.getEncoded()); + String pemKey = "-----BEGIN PRIVATE KEY-----\n" + encodedKey + "\n-----END PRIVATE KEY-----"; + + //localhost.pem and cacert.pem has to be concatenated + String localhost = Files.readString( + Paths.get(localhostCertPath), + StandardCharsets.UTF_8); + String ca = Files.readString(Path.of(caCertPath), + StandardCharsets.UTF_8); + Log.debug("cacert content:"); + Log.debug(ca); + Log.debug(banner); + byte[] concatenate = (localhost + ca + pemKey).getBytes(StandardCharsets.UTF_8); + return concatenate; + } + + private static void assertExecResult(Container.ExecResult res, String cmd) { + if (res.getExitCode() != 0) { + LOG.error("Command: " + cmd); + LOG.error("Stdout: " + res.getStdout()); + LOG.error("Stderr: " + res.getStderr()); + throw new RuntimeException("Command " + cmd + ") failed. " + res.getStdout()); + } else { + LOG.debug("Command: " + cmd + " succeeded!"); + } + } + @Override public void stop() { try { diff --git a/integration-tests/splunk-hec/pom.xml b/integration-tests/splunk-hec/pom.xml index 5fdf50218287..9f031e657e6d 100644 --- a/integration-tests/splunk-hec/pom.xml +++ b/integration-tests/splunk-hec/pom.xml @@ -98,6 +98,139 @@ + + full + + + !quickly + + + + + + org.codehaus.mojo + keytool-maven-plugin + + password + 18250 + RSA + password + + + + generate-splunkca-keypair + generate-sources + + clean + generateKeyPair + + + cxfca + CN=splunkca, OU=eng, O=apache.org + + bc:c=ca:true,pathlen:2147483647 + IssuerAlternativeName=DNS:NOT-FOR-PRODUCTION-USE + + ${project.basedir}/target/certs/splunkca.jks + + + + export-splunkca-certificate + generate-sources + + exportCertificate + + + cxfca + ${project.basedir}/target/certs//splunkca.jks + true + ${project.basedir}/target/certs/splunkca.pem + + + + generate-localhost-keypair + generate-sources + + clean + generateKeyPair + + + localhost + CN=localhost, OU=eng, O=apache.org + + IssuerAlternativeName=DNS:NOT-FOR-PRODUCTION-USE + SubjectAlternativeName=DNS:localhost,IP:127.0.0.1 + + ${project.basedir}/target/certs/localhost.jks + + + + generate-localhost-certificate-request + generate-sources + + generateCertificateRequest + + + localhost + ${project.basedir}/target/certs/localhost.jks + ${project.basedir}/target/certs/localhost.csr + + + + generate-localhost-certificate + generate-sources + + generateCertificate + + + cxfca + ${project.basedir}/target/certs/splunkca.jks + true + ${project.basedir}/target/certs/localhost.csr + ${project.basedir}/target/certs/localhost.pem + + + + generate-wrong-splunkca-keypair + generate-sources + + clean + generateKeyPair + + + cxfca + CN=splunkca, OU=eng, O=apache.org + + bc:c=ca:true,pathlen:2147483647 + IssuerAlternativeName=DNS:NOT-FOR-PRODUCTION-USE + + ${project.basedir}/target/certs/wrong-splunkca.jks + + + + + + + + + ssl debug + + + ssldebug + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + -Djavax.net.debug=ssl,handshake + + + + + virtualDependencies diff --git a/integration-tests/splunk-hec/src/main/java/org/apache/camel/quarkus/component/splunk/hec/it/SplunkHecResource.java b/integration-tests/splunk-hec/src/main/java/org/apache/camel/quarkus/component/splunk/hec/it/SplunkHecResource.java index a0fc5eb515bf..10c6f88627ea 100644 --- a/integration-tests/splunk-hec/src/main/java/org/apache/camel/quarkus/component/splunk/hec/it/SplunkHecResource.java +++ b/integration-tests/splunk-hec/src/main/java/org/apache/camel/quarkus/component/splunk/hec/it/SplunkHecResource.java @@ -16,16 +16,33 @@ */ package org.apache.camel.quarkus.component.splunk.hec.it; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.KeyStore; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import jakarta.inject.Named; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.camel.CamelContext; import org.apache.camel.ProducerTemplate; import org.apache.camel.component.splunkhec.SplunkHECConstants; import org.apache.camel.quarkus.test.support.splunk.SplunkConstants; +import org.apache.camel.support.jsse.SSLContextParameters; import org.eclipse.microprofile.config.inject.ConfigProperty; @Path("/splunk-hec") @@ -47,11 +64,75 @@ public class SplunkHecResource { @ConfigProperty(name = SplunkConstants.PARAM_HEC_TOKEN) String token; - @Path("/send") + @Path("/send/{sslContextParameters}") @POST @Produces(MediaType.TEXT_PLAIN) - public String send(String data, @QueryParam("indexTime") Long indexTime) { - String url = String.format("splunk-hec:%s:%s?token=%s&skipTlsVerify=true&index=%s", host, hecPort, token, index); - return producer.requestBodyAndHeader(url, data, SplunkHECConstants.INDEX_TIME, indexTime, String.class); + public Response send(String data, + @PathParam("sslContextParameters") String sslContextParameters, + @QueryParam("indexTime") Long indexTime) { + String url = String.format( + "splunk-hec:%s:%s?token=%s&sslContextParameters=#%s&skipTlsVerify=false&https=true&index=%s", + host, hecPort, token, sslContextParameters, index); + try { + return Response.status(200) + .entity(producer.requestBodyAndHeader(url, data, SplunkHECConstants.INDEX_TIME, indexTime, String.class)) + .build(); + } catch (Exception e) { + if (e.getCause() instanceof SSLException) { + return Response.status(500).entity("ssl exception").build(); + } + throw new RuntimeException(e); + } + } + + @Named("sslContextParameters") + public SSLContextParameters createServerSSLContextParameters() { + return createServerSSLContextParameters("target/certs/splunkca.jks"); + } + + /** + * Creates SSL Context Parameters for the server + * + * @return + */ + @Named("wrongSslContextParameters") + public SSLContextParameters createWrongServerSSLContextParameters() { + return createServerSSLContextParameters("target/certs/wrong-splunkca.jks"); + } + + private SSLContextParameters createServerSSLContextParameters(String keystore) { + return new SSLContextParameters() { + @Override + public SSLContext createSSLContext(CamelContext camelContext) throws GeneralSecurityException, IOException { + + ///bealdung https://www.baeldung.com/java-custom-truststore + TrustManagerFactory trustManagerFactory = TrustManagerFactory + .getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init((KeyStore) null); + + try (FileInputStream myKeys = new FileInputStream( + Paths.get(keystore).toFile())) { + KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + myTrustStore.load(myKeys, "password".toCharArray()); + trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(myTrustStore); + + X509TrustManager myTrustManager = null; + for (TrustManager tm : trustManagerFactory.getTrustManagers()) { + if (tm instanceof X509TrustManager x509TrustManager) { + myTrustManager = x509TrustManager; + break; + } + } + + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, new TrustManager[] { myTrustManager }, null); + return context; + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + }; } } diff --git a/integration-tests/splunk-hec/src/main/resources/application.properties b/integration-tests/splunk-hec/src/main/resources/application.properties new file mode 100644 index 000000000000..47eb4f67860d --- /dev/null +++ b/integration-tests/splunk-hec/src/main/resources/application.properties @@ -0,0 +1,17 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +quarkus.native.resources.includes = target/certs/*.jks diff --git a/integration-tests/splunk-hec/src/test/java/org/apache/camel/quarkus/component/splunk/hec/it/SplunkHecTest.java b/integration-tests/splunk-hec/src/test/java/org/apache/camel/quarkus/component/splunk/hec/it/SplunkHecTest.java index 701b9cb1535d..376f98d0f5c8 100644 --- a/integration-tests/splunk-hec/src/test/java/org/apache/camel/quarkus/component/splunk/hec/it/SplunkHecTest.java +++ b/integration-tests/splunk-hec/src/test/java/org/apache/camel/quarkus/component/splunk/hec/it/SplunkHecTest.java @@ -21,30 +21,36 @@ import java.util.concurrent.TimeUnit; import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.common.ResourceArg; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; import io.restassured.http.ContentType; import org.apache.camel.quarkus.test.support.splunk.SplunkConstants; import org.apache.camel.quarkus.test.support.splunk.SplunkTestResource; import org.eclipse.microprofile.config.ConfigProvider; +import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; import org.testcontainers.shaded.org.awaitility.Awaitility; import org.testcontainers.shaded.org.hamcrest.core.StringContains; @QuarkusTest -@QuarkusTestResource(SplunkTestResource.class) -class SplunkHecTest { +@QuarkusTestResource(value = SplunkTestResource.class, initArgs = { + @ResourceArg(name = "localhost_cert", value = "target/certs/localhost.pem"), + @ResourceArg(name = "ca_cert", value = "target/certs/splunkca.pem"), + @ResourceArg(name = "localhost_keystore", value = "target/certs/localhost.jks"), + @ResourceArg(name = "keystore_password", value = "password") }) +public class SplunkHecTest { @Test public void produce() { - String url = String.format("http://%s:%d", + String url = String.format("https://%s:%d", getConfigValue(SplunkConstants.PARAM_REMOTE_HOST, String.class), getConfigValue(SplunkConstants.PARAM_REMOTE_PORT, Integer.class)); RestAssured.given() .body("Hello Sheldon") - .post("/splunk-hec/send") + .post("/splunk-hec/send/sslContextParameters") .then() .statusCode(200); @@ -60,12 +66,22 @@ public void produce() { .then().statusCode(200) .extract().asString(), StringContains.containsString("Hello Sheldon")); + } + @Test + public void produceWithWrongCertificate() { + RestAssured.given() + .body("Hello Sheldon") + .post("/splunk-hec/send/wrongSslContextParameters") + .then() + .statusCode(500) + .body(Matchers.either(org.hamcrest.core.StringContains.containsString("ssl exception")) + .or(org.hamcrest.core.StringContains.containsString("Connection refused"))); } @Test public void testIndexTime() { - String url = String.format("http://%s:%d", + String url = String.format("https://%s:%d", getConfigValue(SplunkConstants.PARAM_REMOTE_HOST, String.class), getConfigValue(SplunkConstants.PARAM_REMOTE_PORT, Integer.class)); @@ -76,7 +92,7 @@ public void testIndexTime() { //send an event with text 'Hello Time 01' RestAssured.given() .body("Hello time 01") - .post("/splunk-hec/send") + .post("/splunk-hec/send/sslContextParameters") .then() .statusCode(200); @@ -84,7 +100,7 @@ public void testIndexTime() { RestAssured.given() .body("Hello time 02") .queryParam("indexTime", calendar.getTimeInMillis()) - .post("/splunk-hec/send") + .post("/splunk-hec/send/sslContextParameters") .then() .statusCode(200); diff --git a/integration-tests/splunk-hec/src/test/resources/local_inputs.conf b/integration-tests/splunk-hec/src/test/resources/local_inputs.conf new file mode 100644 index 000000000000..03ce3b62fe21 --- /dev/null +++ b/integration-tests/splunk-hec/src/test/resources/local_inputs.conf @@ -0,0 +1,6 @@ +[default] +host = localhost + +[splunktcp-ssl:9997] + +disabled=0 \ No newline at end of file diff --git a/integration-tests/splunk-hec/src/test/resources/local_server.conf b/integration-tests/splunk-hec/src/test/resources/local_server.conf new file mode 100644 index 000000000000..93a12d0e8772 --- /dev/null +++ b/integration-tests/splunk-hec/src/test/resources/local_server.conf @@ -0,0 +1,33 @@ +[general] +serverName = b66b768099db +pass4SymmKey = $7$oeVDSaCZOSOMIVB0Yv2lBqldGvLt0pXWuQdDV7YvZ0d6Kwf/f0RwhQ== +allowRemoteLogin = always + +[sslConfig] +caTrustStore = splunk +serverCert = /opt/splunk/etc/auth/mycerts/myServerCert.pem +sslPassword = password +requireClientCert = false +cipherSuite = ECDHE-RSA-AES256-GCM-SHA384 +sslRootCAPath = /opt/splunk/etc/auth/mycerts/cacert.pem + +[lmpool:auto_generated_pool_download-trial] +description = auto_generated_pool_download-trial +peers = * +quota = MAX +stack_id = download-trial + +[lmpool:auto_generated_pool_forwarder] +description = auto_generated_pool_forwarder +peers = * +quota = MAX +stack_id = forwarder + +[lmpool:auto_generated_pool_free] +description = auto_generated_pool_free +peers = * +quota = MAX +stack_id = free + +[license] +active_group = Free diff --git a/integration-tests/splunk/pom.xml b/integration-tests/splunk/pom.xml index 445fcbb8fcfe..be5241bc88ec 100644 --- a/integration-tests/splunk/pom.xml +++ b/integration-tests/splunk/pom.xml @@ -69,6 +69,11 @@ test-jar test + + org.apache.camel.quarkus + camel-quarkus-integration-tests-support-certificate-generator + test + diff --git a/integration-tests/splunk/src/main/java/org/apache/camel/quarkus/component/splunk/it/SplunkResource.java b/integration-tests/splunk/src/main/java/org/apache/camel/quarkus/component/splunk/it/SplunkResource.java index 18fd9b8db2c1..c87ca43b544a 100644 --- a/integration-tests/splunk/src/main/java/org/apache/camel/quarkus/component/splunk/it/SplunkResource.java +++ b/integration-tests/splunk/src/main/java/org/apache/camel/quarkus/component/splunk/it/SplunkResource.java @@ -42,6 +42,7 @@ import org.apache.camel.component.splunk.event.SplunkEvent; import org.apache.camel.quarkus.test.support.splunk.SplunkConstants; import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jetbrains.annotations.NotNull; @Path("/splunk") @ApplicationScoped @@ -72,26 +73,37 @@ SplunkComponent splunk() { return component; } + @Path("/ssl/results/{name}") + @POST + public String resultsSsl(@PathParam("name") String mapName) { + return results(true, mapName); + } + @Path("/results/{name}") @POST - public String results(@PathParam("name") String mapName) throws Exception { + public String results(@PathParam("name") String mapName) { + return results(false, mapName); + } + + private String results(boolean ssl, String mapName) { String url; int count = 3; if ("savedSearch".equals(mapName)) { url = String.format( - "splunk://savedsearch?username=admin&password=changeit&scheme=http&host=%s&port=%d&delay=500&initEarliestTime=-10m&savedsearch=%s", - host, port, SAVED_SEARCH_NAME); + "%s://savedsearch?username=admin&password=changeit&scheme=%s&host=%s&port=%d&delay=500&initEarliestTime=-10m&savedsearch=%s", + getComponent(ssl), ssl ? "https&validateCertificates=false" : "http", host, port, SAVED_SEARCH_NAME); } else if ("normalSearch".equals(mapName)) { url = String.format( - "splunk://normal?username=admin&password=changeit&scheme=http&host=%s&port=%d&delay=5000&initEarliestTime=-10s&search=" + "%s://normal?username=admin&password=changeit&scheme=%s&host=%s&port=%d&delay=5000&initEarliestTime=-10s&search=" + "search sourcetype=\"SUBMIT\" | rex field=_raw \"Name: (?.*) From: (?.*)\"", - host, port); + getComponent(ssl), ssl ? "https&validateCertificates=false" : "http", host, port); } else { url = String.format( - "splunk://realtime?username=admin&password=changeit&scheme=http&host=%s&port=%d&delay=3000&initEarliestTime=rt-10s&latestTime=RAW(rt+40s)&search=" + "%s://realtime?username=admin&password=changeit&scheme=%s&host=%s&port=%d&delay=3000&initEarliestTime=rt-10s&latestTime=RAW(rt+40s)&search=" + "search sourcetype=\"STREAM\" | rex field=_raw \"Name: (?.*) From: (?.*)\"", - host, port, ProducerType.STREAM.name()); + getComponent(ssl), ssl ? "https&validateCertificates=false" : "http", host, port, + ProducerType.STREAM.name()); } List events = new LinkedList<>(); @@ -113,6 +125,21 @@ public String results(@PathParam("name") String mapName) throws Exception { return result.toString(); } + private static @NotNull String getComponent(boolean ssl) { + String component = ssl ? "splunk" : "splunk"; + return component; + } + + @Path("/ssl/write/{producerType}") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.TEXT_PLAIN) + public Response writeSsl(Map message, + @PathParam("producerType") String producerType, + @QueryParam("index") String index) throws URISyntaxException { + return write(true, message, producerType, index); + } + @Path("/write/{producerType}") @POST @Consumes(MediaType.APPLICATION_JSON) @@ -120,8 +147,15 @@ public String results(@PathParam("name") String mapName) throws Exception { public Response write(Map message, @PathParam("producerType") String producerType, @QueryParam("index") String index) throws URISyntaxException { + return write(false, message, producerType, index); + } + + private Response write(boolean ssl, Map message, + String producerType, + String index) throws URISyntaxException { + if (message.containsKey("_rawData")) { - return writeRaw(message.get("_rawData"), producerType, index); + return writeRaw(ssl, message.get("_rawData"), producerType, index); } SplunkEvent se = new SplunkEvent(); @@ -129,23 +163,33 @@ public Response write(Map message, se.addPair(e.getKey(), e.getValue()); } - return writeRaw(se, producerType, index); + return writeRaw(ssl, se, producerType, index); } - private Response writeRaw(Object message, + private Response writeRaw(boolean ssl, Object message, String producerType, String index) throws URISyntaxException { + String url; if (ProducerType.TCP == ProducerType.valueOf(producerType)) { url = String.format( - "splunk:%s?raw=%b&username=admin&password=changeit&scheme=http&host=%s&port=%d&index=%s&sourceType=%s&source=%s&tcpReceiverLocalPort=%d&tcpReceiverPort=%d", - producerType.toLowerCase(), !(message instanceof SplunkEvent), host, port, index, producerType, SOURCE, + "%s:%s?raw=%b&username=admin&password=changeit&scheme=%s&host=%s&port=%d&index=%s&sourceType=%s&source=%s&tcpReceiverLocalPort=%d&tcpReceiverPort=%d", + getComponent(ssl), producerType.toLowerCase(), !(message instanceof SplunkEvent), + ssl ? "https&validateCertificates=false" : "http", + host, port, index, + producerType, + SOURCE, SplunkConstants.TCP_PORT, tcpPort); } else { url = String.format( - "splunk:%s?raw=%b&scheme=http&host=%s&port=%d&index=%s&sourceType=%s&source=%s", - producerType.toLowerCase(), !(message instanceof SplunkEvent), host, port, index, producerType, SOURCE); + "%s:%s?raw=%b&username=admin&password=changeit&scheme=%s&host=%s&port=%d&index=%s&sourceType=%s&source=%s", + getComponent(ssl), producerType.toLowerCase(), !(message instanceof SplunkEvent), + ssl ? "https&validateCertificates=false" : "http", + host, + port, index, + producerType, + SOURCE); } final String response = producerTemplate.requestBody(url, message, String.class); return Response diff --git a/integration-tests/splunk/src/test/java/org/apache/camel/quarkus/component/splunk/it/SplunkTest.java b/integration-tests/splunk/src/test/java/org/apache/camel/quarkus/component/splunk/it/SplunkTest.java index 565b0e488bff..d14dc6af7a08 100644 --- a/integration-tests/splunk/src/test/java/org/apache/camel/quarkus/component/splunk/it/SplunkTest.java +++ b/integration-tests/splunk/src/test/java/org/apache/camel/quarkus/component/splunk/it/SplunkTest.java @@ -32,9 +32,11 @@ import org.apache.camel.component.splunk.ProducerType; import org.apache.camel.quarkus.test.support.splunk.SplunkConstants; import org.apache.camel.quarkus.test.support.splunk.SplunkTestResource; +import org.apache.http.NoHttpResponseException; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; import org.hamcrest.Matchers; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.testcontainers.shaded.org.awaitility.Awaitility; @@ -43,21 +45,24 @@ import static org.hamcrest.Matchers.is; @QuarkusTest -@QuarkusTestResource(SplunkTestResource.class) -class SplunkTest { +@QuarkusTestResource(value = SplunkTestResource.class) +public class SplunkTest { + + private final static int TIMEOUT_IN_SECONDS = 60; @Test - public void testNormalSearchWithSubmitWithRawData() { + public void testNormalSearchWithSubmitWithRawData() throws InterruptedException { String suffix = "_normalSearchOfSubmit"; + String restUrl = "/splunk/ssl/results/normalSearch"; write(suffix, ProducerType.SUBMIT, 0, true); - Awaitility.await().pollInterval(1000, TimeUnit.MILLISECONDS).atMost(60, TimeUnit.SECONDS).until( + Awaitility.await().pollInterval(1000, TimeUnit.MILLISECONDS).atMost(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS).until( () -> { String result = RestAssured.given() .contentType(ContentType.TEXT) - .post("/splunk/results/normalSearch") + .post(restUrl) .then() .statusCode(200) .extract().asString(); @@ -70,44 +75,13 @@ public void testNormalSearchWithSubmitWithRawData() { @Test public void testSavedSearchWithTcp() throws InterruptedException { - String suffix = "_SavedSearchOfTcp"; - //create saved search - Config config = ConfigProvider.getConfig(); - RestAssured.given() - .baseUri("http://" + config.getValue(SplunkConstants.PARAM_REMOTE_HOST, String.class)) - .port(config.getValue(SplunkConstants.PARAM_REMOTE_PORT, Integer.class)) - .contentType(ContentType.JSON) - .param("name", SplunkResource.SAVED_SEARCH_NAME) - .param("disabled", "0") - .param("description", "descriptionText") - .param("search", - "sourcetype=\"TCP\" | rex field=_raw \"Name: (?.*) From: (?.*)\"") - .post("/services/saved/searches") - .then() - .statusCode(anyOf(is(201), is(409))); - - //write data via tcp - write(suffix, ProducerType.TCP, 0, false); - - //there might by delay in receiving the data - Awaitility.await().pollInterval(1000, TimeUnit.MILLISECONDS).atMost(60, TimeUnit.SECONDS).until( - () -> { - String result = RestAssured.given() - .contentType(ContentType.TEXT) - .post("/splunk/results/savedSearch") - .then() - .statusCode(200) - .extract().asString(); - - return result.contains("Name: Sheldon" + suffix) - && result.contains("Name: Leonard" + suffix) - && result.contains("Name: Irma" + suffix); - }); + testSavedSearchWithTcp(true); } @Test public void testStreamForRealtime() throws InterruptedException, ExecutionException { String suffix = "_RealtimeSearchOfStream"; + String restUrl = "/splunk/ssl/results/realtimeSearch"; //there is a buffer for stream writing, therefore about 1MB of data has to be written into Splunk //data are written in separated thread @@ -122,12 +96,12 @@ public void testStreamForRealtime() throws InterruptedException, ExecutionExcept }); try { - Awaitility.await().pollInterval(1000, TimeUnit.MILLISECONDS).atMost(60, TimeUnit.SECONDS).until( + Awaitility.await().pollInterval(1000, TimeUnit.MILLISECONDS).atMost(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS).until( () -> { String result = RestAssured.given() .contentType(ContentType.TEXT) - .post("/splunk/results/realtimeSearch") + .post(restUrl) .then() .statusCode(200) .extract().asString(); @@ -141,12 +115,59 @@ public void testStreamForRealtime() throws InterruptedException, ExecutionExcept } } + @Test + public void testSavedSearchWithTcpNoSSL() { + Assertions.assertThrowsExactly(NoHttpResponseException.class, + () -> testSavedSearchWithTcp(false)); + } + + void testSavedSearchWithTcp(boolean ssl) throws InterruptedException { + String suffix = "_SavedSearchOfTcp"; + String urlPrefix = ssl ? "https://" : "http://"; + String restUrl = ssl ? "/splunk/ssl/results/savedSearch" : "/splunk/results/savedSearch"; + //create saved search + Config config = ConfigProvider.getConfig(); + RestAssured.given() + .relaxedHTTPSValidation() + .baseUri(urlPrefix + config.getValue(SplunkConstants.PARAM_REMOTE_HOST, String.class)) + .port(config.getValue(SplunkConstants.PARAM_REMOTE_PORT, + Integer.class)) + .contentType(ContentType.JSON) + .param("name", SplunkResource.SAVED_SEARCH_NAME) + .param("disabled", "0") + .param("description", "descriptionText") + .param("search", + "sourcetype=\"TCP\" | rex field=_raw \"Name: (?.*) From: (?.*)\"") + .post("/services/saved/searches") + .then() + .statusCode(anyOf(is(201), is(409))); + + //write data via tcp + write(suffix, ProducerType.TCP, 0, false); + + //there might by delay in receiving the data + Awaitility.await().pollInterval(1000, TimeUnit.MILLISECONDS).atMost(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS).until( + () -> { + String result = RestAssured.given() + .contentType(ContentType.TEXT) + .post(restUrl) + .then() + .statusCode(200) + .extract().asString(); + + return result.contains("Name: Sheldon" + suffix) + && result.contains("Name: Leonard" + suffix) + && result.contains("Name: Irma" + suffix); + }); + } + private void write(String suffix, ProducerType producerType, int lengthOfRandomString, boolean raw) { + String restUrl = "/splunk/ssl/write/"; Consumer> write = data -> RestAssured.given() .contentType(ContentType.JSON) .queryParam("index", SplunkTestResource.TEST_INDEX) .body(data) - .post("/splunk/write/" + producerType.name()) + .post(restUrl + producerType.name()) .then() .statusCode(201) .body(Matchers.containsString(expectedResult(data))); diff --git a/integration-tests/splunk/src/test/resources/local_inputs.conf b/integration-tests/splunk/src/test/resources/local_inputs.conf new file mode 100644 index 000000000000..03ce3b62fe21 --- /dev/null +++ b/integration-tests/splunk/src/test/resources/local_inputs.conf @@ -0,0 +1,6 @@ +[default] +host = localhost + +[splunktcp-ssl:9997] + +disabled=0 \ No newline at end of file diff --git a/integration-tests/splunk/src/test/resources/local_server.conf b/integration-tests/splunk/src/test/resources/local_server.conf new file mode 100644 index 000000000000..ed21133c7a0e --- /dev/null +++ b/integration-tests/splunk/src/test/resources/local_server.conf @@ -0,0 +1,25 @@ +[general] +serverName = b66b768099db +pass4SymmKey = $7$oeVDSaCZOSOMIVB0Yv2lBqldGvLt0pXWuQdDV7YvZ0d6Kwf/f0RwhQ== +allowRemoteLogin = always + +[lmpool:auto_generated_pool_download-trial] +description = auto_generated_pool_download-trial +peers = * +quota = MAX +stack_id = download-trial + +[lmpool:auto_generated_pool_forwarder] +description = auto_generated_pool_forwarder +peers = * +quota = MAX +stack_id = forwarder + +[lmpool:auto_generated_pool_free] +description = auto_generated_pool_free +peers = * +quota = MAX +stack_id = free + +[license] +active_group = Free