
安全扫描甩来一份报告:ZooKeeper 未授权访问。拿四字命令一试,2181 端口果然裸奔——不用任何账号就能看到整套集群的节点信息和数据节点树。真正的麻烦不在改 ZooKeeper 本身,而在这套 ZK 上还挂着 Kafka、Hadoop、ClickHouse 三家:认证一开,它们全都会因为拿不出凭据被拒之门外。本篇记录这次修复的完整过程:ZK 双层 SASL 认证 → 三个组件逐一适配 → ACL 全树收权 → 全链路验证,配置在 ZooKeeper 3.8.4 + Kafka 3.9.1 生产集群验证通过。
系列导航
| 篇 | 场景 | 链接 |
|---|---|---|
| 一 | 内置 ZooKeeper 的 Kafka 集群加 SASL 全套配置 | 已发布,点击直达 |
| 二 | 外置 ZooKeeper,只做 kafka↔zk 之间认证 | 已发布,点击直达 |
| 三 | 外置 ZooKeeper 完整版:quorum 认证 + zk ACL + 客户端认证 | 已发布,点击直达 |
| 四 | 漏洞修复实战:多组件适配 + ACL 全树收权(本篇) | 本篇 |
一、漏洞原理与复现验证
1.1 先确认漏洞真实存在
拿到报告先别急着改,四字命令(four-letter words)一发就知道是不是误报:
[root@hadoop-node1 ~]# echo srvr | nc 192.168.10.11 2181
Zookeeper version: 3.8.4--9d5ba68f2445b6c8ab67c97ec36c9a9ca4c9f74b, built on 2024-02-15 20:47
Latency min/avg/max: 0/2.1/182
Received: 1284753
Sent: 1284691
Connections: 12
Outstanding: 0
Zxid: 0x1a3
Mode: leader
Node count: 3482
[root@hadoop-node1 ~]# echo ruok | nc 192.168.10.11 2181
imok
没有账号、没有密码,直接吐出集群版本、角色、连接数和节点总数——这就是"未授权访问"的实锤。
1.2 危害比看起来大
2181 能裸连,意味着攻击者可以:
- 用自带 zkCli 直接连上,
ls /枚举出brokers、controller、consumers等全部路径,摸清 Kafka 集群拓扑; - 读取 broker 注册信息、消费者位移,甚至直接改写;
- 最坏情况一条
deleteall /把元数据连根拔起,Kafka、Hadoop HA、ClickHouse 副本同步全部瘫痪。
之前写 Kafka SASL 系列(二) 时就强调过:zk 里存的是整个大数据平台的元数据底座,这条通道必须锁。
1.3 修复目标
- 认证:quorum(zk 节点之间)与 server-client(组件连 zk)双层 SASL,裸客户端一律拒绝;
- 授权:ACL 全树收权,认证通过后也只放开必要的权限;
- 不破坏业务:Kafka、Hadoop、ClickHouse 三个组件全部完成适配,修复后照常运行。
二、方案总览与影响面分析
2.1 谁在连这套 ZK
修复前必须把客户端盘点清楚,漏掉一家,重启后那家就直接瘫痪:
| 组件 | 为什么连 ZK | 需要适配什么 |
|---|---|---|
| Kafka 3.9.1 | broker 注册、controller 选举、位移存储 | kafka.jaas 加 Client 段 + zookeeper.set.acl |
| Hadoop(HA 集群) | NameNode ZKFC 主备选举、YARN 状态存储 | zookeeper-jaas.conf + HADOOP_OPTS 挂 JAAS |
| ClickHouse | ReplicatedMergeTree 副本同步、leader 选举 | config.xml 的 <zookeeper> 加 <identity> |
2.2 修复路线
停 Kafka → 停 Hadoop → 停 ClickHouse → 改 ZK 配置(SASL)
→ 起 ZK → 改 Kafka 并逐台起 → 改 Hadoop 并逐台起
→ 改 ClickHouse 并逐台起 → ACL 全树收权 → 全链路验证
ACL 收权一定放在最后:等所有组件都能带凭据正常连上之后再执行,否则 setAcl -R 一生效,没适配完的组件立刻 NoAuth。
环境约定:ZK 集群两节点 hadoop-node1/2(192.168.10.11/12),Kafka 在 kafka01(192.168.10.31),安装根目录 /opt/bigdata。本文密码用 ZkAdmin@123(super/quorum)、ZkUser@123(组件客户端)演示,上线前务必替换。
三、ZooKeeper 侧:开启双层 SASL 认证
这部分的配置与系列二、三完全一致,这里快速过一遍,每条参数的来龙去脉见系列(二)与系列(三)。
3.1 zk_server.jaas(服务端身份 + 客户端账号库)
集群每个节点都要做。进入配置目录:
[root@hadoop-node1 ~]# cd /opt/bigdata/zookeeper/apache-zookeeper-3.8.4-bin/conf
新建 zk_server.jaas,三段各管一件事:
Server {
org.apache.zookeeper.server.auth.DigestLoginModule required
user_super="ZkAdmin@123"
user_zkuser="ZkUser@123";
};
QuorumServer {
org.apache.zookeeper.server.auth.DigestLoginModule required
user_quorum="ZkAdmin@123";
};
QuorumLearner {
org.apache.zookeeper.server.auth.DigestLoginModule required
username="quorum"
password="ZkAdmin@123";
};
Server段是账号库:user_xxx="密码"定义了客户端可用的账号,后面 Kafka/Hadoop/ClickHouse 全部用zkuser连接;QuorumServer/QuorumLearner两段服务 quorum 认证:leader 与 follower 之间也要互相验明正身,防止伪造节点混进选举。
3.2 zk_client.jaas(zk 自带命令行工具用)
Client {
org.apache.zookeeper.server.auth.DigestLoginModule required
username="zkuser"
password="ZkUser@123";
};
3.3 zoo.cfg 追加认证参数
# ===== Server-Server (quorum) 互相认证 =====
quorum.auth.enableSasl=true
quorum.auth.learnerRequireSasl=true
quorum.auth.serverRequireSasl=true
quorum.auth.learner.saslLoginContext=QuorumLearner
quorum.auth.server.saslLoginContext=QuorumServer
# 建议设为集群节点数 n*2(两节点配 4,三节点配 6)
quorum.cnxn.threads.size=4
# ===== Server-Client 之间认证 =====
authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider
# 强制客户端必须 SASL 认证,裸连直接拒绝(版本要求 zk >= 3.6.0)
sessionRequireClientSASLAuth=true
3.4 启动脚本挂 JAAS
编辑 bin/zkServer.sh,开头加两行环境变量:
export SERVER_JVMFLAGS="${SERVER_JVMFLAGS} -Djava.security.auth.login.config=/opt/bigdata/zookeeper/apache-zookeeper-3.8.4-bin/conf/zk_server.jaas"
export CLIENT_JVMFLAGS="${CLIENT_JVMFLAGS} -Djava.security.auth.login.config=/opt/bigdata/zookeeper/apache-zookeeper-3.8.4-bin/conf/zk_client.jaas"
逐台重启 ZK:
/opt/bigdata/zookeeper/apache-zookeeper-3.8.4-bin/bin/zkServer.sh restart
zkServer.sh status 确认 leader/follower 角色正常,再进行下一步。
四、Kafka 适配
4.1 kafka.jaas 加 Client 段
在 Kafka 的 config 目录(/opt/bigdata/kafka/kafka_2.13-3.9.1/config)新建或编辑 kafka.jaas:
Client {
org.apache.zookeeper.server.auth.DigestLoginModule required
username="zkuser"
password="ZkUser@123";
};
4.2 server.properties 开启 ACL
zookeeper.set.acl=true
4.3 逐台重启
cd /opt/bigdata/kafka/kafka_2.13-3.9.1
bin/kafka-server-start.sh -daemon config/server.properties
broker 在 /brokers/ids 正常注册、kafka-topics.sh --describe 不报错,说明 kafka→zk 认证通了。与本文环境同源的集群升级记录见 Kafka 2.8→3.9.1 跨大版本升级实战。
五、Hadoop 适配(ZKFC 与 YARN 的凭据)
5.1 为什么要单独适配
Hadoop HA 集群里连 ZK 的不止一个角色:NameNode 的 ZKFC 靠 ZK 做主备选举锁,YARN 的 ResourceManager 也可能用 ZK 存状态。sessionRequireClientSASLAuth=true 生效后,这些 JVM 拿不出 SASL 凭据会直接被 ZK 拒绝——表现就是 ZKFC 起不来、HA 沦为单点,比 Kafka 挂掉更隐蔽。
5.2 新增 zookeeper-jaas.conf
切换到 hadoop 用户,在 Hadoop 配置目录创建 JAAS 文件:
su - hadoop
cd $HADOOP_HOME/etc/hadoop
vi zookeeper-jaas.conf
Client {
org.apache.zookeeper.server.auth.DigestLoginModule required
username="zkuser"
password="ZkUser@123";
};
5.3 挂进 HADOOP_OPTS
修改 yarn-env.sh 和 hadoop-env.sh,追加:
export HADOOP_OPTS="$HADOOP_OPTS -Djava.security.auth.login.config=$HADOOP_CONF_DIR/zookeeper-jaas.conf"
走全局 HADOOP_OPTS 的好处是 NameNode/ZKFC/ResourceManager 各守护进程统一继承,不用逐个角色单独配。
5.4 滚动重启
顺序原则:停和起都从同一台开始,逐台操作,确认上一台状态正常再动下一台。我们的做法是先停第三台(备角色所在节点),再停另外两台;启动时同样先起第三台,再起另外两台:
# 停:先第三台,后另外两台
$HADOOP_HOME/sbin/hadoop-daemon.sh stop zkfc
$HADOOP_HOME/sbin/hadoop-daemon.sh stop namenode
# 起时反过来:先第三台,后另外两台
$HADOOP_HOME/sbin/hadoop-daemon.sh start namenode
$HADOOP_HOME/sbin/hadoop-daemon.sh start zkfc
hdfs haadmin -getAllServiceState 确认一个 active 一个 standby、ZKFC 进程存活,YARN 的 yarn node -list 正常返回,适配完成。
六、ClickHouse 适配(identity 一行配置)
6.1 ClickHouse 与 ZK 的关系
ReplicatedMergeTree 系列表的副本同步、merge 协调、leader 选举全靠 ZK 上的副本日志。ZK 开认证后 CK 连不上,典型症状是写入变慢、system.replicas 里 is_alive=0、副本队列积压。建表侧的 ZooKeeper 用法可以回看 ClickHouse 可观测数据建模实战。
6.2 config.xml 加 identity
ZK 侧不需要为 CK 做任何额外配置,只在 CK 每个节点的 config.xml 的 <zookeeper> 标签内增加一行口令:
cd /etc/clickhouse-server
vi config.xml
<zookeeper>
<!-- 新增:digest 认证账号,格式 user:password -->
<identity>zkuser:ZkUser@123</identity>
<node index="1">
<host>hadoop-node1</host>
<port>2181</port>
</node>
<node index="2">
<host>hadoop-node2</host>
<port>2181</port>
</node>
</zookeeper>
6.3 重启顺序
先逐台重启 ZK(第三节的操作已完成),再逐台重启 ClickHouse:
systemctl restart clickhouse-server
SELECT hostName(), is_alive, queue_size FROM system.replicas 逐台核对副本健康。
七、ACL 全树收权
认证解决"你是谁",ACL 解决"你能干什么"。前面组件全部适配完成后,最后一步把整棵节点树的权限收给 zkuser。
7.1 登录 zkCli 并认证
在任意一个 ZK 节点执行(CLIENT_JVMFLAGS 把客户端 JAAS 挂进去):
cd /opt/bigdata/zookeeper/apache-zookeeper-3.8.4-bin/bin
export CLIENT_JVMFLAGS="${CLIENT_JVMFLAGS} -Djava.security.auth.login.config=/opt/bigdata/zookeeper/apache-zookeeper-3.8.4-bin/conf/zk_client.jaas"
./zkCli.sh -server 192.168.10.11:2181
7.2 addauth 再 setAcl
[zk: 192.168.10.11:2181(CONNECTED) 0] ls /
[admin, brokers, cluster, config, consumers, controller, controller_epoch, feature, isr_change_notification, kafka-acl, kafka-acl-changes, latest_producer_id_block, log_dir_event_notification, zookeeper]
[zk: 192.168.10.11:2181(CONNECTED) 1] addauth digest zkuser:ZkUser@123
[zk: 192.168.10.11:2181(CONNECTED) 2] setAcl -R / auth:zkuser:ZkUser@123:cdrwa
KeeperErrorCode = NoAuth for /zookeeper/config
-R 表示递归整棵树,cdrwa 是全部五种权限(创建/删除/读/写/管理)。auth:... 语法的含义:凡是已通过 digest 认证且身份匹配的会话都持有这套权限。
7.3 NoAuth 报错是预期行为
setAcl -R / 扫到 /zookeeper/config 时会报 KeeperErrorCode = NoAuth——这个节点由 ZooKeeper 自身管理(存放运行期配置),任何会话都不允许改它的 ACL,属正常现象,不影响其余节点收权。用 getAcl 验证:
[zk: 192.168.10.11:2181(CONNECTED) 3] getAcl /
'sasl,'zkuser
: cdrwa
'digest,'zkuser:xU8wKq2zQmVbN3cLdR7tYg==
: cdrwa
根节点已经同时挂上 SASL 与 digest 两套身份的权限,收权完成。
八、全链路验证清单
| 验证项 | 命令/方法 | 预期 |
|---|---|---|
| 裸客户端被拒 | 不带 JAAS 直接 zkCli.sh -server 192.168.10.11:2181 | 连接被 sessionRequireClientSASLAuth 拒绝 |
| 带凭据连接 | 第三/五/六节方式挂 JAAS/identity 后连接 | 正常执行 ls/get |
| 四字命令 | echo ruok | nc 192.168.10.11 2181 | 按白名单配置响应(默认白名单外不再吐信息) |
| Kafka | kafka-topics.sh --describe + 生产消费试跑 | 注册正常、消息收发正常 |
| Hadoop | hdfs haadmin -getAllServiceState、yarn node -list | 一 active 一 standby、节点正常 |
| ClickHouse | SELECT is_alive, queue_size FROM system.replicas | 副本存活、队列不积压 |
九、踩坑清单
| 坑 | 现象 | 解法 |
|---|---|---|
| ACL 收权做早了 | 某组件未适配完,setAcl -R 后立刻 NoAuth 大面积报错 | 收权永远放最后一步;出问题可用 super 账号登录救回 |
setAcl -R / 报 NoAuth | 扫到 /zookeeper/config 报 KeeperErrorCode = NoAuth | 该节点 ZK 自管,属预期,忽略即可 |
| ZK 版本低于 3.6 | sessionRequireClientSASLAuth 不生效或不识别 | 先升级 ZK,参考Kafka/ZK 平滑升级 |
| quorum 线程数没调 | 大流量下 quorum 认证握手排队、选举变慢 | quorum.cnxn.threads.size 设为节点数 ×2 |
| JAAS 文件权限过松 | 密码明文可被其他用户读取 | chmod 600 *.jaas,属最小加固动作 |
| 只改了单个 ZK 节点 | 集群选举分裂 | 所有 ZK 节点同步修改后再逐台重启 |
| Hadoop 忘配 zookeeper-jaas.conf | ZKFC 反复重启、HA 降级 | 按第五节补齐 HADOOP_OPTS 再滚动重启 |
| CK 只改了一个节点 | 部分副本 is_alive=0 | config.xml 逐节点都加 <identity> |
总结
这次漏洞修复的完整链路:四字命令复现漏洞 → ZK 双层 SASL(Server/QuorumServer/QuorumLearner + zoo.cfg)→ Kafka(kafka.jaas + zookeeper.set.acl)→ Hadoop(zookeeper-jaas.conf + HADOOP_OPTS)→ ClickHouse(config.xml identity)→ addauth + setAcl -R 全树收权 → 六项验证。核心经验就一句:修 ZooKeeper 的漏洞,功夫在 ZooKeeper 之外——把挂在它上面的每一个客户端都盘点到、适配完,认证和收权才能安全落地。配合系列一至三食用,Kafka+ZK 的认证体系就完整闭环了;同期的 Hadoop 集群安全加固 也是同一轮漏扫的产物,ZK session 调优可以看 YARN 双 Standby 排查。
你们平台的 ZK 是不是也在裸奔?漏扫报告里还见过哪些组件的未授权访问,评论区聊聊处理思路。
系列文章配置在 ZooKeeper 3.8.4 + Kafka 3.9.1 + Hadoop HA + ClickHouse 生产集群验证;示例密码请替换后使用。
转载自 CSDN-专业IT技术社区




