From eed35de014489ef62698d828a74095e01ab1fc3f Mon Sep 17 00:00:00 2001 From: Roman Zabaluev Date: Fri, 30 Jul 2021 17:56:32 +0300 Subject: [PATCH] TLS enabled zookeeper (#722) * Switch zk client. Resolves #683 * Add an example docker compose file with TLS enabled zookeeper * Update readme a bit * Fix annoying sonar boy * Apply review suggestion * Rename zookeeper ssl options --- README.md | 2 + docker/kafka-ui-zookeeper-ssl.yml | 145 ++++++++++++++++++ kafka-ui-api/pom.xml | 6 +- .../ui/exception/ZooKeeperException.java | 9 ++ .../kafka/ui/service/KafkaService.java | 5 +- .../kafka/ui/service/ZookeeperService.java | 45 ++++-- pom.xml | 132 +++++++++------- 7 files changed, 274 insertions(+), 70 deletions(-) create mode 100644 docker/kafka-ui-zookeeper-ssl.yml create mode 100644 kafka-ui-api/src/main/java/com/provectus/kafka/ui/exception/ZooKeeperException.java diff --git a/README.md b/README.md index 11f4568b27..db4df22f28 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,8 @@ To be continued # Configuration +We have a plenty of docker-compose files as examples. Please check them out in ``docker`` directory. + ## Configuration File Example of how to configure clusters in the [application-local.yml](https://github.com/provectus/kafka-ui/blob/master/kafka-ui-api/src/main/resources/application-local.yml) configuration file: diff --git a/docker/kafka-ui-zookeeper-ssl.yml b/docker/kafka-ui-zookeeper-ssl.yml new file mode 100644 index 0000000000..0ae5381b64 --- /dev/null +++ b/docker/kafka-ui-zookeeper-ssl.yml @@ -0,0 +1,145 @@ +--- +version: '2' +services: + + kafka-ui: + container_name: kafka-ui + image: provectuslabs/kafka-ui:latest + ports: + - 8080:8080 + - 5005:5005 + volumes: + - /tmp/kafka/secrets/kafka.kafka1.keystore.jks:/etc/kafka/secrets/kafka.zookeeper.keystore.jks + - /tmp/kafka/secrets/kafka.zookeeper.truststore.jks:/etc/kafka/secrets/kafka.zookeeper.truststore.jks + depends_on: + - zookeeper0 + - kafka0 + - schemaregistry0 + - kafka-connect0 + environment: + KAFKA_CLUSTERS_0_NAME: local + KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka0:29092 + KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper0:2182 + KAFKA_CLUSTERS_0_JMXPORT: 9997 + KAFKA_CLUSTERS_0_SCHEMAREGISTRY: http://schemaregistry0:8085 + KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME: first + KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS: http://kafka-connect0:8083 + KAFKA_CLUSTERS_0_ZOOKEEPER_CLIENTCNXNSOCKET: org.apache.zookeeper.ClientCnxnSocketNetty + KAFKA_CLUSTERS_0_ZOOKEEPER_CLIENT_SECURE: 'true' + KAFKA_CLUSTERS_0_ZOOKEEPER_SSL_KEYSTORE_LOCATION: /etc/kafka/secrets/kafka.zookeeper.keystore.jks + KAFKA_CLUSTERS_0_ZOOKEEPER_SSL_KEYSTORE_PASSWORD: 12345678 + KAFKA_CLUSTERS_0_ZOOKEEPER_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/kafka.zookeeper.truststore.jks + KAFKA_CLUSTERS_0_ZOOKEEPER_SSL_TRUSTSTORE_PASSWORD: 12345678 + + zookeeper0: + image: confluentinc/cp-zookeeper:5.2.4 + volumes: + - /tmp/kafka/secrets/kafka.kafka1.keystore.jks:/etc/kafka/secrets/kafka.zookeeper.keystore.jks + - /tmp/kafka/secrets/kafka.zookeeper.truststore.jks:/etc/kafka/secrets/kafka.zookeeper.truststore.jks + environment: + ZOOKEEPER_CLIENT_PORT: 2182 + ZOOKEEPER_TICK_TIME: 2000 + + ZOOKEEPER_SECURE_CLIENT_PORT: 2182 + ZOOKEEPER_SERVER_CNXN_FACTORY: org.apache.zookeeper.server.NettyServerCnxnFactory + ZOOKEEPER_SSL_KEYSTORE_LOCATION: /etc/kafka/secrets/kafka.zookeeper.keystore.jks + ZOOKEEPER_SSL_KEYSTORE_PASSWORD: 12345678 + ZOOKEEPER_SSL_KEYSTORE_TYPE: PKCS12 + ZOOKEEPER_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/kafka.zookeeper.truststore.jks + ZOOKEEPER_SSL_TRUSTSTORE_PASSWORD: 12345678 + ZOOKEEPER_SSL_TRUSTSTORE_TYPE: JKS + # TLS 1.2 is the tested-default - TLS 1.3 has not been tested for production + # You can evaluate TLS 1.3 for ZooKeeper by uncommenting the following two properties + # and setting KAFKA_ZOOKEEPER_SSL_PROTOCOL on brokers + ZOOKEEPER_SSL_ENABLED_PROTOCOLS: TLSv1.3,TLSv1.2 + ZOOKEEPER_SSL_QUORUM_ENABLED_PROTOCOLS: TLSv1.3,TLSv1.2 + ZOOKEEPER_SSL_CIPHER_SUITES: TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + ZOOKEEPER_SSL_CLIENT_AUTH: need + ZOOKEEPER_AUTH_PROVIDER_X509: org.apache.zookeeper.server.auth.X509AuthenticationProvider + ZOOKEEPER_AUTH_PROVIDER_SASL: org.apache.zookeeper.server.auth.SASLAuthenticationProvider + ports: + - 2182:2182 + + kafka0: + image: confluentinc/cp-kafka:5.2.4 + depends_on: + - zookeeper0 + ports: + - 9092:9092 + - 9997:9997 + volumes: + - /tmp/kafka/secrets/kafka.kafka1.keystore.jks:/etc/kafka/secrets/kafka.kafka1.keystore.jks + - /tmp/kafka/secrets/kafka.server.truststore.jks:/etc/kafka/secrets/kafka.kafka1.truststore.jks + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper0:2182 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka0:29092,PLAINTEXT_HOST://localhost:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + JMX_PORT: 9997 + KAFKA_JMX_OPTS: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka0 -Dcom.sun.management.jmxremote.rmi.port=9997 + KAFKA_ZOOKEEPER_SSL_CLIENT_ENABLE: 'true' + KAFKA_ZOOKEEPER_SSL_CIPHER_SUITES: TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 + KAFKA_ZOOKEEPER_CLIENT_CNXN_SOCKET: org.apache.zookeeper.ClientCnxnSocketNetty + KAFKA_ZOOKEEPER_SSL_KEYSTORE_LOCATION: /etc/kafka/secrets/kafka.kafka1.keystore.jks + KAFKA_ZOOKEEPER_SSL_KEYSTORE_PASSWORD: 12345678 + KAFKA_ZOOKEEPER_SSL_KEYSTORE_TYPE: PKCS12 + KAFKA_ZOOKEEPER_SSL_TRUSTSTORE_LOCATION: /etc/kafka/secrets/kafka.kafka1.truststore.jks + KAFKA_ZOOKEEPER_SSL_TRUSTSTORE_PASSWORD: 12345678 + KAFKA_ZOOKEEPER_SSL_TRUSTSTORE_TYPE: JKS + + schemaregistry0: + image: confluentinc/cp-schema-registry:5.2.4 + ports: + - 8085:8085 + depends_on: + - zookeeper0 + - kafka0 + environment: + SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka0:29092 + SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL: zookeeper0:2182 + SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT + SCHEMA_REGISTRY_HOST_NAME: schemaregistry0 + SCHEMA_REGISTRY_LISTENERS: http://schemaregistry0:8085 + + SCHEMA_REGISTRY_SCHEMA_REGISTRY_INTER_INSTANCE_PROTOCOL: "http" + SCHEMA_REGISTRY_LOG4J_ROOT_LOGLEVEL: INFO + SCHEMA_REGISTRY_KAFKASTORE_TOPIC: _schemas + + kafka-connect0: + image: confluentinc/cp-kafka-connect:5.2.4 + ports: + - 8083:8083 + depends_on: + - kafka0 + - schemaregistry0 + environment: + CONNECT_BOOTSTRAP_SERVERS: kafka0:29092 + CONNECT_GROUP_ID: compose-connect-group + CONNECT_CONFIG_STORAGE_TOPIC: _connect_configs + CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1 + CONNECT_OFFSET_STORAGE_TOPIC: _connect_offset + CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1 + CONNECT_STATUS_STORAGE_TOPIC: _connect_status + CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1 + CONNECT_KEY_CONVERTER: org.apache.kafka.connect.storage.StringConverter + CONNECT_KEY_CONVERTER_SCHEMA_REGISTRY_URL: http://schemaregistry0:8085 + CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.storage.StringConverter + CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: http://schemaregistry0:8085 + CONNECT_INTERNAL_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter + CONNECT_INTERNAL_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter + CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect0 + CONNECT_PLUGIN_PATH: "/usr/share/java,/usr/share/confluent-hub-components" + + kafka-init-topics: + image: confluentinc/cp-kafka:5.2.4 + volumes: + - ./message.json:/data/message.json + depends_on: + - kafka0 + command: "bash -c 'echo Waiting for Kafka to be ready... && \ + cub kafka-ready -b kafka0:29092 1 30 && \ + kafka-topics --create --topic second.users --partitions 3 --replication-factor 1 --if-not-exists --zookeeper zookeeper0:2182 && \ + kafka-topics --create --topic first.messages --partitions 2 --replication-factor 1 --if-not-exists --zookeeper zookeeper0:2182 && \ + kafka-console-producer --broker-list kafka0:29092 -topic second.users < /data/message.json'" diff --git a/kafka-ui-api/pom.xml b/kafka-ui-api/pom.xml index ea7a888173..11c07f77d5 100644 --- a/kafka-ui-api/pom.xml +++ b/kafka-ui-api/pom.xml @@ -62,9 +62,9 @@ ${kafka.version} - com.101tec - zkclient - ${zkclient.version} + org.apache.zookeeper + zookeeper + ${zookeper.version} org.projectlombok diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/exception/ZooKeeperException.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/exception/ZooKeeperException.java new file mode 100644 index 0000000000..761e8f1c1c --- /dev/null +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/exception/ZooKeeperException.java @@ -0,0 +1,9 @@ +package com.provectus.kafka.ui.exception; + +public class ZooKeeperException extends RuntimeException { + + public ZooKeeperException(Throwable cause) { + super(cause); + } + +} diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaService.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaService.java index 13ec1e87c6..1a9852eaa7 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaService.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/KafkaService.java @@ -159,8 +159,9 @@ public class KafkaService { ServerStatus zookeeperStatus = ServerStatus.OFFLINE; Throwable zookeeperException = null; try { - zookeeperStatus = zookeeperService.isZookeeperOnline(currentCluster) ? ServerStatus.ONLINE : - ServerStatus.OFFLINE; + zookeeperStatus = zookeeperService.isZookeeperOnline(currentCluster) + ? ServerStatus.ONLINE + : ServerStatus.OFFLINE; } catch (Throwable e) { zookeeperException = e; } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ZookeeperService.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ZookeeperService.java index 02123ac957..31bd317e78 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ZookeeperService.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/ZookeeperService.java @@ -1,11 +1,15 @@ package com.provectus.kafka.ui.service; +import com.provectus.kafka.ui.exception.ZooKeeperException; import com.provectus.kafka.ui.model.KafkaCluster; +import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; -import org.I0Itec.zkclient.ZkClient; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooKeeper; +import org.jetbrains.annotations.Nullable; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -14,7 +18,7 @@ import org.springframework.util.StringUtils; @Log4j2 public class ZookeeperService { - private final Map cachedZkClient = new ConcurrentHashMap<>(); + private final Map cachedZkClient = new ConcurrentHashMap<>(); public boolean isZookeeperOnline(KafkaCluster kafkaCluster) { var isConnected = false; @@ -28,20 +32,41 @@ public class ZookeeperService { return isConnected; } - private boolean isZkClientConnected(ZkClient zkClient) { - zkClient.getChildren("/brokers/ids"); + private boolean isZkClientConnected(ZooKeeper zkClient) { + try { + zkClient.getChildren("/brokers/ids", null); + } catch (KeeperException e) { + log.error("A zookeeper exception has occurred", e); + return false; + } catch (InterruptedException e) { + log.error("Interrupted: ", e); + Thread.currentThread().interrupt(); + } return true; } - private ZkClient getOrCreateZkClient(KafkaCluster cluster) { + @Nullable + private ZooKeeper getOrCreateZkClient(KafkaCluster cluster) { + final var clusterName = cluster.getName(); + final var client = cachedZkClient.get(clusterName); + if (client != null && client.getState() != ZooKeeper.States.CONNECTED) { + cachedZkClient.remove(clusterName); + } try { - return cachedZkClient.computeIfAbsent( - cluster.getName(), - (n) -> new ZkClient(cluster.getZookeeper(), 1000) - ); + return cachedZkClient.computeIfAbsent(clusterName, n -> createClient(cluster)); } catch (Exception e) { - log.error("Error while creating zookeeper client for cluster {}", cluster.getName()); + log.error("Error while creating zookeeper client for cluster {}", clusterName); return null; } } + + private ZooKeeper createClient(KafkaCluster cluster) { + try { + return new ZooKeeper(cluster.getZookeeper(), 60 * 1000, watchedEvent -> {}); + } catch (IOException e) { + log.error("Error while creating a zookeeper client for cluster [{}]", + cluster.getName()); + throw new ZooKeeperException(e); + } + } } diff --git a/pom.xml b/pom.xml index 0c74e664aa..d1ba567fa8 100644 --- a/pom.xml +++ b/pom.xml @@ -1,68 +1,90 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 pom kafka-ui-contract - kafka-ui-api - kafka-ui-e2e-checks - + kafka-ui-api + kafka-ui-e2e-checks + - - 13 - 13 - UTF-8 + + 13 + 13 + UTF-8 - 2.2.4.RELEASE - 0.2.1 - 1.3.1.Final - 1.18.10 + 2.2.4.RELEASE + 0.2.1 + 1.3.1.Final + 1.18.10 1.18.20 - latest - 0.11 - 2.4.1 - v14.17.1 - 1.4.10 - 1.8.0 - 3.5.1 - 3.1.0 - 3.1.0 - 2.22.0 - 4.3.0 - 1.6.0 - 1.2.32 - 2.4.1 - 1.9.2 - 5.5.1 - 2.2 - 1.15.1 - 5.4.0 - 2.21.0 - 3.19.0 + latest + 3.5.7 + 2.4.1 + v14.17.1 + 1.4.10 + 1.8.0 + 3.5.1 + 3.1.0 + 3.1.0 + 2.22.0 + 4.3.0 + 1.6.0 + 1.2.32 + 2.4.1 + 1.9.2 + 5.5.1 + 2.2 + 1.15.1 + 5.4.0 + 2.21.0 + 3.19.0 - ..//kafka-ui-react-app/src/generated-sources - provectus - https://sonarcloud.io - + ..//kafka-ui-react-app/src/generated-sources + + provectus + https://sonarcloud.io + - - - confluent - https://packages.confluent.io/maven/ - - + + + confluent + https://packages.confluent.io/maven/ + + + central + Central Repository + https://repo.maven.apache.org/maven2 + default + + false + + + - - - confluent - https://packages.confluent.io/maven/ - - + + + confluent + https://packages.confluent.io/maven/ + + + central + Central Repository + https://repo.maven.apache.org/maven2 + default + + false + + + never + + + - com.provectus - kafka-ui - 0.1.1-SNAPSHOT - kafka-ui - Kafka metrics for UI panel + com.provectus + kafka-ui + 0.1.1-SNAPSHOT + kafka-ui + Kafka metrics for UI panel