From ef45f3088bab8f07bbe86c3686dc728036f5626d Mon Sep 17 00:00:00 2001 From: Nil MALHOMME Date: Sat, 4 Jul 2026 17:50:23 +0200 Subject: [PATCH 1/2] feat: add JVM wide truststore configuration and tests --- .../ktestify/config/KtestifyConfig.java | 40 +++++++ src/main/resources/reference.conf | 22 +++- .../ktestify/config/KtestifyConfigTest.java | 108 ++++++++++++++++++ 3 files changed, 169 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/github/ktestify/config/KtestifyConfig.java b/src/main/java/io/github/ktestify/config/KtestifyConfig.java index 81f30c8..5ebc202 100644 --- a/src/main/java/io/github/ktestify/config/KtestifyConfig.java +++ b/src/main/java/io/github/ktestify/config/KtestifyConfig.java @@ -64,6 +64,7 @@ private KtestifyConfig(Config config) { this.schemaRegistry = new SchemaRegistryConfig(config.getConfig("ktestify.schema-registry")); this.framework = new FrameworkConfig(config.getConfig("ktestify.framework")); applyLogLevels(config); + applyJvmTruststore(config); log.info("KtestifyConfig loaded successfully"); log.debug("Kafka bootstrap servers: {}", kafka.getBootstrapServers()); log.debug("Schema Registry URL: {}", schemaRegistry.getUrl()); @@ -105,6 +106,45 @@ private static void applyLogLevels(Config config) { lc.getString("confluent-level")); } + /** + * Applies the JVM-level truststore defined in {@code ktestify.jvm.truststore} HOCON section by setting + * the corresponding {@code javax.net.ssl.*} system properties. This truststore is shared by all plugins + * and underlying libraries that don't provide their own SSL configuration. + * + *

If {@code location} is blank, no system properties are set and the JVM's built-in cacerts are used. + * + *

Supported keys and their environment variable overrides: + * + *

+ * + * @param config the resolved Config object containing {@code ktestify.jvm.truststore} + */ + public static void applyJvmTruststore(Config config) { + String location = config.getString("ktestify.jvm.truststore.location"); + if (location == null || location.isBlank()) { + log.debug("No JVM truststore location configured; keeping JVM default cacerts"); + return; + } + + System.setProperty("javax.net.ssl.trustStore", location); + System.setProperty( + "javax.net.ssl.trustStoreType", config.getString("ktestify.jvm.truststore.type")); + + String password = config.getString("ktestify.jvm.truststore.password"); + if (password != null && !password.isBlank()) { + System.setProperty("javax.net.ssl.trustStorePassword", password); + } + + log.info( + "JVM truststore applied — location={} type={}", + location, + config.getString("ktestify.jvm.truststore.type")); + } + /** * Loads the default configuration using the standard HOCON loading mechanism. Configuration is loaded from: * diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 522e47d..5ef30e4 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -193,5 +193,25 @@ ktestify { confluent-level = "WARN" confluent-level = ${?KTESTIFY_CONFLUENT_LOG_LEVEL} } -} + # JVM Configuration + # ================= + # Settings applied to the JVM itself at KtestifyConfig load time. + jvm { + # Default JVM truststore (javax.net.ssl.trustStore system properties). + # Used by any HTTPS/TLS client that doesn't have its own SSL config, + # e.g. plain HTTP calls, OAuth endpoints, etc. + # Leave location blank to keep the JVM's built-in cacerts. + truststore { + location = "" + location = ${?KTESTIFY_JVM_TRUSTSTORE_LOCATION} + + password = "" + password = ${?KTESTIFY_JVM_TRUSTSTORE_PASSWORD} + + # JKS or PKCS12 + type = "JKS" + type = ${?KTESTIFY_JVM_TRUSTSTORE_TYPE} + } + } +} diff --git a/src/test/java/io/github/ktestify/config/KtestifyConfigTest.java b/src/test/java/io/github/ktestify/config/KtestifyConfigTest.java index fdef227..7a58dd9 100644 --- a/src/test/java/io/github/ktestify/config/KtestifyConfigTest.java +++ b/src/test/java/io/github/ktestify/config/KtestifyConfigTest.java @@ -693,4 +693,112 @@ void testFrameworkTimeoutHelpers() { assertEquals(100L, fw.getPollIntervalMillis()); assertEquals(5000L, fw.getBufferTimeMillis()); } + + // ========================================== + // JVM TRUSTSTORE TESTS + // ========================================== + + @Nested + @DisplayName("JVM Truststore") + class JvmTruststoreTests { + + @BeforeEach + void cleanSslProperties() { + System.clearProperty("javax.net.ssl.trustStore"); + System.clearProperty("javax.net.ssl.trustStoreType"); + System.clearProperty("javax.net.ssl.trustStorePassword"); + } + + @AfterEach + void restoreSslProperties() { + System.clearProperty("javax.net.ssl.trustStore"); + System.clearProperty("javax.net.ssl.trustStoreType"); + System.clearProperty("javax.net.ssl.trustStorePassword"); + } + + @Test + @DisplayName("Should not set truststore properties when location is blank") + void shouldNotSetTruststoreWhenLocationBlank() { + Config customConfig = ConfigFactory.parseString(""" + ktestify { + jvm { + truststore { + location = "" + password = "" + type = "JKS" + } + } + } + """); + + KtestifyConfig.load(customConfig); + + assertNull(System.getProperty("javax.net.ssl.trustStore")); + assertNull(System.getProperty("javax.net.ssl.trustStoreType")); + assertNull(System.getProperty("javax.net.ssl.trustStorePassword")); + } + + @Test + @DisplayName("Should set truststore location and type when location is provided") + void shouldSetTruststoreWhenLocationProvided() { + Config customConfig = ConfigFactory.parseString(""" + ktestify { + jvm { + truststore { + location = "/path/to/truststore.jks" + password = "" + type = "PKCS12" + } + } + } + """); + + KtestifyConfig.load(customConfig); + + assertEquals("/path/to/truststore.jks", System.getProperty("javax.net.ssl.trustStore")); + assertEquals("PKCS12", System.getProperty("javax.net.ssl.trustStoreType")); + assertNull(System.getProperty("javax.net.ssl.trustStorePassword")); + } + + @Test + @DisplayName("Should set truststore password when provided") + void shouldSetTruststorePasswordWhenProvided() { + Config customConfig = ConfigFactory.parseString(""" + ktestify { + jvm { + truststore { + location = "/path/to/truststore.jks" + password = "secret" + type = "JKS" + } + } + } + """); + + KtestifyConfig.load(customConfig); + + assertEquals("/path/to/truststore.jks", System.getProperty("javax.net.ssl.trustStore")); + assertEquals("JKS", System.getProperty("javax.net.ssl.trustStoreType")); + assertEquals("secret", System.getProperty("javax.net.ssl.trustStorePassword")); + } + + @Test + @DisplayName("Should default truststore type to JKS from reference.conf") + void shouldDefaultTruststoreTypeToJks() { + Config customConfig = ConfigFactory.parseString(""" + ktestify { + jvm { + truststore { + location = "/path/to/truststore.jks" + } + } + } + """); + + KtestifyConfig.load(customConfig); + + assertEquals("/path/to/truststore.jks", System.getProperty("javax.net.ssl.trustStore")); + assertEquals("JKS", System.getProperty("javax.net.ssl.trustStoreType")); + } + } } From 2b5990a84e0f1f25a21ef7739ce56264e8e957a4 Mon Sep 17 00:00:00 2001 From: Nil MALHOMME Date: Sat, 4 Jul 2026 17:55:30 +0200 Subject: [PATCH 2/2] spotless apply --- .../java/io/github/ktestify/config/KtestifyConfig.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/github/ktestify/config/KtestifyConfig.java b/src/main/java/io/github/ktestify/config/KtestifyConfig.java index 5ebc202..955f89a 100644 --- a/src/main/java/io/github/ktestify/config/KtestifyConfig.java +++ b/src/main/java/io/github/ktestify/config/KtestifyConfig.java @@ -107,9 +107,9 @@ private static void applyLogLevels(Config config) { } /** - * Applies the JVM-level truststore defined in {@code ktestify.jvm.truststore} HOCON section by setting - * the corresponding {@code javax.net.ssl.*} system properties. This truststore is shared by all plugins - * and underlying libraries that don't provide their own SSL configuration. + * Applies the JVM-level truststore defined in {@code ktestify.jvm.truststore} HOCON section by setting the + * corresponding {@code javax.net.ssl.*} system properties. This truststore is shared by all plugins and underlying + * libraries that don't provide their own SSL configuration. * *

If {@code location} is blank, no system properties are set and the JVM's built-in cacerts are used. * @@ -131,8 +131,7 @@ public static void applyJvmTruststore(Config config) { } System.setProperty("javax.net.ssl.trustStore", location); - System.setProperty( - "javax.net.ssl.trustStoreType", config.getString("ktestify.jvm.truststore.type")); + System.setProperty("javax.net.ssl.trustStoreType", config.getString("ktestify.jvm.truststore.type")); String password = config.getString("ktestify.jvm.truststore.password"); if (password != null && !password.isBlank()) {