Skip to content

Commit

Permalink
fixes #4127 - splunk, splunk-hec SSL tests (#6314) (#6650)
Browse files Browse the repository at this point in the history
  • Loading branch information
JiriOndrusek authored Oct 17, 2024
1 parent 194cc3d commit f4981d1
Show file tree
Hide file tree
Showing 14 changed files with 667 additions and 81 deletions.
16 changes: 16 additions & 0 deletions integration-tests-support/splunk/README.adoc
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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<String, String> start() {

String banner = StringUtils.repeat("*", 50);

Map<String, String> 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() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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<String, String> initArgs) {
localhostCertPath = initArgs.get("localhost_cert");
caCertPath = initArgs.get("ca_cert");
localhostKeystorePath = initArgs.get("localhost_keystore");
keystorePassword = initArgs.get("keystore_password");
}

@Override
public Map<String, String> start() {

String banner = StringUtils.repeat("*", 50);

try {
container = new GenericContainer<>(SPLUNK_IMAGE_NAME)
.withExposedPorts(REMOTE_PORT, SplunkConstants.TCP_PORT, WEB_PORT, HEC_PORT)
Expand All @@ -54,41 +90,137 @@ public Map<String, String> 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<String, String> 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 {
Expand Down
Loading

0 comments on commit f4981d1

Please sign in to comment.