diff --git a/src/main/java/io/github/ktestify/config/KtestifyConfig.java b/src/main/java/io/github/ktestify/config/KtestifyConfig.java
index 81f30c8..955f89a 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,44 @@ 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:
+ *
+ *
+ * - {@code location} / {@code KTESTIFY_JVM_TRUSTSTORE_LOCATION}
+ *
- {@code password} / {@code KTESTIFY_JVM_TRUSTSTORE_PASSWORD}
+ *
- {@code type} / {@code KTESTIFY_JVM_TRUSTSTORE_TYPE} — {@code JKS} or {@code PKCS12}
+ *
+ *
+ * @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"));
+ }
+ }
}