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を有効にする場合、truststore
やkeystore
の設定を有効にする必要があるので生成していく。
なお、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