うさぎ好きエンジニアの備忘録

うさぎたちに日々癒されているエンジニアが業務で直面したもの & 個人的な学習メモを残していきます。

ZooKeeper TLSを有効にしてみる

最近ZooKeeperの新しめのバージョンに触れる機会があったので、TLS enabledなZooKeeperクラスタを構築してみた。

概要

v3.5.7以上のZooKeeperではQuorum間での通信やクライアントとの接続にTLSを利用することが可能になったので、実際にクラスタを構築して試してみる。

なお、今回利用するZookeeperのバージョンは3.5.8であり、3台構成。

またZooKeeperについてはApache Bigtopでビルドしたものを使う。

事前準備 - 普通にZooKeeperクラスタを作る

TLSを有効にするにあたって、まずは通常のZooKeeperクラスタを構築する。

今回はzk00[1-3].ponteru.co.jpという2台のホストを使ってクラスタを構築していく。 以下の作業は全て上記のホスト上で同様に行う。

必要なパッケージのインストール

(今回はJava11で構築)

$ sudo yum install java-11-openjdk java-11-openjdk-devel -y
$ sudo yum install zookeeper zookeeper-server -y

設定ファイルの更新

インストールが終わったらzoo.cfgにQuorumを組むノードの情報を記載。

server.1=zk001.ponteru.co.jp:2888:3888
server.2=zk002.ponteru.co.jp:2888:3888
server.3=zk003.ponteru.co.jp:2888:3888

また、それぞれのホスト上でmyidを作成しておく。

# zk001.ponteru.co.jp
$ cat /var/lib/zookeeper/myid
1

# zk002.ponteru.co.jp
$ cat /var/lib/zookeeper/myid
2

# zk003.ponteru.co.jp
$ cat /var/lib/zookeeper/myid
3

snapshot用のディレクトリ作成

znodeのsnapshotを保存する用のディレクトリも作成。

(ここら辺はそれぞれのビルドの仕方であったりで異なるので柔軟にお願いします。)

$ sudo mkdir -p /var/lib/zookeeper/version-2
$ sudo chown zookeeper:hadoop /var/lib/zookeeper/version-2
$ sudo chmod 755 /var/lib/zookeeper/version-2

プロセスの起動

ここまで準備ができたらあとはプロセスをsystemctlで起動しておく。

$ sudo systemctl zookeeper-server start

$ /usr/lib/zookeeper/bin/zkServer.sh status
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /usr/lib/zookeeper/bin/../conf/zoo.cfg
mkdir: cannot create directory ‘/usr/lib/zookeeper/bin/../logs’: Permission denied
Client port found: 2181. Client address: localhost.
Mode: leader

現状Client Portが2181/tcpで起動しているので、何もせずともzkCli.shで接続ができる。

$ /usr/lib/zookeeper/bin/zkCli.sh -server `hostname`:2181
...
[zk: zk001.ponteru.co.jp:2181(CONNECTED) 0] ls /
[zookeeper]

クライアント接続時にTLSを有効にしてみる

まずはzkCli.shなど、クライアントで接続する時の通信でTLSを有効にしてみる。

証明書の用意

TLSを有効にする場合、truststorekeystoreの設定を有効にする必要があるので生成していく。

なお、CNはZooKeeperのホスト名を含むことができるような正規表現で記述すること。そうしないとCertificate for <hogehoge> doesn't match common name of the certificate subjectのように、証明書の確認時にエラーが発生してしまう。

実行コマンドとしては以下。

$ keytool -keystore server.keystore.jks -alias localhost -validity 365 -genkeypair -keyalg RSA
$ openssl req -new -x509 -keyout ca-key -out ca-cert -days 365
$ keytool -keystore server.truststore.jks -alias CARoot -import -file ca-cert
$ keytool -keystore client.truststore.jks -alias CARoot -import -file ca-cert
$ keytool -keystore 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
$ keytool -keystore server.keystore.jks -alias CARoot -import -file ca-cert
$ keytool -keystore server.keystore.jks -alias localhost -import -file cert-signed

全て完了すると以下のように証明書一式ができている。

$ ls -l
total 36
-rw-r--r-- 1 ponteru users 1346 Dec 26 20:33 ca-cert
-rw-r--r-- 1 ponteru users   17 Dec 26 20:34 ca-cert.srl
-rw-r--r-- 1 ponteru users 1834 Dec 26 20:33 ca-key
-rw-r--r-- 1 ponteru users 1129 Dec 26 20:34 cert-file
-rw-r--r-- 1 ponteru users 1253 Dec 26 20:34 cert-signed
-rw-r--r-- 1 ponteru users 1250 Dec 26 20:34 client.truststore.jks
-rw-r--r-- 1 ponteru users 4829 Dec 26 20:35 server.keystore.jks
-rw-r--r-- 1 ponteru users 1250 Dec 26 20:33 server.truststore.jks

証明書の配置

作成した証明書を配置していく。今回は/etc/private/ssl配下に置くことを想定。

$ sudo mkdir -p /etc/private/ssl

# 必要なのはserver.{truststore,keystore}.jksのみ
$ sudo cp -v server.{truststore,keystore}.jks /etc/private/ssl
‘server.keystore.jks’ -> ‘/etc/private/ssl/server.keystore.jks’
‘server.truststore.jks’ -> ‘/etc/private/ssl/server.truststore.jks’

# パーミッションも適宜変更
$ sudo chown zookeeper:hadoop /etc/private/ssl/*
$ sudo chmod 440 /etc/private/ssl/*

# 最終的に以下のような状態になっていればOK
$ ls -l /etc/private/ssl
total 12
-r--r----- 1 zookeeper hadoop 4829 Dec 26 20:39 server.keystore.jks
-r--r----- 1 zookeeper hadoop 1250 Dec 26 20:39 server.truststore.jks

設定の変更 & プロセス再起動

証明書の準備が整ったらzoo.cfgに以下を追加。

secureClientPort=2182
serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory
authProvider.x509=org.apache.zookeeper.server.auth.X509AuthenticationProvider
ssl.keyStore.location=/etc/private/ssl/server.keystore.jks
ssl.keyStore.password=changeit
ssl.trustStore.location=/etc/private/ssl/server.truststore.jks
ssl.trustStore.password=changeit
ssl.clientAuth=none

ここまで出来たらあとはプロセスを再起動。

$ sudo systemctl restart zookeeper-server

secureClientPortで接続してみる

設定が完了したのでsecureClientPortで接続をしてみる。

何もせず、non-TLSなポートと同じように接続をしてみると接続が拒否されることがわかった。

# ポートだけ2182を指定
$ /usr/lib/zookeeper/bin/zkCli.sh -server `hostname`:2182 -- ls /
...
2020-12-26 21:23:13,899 [myid:zk001.ponteru.co.jp:2182] - INFO  [main-SendThread(zk001.ponteru.co.jp:2182):ClientCnxn$SendThread@1240] - Unable to read additional data from server sessionid 0x0, likely server has closed socket, closing socket connection and attempting reconnect
KeeperErrorCode = ConnectionLoss for /

以下のように証明書関連の情報を指定してあげることでsecureClientPort接続することができた。

$ sudo -u zookeeper CLIENT_JVMFLAGS="
-Dzookeeper.clientCnxnSocket=org.apache.zookeeper.ClientCnxnSocketNetty
-Dzookeeper.ssl.trustStore.location=/etc/private/ssl/server.truststore.jks
-Dzookeeper.ssl.trustStore.password=changeit
-Dzookeeper.ssl.keyStore.location=/etc/private/ssl/server.keystore.jks
-Dzookeeper.ssl.keyStore.password=changeit
-Dzookeeper.client.secure=true" /usr/lib/zookeeper/bin/zkCli.sh -server `hostname`:2182 -- ls /
...
[zookeeper]

Quorum間通信をTLSにする

次にQuorum間の通信をTLSにしてみる。

(すでに証明書などはクライアントのTLS化をしたときに準備しているので、それを流用する形をとる。)

以下の設定を各ZooKeeperサーバのzoo.cfgに追加して再起動。

sslQuorum=true
ssl.quorum.keyStore.location=/etc/private/ssl/server.keystore.jks
ssl.quorum.keyStore.password=changeit
ssl.quorum.trustStore.location=/etc/private/ssl/server.truststore.jks
ssl.quorum.trustStore.password=changeit
ssl.quorum.clientAuth=none
$ sudo systemctl restart zookeeper-server

再起動するとログ上にTLS通信のみのQurorum用ソケットを作成する旨が出力されている。

2020-12-26 22:13:16,644 [myid:1] - INFO  [QuorumPeerListener:QuorumCnxManager$Listener@912] - Creating TLS-only quorum server socket

また、以下のように他ZooKeeperサーバからTLS接続を受け付けたこともログから確認できた。

2020-12-26 22:13:19,938 [myid:1] - INFO  [zk001.ponteru.co.jp/xxx.yyy.zzz.123:3888:UnifiedServerSocket$UnifiedSocket@273] - Accepted TLS connection from /xxx.yyy.zzz.456:35832 - TLSv1.2 - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256