From 3de32b4789320bcecda6b5f665cc4ec1b5471823 Mon Sep 17 00:00:00 2001 From: Samuel Vila Date: Mon, 12 Oct 2026 12:24:42 +0200 Subject: [PATCH] Respect user-defined security protocol for additional Kafka listeners `KafkaHelper` always appended `TC-:PLAINTEXT` to `KAFKA_LISTENER_SECURITY_PROTOCOL_MAP` for listeners registered via `withListener(...)`, even when the user had already mapped that listener (e.g. `TC-0:SASL_PLAINTEXT`). Kafka then used `PLAINTEXT`, so additional listeners could not be secured. The default mapping is now only added when no mapping exists for the listener, matched case-insensitively as Kafka normalises listener names. Fixes #10035 --- .../org/testcontainers/kafka/KafkaHelper.java | 9 +++++-- .../kafka/ConfluentKafkaContainerTest.java | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaHelper.java b/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaHelper.java index 61e790d474f..d6bf2305cf7 100644 --- a/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaHelper.java +++ b/modules/kafka/src/main/java/org/testcontainers/kafka/KafkaHelper.java @@ -8,6 +8,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Supplier; @@ -78,9 +79,13 @@ static void resolveListeners(GenericContainer kafkaContainer, Set lis String listenerHost = listener.split(":")[0]; String listenerPort = listener.split(":")[1]; String listenerProtocol = String.format("%s://%s:%s", protocol, listenerHost, listenerPort); - String protocolMap = String.format("%s:PLAINTEXT", protocol); listeners.add(listenerProtocol); - listenerSecurityProtocolMap.add(protocolMap); + boolean securityProtocolConfigured = listenerSecurityProtocolMap + .stream() + .anyMatch(entry -> entry.trim().toUpperCase(Locale.ROOT).startsWith(protocol + ":")); + if (!securityProtocolConfigured) { + listenerSecurityProtocolMap.add(String.format("%s:PLAINTEXT", protocol)); + } String host = listener.split(":")[0]; kafkaContainer.withNetworkAliases(host); diff --git a/modules/kafka/src/test/java/org/testcontainers/kafka/ConfluentKafkaContainerTest.java b/modules/kafka/src/test/java/org/testcontainers/kafka/ConfluentKafkaContainerTest.java index 1c95bf95e02..07579b220ea 100644 --- a/modules/kafka/src/test/java/org/testcontainers/kafka/ConfluentKafkaContainerTest.java +++ b/modules/kafka/src/test/java/org/testcontainers/kafka/ConfluentKafkaContainerTest.java @@ -66,6 +66,30 @@ void testUsageWithListenerFromProxy() throws Exception { } } + @SneakyThrows + @Test + void shouldKeepSecurityProtocolConfiguredForListener() { + try ( + Network network = Network.newNetwork(); + SocatContainer socat = new SocatContainer().withNetwork(network).withTarget(2000, "kafka", 19092); + ConfluentKafkaContainer kafka = new ConfluentKafkaContainer("confluentinc/cp-kafka:7.7.0") + .withListener("kafka:19092", () -> socat.getHost() + ":" + socat.getMappedPort(2000)) + .withNetwork(network) + .withEnv( + "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP", + "BROKER:PLAINTEXT,PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT,TC-0:SASL_PLAINTEXT" + ) + .withEnv("KAFKA_LISTENER_NAME_TC-0_SASL_ENABLED_MECHANISMS", "PLAIN") + .withEnv("KAFKA_LISTENER_NAME_TC-0_PLAIN_SASL_JAAS_CONFIG", getJaasConfig()) + ) { + socat.start(); + kafka.start(); + + String bootstrapServers = String.format("%s:%s", socat.getHost(), socat.getMappedPort(2000)); + testSecurePlainKafkaFunctionality(bootstrapServers); + } + } + @SneakyThrows @Test void shouldConfigureAuthenticationWithSaslUsingJaas() {