This is an automated email from the ASF dual-hosted git repository.

chia7712 pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/kafka.git


The following commit(s) were added to refs/heads/trunk by this push:
     new fdc59be8c84 KAFKA-18448 SSL support for ClusterTestExtensions (#22184)
fdc59be8c84 is described below

commit fdc59be8c84af1424b2f51a2df81386927f01a90
Author: Igor Soarez <[email protected]>
AuthorDate: Sun Jul 19 08:22:34 2026 +0100

    KAFKA-18448 SSL support for ClusterTestExtensions (#22184)
    
    Mixed protocols - SSL only between brokers and clients, not between
    brokers and the quorum or vice versa is not yet supported.
    
    Reviewers: Chia-Ping Tsai <[email protected]>, Ken Huang
     <[email protected]>
---
 .../kafka/common/test/KafkaClusterTestKit.java     |  2 ++
 .../org/apache/kafka/common/test/TestKitNodes.java | 17 ++++++---
 .../test/junit/RaftClusterInvocationContext.java   |  6 ++++
 .../apache/kafka/common/test/TestKitNodeTest.java  | 23 +++++++-----
 .../test/junit/ClusterTestExtensionsTest.java      | 41 ++++++++++++++++++++++
 5 files changed, 77 insertions(+), 12 deletions(-)

diff --git 
a/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/KafkaClusterTestKit.java
 
b/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/KafkaClusterTestKit.java
index 747818a9864..eec40e2bb82 100644
--- 
a/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/KafkaClusterTestKit.java
+++ 
b/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/KafkaClusterTestKit.java
@@ -251,6 +251,8 @@ public class KafkaClusterTestKit implements AutoCloseable {
                 
props.putIfAbsent(StandardAuthorizer.ALLOW_EVERYONE_IF_NO_ACL_IS_FOUND_CONFIG, 
"false");
                 props.putIfAbsent(StandardAuthorizer.SUPER_USERS_CONFIG, 
"User:" + JaasUtils.KAFKA_PLAIN_ADMIN);
                 sslConfig.forEach(props::putIfAbsent);
+            } else if (securityProtocol.equals(SecurityProtocol.SSL.name)) {
+                sslConfig.forEach(props::putIfAbsent);
             }
         }
 
diff --git 
a/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/TestKitNodes.java
 
b/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/TestKitNodes.java
index b1c7fc00e88..573ff79ef92 100644
--- 
a/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/TestKitNodes.java
+++ 
b/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/TestKitNodes.java
@@ -162,10 +162,19 @@ public class TestKitNodes {
             if (numDisksPerBroker <= 0) {
                 throw new IllegalArgumentException("Invalid value for 
numDisksPerBroker");
             }
-            // TODO: remove this assertion after 
https://issues.apache.org/jira/browse/KAFKA-16680 is finished
-            if ((brokerSecurityProtocol != SecurityProtocol.PLAINTEXT && 
brokerSecurityProtocol != SecurityProtocol.SASL_PLAINTEXT && 
brokerSecurityProtocol != SecurityProtocol.SASL_SSL) ||
-                (controllerSecurityProtocol != SecurityProtocol.PLAINTEXT && 
controllerSecurityProtocol != SecurityProtocol.SASL_PLAINTEXT && 
brokerSecurityProtocol != SecurityProtocol.SASL_SSL)) {
-                throw new IllegalArgumentException("Currently only support 
PLAINTEXT / SASL_PLAINTEXT / SASL_SSL security protocol");
+            var supportedProtocols = List.of(
+                    SecurityProtocol.PLAINTEXT,
+                    SecurityProtocol.SASL_PLAINTEXT,
+                    SecurityProtocol.SASL_SSL,
+                    SecurityProtocol.SSL
+            );
+            if (!supportedProtocols.contains(brokerSecurityProtocol) || 
!supportedProtocols.contains(controllerSecurityProtocol)) {
+                throw new IllegalArgumentException(String.format("Currently 
only support %s security protocol",
+                        supportedProtocols.stream().map(sp -> 
sp.name).collect(Collectors.joining(" / ")))
+                );
+            }
+            if ((brokerSecurityProtocol == SecurityProtocol.SSL) != 
(controllerSecurityProtocol == SecurityProtocol.SSL)) {
+                throw new IllegalArgumentException("Mixed broker and 
controller SSL security protocol configurations are not yet supported");
             }
             if (baseDirectory == null) {
                 this.baseDirectory = TestUtils.tempDirectory().toPath();
diff --git 
a/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/junit/RaftClusterInvocationContext.java
 
b/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/junit/RaftClusterInvocationContext.java
index b5d93066232..699adaa3ec7 100644
--- 
a/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/junit/RaftClusterInvocationContext.java
+++ 
b/test-common/test-common-runtime/src/main/java/org/apache/kafka/common/test/junit/RaftClusterInvocationContext.java
@@ -199,6 +199,12 @@ public class RaftClusterInvocationContext implements 
TestTemplateInvocationConte
                     
props.putAll(clusterTestKit.sslManager().createClientSslConfig());
                     
props.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
                 }
+            } else if (config().brokerSecurityProtocol() == 
SecurityProtocol.SSL) {
+                
props.putIfAbsent(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, 
SecurityProtocol.SSL.name);
+                if (clusterTestKit.sslManager() != null) {
+                    
props.putAll(clusterTestKit.sslManager().createClientSslConfig());
+                    
props.put(SslConfigs.SSL_ENDPOINT_IDENTIFICATION_ALGORITHM_CONFIG, "");
+                }
             }
             return props;
         }
diff --git 
a/test-common/test-common-runtime/src/test/java/org/apache/kafka/common/test/TestKitNodeTest.java
 
b/test-common/test-common-runtime/src/test/java/org/apache/kafka/common/test/TestKitNodeTest.java
index 4a9dcaf9e5e..adbb5871a46 100644
--- 
a/test-common/test-common-runtime/src/test/java/org/apache/kafka/common/test/TestKitNodeTest.java
+++ 
b/test-common/test-common-runtime/src/test/java/org/apache/kafka/common/test/TestKitNodeTest.java
@@ -32,14 +32,21 @@ public class TestKitNodeTest {
     @ParameterizedTest
     @EnumSource(SecurityProtocol.class)
     public void testSecurityProtocol(SecurityProtocol securityProtocol) {
-        if (securityProtocol != SecurityProtocol.PLAINTEXT && securityProtocol 
!= SecurityProtocol.SASL_PLAINTEXT && securityProtocol != 
SecurityProtocol.SASL_SSL) {
-            assertEquals("Currently only support PLAINTEXT / SASL_PLAINTEXT / 
SASL_SSL security protocol",
-                assertThrows(IllegalArgumentException.class,
-                    () -> new 
TestKitNodes.Builder().setBrokerSecurityProtocol(securityProtocol).build()).getMessage());
-            assertEquals("Currently only support PLAINTEXT / SASL_PLAINTEXT / 
SASL_SSL security protocol",
-                assertThrows(IllegalArgumentException.class,
-                    () -> new 
TestKitNodes.Builder().setControllerSecurityProtocol(securityProtocol).build()).getMessage());
-        }
+        // All security protocols are supported when broker and controller 
configurations are consistent
+        new TestKitNodes.Builder()
+            .setBrokerSecurityProtocol(securityProtocol)
+            .setControllerSecurityProtocol(securityProtocol)
+            .build();
+    }
+
+    @Test
+    public void testMixedSslSecurityProtocolIsNotSupported() {
+        assertEquals("Mixed broker and controller SSL security protocol 
configurations are not yet supported",
+            assertThrows(IllegalArgumentException.class,
+                () -> new 
TestKitNodes.Builder().setBrokerSecurityProtocol(SecurityProtocol.SSL).build()).getMessage());
+        assertEquals("Mixed broker and controller SSL security protocol 
configurations are not yet supported",
+            assertThrows(IllegalArgumentException.class,
+                () -> new 
TestKitNodes.Builder().setControllerSecurityProtocol(SecurityProtocol.SSL).build()).getMessage());
     }
 
     @Test
diff --git 
a/test-common/test-common-runtime/src/test/java/org/apache/kafka/common/test/junit/ClusterTestExtensionsTest.java
 
b/test-common/test-common-runtime/src/test/java/org/apache/kafka/common/test/junit/ClusterTestExtensionsTest.java
index e2862938f82..e6fa0d5d8c4 100644
--- 
a/test-common/test-common-runtime/src/test/java/org/apache/kafka/common/test/junit/ClusterTestExtensionsTest.java
+++ 
b/test-common/test-common-runtime/src/test/java/org/apache/kafka/common/test/junit/ClusterTestExtensionsTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.kafka.common.test.junit;
 
+import kafka.network.SocketServer;
 import kafka.server.ControllerServer;
 import kafka.server.KafkaBroker;
 
@@ -68,6 +69,7 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -77,6 +79,8 @@ import java.util.Set;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 import scala.jdk.javaapi.CollectionConverters;
 
@@ -595,4 +599,41 @@ public class ClusterTestExtensionsTest {
             assertInstanceOf(SaslAuthenticationException.class, 
exception.getCause());
         }
     }
+
+    @ClusterTest(
+        types = {Type.KRAFT, Type.CO_KRAFT},
+        brokerSecurityProtocol = SecurityProtocol.SSL,
+        controllerSecurityProtocol = SecurityProtocol.SSL,
+        serverProperties = {
+            @ClusterConfigProperty(key = 
GroupCoordinatorConfig.OFFSETS_TOPIC_PARTITIONS_CONFIG, value = "1"),
+            @ClusterConfigProperty(key = 
GroupCoordinatorConfig.OFFSETS_TOPIC_REPLICATION_FACTOR_CONFIG, value = "1")
+        }
+    )
+    public void testSsl(ClusterInstance clusterInstance) throws 
InterruptedException, ExecutionException {
+        KafkaBroker broker = 
clusterInstance.brokers().values().iterator().next();
+        ControllerServer controller = 
clusterInstance.controllers().values().iterator().next();
+        Function<SocketServer, String> endpoints = socketServer -> 
Collections.list(socketServer.dataPlaneAcceptors().keys())
+                .stream().map(endpoint -> String.format("%s:%s", 
endpoint.listener(), 
endpoint.securityProtocol())).collect(Collectors.joining(","));
+        assertEquals("EXTERNAL:SSL", endpoints.apply(broker.socketServer()));
+        assertEquals("CONTROLLER:SSL", 
endpoints.apply(controller.socketServer()));
+
+        String topic = "ssl-topic";
+        clusterInstance.createTopic(topic, 1, (short) 1);
+        try (Admin admin = clusterInstance.admin()) {
+            Set<String> topics = admin.listTopics().names().get();
+            assertTrue(topics.contains(topic), String.format("%s not included 
in %s", topic, topics));
+        }
+
+        try (Producer<byte[], byte[]> producer = clusterInstance.producer()) {
+            producer.send(new ProducerRecord<>(topic, Utils.utf8("key"), 
Utils.utf8("value"))).get();
+            producer.flush();
+        }
+        try (Consumer<byte[], byte[]> consumer = clusterInstance.consumer()) {
+            consumer.subscribe(List.of(topic));
+            RaftClusterInvocationContext.waitForCondition(() -> {
+                ConsumerRecords<byte[], byte[]> records = 
consumer.poll(Duration.ofMillis(100));
+                return records.count() == 1;
+            }, "Failed to receive message");
+        }
+    }
 }

Reply via email to