Безопасный Zookeeper: SASL with Digest-MD5

от автора

Привет! Столкнулся с тем, что быстро не нашел простой инструкции, как с использованием SSL и SASL настроить безопасный кластер из нескольких Linux узлов Zookeeper, и решил это исправить.

В этой статье поговорим о том, как:

  • Настроить Zookeeper в кластере из трех узлов без шифрования (Plain);

  • Добавить шифрование во внутрикластерное взаимодействие (Quorum TLS);

  • Создать сертификаты для подключения к узлам Zookeeper клиентов (Server TLS);

  • Создать сертификаты для подключения клиентов к узлам (Client TLS);

  • Добавить авторизацию в шифрованный кластер (SASL with MD5);

  • Показать на примере, как работают ACL, посмотреть, чем отличается суперпользователь super от всех остальных (как работает ACL в действии).

Plain

Для понимания, какая настройка за что отвечает, начнем установку с простой кластерной конфигурации, а потом усложним ее.

Возьмем три узла с Linux, например AlmaLinux 9.2.

Важно, чтобы они знали свои имена, поэтому нужно либо настроить DNS, либо добавить информацию об их именах в /etc/hosts на всех трех узлах:

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1         localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.1.11 zoo1.local zoo1 192.168.1.12 zoo2.local zoo2  192.168.1.13 zoo3.local zoo3

Ниже мы будем использовать команду hostname -f, для которой будет важно, в каком порядке определены имена в /etc/hosts (сначала zoo1.local, а только потом zoo1, но не наоборот).

В документации Zookeeper(Zk) говорится, что он хорошо работает на стабильной JDK11 LTS (в списке JDK 8 LTS, JDK 11 LTS, JDK 12 ), установим её на все узлы:

sudo dnf install -y java-11-openjdk.x86_64

Скачаем стабильный дистрибутив Zk, сейчас это версия 3.8.3:

wget https://dlcdn.apache.org/zookeeper/stable/apache-zookeeper-3.8.3-bin.tar.gz

Стабильные версии Zk можно найти тут.

На всех трех узлах сразу создадим отдельного пользователя zookeeper:

sudo useradd -m -d /opt/zookeeper -s /bin/bash zookeeper

После того, как все настроим, можно будет поменять шелл пользователя на /bin/false. Команда создаст папку /opt/zookeeper.

Распакуем в нее скачанный файл, а также создадим папки для сертификатов, данных и настроек для подключения клиентов:

sudo tar -xf apache-zookeeper-3.8.3-bin.tar.gz --directory /opt/zookeeper sudo chown -R zookeeper:zookeeper /opt/zookeeper sudo su - zookeeper mv apache-zookeeper-3.8.3-bin/{bin,conf,lib,docs} .  rm -r apache-zookeeper-3.8.3-bin mkdir -m 700 ssl client-settings data .ssh

На всех узлах сразу запишем простую стартовую конфигурацию в файл /opt/zookeeper/conf/zookeeper.properties:

dataDir=/opt/zookeeper/data  # zoo cluster node info #server.id=hostname:port1:port2   # id - The ID of the Zookeeper cluster node.   # hostname - The hostname or IP address where the node listens for connections.   # port1 - The port number used for intra-cluster communication.   # port2 - The port number used for leader election. server.1=zoo1.local:2888:3888 server.2=zoo2.local:2888:3888 server.3=zoo3.local:2888:3888  ## Metrics Providers # https://prometheus.io Metrics Exporter metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpHost=0.0.0.0 metricsProvider.httpPort=8006 metricsProvider.exportJvmInfo=true  # The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial synchronization phase can take initLimit=10 # The number of ticks that can pass between sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored.   #max client connections number maxClientCnxns=60  # the port at which the clients will connect in plain text (it should be commented and disabled) clientPort=2181

И на всех узлах напишем unit file для systemd /etc/systemd/system/zookeeper.service. В этом файле мы установим важную переменную JAVA_HOME, а также через переменную ZK_SERVER_HEAP немного расширим HEAP память:

exit sudo vim /etc/systemd/system/zookeeper.service
[Unit] Description=Apache Zookeeper server Documentation=http://zookeeper.apache.org Requires=network.target remote-fs.target After=network.target remote-fs.target   [Service] Type=forking User=zookeeper Group=zookeeper  Environment="JAVA_HOME=/usr/lib/jvm/jre-11"  #default heap size is 1000 Mb (set in zkEnv.sh) Environment="ZK_SERVER_HEAP=2000"  ExecStart=/opt/zookeeper/bin/zkServer.sh start /opt/zookeeper/conf/zookeeper.properties ExecStop=/opt/zookeeper/bin/zkServer.sh stop Restart=always   [Install] WantedBy=multi-user.target

Перед первым запуском важно в папке data создать уникальные идентификаторы узлов. Это важно, иначе ничего не заработает:

sudo su - zookeeper # Только на первом узле  echo 1 > /opt/zookeeper/data/myid  # Только на втором узле echo 2 > /opt/zookeeper/data/myid  # Только на третьем узле echo 3 > /opt/zookeeper/data/myid exit

На всех узлах читаем unit файл systemd, запускаемся и пробуем подключиться к Zk консольным клиентом. Должно быть все в порядке, но на всякий случай следует заглянуть в лог файлы Zk:

sudo systemctl daemon-reload sudo systemctl enable --now zookeeper sudo systemctl status zookeeper --no-pager sudo su - zookeeper bin/zkCli.sh -server 127.0.0.1:2181 ls /zookeeper       #должны увидеть строку       #[config, quota] tail -f logs/zookeeper-zookeeper-server-$(hostname -f).out

Если видите кучу ошибок и узлы не подключаются друг к другу — еще раз проверьте, что файлы /opt/zookeeper/data/myid на всех узлах содержат уникальный номер.

Quorum TLS

В предыдущей серии мы уже создали папку /opt/zookeeper/ssl, в ней будут лежать все хранилища ключей. Сейчас мы займемся сертификатами для шифрования данных, передаваемых между узлами кластера.

Перейдем в папку /opt/zookeeper/ssl, создадим на всех узлах еще одну вложенную — Quorum-TLS-CA. В ней будет размещен корневой сертификат, которым мы подпишем ключи для шифрования внутрикластерного трафика:

cd /opt/zookeeper/ssl mkdir -m 700 Quorum-TLS-CA

На одном из узлов создаем CA сертификат для Quorum, со сроком действия в 10 лет:

openssl req -new -newkey rsa:4096 -days 3650 -x509 -subj "/CN=ZooKeeper-Security-CA" -keyout Quorum-TLS-CA/ca-key -out Quorum-TLS-CA/ca-cert -nodes

Для удобства дальнейшей настройки лучше скопировать полученные файлы Quorum-TLS-CA/ca-key и Quorum-TLS-CA/ca-cert на все другие узлы (не нужно генерировать свой CA на каждом узле, он нужен один):

# Только на первом узле ssh-keygen -C zookeeperKey -t ed25519 cat /opt/zookeeper/.ssh/id_ed25519.pub >> /opt/zookeeper/.ssh/authorized_keys chmod 600 /opt/zookeeper/.ssh/authorized_keys cat /opt/zookeeper/.ssh/id_ed25519     #секретный ключ cat /opt/zookeeper/.ssh/id_ed25519.pub     #публичный ключ  # На втором и третьем узле mkdir .ssh vim /opt/zookeeper/.ssh/id_ed25519     #вставить секретный ключ из сессии первого хоста vim /opt/zookeeper/.ssh/authorized_keys     #вставить публичный ключ из сессии первого хоста chmod 600 /opt/zookeeper/.ssh/authorized_keys /opt/zookeeper/.ssh/id_ed25519  # Только на первом узле cd /opt/zookeeper/ssl scp Quorum-TLS-CA/* zoo2.local:/opt/zookeeper/ssl/Quorum-TLS-CA scp Quorum-TLS-CA/* zoo3.local:/opt/zookeeper/ssl/Quorum-TLS-CA

На всех узлах в переменную окружения задаем пароль для хранилищ сертификатов для кворума. Этот пароль будет одинаковым и для сертификата, и для хранилищ сертификатов key/trust:

export QPASS=ChangeMeSecretQpass

На всех узлах создаём хранилище доверенных сертификатов (truststore) для zookeeper-quorum, добавляем в него сертификат CA. Файл хранилища будет называться zookeeper-quorum.truststore.jks:

keytool -keystore zookeeper-quorum.truststore.jks -alias CARoot -import -file Quorum-TLS-CA/ca-cert -storepass $QPASS -keypass $QPASS -noprompt

На всех узлах создаём хранилище ключей (keystore) для zookeeper-quorum. Тут указывается имя узла, предподстановка шелла $(hostname -f) нам в этом поможет. Сгенерированный ключ надо будет подписать:

keytool -genkeypair -alias $(hostname -f) -keyalg RSA -keysize 2048 -dname "cn=$(hostname -f)" -keypass $QPASS -keystore zookeeper-quorum.keystore.jks -storepass $QPASS

На всех узлах создаем CSR (Certificate Signing Request) = запрос на выдачу (подпись) SSL сертификата:

keytool -keystore zookeeper-quorum.keystore.jks -alias $(hostname -f) -certreq -file ca-request-zookeeper-$(hostname -f) -storepass $QPASS -keypass $QPASS

Если вы заранее скопировали Quorum-TLS-CA/ca-key и Quorum-TLS-CA/ca-cert на все узлы, то дальше просто выполните команду по подписи этих CSR, в противном случае вам нужно подписать эти файлы там, где есть CA.

Подписываем все три сертификата. В нашем случае ca-key и ca-cert уже есть на всех узлах кластера:

openssl x509 -req -CA Quorum-TLS-CA/ca-cert -CAkey Quorum-TLS-CA/ca-key -in ca-request-zookeeper-$(hostname -f) -out cert-zookeeper-$(hostname -f)-signed -days 3650 -CAcreateserial -passin pass:$QPASS

На всех узлах добавляем сертификат CA в хранилище ключей Zookeeper (keystore):

keytool -keystore zookeeper-quorum.keystore.jks -import -file Quorum-TLS-CA/ca-cert -storepass $QPASS -keypass $QPASS -noprompt -alias CARoot

На всех узлах добавляем подписанный сертификат Zookeeper в хранилище ключей keystore:

keytool -keystore zookeeper-quorum.keystore.jks -import -file cert-zookeeper-$(hostname -f)-signed -storepass $QPASS -keypass $QPASS -alias $(hostname -f) -noprompt

На всех узлах проверяем, что внутри zookeeper-quorum.keystore.jks есть CA и сертификат ОДНОГО узла (т.е. всего два сертификата, не больше):

keytool -list -v -keystore zookeeper-quorum.keystore.jks -storepass $QPASS

На всех узлах удаляем теперь не нужный файл CSR, удаляем ненужный файл подписанного сертификата Zookeeper (он теперь в keystore):

rm cert-zookeeper-*-signed  ca-request-zookeeper-* Quorum-TLS-CA/ca-cert.srl

На всех узлах в /opt/zookeeper/conf/zookeeper.properties добавляем новые строчки конфигурации внутрикластерного взаимодействия. Особое внимание надо уделить паролю: раньше мы его указывали в переменной QPASS, теперь его надо указать дважды.

#Quorum TLS sslQuorum=true  serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory  ssl.quorum.keyStore.location=/opt/zookeeper/ssl/zookeeper-quorum.keystore.jks ssl.quorum.keyStore.password=ChangeMeSecretQpass ssl.quorum.trustStore.location=/opt/zookeeper/ssl/zookeeper-quorum.truststore.jks ssl.quorum.trustStore.password=ChangeMeSecretQpass

На всех узлах необходимо перезапустить Zookeeper, проверить логи, попробовать подключиться:

exit sudo systemctl restart zookeeper sudo su - zookeeper tail -f logs/zookeeper-zookeeper-server-$(hostname -f).out

Сейчас кластер внутри работает по TLS, но снаружи пока PLAIN:

/opt/zookeeper/bin/zkCli.sh -server 127.0.0.1:2181 ls /zookeeper

Server TLS

В этой серии мы создадим CA для клиентских подключений, а также сертификаты для узлов кластера Zookeeper для внешних подключений. Затем аналогичным образом создадим сертификат для клиента.

На одном узле создаем CA для внешних подключений сроком действия 10 лет, разместим его в папке Client-TLS-CA:

cd /opt/zookeeper/ssl mkdir -m 700 Client-TLS-CA openssl req -new -newkey rsa:4096 -days 3650 -x509 -subj "/CN=ZooKeeper-Client-CA" -keyout Client-TLS-CA/ca-key -out Client-TLS-CA/ca-cert -nodes 

Для удобства дальнейшей настройки лучше скопировать полученные файлы Client-TLS-CA/ca-key и Client-TLS-CA/ca-cert на все другие узлы (не нужно генерировать свой CA на каждом узле, он нужен один):

# Только на первом узле cd /opt/zookeeper/ssl for h in zoo2.local zoo3.local; do   ssh ${h} mkdir -m 700 /opt/zookeeper/ssl/Client-TLS-CA   scp Client-TLS-CA/* ${h}:/opt/zookeeper/ssl/Client-TLS-CA done

На всех узлах в переменную окружения задаем пароль для серверных хранилищ сертификатов. Этот пароль будет одинаковым и для сертификата, и для хранилищ сертификатов key/trust:

export SPASS=ChangeMeSecretSpass

На всех узлах создаём хранилище доверенных сертификатов (truststore) для zookeeper-server, добавляем в него сертификат CA:

keytool -keystore zookeeper-server.truststore.jks -alias CARoot -import -file Client-TLS-CA/ca-cert -storepass $SPASS -keypass $SPASS -noprompt

На всех узлах создаём хранилище ключей (keystore) для zookeeper-server. Тут указывается имя узла, сгенерированный ключ надо будет подписать. Для удобства я использую предподстановку шелла $(hostname -f):

keytool -genkeypair -alias $(hostname -f) -keyalg RSA -keysize 2048 -dname "cn=$(hostname -f)" -keypass $SPASS -keystore zookeeper-server.keystore.jks -storepass $SPASS

На всех узлах создаём CSR (Certificate Signing Request) = запрос на выдачу (подпись) SSL сертификата:

keytool -keystore zookeeper-server.keystore.jks -alias $(hostname -f) -certreq -file ca-request-zookeeper-$(hostname -f) -storepass $SPASS -keypass $SPASS

Теперь подписываем сертификат. В нашем случае ca-key уже есть на всех узлах кластера, поэтому просто выполняем команду подписи:

openssl x509 -req -CA Client-TLS-CA/ca-cert -CAkey Client-TLS-CA/ca-key -in ca-request-zookeeper-$(hostname -f) -out cert-zookeeper-$(hostname -f)-signed -days 3650 -CAcreateserial -passin pass:$SPASS

На всех узлах добавляем сертификат CA в хранилище ключей Zookeeper (keystore):

keytool -keystore zookeeper-server.keystore.jks -import -file Client-TLS-CA/ca-cert -storepass $SPASS -keypass $SPASS -noprompt -alias CARoot

На всех узлах добавляем подписанный сертификат zookeeper в хранилище ключей Zookeeper (keystore):

keytool -keystore zookeeper-server.keystore.jks -import -file cert-zookeeper-$(hostname -f)-signed -storepass $SPASS -keypass $SPASS -alias $(hostname -f) -noprompt

На всех узлах проверяем, что внутри есть CA и сертификат ОДНОГО узла (т.е. всего два сертификата, не больше):

keytool -list -v -keystore zookeeper-server.keystore.jks -storepass $SPASS

На всех узлах удаляем теперь ненужный файл CSR, удаляем ненужный файл подписанного сертификата zookeeper-client (он теперь в keystore):

rm cert-zookeeper-*-signed  ca-request-zookeeper-* Client-TLS-CA/ca-cert.srl

На всех узлах необходимо добавить дополнительные настройки в /opt/zookeeper/conf/zookeeper.properties, уделяя внимание паролю ChangeMeSecretSpass:

#Server TLS secureClientPort=2182 authProvider.x509=org.apache.zookeeper.server.auth.X509AuthenticationProvider serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory ssl.trustStore.location=/opt/zookeeper/ssl/zookeeper-server.truststore.jks ssl.trustStore.password=ChangeMeSecretSpass ssl.keyStore.location=/opt/zookeeper/ssl/zookeeper-server.keystore.jks ssl.keyStore.password=ChangeMeSecretSpass ssl.hostnameVerification=false                                                                                                                                           ssl.clientAuth=need  

А также в этом файле следует закомментировать уже ненужную строку — поставить перед ней #:

#clientPort=2181

Ранее она отвечала за нешифрованные соединения, теперь они будут невозможны. Благодаря настройке ssl.hostnameVerification=false мы можем выдавать клиентам обезличенный файл с их сертификатом, и нам будет не важен их реальный hostname.

Client-TLS

На один из узлов добавляем обезличенные store файлы для клиентов. CA будет тот же, все команды аналогичны, просто у клиентов будет другой пароль и имя хоста по маске ‘*.local’. Альтернативно можно пускать всех с настройкой ssl.clientAuth=none (но так я не пробовал).

Для клиентов будет отдельный пароль, но тот же CA.

Для удобства все файлы для подключений клиентов будут отдельно в /opt/zookeeper/client-settings:

cd /opt/zookeeper/client-settings/ export CPASS=ChangeMeSecretCpass #truststore keytool -keystore zookeeper-client.truststore.jks -alias CARoot -import -file ../ssl/Client-TLS-CA/ca-cert -storepass $CPASS -keypass $CPASS -noprompt #keystore keytool -genkeypair -alias zkclient -keyalg RSA -keysize 2048 -dname "cn=*.local" -keypass $CPASS -keystore zookeeper-client.keystore.jks -storepass $CPASS #gen CSR keytool -keystore zookeeper-client.keystore.jks -alias zkclient -certreq -file ca-request-zookeeper-zkclient -storepass $CPASS -keypass $CPASS #sign openssl x509 -req -CA ../ssl/Client-TLS-CA/ca-cert -CAkey ../ssl/Client-TLS-CA/ca-key -in ca-request-zookeeper-zkclient -out cert-zookeeper-zkclient-signed -days 3650 -CAcreateserial -passin pass:$CPASS #import signed keytool -keystore zookeeper-client.keystore.jks -import -file ../ssl/Client-TLS-CA/ca-cert -storepass $CPASS -keypass $CPASS -noprompt -alias CARoot keytool -keystore zookeeper-client.keystore.jks -import -file cert-zookeeper-zkclient-signed -storepass $CPASS -keypass $CPASS -alias zkclient -noprompt #check keytool -list -v -keystore zookeeper-client.keystore.jks -storepass $CPASS #clean up rm cert-zookeeper-*-signed  ca-request-zookeeper-* ..srl

Создадим файл с примером конфигурации zkCli.sh /opt/zookeeper/client-settings/zookeeper-client.properties, не забываем менять ChangeMeSecretCpass:

zookeeper.clientCnxnSocket=org.apache.zookeeper.ClientCnxnSocketNetty zookeeper.client.secure=true zookeeper.ssl.client.enable=true zookeeper.ssl.protocol=TLSv1.2 zookeeper.ssl.trustStore.location=/opt/zookeeper/client-settings/zookeeper-client.truststore.jks zookeeper.ssl.trustStore.password=ChangeMeSecretCpass zookeeper.ssl.keyStore.location=/opt/zookeeper/client-settings/zookeeper-client.keystore.jks zookeeper.ssl.keyStore.password=ChangeMeSecretCpass  ##client will NOT ignore Zk server hostname #zookeeper.ssl.hostnameVerification=false

Теперь содержимое папки /opt/zookeeper/client-settings можно синхронизировать между узлами кластера Zk, и в дальнейшем просто её выборочно предоставлять клиентам Zk:

scp /opt/zookeeper/client-settings/* zoo2.local:/opt/zookeeper/client-settings/ scp /opt/zookeeper/client-settings/* zoo3.local:/opt/zookeeper/client-settings/

Перезапустим сервис Zookeeper c новой конфигурацией. Мы его остановим, а уже настроенный systemd его запустит:

for h in zoo{1..3}.local ; do    echo Reloading Zk on $h   ssh $h "pkill -u zookeeper java && sleep 5" done

Попробуем проверить подключение по SSL:

/opt/zookeeper/bin/zkCli.sh -server zoo1.local:2182 -client-configuration /opt/zookeeper/client-settings/zookeeper-client.properties ls /zookeeper

SASL with MD5

Если вам не нужен ACL, то можно было бы остановиться и на этом, т.к. в кластер Zk смогут попасть только те клиенты, которые обладают подписанным сертификатом (аутентификацией), но мы добавим авторизацию.

Добавляем на всех узлах в /opt/zookeeper/conf/zookeeper.properties:

#SASL with Digest-MD5 requireClientAuthScheme=sasl zookeeper.allowSaslFailedClients=false zookeeper.sessionRequireClientSASLAuth=true # You must add the authProvider.<ID> property for every server that is part of the Zookeeper cluster authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider authProvider.2=org.apache.zookeeper.server.auth.SASLAuthenticationProvider authProvider.3=org.apache.zookeeper.server.auth.SASLAuthenticationProvider

authProvider тут три, по числу узлов в кластере 1,2,3.

К сожалению, в Zookeeper есть глюк, и он не считывает нужные переменные, позволяя подключаться к кластеру как с паролем, так и без него, поэтому позже часть настроек вынесем в unit файл systemd, там сработает.

На всех узлах продолжим составление файлов конфигураций.

Создадим новый файл /opt/zookeeper/client-settings/zookeeper-client-jaas.conf. Это клиентская версия конфига с паролем:

Client {    org.apache.zookeeper.server.auth.DigestLoginModule required    username="bercut"    password="ChangeMeSecretBUpass"; };

А /opt/zookeeper/conf/zookeeper-server-jaas.conf — серверные настройки с паролями. Особую роль играет пользователь super, т.к. на него не распространяются права ACL, ему можно все. Вместо пользователей bercut и test могут быть любые другие:

QuorumServer {    org.apache.zookeeper.server.auth.DigestLoginModule required    user_zookeeper="ChangeMeSecretZUpass"; }; QuorumLearner {        org.apache.zookeeper.server.auth.DigestLoginModule required    username="zookeeper"    password="ChangeMeSecretZUpass"; }; Server {    org.apache.zookeeper.server.auth.DigestLoginModule required    user_super="ChangeMeSecretSUpass"    user_bercut="ChangeMeSecretBUpass"    user_test="ChangeMeSecretTUpass"; };

На всех узлах добавим важные переменные окружения в unit systemd /etc/systemd/system/zookeeper.service:

Environment="SERVER_JVMFLAGS=-Djava.security.auth.login.config=/opt/zookeeper/conf/zookeeper-server-jaas.conf \  -Dzookeeper.authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider \  -Dzookeeper.authProvider.2=org.apache.zookeeper.server.auth.SASLAuthenticationProvider \  -Dzookeeper.authProvider.3=org.apache.zookeeper.server.auth.SASLAuthenticationProvider \  -Dzookeeper.allowSaslFailedClients=false \  -Dzookeeper.sessionRequireClientSASLAuth=true"
exit sudo vim /etc/systemd/system/zookeeper.service sudo systemctl daemon-reload sudo systemctl restart zookeeper.service sudo su - zookeeper

Если не добавить опции после jaas, то Zk будет пускать и с паролем, и без пароля по SSL. Эти настройки принудительно разрешают только SSL.

/opt/zookeeper/conf/zookeeper.properties (что получилось в итоге)
# zoo cluster node info    #server.id=hostname:port1:port2       # id - The ID of the Zookeeper cluster node.       # hostname - The hostname or IP address where the node listens for connections.       # port1 - The port number used for intra-cluster communication.       # port2 - The port number used for leader election. server.1=zoo1.local:2888:3888 server.2=zoo2.local:2888:3888 server.3=zoo3.local:2888:3888  # The number of milliseconds of each tick tickTime=2000 # The number of ticks that the initial synchronization phase can take initLimit=10 # The number of ticks that can pass between sending a request and getting an acknowledgement syncLimit=5 # the directory where the snapshot is stored.  dataDir=/opt/zookeeper/data # the port at which the clients will connect in plain text (it should be commented and disabled) #clientPort=2181  # the maximum number of client connections. increase this if you need to handle more clients maxClientCnxns=60 # # Be sure to read the maintenance section of the  # administrator guide before turning on autopurge. # # https://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance # # The number of snapshots to retain in dataDir autopurge.snapRetainCount=3 # Purge task interval in hours # Set to "0" to disable auto purge feature autopurge.purgeInterval=1  ## Metrics Providers # https://prometheus.io Metrics Exporter metricsProvider.className=org.apache.zookeeper.metrics.prometheus.PrometheusMetricsProvider metricsProvider.httpHost=0.0.0.0 metricsProvider.httpPort=8006 metricsProvider.exportJvmInfo=true  #Quorum TLS sslQuorum=true  serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory  ssl.quorum.keyStore.location=/opt/zookeeper/ssl/zookeeper-quorum.keystore.jks ssl.quorum.keyStore.password=ChangeMeSecretQpass ssl.quorum.trustStore.location=/opt/zookeeper/ssl/zookeeper-quorum.truststore.jks ssl.quorum.trustStore.password=ChangeMeSecretQpass ssl.quorum.hostnameVerification=true  #Server TLS secureClientPort=2182 authProvider.x509=org.apache.zookeeper.server.auth.X509AuthenticationProvider serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory ssl.trustStore.location=/opt/zookeeper/ssl/zookeeper-server.truststore.jks ssl.trustStore.password=ChangeMeSecretCpass ssl.keyStore.location=/opt/zookeeper/ssl/zookeeper-server.keystore.jks ssl.keyStore.password=ChangeMeSecretCpass ssl.hostnameVerification=false ssl.clientAuth=none  #Quorum SASL quorum.auth.enableSasl=true quorum.auth.learnerRequireSasl=true quorum.auth.serverRequireSasl=true quorum.auth.learner.loginContext=QuorumLearner quorum.auth.server.loginContext=QuorumServer quorum.cnxn.threads.size=20  #SASL with Digest-MD5 requireClientAuthScheme=sasl zookeeper.allowSaslFailedClients=false zookeeper.sessionRequireClientSASLAuth=true # You must add the authProvider.<ID> property for every server that is part of the Zookeeper cluster authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider authProvider.2=org.apache.zookeeper.server.auth.SASLAuthenticationProvider authProvider.3=org.apache.zookeeper.server.auth.SASLAuthenticationProvider

/etc/systemd/system/zookeeper.service (что получилось в итоге)
[Unit] Description=Apache Zookeeper server Documentation=http://zookeeper.apache.org Requires=network.target remote-fs.target After=network.target remote-fs.target   [Service] Type=forking User=zookeeper Group=zookeeper  Environment="JAVA_HOME=/usr/lib/jvm/jre-11"  #default heap size is 1000 Mb (set in zkEnv.sh) Environment="ZK_SERVER_HEAP=2000"  Environment="SERVER_JVMFLAGS=-Djava.security.auth.login.config=/opt/zookeeper/conf/zookeeper-server-jaas.conf \                              -Dzookeeper.authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider \                              -Dzookeeper.authProvider.2=org.apache.zookeeper.server.auth.SASLAuthenticationProvider \                              -Dzookeeper.authProvider.3=org.apache.zookeeper.server.auth.SASLAuthenticationProvider \                              -Dzookeeper.allowSaslFailedClients=false \                              -Dzookeeper.sessionRequireClientSASLAuth=true"  ExecStart=/opt/zookeeper/bin/zkServer.sh start /opt/zookeeper/conf/zookeeper.properties ExecStop=/opt/zookeeper/bin/zkServer.sh stop Restart=always   [Install] WantedBy=multi-user.target

Проверка SASL+SSL

Зайдем в Zk через SSL+SASL и проверим, что мы можем посмотреть дерево:

CLIENT_JVMFLAGS="-Djava.security.auth.login.config=/opt/zookeeper/client-settings/zookeeper-client-jaas.conf"  export CLIENT_JVMFLAGS /opt/zookeeper/bin/zkCli.sh -server zoo1.local:2182,zoo2.local:2182,zoo3.local:2182 -client-configuration /opt/zookeeper/client-settings/zookeeper-client.properties ls /

Меняя логин-пароль в /opt/zookeeper/client-settings/zookeeper-client-jaas.conf, мы заходим в Zk под разными пользователями.

Если поменять логин-пароль в zookeeper-client-jaas.conf на заведомо неверный, то команда должна ломаться. Также подключение на несекретный порт 2181 тоже уже не должно работать.

Как работает ACL в действии?

С помощью утилиты zkCli подключимся к Zk пользователем bercut, создадим новый узел /test:

ls / [zookeeper] create /test create /test/1 set /test/1 123456

В корне любой пользователь по умолчанию может делать все что угодно. Проверим:

getAcl  / 'world,'anyone     : cdrwa  getAcl /test 'world,'anyone     : cdrwa

Буквы cdrwa означают:
(c) CREATE: можно создавать child node;
(d) DELETE: можно удалять узел;
(r) READ: можно читать узел и просматривать его подузлы;
(w) WRITE: можно записывать в узел;
(a) ADMIN: можно назначать права.

Изменим права. Заберем все права у всех пользователей, кроме bercut, на узел /test:

setAcl /test world:anyone:,sasl:bercut:cdrwa

Проверим изменения:

getAcl /test 'world,'anyone :  'sasl,'bercut : cdrwa  getAcl /test/1 'world,'anyone : cdrwa

Подключимся пользователем test. Для этого изменим конфиг для подключения /opt/zookeeper/client-settings/zookeeper-client-jaas.conf:

Client {    org.apache.zookeeper.server.auth.DigestLoginModule required    username="test"    password="ChangeMeSecretTUpass"; };

Он не может посмотреть, изменить или удалить узел /test:

ls /   [test, zookeeper]  ls /test Insufficient permission : /test

Пользователь test теперь не может посмотреть на узел /test, ACL сработали.

Однако, если подключиться пользователем super (super/ChangeMeSecretSUpass), то ACL работать не будут, повторюсь, пользователю super можно все, он же суперпользователь:

ls /     [test, zookeeper]  ls /test     [1]  get /test/1 123456

Мониторинг

Внимательный читатель наверняка заметил в конфигурации необъясненные настройки metricsProvider.xxx. Да, это про мониторинг. Сильно не углубляюсь в то, как, что и зачем — статья не совсем об этом.

Если вам все же это интересно

Пример настройки мониторинга через Prometheus и Grafana

prometheus.yml (основная конфигурация Prometheus)
rule_files:    - 'alerts/alerts.zookeeper.yml' ... scrape_configs:   - job_name: 'zookeeper'     scrape_interval: 10s     scrape_timeout:  9s     metrics_path: /metrics     file_sd_configs:       - files:         - 'inventory/*.yml'     relabel_configs:       - source_labels: [jobs]               #pick up config if it has 'zookeeper' in comma-separated list in label 'jobs', drop if not         regex: '(.*,|^)zookeeper(,.*|$)'         action: keep       - regex: '^jobs$'                     #drop unused label 'jobs'         action: labeldrop       - source_labels: [__address__]        #save ip address into 'ip' label         regex: '(.*)(:.*)?'         replacement: '$1'         target_label: ip       - source_labels: [__address__]        #add port if it is absent in target, save ip:port to '__param_target' label         regex: (.*)         replacement: ${1}:8006         target_label: __param_target       - source_labels: [__param_target]     #copy '__param_target' label to 'instance' label          target_label: instance       - source_labels: [__param_target]     #copy '__param_target' label to '__address__' label         target_label: __address__ ...

inventory/Bercut1.yml (список узлов)
--- - targets:   - 192.168.1.11   labels:     jobs: "node_exporter,blackbox-ssh,zookeeper"     owner: "habr"     os: "linux"     hostname: "zoo1.local"     zooCluster: "Bercut1"     noAlarmOn: "predict" - targets:   - 192.168.1.12   labels:     jobs: "node_exporter,blackbox-ssh,zookeeper"     owner: "habr"     os: "linux"     hostname: "zoo2.local"     zooCluster: "Bercut1"     noAlarmOn: "predict" - targets:   - 192.168.1.13   labels:     jobs: "node_exporter,blackbox-ssh,zookeeper"     owner: "habr"     os: "linux"     hostname: "zoo3.local"     zooCluster: "Bercut1"     noAlarmOn: "predict"

alerts/alerts.zookeeper.yml (описания алертов)
groups: - name: zk-alerts   rules:   - alert: ZooKeeper server is down     expr:  up{job="zookeeper"} == 0     for: 1m     labels:       severity: critical     annotations:       summary: "Instance {{ $labels.instance }} ZooKeeper server is down"       description: "{{ $labels.instance }} of job {{$labels.job}} ZooKeeper server is down: [{{ $value }}]."    - alert: create too many znodes     expr: znode_count{job="zookeeper"} > 1000000     for: 1m     labels:       severity: warning     annotations:       summary: "Instance {{ $labels.instance }} create too many znodes"       description: "{{ $labels.instance }} of job {{$labels.job}} create too many znodes: [{{ $value }}]."    - alert: create too many connections     expr: num_alive_connections{job="zookeeper"} > 50 # suppose we use the default maxClientCnxns: 60     for: 1m     labels:       severity: warning     annotations:       summary: "Instance {{ $labels.instance }} create too many connections"       description: "{{ $labels.instance }} of job {{$labels.job}} create too many connections: [{{ $value }}]."    - alert: znode total occupied memory is too big     expr: approximate_data_size{job="zookeeper"} /1024 /1024 > 1 * 1024 # more than 1024 MB(1 GB)     for: 1m     labels:       severity: warning     annotations:       summary: "Instance {{ $labels.instance }} znode total occupied memory is too big"       description: "{{ $labels.instance }} of job {{$labels.job}} znode total occupied memory is too big: [{{ $value }}] MB."    - alert: set too many watch     expr: watch_count{job="zookeeper"} > 10000     for: 1m     labels:       severity: warning     annotations:       summary: "Instance {{ $labels.instance }} set too many watch"       description: "{{ $labels.instance }} of job {{$labels.job}} set too many watch: [{{ $value }}]."    - alert: a leader election happens     expr: increase(election_time_count{job="zookeeper"}[5m]) > 0     for: 1m     labels:       severity: warning     annotations:       summary: "Instance {{ $labels.instance }} a leader election happens"       description: "{{ $labels.instance }} of job {{$labels.job}} a leader election happens: [{{ $value }}]."    - alert: open too many files     expr: open_file_descriptor_count{job="zookeeper"} > 300     for: 1m     labels:       severity: warning     annotations:       summary: "Instance {{ $labels.instance }} open too many files"       description: "{{ $labels.instance }} of job {{$labels.job}} open too many files: [{{ $value }}]."    - alert: fsync time is too long     expr: rate(fsynctime_sum{job="zookeeper"}[1m]) > 100     for: 1m     labels:       severity: warning     annotations:       summary: "Instance {{ $labels.instance }} fsync time is too long"       description: "{{ $labels.instance }} of job {{$labels.job}} fsync time is too long: [{{ $value }}]."    - alert: take snapshot time is too long     expr: rate(snapshottime_sum{job="zookeeper"}[5m]) > 100     for: 1m     labels:       severity: warning     annotations:       summary: "Instance {{ $labels.instance }} take snapshot time is too long"       description: "{{ $labels.instance }} of job {{$labels.job}} take snapshot time is too long: [{{ $value }}]."    - alert: avg latency is too high     expr: avg_latency{job="zookeeper"} > 100     for: 1m     labels:       severity: warning     annotations:       summary: "Instance {{ $labels.instance }} avg latency is too high"       description: "{{ $labels.instance }} of job {{$labels.job}} avg latency is too high: [{{ $value }}]."    - alert: JvmMemoryFillingUp     expr: jvm_memory_bytes_used{job="zookeeper"} / jvm_memory_bytes_max{area="heap"} > 0.8     for: 5m     labels:       severity: warning     annotations:       summary: "JVM memory filling up (instance {{ $labels.instance }})"       description: "JVM memory is filling up (> 80%)\n labels: {{ $labels }}  value = {{ $value }}\n"

Grafana default ZooKeeper dashboard

Послесловие

Я потратил достаточно много времени, чтобы найти описанные выше настройки. Некоторые из них хорошо описаны в официальной документации Zookeeper. Какие-то настройки там раскрыты очень скомкано и их по кусочкам нужно было вытягивать из настроек продуктов, которые работают вместе с Zk. А кое-что вообще пришлось искать на StackOverflow с дальнейшим блужданием по тикетам разработчиков Zk.

Конечно, было бы классно сразу просто выложить сценарий Ansible, который все автоматически настроит, но тогда бы не сложилось понимания, как это все работает.


ссылка на оригинал статьи https://habr.com/ru/articles/764512/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *