From 9593c7e6c39c8395cf684bdc26232beb11685dbf Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Wed, 21 Feb 2024 09:01:08 +0100 Subject: [PATCH] Extend Kafka integration tests to verify JKS and PEM handling --- bom/application/pom.xml | 5 -- build-parent/pom.xml | 7 +++ integration-tests/kafka-ssl/pom.xml | 5 ++ .../it/kafka/ssl/CertificateFormat.java | 7 +++ .../it/kafka/ssl/SslKafkaEndpoint.java | 30 ++++++----- .../it/kafka/KafkaSSLTestResource.java | 6 +-- .../it/kafka/SslKafkaConsumerTest.java | 49 ++++++++++++++---- .../kafka-ssl/src/test/resources/README.md | 23 -------- .../src/test/resources/kafka-keystore.p12 | Bin 2451 -> 0 bytes .../src/test/resources/kafka-truststore.p12 | Bin 1010 -> 0 bytes 10 files changed, 77 insertions(+), 55 deletions(-) create mode 100644 integration-tests/kafka-ssl/src/main/java/io/quarkus/it/kafka/ssl/CertificateFormat.java delete mode 100644 integration-tests/kafka-ssl/src/test/resources/README.md delete mode 100644 integration-tests/kafka-ssl/src/test/resources/kafka-keystore.p12 delete mode 100644 integration-tests/kafka-ssl/src/test/resources/kafka-truststore.p12 diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 8ee8609c13092..2ea68c8b6b08b 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -3593,11 +3593,6 @@ hamcrest ${hamcrest.version} - - me.escoffier.certs - certificate-generator-junit5 - 0.3.0 - org.antlr diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 12ef10026f472..f35d317404593 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -371,6 +371,13 @@ + + me.escoffier.certs + certificate-generator-junit5 + 0.4.0 + test + + diff --git a/integration-tests/kafka-ssl/pom.xml b/integration-tests/kafka-ssl/pom.xml index fed5b581d11ba..0b912f72f02fc 100644 --- a/integration-tests/kafka-ssl/pom.xml +++ b/integration-tests/kafka-ssl/pom.xml @@ -83,6 +83,11 @@ testcontainers test + + me.escoffier.certs + certificate-generator-junit5 + test + diff --git a/integration-tests/kafka-ssl/src/main/java/io/quarkus/it/kafka/ssl/CertificateFormat.java b/integration-tests/kafka-ssl/src/main/java/io/quarkus/it/kafka/ssl/CertificateFormat.java new file mode 100644 index 0000000000000..77102121ce606 --- /dev/null +++ b/integration-tests/kafka-ssl/src/main/java/io/quarkus/it/kafka/ssl/CertificateFormat.java @@ -0,0 +1,7 @@ +package io.quarkus.it.kafka.ssl; + +public enum CertificateFormat { + PKCS12, + JKS, + PEM +} diff --git a/integration-tests/kafka-ssl/src/main/java/io/quarkus/it/kafka/ssl/SslKafkaEndpoint.java b/integration-tests/kafka-ssl/src/main/java/io/quarkus/it/kafka/ssl/SslKafkaEndpoint.java index 773fd8681bb51..ab872e9ee0ac0 100644 --- a/integration-tests/kafka-ssl/src/main/java/io/quarkus/it/kafka/ssl/SslKafkaEndpoint.java +++ b/integration-tests/kafka-ssl/src/main/java/io/quarkus/it/kafka/ssl/SslKafkaEndpoint.java @@ -5,10 +5,10 @@ import java.util.Collections; import java.util.Properties; -import jakarta.annotation.PostConstruct; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; import org.apache.kafka.clients.CommonClientConfigs; import org.apache.kafka.clients.consumer.Consumer; @@ -26,26 +26,21 @@ @Path("/ssl") public class SslKafkaEndpoint { - private Consumer consumer; - @Inject Config config; - @PostConstruct - public void create() { - consumer = createConsumer(); - } - @GET - public String get() { + public String get(@QueryParam("format") CertificateFormat format) { + Consumer consumer = createConsumer(format); final ConsumerRecords records = consumer.poll(Duration.ofMillis(60000)); if (records.isEmpty()) { return null; } + consumer.close(); return records.iterator().next().value(); } - public KafkaConsumer createConsumer() { + public KafkaConsumer createConsumer(CertificateFormat format) { Properties props = new Properties(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, config.getValue("kafka.bootstrap.servers", String.class)); props.put(ConsumerConfig.GROUP_ID_CONFIG, "test-consumer"); @@ -53,11 +48,20 @@ public KafkaConsumer createConsumer() { props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true"); props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); - File tsFile = new File(config.getValue("ssl-dir", String.class), "kafka-truststore.p12"); + + String truststore = switch (format) { + case PKCS12 -> "kafka-truststore.p12"; + case JKS -> "kafka-truststore.jks"; + case PEM -> "kafka-ca.crt"; + }; + + File tsFile = new File(config.getValue("ssl-dir", String.class), truststore); props.setProperty(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SSL"); props.setProperty(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, tsFile.getPath()); - props.setProperty(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "Z_pkTh9xgZovK4t34cGB2o6afT4zZg0L"); - props.setProperty(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, "PKCS12"); + if (format != CertificateFormat.PEM) { + props.setProperty(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "Z_pkTh9xgZovK4t34cGB2o6afT4zZg0L"); + } + props.setProperty(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, format.name()); props.setProperty(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, ""); KafkaConsumer consumer = new KafkaConsumer<>(props); diff --git a/integration-tests/kafka-ssl/src/test/java/io/quarkus/it/kafka/KafkaSSLTestResource.java b/integration-tests/kafka-ssl/src/test/java/io/quarkus/it/kafka/KafkaSSLTestResource.java index ad140e14fce63..429c927722cc2 100644 --- a/integration-tests/kafka-ssl/src/test/java/io/quarkus/it/kafka/KafkaSSLTestResource.java +++ b/integration-tests/kafka-ssl/src/test/java/io/quarkus/it/kafka/KafkaSSLTestResource.java @@ -16,9 +16,9 @@ public class KafkaSSLTestResource implements QuarkusTestResourceLifecycleManager private final StrimziKafkaContainer kafka = new StrimziKafkaContainer() .withBootstrapServers(c -> String.format("SSL://%s:%s", c.getHost(), c.getMappedPort(KAFKA_PORT))) .withServerProperties(MountableFile.forClasspathResource("server.properties")) - .withCopyFileToContainer(MountableFile.forClasspathResource("kafka-keystore.p12"), + .withCopyFileToContainer(MountableFile.forHostPath("target/certs/kafka-keystore.p12"), "/opt/kafka/config/kafka-keystore.p12") - .withCopyFileToContainer(MountableFile.forClasspathResource("kafka-truststore.p12"), + .withCopyFileToContainer(MountableFile.forHostPath("target/certs/kafka-truststore.p12"), "/opt/kafka/config/kafka-truststore.p12"); @Override @@ -29,7 +29,7 @@ public Map start() { // Used by the application Map properties = new HashMap<>(); properties.put("kafka.bootstrap.servers", kafka.getBootstrapServers()); - properties.put("ssl-dir", new File("src/test/resources").getAbsolutePath()); + properties.put("ssl-dir", new File("target/certs").getAbsolutePath()); return properties; } diff --git a/integration-tests/kafka-ssl/src/test/java/io/quarkus/it/kafka/SslKafkaConsumerTest.java b/integration-tests/kafka-ssl/src/test/java/io/quarkus/it/kafka/SslKafkaConsumerTest.java index 443fd19106c11..aa33c97a8b676 100644 --- a/integration-tests/kafka-ssl/src/test/java/io/quarkus/it/kafka/SslKafkaConsumerTest.java +++ b/integration-tests/kafka-ssl/src/test/java/io/quarkus/it/kafka/SslKafkaConsumerTest.java @@ -12,38 +12,65 @@ import org.apache.kafka.common.serialization.IntegerSerializer; import org.apache.kafka.common.serialization.StringSerializer; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import io.quarkus.it.kafka.ssl.CertificateFormat; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; +import me.escoffier.certs.Format; +import me.escoffier.certs.junit5.Certificate; +import me.escoffier.certs.junit5.Certificates; +@Certificates(certificates = { + @Certificate(name = "kafka", formats = { Format.PKCS12, Format.JKS, + Format.PEM }, alias = "kafka-test-store", password = "Z_pkTh9xgZovK4t34cGB2o6afT4zZg0L") +}, baseDir = "target/certs") @QuarkusTest @QuarkusTestResource(KafkaSSLTestResource.class) public class SslKafkaConsumerTest { - public static Producer createProducer() { + public static Producer createProducer(CertificateFormat format) { Properties props = new Properties(); props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, System.getProperty("bootstrap.servers")); props.put(ProducerConfig.CLIENT_ID_CONFIG, "test-ssl-producer"); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class.getName()); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); - File tsFile = new File("src/test/resources/kafka-truststore.p12"); + + String truststore = switch (format) { + case PKCS12 -> "kafka-truststore.p12"; + case JKS -> "kafka-truststore.jks"; + case PEM -> "kafka-ca.crt"; + }; + + File tsFile = new File("target/certs/" + truststore); props.setProperty(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SSL"); props.setProperty(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, tsFile.getPath()); - props.setProperty(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "Z_pkTh9xgZovK4t34cGB2o6afT4zZg0L"); - props.setProperty(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, "PKCS12"); + if (format != CertificateFormat.PEM) { + props.setProperty(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "Z_pkTh9xgZovK4t34cGB2o6afT4zZg0L"); + } + props.setProperty(SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG, format.name()); props.setProperty(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, ""); return new KafkaProducer<>(props); } - @Test - public void testReception() { - Producer consumer = createProducer(); - consumer.send(new ProducerRecord<>("test-ssl-consumer", 1, "hi world")); - String string = RestAssured.when().get("/ssl").andReturn().asString(); - Assertions.assertEquals("hi world", string); + @ParameterizedTest + @CsvSource({ + "PKCS12", + "JKS", + "PEM" + }) + public void testReception(String format) { + try (Producer producer = createProducer(CertificateFormat.valueOf(format))) { + producer.send(new ProducerRecord<>("test-ssl-consumer", 1, "hi world")); + String string = RestAssured + .given().queryParam("format", format) + .when().get("/ssl") + .andReturn().asString(); + Assertions.assertEquals("hi world", string); + } } } diff --git a/integration-tests/kafka-ssl/src/test/resources/README.md b/integration-tests/kafka-ssl/src/test/resources/README.md deleted file mode 100644 index ab3ac1cbc7e1c..0000000000000 --- a/integration-tests/kafka-ssl/src/test/resources/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# How to generate the key and trust stores - -Run: - -```shell script -cd src/test/resources -rm -Rf *.p12 -export SECRET=Z_pkTh9xgZovK4t34cGB2o6afT4zZg0L -export JKS_FILE=kafka-keystore.jks -export JKS_TRUST_FILE=kafka-truststore.jks -export CERT_FILE=localhost.crt -export PKCS_FILE=kafka-keystore.p12 -export PKCS_TRUST_FILE=kafka-truststore.p12 -export PEM_FILE_CERT=kafka-cert.pem -export PEM_FILE_KEY=kafka-key.pem -keytool -genkey -alias kafka-test-store -keyalg RSA -keystore ${JKS_FILE} -keysize 2048 -validity 1095 -dname CN=localhost -keypass ${SECRET} -storepass ${SECRET} -keytool -export -alias kafka-test-store -file ${CERT_FILE} -keystore ${JKS_FILE} -keypass ${SECRET} -storepass ${SECRET} -keytool -importkeystore -srckeystore ${JKS_FILE} -srcstorepass ${SECRET} -destkeystore ${PKCS_FILE} -deststoretype PKCS12 -deststorepass ${SECRET} -keytool -keystore ${JKS_TRUST_FILE} -import -file ${CERT_FILE} -keypass ${SECRET} -storepass ${SECRET} -noprompt -keytool -importkeystore -srckeystore ${JKS_TRUST_FILE} -srcstorepass ${SECRET} -destkeystore ${PKCS_TRUST_FILE} -deststoretype PKCS12 -deststorepass ${SECRET} -rm -Rf localhost.crt *.jks -cd ../../.. || exit -1 -``` \ No newline at end of file diff --git a/integration-tests/kafka-ssl/src/test/resources/kafka-keystore.p12 b/integration-tests/kafka-ssl/src/test/resources/kafka-keystore.p12 deleted file mode 100644 index 04b1592caeb08f333d4c094f0ed3361234ba399a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2451 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