6 classic-editor-remember: classic-editor
7 date: "2020-09-25T23:23:17+00:00"
8 guid: http://juplo.de/?p=881
18 title: Encrypt Communication Between Kafka And ZooKeeper With TLS
19 url: /encrypt-communication-between-kafka-and-zookeeper-with-tls/
24 1. Download and unpack [zookeeper+tls.tgz](/wp-uploads/zookeeper+tls.tgz).
25 1. Run [README.sh](/wp-uploads/zookeeper+tls/README.sh) for a fully automated example of the presented setup.
27 Copy and paste to execute the two steps on Linux:
30 curl -sc - /wp-uploads/zookeeper+tls.tgz | tar -xzv && cd zookeeper+tls && ./README.sh
34 A [german translation](https://www.trion.de/news/2019/06/28/kafka-zookeeper-tls.html "Hier findest du eine deutsche Übersetzung dieses Artikels") of this article can be found on [http://trion.de](https://www.trion.de/news/ "A lot of intresting posts about Java, Docker, Kubernetes, Spring Boot and so on can be found @trion").
36 ## Current Kafka Cannot Encrypt ZooKeeper-Communication
38 Up until now ( [Version 2.3.0 of Apache Kafka](https://kafka.apache.org/documentation/#security_overview "Read more about the supported options in the original documentation of version 2.3.0")) it is not possible, to encrypt the communication between the Kafka-Brokers and their ZooKeeper-ensemble.
39 This is not possiible, because ZooKeeper 3.4.13, which is shipped with Apache Kafka 2.3.0, lacks support for TLS-encryption.
41 The documentation deemphasizes this, with the observation, that usually only non-sensitive data (configuration-data and status information) is stored in ZooKeeper and that it would not matter, if this data is world-readable, as long as it can be protected against manipulation, which can be done through proper authentication and ACL's for zNodes:
43 > _The rationale behind this decision is that the data stored in ZooKeeper is not sensitive, but inappropriate manipulation of znodes can cause cluster disruption._ ( [Kafka-Documentation](https://kafka.apache.org/documentation/#zk_authz "Read the documentation about how to secure ZooKeeper"))
45 This quote obfuscates the [elsewhere mentioned fact](https://kafka.apache.org/documentation/#security_sasl_scram_security "The security considerations for SASL/SCRAM are clearly stating, that ZooKeeper must be protected, because it stores sensitive authentication data in this case"), that there are use-cases that store sensible data in ZooKeeper.
46 For example, if authentication via [SASL/SCRAM](https://kafka.apache.org/documentation/#security_sasl_scram_clientconfig "Read more about authentication via SASL/SCRAM") or [Delegation Tokens](https://kafka.apache.org/documentation/#security_delegation_token) is used.
47 Accordingly, the documentation often stresses, that usually there is no need to make ZooKeeper accessible to normal clients.
48 Nowadays, only admin-tools need direct access to the ZooKeeper-ensemble.
49 Hence, it is stated as a best practice, to make the ensemble only available on a local network, hidden behind a firewall or such.
51 **In cleartext: One must not run a Kafka-Cluster, that spans more than one data-center — or at least make sure, that all communication is tunneled through a virtual private network.**
53 ## ZooKeeper 3.5.5 To The Rescue
55 On may the 20th 2019, [version 3.5.5 of ZooKeeper](http://zookeeper.apache.org/releases.html#releasenotes "Read the release notes") has been released.
56 Version 3.5.5 is the first stable release of the 3.5.x branch, that introduces the support for TLS-encryption, the community has yearned for so long.
57 It supports the encryption of all communication between the nodes of a ZooKeeper-ensemble and between ZooKeeper-Servers and -Clients.
59 Part of ZooKeeper is a sophisticated client-API, that provide a convenient abstraction for the communication between clients and servers over the _Atomic Broadcast Protocol_.
60 The TLS-encryption is applied by this API transparently.
61 Because of that, all client-implementations can profit from this new feature through a simple library-upgrade from 3.4.13 to 3.5.5.
62 **This article will walk you through an example, that shows how to carry out such a library-upgrade for Apache Kafka 2.3.0 and configure a cluster to use TLS-encryption, when communicating with a standalone ZooKeeper.**
66 **The presented setup is ment for evaluation only!**
68 It fiddles with the libraries, used by Kafka, which might cause unforseen issues.
69 Furthermore, using TLS-encryption in ZooKeeper requires one to switch from the battle-tested `NIOServerCnxnFactory`, which uses the [NIO-API](https://en.wikipedia.org/wiki/Non-blocking_I/O_(Java) "Learn more about non-blocking I/O in Java") directly, to the newly introduced `NettyServerCnxnFactory`, which is build on top of [Netty](https://netty.io/ "Learn more about Netty").
71 ## Recipe To Enable TLS Between Broker And ZooKeeper
73 The article will walk you step by step through the setup now.
74 If you just want to evaluate the example, you can [jump to the download-links](#scripts "I am so inpatient, just get me to the fully automated example").
76 All commands must be executed in the same directory.
77 We recommend, to create a new directory for that purpose.
79 ### Download Kafka and ZooKeeper
81 First of all: Download version 2.3.0 of Apache Kafka and version 3.5.5 of Apache ZooKeeper:
84 curl -sc - http://ftp.fau.de/apache/zookeeper/zookeeper-3.5.5/apache-zookeeper-3.5.5-bin.tar.gz | tar -xzv
85 curl -sc - http://ftp.fau.de/apache/kafka/2.3.0/kafka_2.12-2.3.0.tgz | tar -xzv
89 ### Switch Kafka 2.3.0 from ZooKeeper 3.4.13 to ZooKeeper 3.5.5
91 Remove the 3.4.13-version from the `libs`-directory of Apache Kafka:
94 rm -v kafka_2.12-2.3.0/libs/zookeeper-3.4.14.jar
98 Then copy the JAR's of the new version of Apache ZooKeeper into that directory. (The last JAR is only needed for CLI-clients, like for example `zookeeper-shell.sh`.)
101 cp -av apache-zookeeper-3.5.5-bin/lib/zookeeper-3.5.5.jar kafka_2.12-2.3.0/libs/
102 cp -av apache-zookeeper-3.5.5-bin/lib/zookeeper-jute-3.5.5.jar kafka_2.12-2.3.0/libs/
103 cp -av apache-zookeeper-3.5.5-bin/lib/netty-all-4.1.29.Final.jar kafka_2.12-2.3.0/libs/
104 cp -av apache-zookeeper-3.5.5-bin/lib/commons-cli-1.2.jar kafka_2.12-2.3.0/libs/
108 That is all there is to do to upgrade ZooKeeper.
109 If you run one of the Kafka-commands, it will use ZooKeeper 3.5.5. from now on.
111 ### Create A Private CA And The Needed Certificates
113 _You can [read more about setting up a private CA in this post](/create-self-signed-multi-domain-san-certificates/ "Lern how to set up a private CA and create self-signed certificates")..._
115 Create the root-certificate for the CA and store it in a Java-truststore:
118 openssl req -new -x509 -days 365 -keyout ca-key -out ca-cert -subj "/C=DE/ST=NRW/L=MS/O=juplo/OU=kafka/CN=Root-CA" -passout pass:superconfidential
119 keytool -keystore truststore.jks -storepass confidential -import -alias ca-root -file ca-cert -noprompt
123 The following commands will create a self-signed certificate in **`zookeeper.jks`**.
126 1. Create a new key-pair and certificate for `zookeeper`
127 1. Generate a certificate-signing-request for that certificate
128 1. Sign the request with the key of private CA and also add a SAN-extension, so that the signed certificate is also valid for `localhost`
129 1. Import the root-certificate of the private CA into the keystore `zookeeper.jks`
130 1. Import the signed certificate for `zookeeper` into the keystore `zookeeper.jks`
132 _You can [read more about creating self-signed certificates with multiple domains and building a Chain-of-Trust here](/create-self-signed-multi-domain-san-certificates/#sign-with-san "Lern how to sign certificates with SAN-extension")..._
136 keytool -keystore $NAME.jks -storepass confidential -alias $NAME -validity 365 -genkey -keypass confidential -dname "CN=$NAME,OU=kafka,O=juplo,L=MS,ST=NRW,C=DE"
137 keytool -keystore $NAME.jks -storepass confidential -alias $NAME -certreq -file cert-file
138 openssl x509 -req -CA ca-cert -CAkey ca-key -in cert-file -out $NAME.pem -days 365 -CAcreateserial -passin pass:superconfidential -extensions SAN -extfile <(printf "\n[SAN]\nsubjectAltName=DNS:$NAME,DNS:localhost")
139 keytool -keystore $NAME.jks -storepass confidential -import -alias ca-root -file ca-cert -noprompt
140 keytool -keystore $NAME.jks -storepass confidential -import -alias $NAME -file $NAME.pem
150 Now we have signed certificates for all participants in our small example, that are stored in separate keystores, each with a Chain-of-Trust set up, that is rooting in our private CA.
151 We also have a truststore, that will validate all these certificates, because it contains the root-certificate of the Chain-of-Trust: the certificate of our private CA.
153 ### Configure And Start ZooKeeper
155 _We hightlight/explain only the configuration-options here, that are needed for TLS-encryption!_
157 In our setup, the standalone ZooKeeper essentially needs two specially tweaked configuration files, to use encryption.
159 Create the file **`java.env`**:
162 SERVER_JVMFLAGS="-Xms512m -Xmx512m -Dzookeeper.serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory"
167 - The Java-Environmentvariable **`zookeeper.serverCnxnFactory`** switches the connection-factory to use the Netty-Framework.
168 **Without this, TLS is not possible!**
170 Create the file **`zoo.cfg`**:
173 dataDir=/tmp/zookeeper
174 secureClientPort=2182
176 authProvider.1=org.apache.zookeeper.server.auth.X509AuthenticationProvider
177 ssl.keyStore.location=zookeeper.jks
178 ssl.keyStore.password=confidential
179 ssl.trustStore.location=truststore.jks
180 ssl.trustStore.password=confidential
184 - **`secureClientPort`**: We only allow encrypted connections!
185 (If we want to allow unencrypted connections too, we can just specify `clientPort` additionally.)
186 - **`authProvider.1`**: Selects authentification through client certificates
187 - **`ssl.keyStore.*`**: Specifies the path to and password of the keystore, with the `zookeeper`-certificate
188 - **`ssl.trustStore.*`**: Specifies the path to and password of the common truststore with the root-certificate of our private CA
190 Copy the file **`log4j.properties`** into the current working directory, to enable logging for ZooKeeper (see also `java.env`):
193 cp -av apache-zookeeper-3.5.5-bin/conf/log4j.properties .
197 Start the ZooKeeper-Server:
200 apache-zookeeper-3.5.5-bin/bin/zkServer.sh --config . start
204 - **`--config .`**: The script should search in the current directory for the configration data and certificates.
206 ### Konfigure And Start The Brokers
208 _We hightlight/explain only the configuration-options and start-parameters here, that are needed to encrypt the communication between the Kafka-Brokers and the ZooKeeper-Server!_
210 The other parameters shown here, that are concerned with SSL are only needed for securing the communication between the Brokers itself and between Brokers and Clients.
211 You can read all about them in the [standard documentation](https://kafka.apache.org/documentation/#security).
212 In short: This example is set up, to use SSL for authentication between the brokers and SASL/PLAIN for client-authentification — both channels are encrypted with TLS.
214 TLS for the ZooKeeper Client-API is configured through Java-Environmentvariables.
215 Hence, most of the SSL-configuration for connecting to ZooKeeper has to be specified, when starting the broker.
216 Only the address and port for the connction itself is specified in the configuration-file.
218 Create the file **`kafka-1.properties`**:
222 zookeeper.connect=zookeeper:2182
223 listeners=SSL://kafka-1:9193,SASL_SSL://kafka-1:9194
224 security.inter.broker.protocol=SSL
225 ssl.client.auth=required
226 ssl.keystore.location=kafka-1.jks
227 ssl.keystore.password=confidential
228 ssl.key.password=confidential
229 ssl.truststore.location=truststore.jks
230 ssl.truststore.password=confidential
231 listener.name.sasl_ssl.plain.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required user_consumer="pw4consumer" user_producer="pw4producer";
232 sasl.enabled.mechanisms=PLAIN
233 log.dirs=/tmp/kafka-1-logs
234 offsets.topic.replication.factor=2
235 transaction.state.log.replication.factor=2
236 transaction.state.log.min.isr=2
240 - **`zookeeper.connect`**: If you allow unsecure connections too, be sure to specify the right port here!
241 - _All other options are not relevant for encrypting the connections to ZooKeeper_
243 Start the broker in the background and remember its PID in the file **`KAFKA-1`**:
248 -Dzookeeper.clientCnxnSocket=org.apache.zookeeper.ClientCnxnSocketNetty
249 -Dzookeeper.client.secure=true
250 -Dzookeeper.ssl.keyStore.location=kafka-1.jks
251 -Dzookeeper.ssl.keyStore.password=confidential
252 -Dzookeeper.ssl.trustStore.location=truststore.jks
253 -Dzookeeper.ssl.trustStore.password=confidential
255 kafka_2.12-2.3.0/bin/kafka-server-start.sh kafka-1.properties & echo $! > KAFKA-1
260 Check the logfile **`kafka-1.log`** to confirm that the broker starts without errors!
262 - **`zookeeper.clientCnxnSocket`**: Switches from NIO to the Netty-Framework.
263 **Without this, the ZooKeeper Client-API (just like the ZooKeeper-Server) cannot use TLS!**
264 - **`zookeeper.client.secure=true`**: Switches on TLS-encryption, for all connections to any ZooKeeper-Server
265 - **`zookeeper.ssl.keyStore.*`**: Specifies the path to and password of the keystore, with the `kafka-1`-certificate
266 - **`zookeeper.ssl.trustStore.*`**: Specifies the path to and password of the common truststore with the root-certificate of our private CA
268 _Do the same for **`kafka-2`**!_
269 _And do not forget, to adapt the config-file accordingly — or better: just [download a copy](/wp-uploads/zookeeper+tls/kafka-2.properties)..._
271 ### Configure And Execute The CLI-Clients
273 All scripts from the Apache-Kafka-Distribution that connect to ZooKeeper are configured in the same way as seen for `kafka-server-start.sh`.
274 For example, to create a topic, you will run:
278 -Dzookeeper.clientCnxnSocket=org.apache.zookeeper.ClientCnxnSocketNetty
279 -Dzookeeper.client.secure=true
280 -Dzookeeper.ssl.keyStore.location=client.jks
281 -Dzookeeper.ssl.keyStore.password=confidential
282 -Dzookeeper.ssl.trustStore.location=truststore.jks
283 -Dzookeeper.ssl.trustStore.password=confidential
285 kafka_2.12-2.3.0/bin/kafka-topics.sh \
286 --zookeeper zookeeper:2182 \
287 --create --topic test \
288 --partitions 1 --replication-factor 2
292 _Note:_ A different keystore is used here ( `client.jks`)!
294 CLI-clients, that connect to the brokers, can be called as usual.
296 In this example, they use an encrypted listener on port 9194 (for `kafka-1`) and are authenticated using SASL/PLAIN.
297 The client-configuration is kept in the files `consumer.config` and `producer.config`.
298 Take a look at that files and compare them with the broker-configuration above.
299 If you want to lern more about securing broker/client-communication, we refere you to the [official documentation](https://kafka.apache.org/documentation/#security "The official documentation does a good job on this topic!").
301 _If you have trouble to start these clients, download the scripts and take a look at the examples in [README.sh](/wp-uploads/zookeeper+tls/README.sh)_
303 ### TBD: Further Steps To Take...
305 This recipe only activates TLS-encryption between Kafka-Brokers and a Standalone ZooKeeper.
306 It does not show, how to enable TLS between ZooKeeper-Nodes (which should be easy) or if it is possible to authenticate Kafka-Brokers via TLS-certificates. These topics will be covered in future articles...
308 ## Fully Automated Example Of The Presented Setup
310 Download and unpack [zookeeper+tls.tgz](/wp-uploads/zookeeper+tls.tgz) for an evaluation of the presented setup:
313 curl -sc - /wp-uploads/zookeeper+tls.tgz | tar -xzv
317 The archive contains a fully automated example.
318 Just run [README.sh](/wp-uploads/zookeeper+tls/README.sh) in the unpacked directory.
320 It downloads the required software, carries out the library-upgrade, creates the required certificates and starts a standalone ZooKeeper and two Kafka-Brokers, that use TLS to encrypt all communication.
321 It also executes a console-consumer and a console-producer, that read and write to a topic, and a zookeeper-shell, that communicates directly with the ZooKeeper-node, to proof, that the setup is working.
322 The ZooKeeper and the Brokers-instances are left running, to enable the evaluation of the fully encrypted cluster.
326 - Run **`README.sh`**, to execute the automated example
327 - After running `README.sh`, the Kafka-Cluster will be still running, so that one can experiment with commands from `README.sh` by hand
328 - `README.sh` can be executed repeatedly: it will skip all setup-steps, that are already done automatically
329 - Run **`README.sh stop`**, to stop the Kafka-Cluster (it can be restarted by re-running `README.sh`)
330 - Run **`README.sh cleanup`**, to stop the Cluster and remove all created files and data (only the downloaded packages will be left untouched)
332 ### Separate Downloads For The Packaged Files
334 - [README.sh](/wp-uploads/zookeeper+tls/README.sh)
335 - [create-certs.sh](/wp-uploads/zookeeper+tls/create-certs.sh)
336 - [gencert.sh](/wp-uploads/zookeeper+tls/gencert.sh)
337 - [zoo.cfg](/wp-uploads/zookeeper+tls/zoo.cfg)
338 - [java.env](/wp-uploads/zookeeper+tls/java.env)
339 - [kafka-1.properties](/wp-uploads/zookeeper+tls/kafka-1.properties)
340 - [kafka-2.properties](/wp-uploads/zookeeper+tls/kafka-2.properties)
341 - [consumer.config](/wp-uploads/zookeeper+tls/consumer.config)
342 - [producer.config](/wp-uploads/zookeeper+tls/producer.config)