Simple way to secure Kafka / Zookeeper cluster on Kubernetes

After successful Kafka and Zookeeper deployment on Kubernetes following this post, it's time to make it more secure.

1- ZooKeeper DIGEST authentication

Account configuration

(one for Zookeeper nodes, another for Kafka broker) in config.secured.yaml file.

Client section will be used by zkCli.sh helper srcipt to test configuration. In integration environement, Client section must be removed.

apiVersion: v1
kind: Secret
metadata:
  name: zookeeper-jaas
type: Opaque
stringData:
  zookeeper-jaas.conf: |-
    QuorumServer {
          org.apache.zookeeper.server.auth.DigestLoginModule required
          user_zk="passcode";
    };
    QuorumLearner {
          org.apache.zookeeper.server.auth.DigestLoginModule required
          username="zk"
          password="passcode";
    }; 
    Server {
          org.apache.zookeeper.server.auth.DigestLoginModule required
          user_kafka="passcode"
          user_client="passcode";
    };
    Client {
          org.apache.zookeeper.server.auth.DigestLoginModule required
          username="client"
          password="passcode";
    };

Create Secret / ConfigMap and deploy StatefuSet:

kubectl apply -f zookeeper/config.secured.yaml 
kubectl apply -f zookeeper/statefulset.secured.yaml 
# Secret for kafka
kubectl apply -f kafka/config.secured.yaml

2- Kafka SSL encryption

If your want to disable the Host Name verification for some raison, you need to omit the extension parameter -ext and add empty environment variable to Kafka broker KAFKA_SSL_ENDPOINT_IDENTIFICATION_ALGORITHM="" or use Kafka helper script for every broker.

kafka-configs.sh --bootstrap-server [kafka-0].kafka-broker.kafka.svc.cluster.local:9092 --entity-type brokers --entity-name 0 --alter --add-config "listener.name.internal.ssl.endpoint.identification.algorithm="

Create CA (certificate authority)

openssl req -new -x509 -keyout ca-key -out ca-cert -days 365

Generate server keystore and client keystore

keytool -keystore kafka.server.keystore.jks -alias localhost -validity 365 -genkey -keyalg RSA 
keytool -keystore kafka.client.keystore.jks -alias localhost -validity 365 -genkey -keyalg RSA

Add generated CA to the trust store

keytool -keystore kafka.server.truststore.jks -alias CARoot -import -file ca-cert 
keytool -keystore kafka.client.truststore.jks -alias CARoot -import -file ca-cert

Sign the key store (with passcode and ssl.cnf)

You need to update alt_names section of ssl.cnf with list of your brokers hostname.

keytool -keystore kafka.server.keystore.jks -alias localhost -certreq -file cert-file 
openssl x509 -req -CA ca-cert -CAkey ca-key -in cert-file -out cert-signed -days 365 -CAcreateserial -passin pass:passcode -extfile ssl.cnf -extensions req_ext 
keytool -keystore kafka.server.keystore.jks -alias CARoot -import -file ca-cert 
keytool -keystore kafka.server.keystore.jks -alias localhost -import -file cert-signed

Sign the client keystore

keytool -keystore kafka.client.keystore.jks -alias localhost -certreq -file cert-file-client 
openssl x509 -req -CA ca-cert -CAkey ca-key -in cert-file-client -out cert-signed-client -days 365 -CAcreateserial -passin pass:passcode -extfile ssl.cnf -extensions req_ext 
keytool -keystore kafka.client.keystore.jks -alias CARoot -import -file ca-cert 
keytool -keystore kafka.client.keystore.jks -alias localhost -import -file cert-signed-client

Kafka SSL Kubernetes

Create kubernetes secret from kafka.keystore.jks and kafka.truststore.jks

kubectl create secret generic ssl --from-literal=keystore_password=passcode --from-file=kafka.keystore.jks=ssl/kafka.server.keystore.jks --from-literal=truststore_password=passcode --from-file=kafka.truststore.jks=ssl/kafka.server.truststore.jks

Update Kafka StatefulSet to mount ssl secret and broker, service configuration:

kubectl apply -f kafka/statefulset.ssl.yaml kubectl apply -f kafka/service.ssl.yaml

Testing

Use openssl to debug connectionto valid certificate data:

kubectl exec -ti zk-0 -- openssl s_client -debug -connect kafka-0.kafka-broker.kafka.svc.cluster.local:9093 -tls1

Create client configuration file (client.properties):

security.protocol=SSL
ssl.truststore.location=/opt/kafka/config/kafka.truststore.jks
ssl.truststore.password=passcode
ssl.keystore.location=/opt/kafka/config/client.keystore.jks
ssl.keystore.password=passcode ssl.key.password=passcode
ssl.enabled.protocols=TLSv1.2,TLSv1.1,TLSv1

Send to test pod:

mkdir -p config 
cp client.properties config/client.properties 
cp ssl/kafka.client.keystore.jks config/client.keystore.jks 
cp ssl/kafka.client.truststore.jks config/kafka.truststore.jks 
kubectl cp config kafka-1:/opt/kafka

Run test:

kubectl exec -ti kafka-1 -- kafka-console-producer.sh --bootstrap-server kafka-0.kafka-broker.kafka.svc.cluster.local:9093 --topic k8s --producer.config /opt/kafka/config/client.properties 
>> Hello with secured connection

kubectl exec -ti kafka-1 -- kafka-console-consumer.sh --bootstrap-server kafka-0.kafka-broker.kafka.svc.cluster.local:9093 --topic k8s -consumer.config /opt/kafka/config/client.properties --from-beginning 
<< Hello with secured connection

Prepare Secret for client:

kubectl create secret generic client-ssl --from-file=ca-certs.pem=ssl/ca-cert --from-file=cert.pem=ssl/cert-signed-client 
kubectl apply -f consumer.secured.yaml kubectl logs consumer-secured


@GitHub source