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: + * + *

+ * + * @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")); + } + } }