如果你手头的Kafka集群到现在还是“裸奔”状态——9092端口随便连、消息明文传输、只要知道Broker地址就能读写——那这篇文章值得你从头到尾看完。这里聊的不是简单加个密码,而是Kafka集群在生产环境落地安全认证与加密机制的一整套方案,涵盖SASL认证、TLS/SSL加密、ACL授权,以及证书轮换和排错经验。适合正在规划Kafka集群安装、准备把消息队列从测试环境推向生产的运维和开发同学参考。
我在这套方案上踩过不少坑,比如证书SAN漏配导致客户端握手超时、开完ACL把所有应用全踢下线、滚动重启时集群集群状态抖动。这次把流程重新整理一遍,按“为什么做、怎么选型、怎么落地、怎么排错”来写,尽量做到看完就能照着操作。
1. 先搞清楚:Kafka集群的安全认证与加密到底解决什么问题
1.1 没有安全机制时,你的Kafka集群在裸奔
Kafka默认配置下,监听器使用的security.protocol=PLAINTEXT,Broker的9092端口对网络内所有主机开放。只要有人能ping通这个IP,他就可以用任意消费组读到topic里的全部消息,甚至执行kafka-topics.sh --delete把topic删掉。很多团队觉得“内网环境安全”,实际上内网里有大量跳板机、测试程序、临时脚本,任何一个环节被攻破,Kafka里的业务数据就等于直接暴露。
还有一点容易被忽视:默认情况下Broker间通信也是明文。消息从Producer到Broker、从Broker到Consumer,整个链路不加密,用tcpdump在任意一台交换机上抓包就能还原消息内容。对涉及订单、用户、交易数据的业务来说,这是完全不可接受的。
我见过一个真实案例:某团队把Kafka和开发测试环境搭在同一个VPC里,结果一个爬虫脚本扫到9092端口,直接消费了线上topic几百万条数据,最后靠消息里的业务字段反查才定位到问题。这个锅不该甩给网络隔离,而是Kafka集群本身缺少认证和授权机制。
1.2 认证、加密、授权三件套的职责边界
Kafka安全体系可以拆成三层,每一层解决一个独立问题:
- 认证(Authentication):确认“你是谁”。Kafka通过
SASL机制实现,支持PLAIN、SCRAM、OAUTHBEARER、GSSAPI等。 - 加密(Encryption):保证传输数据不可读。Kafka通过
TLS/SSL实现,消息在网络上传输时是密文。 - 授权(Authorization):确认“你能干什么”。Kafka用
ACL(Access Control List)控制某个用户对某类资源的操作权限。
很多团队的误区是只做了其中一两项。比如用了SASL_PLAINTEXT,认证有了,但消息还是明文传输,等于大门上了锁但窗户敞开着;又比如只做了TLS加密但没配ACL,所有能连上的客户端都能读写所有topic,跨部门数据隔离就形同虚设。我自己的实践是:生产环境必须三层全上,测试环境也至少要设置SASL认证,否则数据泄露的锅说不清。
1.3 什么时候必须上这套机制
如果你满足以下任意一条,安全认证与加密就不是可选项而是必选项:
- 集群中流转的是用户隐私、交易订单、日志脱敏前的原始数据;
- 集群被多个业务部门或外部系统访问,存在跨信任边界的流量;
- 集群暴露在非完全受控的网络环境,比如混合云、公网接入;
- 有合规审计要求,需要明确“谁能访问、访问了什么、传输是否加密”。
版本兼容也要提前确认。Kafka 2.x到3.x的配置项大体一致,但新版Kafka在逐步摈弃ZooKeeper模式转向KRaft模式,kafka-configs.sh、kafka-acls.sh这类工具的参数也从--zookeeper过渡到--bootstrap-server。我这次演示以经典的3节点ZooKeeper模式为例,但命令会同时标注KRaft模式下的写法,思路完全通用。
2. 方案选型:SASL认证机制和TLS加密策略怎么定
2.1 SASL机制对比:PLAIN、SCRAM、OAUTHBEARER、GSSAPI,选哪个
Kafka在listener.name.<listener>.sasl.enabled.mechanisms里配置允许使用的SASL机制,常见的有下面几种:
| 机制 | 底层依赖 | 凭证存储 | 适用场景 | 我的建议 |
|---|---|---|---|---|
| PLAIN | 无 | 通过JAAS静态配置,或集成外部认证 | 简单认证、测试环境 | 生产环境慎用,凭证更新要重启Broker |
| SCRAM-SHA-256/512 | RFC 5802 | 存放在ZooKeeper或KRaft元数据中 | 生产环境最常见的用户名密码方案 | 推荐,凭证可以动态增删改查 |
| OAUTHBEARER | OAuth 2.0 | 对接公司统一认证中心 | 已有成熟IDP体系的公司 | 成本较高,但权限管理更统一 |
| GSSAPI | Kerberos | Kerberos KDC | 传统大数据平台、公司强制Kerberos | 部署复杂度最高,非必要不上 |
如果你公司还没有统一的认证平台,我建议直接选SCRAM-SHA-512。理由很简单:它支持通过kafka-configs.sh动态维护用户凭证,不用重启Broker;密码字段在ZooKeeper中保存,不会像PLAIN那样直接写在JAAS文件里;客户端配合scram.ScramLoginModule配置很成熟。
注意一点:PLAIN通常要求SASL的“明文通道”设置,也就是ssl.client.auth或加密通道作为前提,否则密码等于裸奔传输。而SCRAM因为采用挑战应答机制,即使只跑在SASL_PLAINTEXT上也不会直接暴露密码,安全性高不少。这也是我在内网环境也坚持用SCRAM的原因。
2.2 SSL和SASL_SSL的应对场景
listener.security.protocol.map里常见的搭配有:
PLAINTEXT:无认证无加密,只适合本地开发;SASL_PLAINTEXT:有认证无加密,适合完全可信的内网但又有权限控制需求;SSL:只做TLS加密,不做应用层认证,可以用证书做客户端身份识别;SASL_SSL:认证和加密同时启用,生产环境的标准组合。
如果你的流量要跨机房、跨云、经过负载均衡,或者中间有任何不受你控制的网络节点,那就必须用SASL_SSL。另外我强烈建议将Broker间通信(inter.broker.listener.name)也纳入SASL机制,否则外部认证做了半天,内部Broker之间还是明文,等于后门大开。
从运维权衡来看,TLS带来的CPU开销主要在握手阶段,连接建立之后对称加密走硬件加速,对吞吐影响很小。我用3节点集群压过,开启TLS后吞吐下降大概在5%到8%之间,对于绝大多数业务完全可以接受。
2.3 证书规划与集群拓扑设计
证书规划往往是整个安全机制里最容易被搞砸的一环。先说说我们这次的拓扑:
- 3个Kafka Broker节点,主机名分别是
kafka1、kafka2、kafka3; - 每个Broker使用独立的TLS证书,证书的SAN(Subject Alternative Name)包含该节点的主机名和内网IP;
- 使用自建CA统一签发证书,所有Broker和客户端都在Java Keystore/Truststore中导入同一个CA证书;
- ZooKeeper节点不需要TLS(如果完全在内网),但Broker与ZooKeeper之间建议通过防火墙或安全组隔离。
为什么每个Broker要独立证书,而不是三个节点共用一张?因为客户端连接时会校验目标主机名是否与证书匹配。如果你通过kafka1:9093访问,而证书里没有kafka1这个SAN,握手阶段就会报SSLHandshakeException或主机名校验失败。共用一张证书不是不行,但那需要证书包含所有可能的访问域名和IP,SAN列表会非常长,轮换时也容易遗漏。
证书有效期我建议一年一签,别一上来签十年。Kafka集群通常每年有版本升级或节点迁移计划,证书跟着生命周期走反而省事。后面还会专门讲轮换流程。
3. 实操落地:给3节点Kafka集群加上SCRAM认证和TLS加密
3.1 准备阶段:证书签发与信任库初始化
我习惯在所有节点上统一建立/etc/kafka/ssl目录,然后在一台管理机上完成CA签发,再把生成的证书文件分发到各Broker节点。下面步骤以kafka1为例,其他节点把别名和SAN换成自己的主机名即可。
第一步,创建自建CA。CA私钥务必保存到安全的位置并设置文件权限,它一旦泄露,整个集群的信任体系就崩塌了。
# 生成CA私钥 openssl genrsa -out ca.key 2048 # 生成CA自签证书,10年有效期 openssl req -new -x509 \ -key ca.key \ -out ca.crt \ -days 3650 \ -subj "/CN=Kafka-CA"第二步,为Broker节点创建Keystore。注意-ext参数指定SAN,这是最容易踩坑的地方。
keytool -genkeypair \ -alias kafka1 \ -keyalg RSA \ -keysize 2048 \ -keystore kafka1.keystore.jks \ -storepass changeit \ -keypass changeit \ -dname "CN=kafka1" \ -ext "SAN=dns:kafka1,ip:192.168.10.11"第三步,生成CSR并用CA签名。
# 导出证书请求 keytool -certreq \ -alias kafka1 \ -keystore kafka1.keystore.jks \ -storepass changeit \ -file kafka1.csr \ -ext "SAN=dns:kafka1,ip:192.168.10.11" # 用CA签名,有效期365天 openssl x509 -req \ -in kafka1.csr \ -CA ca.crt \ -CAkey ca.key \ -CAcreateserial \ -out kafka1.crt \ -days 365 \ -extfile <(printf "subjectAltName=dns:kafka1,ip:192.168.10.11")第四步,把CA证书导回Keystore和Truststore。Truststore每个Broker都需要,客户端的Truststore可以直接用同一个ca.crt生成。
# 将CA证书导入Broker自己的Keystore keytool -importcert \ -alias ca \ -keystore kafka1.keystore.jks \ -storepass changeit \ -trustcacerts \ -file ca.crt # 将签名后的Broker证书导入Keystore keytool -importcert \ -alias kafka1 \ -keystore kafka1.keystore.jks \ -storepass changeit \ -file kafka1.crt # 生成Truststore并导入CA证书 keytool -importcert \ -alias ca \ -keystore truststore.jks \ -storepass changeit \ -trustcacerts \ -file ca.crtkeystore存放的是本节点的私钥和证书链,truststore存放的是需要信任的CA证书集合。两者密码可以相同,但我建议区分,便于权限控制和审计。这里所有密码先用changeit占位,部署到生产时必须换成强密码并统一管理。
3.2 服务端配置server.properties
以kafka1节点为例,server.properties里需要重点配置以下几块:
# 监听地址和协议映射 listeners=INTERNAL://0.0.0.0:9092,EXTERNAL://0.0.0.0:9093 advertised.listeners=INTERNAL://kafka1:9092,EXTERNAL://kafka1:9093 listener.security.protocol.map=INTERNAL:SASL_PLAINTEXT,EXTERNAL:SASL_SSL inter.broker.listener.name=INTERNAL # SASL机制 sasl.enabled.mechanisms=SCRAM-SHA-512 sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512 # SSL配置 ssl.keystore.location=/etc/kafka/ssl/kafka1.keystore.jks ssl.keystore.password=changeit ssl.key.password=changeit ssl.truststore.location=/etc/kafka/ssl/truststore.jks ssl.truststore.password=changeit ssl.client.auth=none这里有个设计上的取舍:我把INTERNAL设置为SASL_PLAINTEXT,EXTERNAL设置为SASL_SSL。内网Broker间通信走认证但不加密,客户端访问走完整加密链路。如果你的内网环境不够可信,也可以把INTERNAL也改成SASL_SSL,代价是多一些CPU开销。
advertised.listeners是Kafka里著名的坑点。客户端拿到的是这个配置里的地址去建连,所以你填kafka1就必须确保客户端能解析这个主机名,并且证书SAN里包含它。很多人在公网环境填内网IP,导致客户端连接超时;填了主机名但证书里没这个SAN,导致SSL握手失败。两个问题我都排查过,最后都指向配置与事实不一致。
配置完成后,服务端的三类日志会给你很多线索:启动时的KafkaConfig打印会显示安全协议映射;认证日志会打印Authentication failed;SSL错误通常在异常堆栈里直接体现。
3.3 用户凭证与ACL先行配置
服务端配置文件改完后,先不要急着启动客户端。我们先把用户凭证创建好,否则认证永远过不了。
ZooKeeper模式的Kafka执行:
kafka-configs.sh \ --zookeeper localhost:2181 \ --alter \ --add-config 'SCRAM-SHA-512=[password=admin-sec]' \ --entity-type users \ --entity-name adminKRaft模式则改成:
kafka-configs.sh \ --bootstrap-server kafka1:9092 \ --alter \ --add-config 'SCRAM-SHA-512=[password=admin-sec]' \ --entity-type users \ --entity-name admin创建完用户后,在server.properties里声明超级用户:
super.users=User:adminsuper.users里的用户拥有全部资源的所有操作权限,不受ACL限制。生产环境这个列表必须严格控制,通常只放管理员账号。
还要设置一个安全默认值:
allow.everyone.if.no.acl.found=false这个参数很关键。如果保持默认的true,当某个topic没有任何ACL规则时,所有认证用户都能访问它,ACL就形同虚设了。设成false后,没有显式授权的资源一律拒绝访问,这才是真正的白名单模式。
3.4 客户端连接配置
服务端配置完成后,客户端的接入方式也需要一并讲透。先看一个标准的client.properties:
security.protocol=SASL_SSL sasl.mechanism=SCRAM-SHA-512 ssl.truststore.location=/etc/kafka/ssl/truststore.jks ssl.truststore.password=changeit sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required \ username="producer" \ password="producer-sec";注意ssl.truststore是客户端用来验证Broker证书的,这里只需要导入CA证书,不需要客户端私钥。sasl.jaas.config这个参数是Kafka 2.2之后引入的,可以直接写在properties里,不用单独去搞JAAS文件,对Java和Spring Boot客户端都很友好。
如果你用的是kafka-console-consumer.sh这类命令行工具,它不直接支持sasl.jaas.config参数,这时候用传统的JAAS文件方式:
# client_jaas.conf KafkaClient { org.apache.kafka.common.security.scram.ScramLoginModule required username="consumer" password="consumer-sec"; };kafka-console-consumer.sh \ --bootstrap-server kafka1:9093,kafka2:9093,kafka3:9093 \ --topic test-topic \ --consumer.config client.properties \ -Djava.security.auth.login.config=/etc/kafka/client_jaas.confJava Producer的配置就是把上面的client.properties加载到Properties对象里。重点提醒一句:bootstrap.servers里填的地址必须是advertised.listeners中对外暴露的地址,也就是走9093端口,不要用9092,否则你的TLS配置根本不生效。
4. 权限控制深入:从“能连上”到“只能干该干的”
4.1 ACL的基础概念和常用命令
认证通过只是第一步,真正区分“谁能做什么”靠的是ACL。Kafka的ACL模型包含几个核心概念:
- Principal:用户主体,格式为
User:producer; - Resource:资源,包括topic、group、cluster、transactional_id等;
- Operation:操作,包括Read、Write、Describe、Create、Alter、Delete等;
- Pattern:匹配模式,支持
LITERAL和PREFIXED。
常用操作命令统一用kafka-acls.sh,新版也可以指定--bootstrap-server,这样不需要直接访问ZooKeeper。我习惯在管理机上执行,命令如下:
# 查看某个topic当前的ACL kafka-acls.sh \ --bootstrap-server kafka1:9093 \ --command-config admin-client.properties \ --topic test-topic \ --list4.2 最小权限策略的实战配置
我们按照最小权限原则来设计一组典型的业务账号:
| 账号 | 用途 | 需要的权限 |
|---|---|---|
| producer | 数据生产 | 对目标topic有Write和Describe权限 |
| consumer | 数据消费 | 对topic有Read和Describe权限,对消费组有Read和Describe权限 |
| admin | 管理维护 | 超级用户,不受ACL限制 |
操作命令我直接给出,方便你照抄:
# 给producer授权,允许向test-topic写入 kafka-acls.sh \ --bootstrap-server kafka1:9093 \ --command-config admin-client.properties \ --add \ --allow-principal User:producer \ --operation Write \ --operation Describe \ --topic test-topic # 给consumer授权,允许读取test-topic,并管理消费组 kafka-acls.sh \ --bootstrap-server kafka1:9093 \ --command-config admin-client.properties \ --add \ --allow-principal User:consumer \ --operation Read \ --operation Describe \ --topic test-topic \ --group consumer-group这里很容易漏给Describe权限。很多人只给Write或Read,结果客户端一启动就报TopicAuthorizationException,排查半天发现只是Describe没放行。原因是Kafka的客户端在获取元数据时需要对topic有Describe权限,否则连分区信息都拿不到。
消费组ACL也经常踩坑。消费者的--group必须与客户端配置的group.id完全一致,如果用*号匹配要特别小心,PREFIXED匹配的语义跟通配符不同,我建议前期全部用LITERAL精确匹配,等规则多了再考虑前缀模式。
遇到过“kafka消费会重复消费吗”这类问题,如果ACL配置正确,重复消费通常不是安全问题,而是消费组或offset位点的问题。不过权限没控制好导致别人也能动你的消费组,就会引发offset被重置、消息被重复消费之类的事故,从安全角度讲这也不是小问题。
4.3 使用AKHQ等可视化工具时如何接入安全配置
很多团队会部署Kafka可视化工具,比如AKHQ(也叫Kafka HQ)、Kafka UI等,用来查看topic、查看Kafka Connector任务状态。一旦集群开启了SASL_SSL,这些工具也需要同步适配,否则只能看到“连接失败”。
以AKHQ为例,它的application.yml里需要配置Kafka集群的认证信息和证书路径。大致配置如下:
akhq: connections: my-cluster: properties: bootstrap.servers: "kafka1:9093,kafka2:9093,kafka3:9093" security.protocol: "SASL_SSL" sasl.mechanism: "SCRAM-SHA-512" ssl.truststore.location: "/etc/akhq/truststore.jks" ssl.truststore.password: "changeit" sasl.jaas.config: "org.apache.kafka.common.security.scram.ScramLoginModule required username=\"admin\" password=\"admin-sec\";" # 可选:配置要展示的 topic、group 等在配AKHQ时我吃过一次亏:工具本身的JVM参数里没有指定truststore,导致它始终无法通过TLS握手。后来我直接把ssl.truststore.location写进连接配置,问题就解决了。如果你用的是容器化部署,记得把Truststore文件挂载进去,别用相对路径。
5. 安全机制跑起来之后:日志、排错与证书轮换
5.1 安全相关的日志到底看哪几行
安全机制配置完毕后,最需要磨炼的就是看日志的能力。Kafka的日志主要分布在每个节点的logs/目录下,最有用的是server.log。我常用的排查命令:
# 查看认证失败相关日志 grep -i "authentication failed" /var/log/kafka/server.log # 查看SSL握手相关错误 grep -i "SSLHandshake" /var/log/kafka/server.log # 查看授权失败 grep -i "authorization" /var/log/kafka/server.log认证失败一行典型日志长这样:
INFO [Auth] Client connection from 192.168.10.100 failed: Authentication failed: Client failed to authenticate as producer这个日志直接告诉你三件事:来源IP、目标用户、失败阶段。SCRAM认证失败大多是用户名密码不对,或者凭证还没在ZooKeeper里创建成功。授权失败会在客户端报TopicAuthorizationException,服务端同时打印类似Principal = User:producer ... denied的日志,这时候去补ACL就对了。
5.2 高频报错排查清单
把我在生产环境遇到过的、社区群里高频出现的报错整理成一张表,按这个思路排查会快很多。
| 报错或现象 | 根本原因 | 处理方法 |
|---|---|---|
InvalidReceiveException: Invalid receive (size = ...) | 客户端用PLAINTEXT协议访问SASL或SSL监听端口,协议不匹配 | 检查客户端security.protocol是否与服务端listener.security.protocol.map一致,最常见的是9093端口用了明文连接 |
SSLHandshakeException: no cipher suites in common | 客户端Truststore缺失、证书链不完整,或TLS版本不匹配 | 重新导入完整的CA证书链,检查两端ssl.enabled.protocols是否兼容 |
Authentication failed: Client failed to authenticate | SCRAM用户名密码错误,或凭证未同步到所有节点 | 用kafka-configs.sh重建凭证,确认所有Broker都能读取到ZooKeeper中的新增用户 |
TopicAuthorizationException: Not authorized to access topic | ACL未配置,或allow.everyone.if.no.acl.found=false | 补上对应topic的Describe和Read/Write权限 |
TimeoutException后客户端不断重连 | advertised.listeners与证书SAN不匹配,客户端路由到了错误地址 | 用nslookup和openssl s_client -connect验证地址解析和证书SAN |
| Broker间复制延迟上升且伴随认证日志大量失败 | sasl.mechanism.inter.broker.protocol配置错误,内部通信频繁重新认证 | 检查inter.broker.listener.name对应的监听器机制是否独立启用SASL |
| 消息延迟高、吞吐明显下降 | 可能开启了TLS但没有启用长连接复用,或客户端每批请求都新建连接 | 确认客户端复用Producer实例,不要每条消息都新建连接,适当调大max.in.flight.requests.per.connection |
5.3 证书轮换与滚动重启血泪经验
证书一年一期,轮换时最怕影响业务。我踩过几次坑之后总结了一套比较稳的流程:
第一步,提前准备新证书。管理机上新签一套证书,做成新的Keystore和Truststore。重点检查SAN是否覆盖了当前所有访问入口的域名和IP。
第二步,灰度加载。先在一台Broker上替换Keystore,然后滚动重启这一台。这时候其他Broker还是旧证书,但因为我们所有Broker的Truststore都信任同一个CA,新旧证书只要都挂在这个CA下面,互相验证没问题。
第三步,观察客户端。客户端通常只配了truststore,信任的是CA而不是Broker的具体证书,所以只要CA没变,客户端不需要立刻更新。但如果你整体换掉了CA,那就必须所有客户端同步更新Truststore,否则全部连接中断。
第四步,全量生效。确认灰度节点正常后,再逐台滚动更新其余节点。滚动重启期间要留意分区副本的ISR状态,等上一个节点恢复并追上最新水位后再动下一台。
关于自动续签,社区里有人用脚本配合openssl x509 -req和keytool -importcert来做,核心逻辑就是把上面几步脚本化。但自动化一定要有回滚预案,否则脚本出了问题,比手工轮换还可怕。
我在实际运维中还有个小习惯:把证书到期时间挂进监控日历,提前两周提醒。很多团队不是不会轮换,而是忘了证书什么时候到期,最后在凌晨被报警叫起来处理。
写在最后的一些经验
按照这套流程,我先后给三套不同规模的Kafka集群加上了安全认证与加密机制,目前稳定运行最久的一套已经超过一年。我的体会是,安全机制真正的难点不在配置本身,而在把所有客户端都收编到新协议下。上线前一定要做全量客户端连通性巡检,把那些还写着PLAINTEXT的旧程序逐个找出来统一替换,否则安全认证就是形同虚设。
最后分享一个小技巧:不管服务端怎么改,先在管理机上用命令行工具跑通一遍kafka-console-producer.sh和kafka-console-consumer.sh,再用业务代码去验证。命令行通了,至少能排除90%的配置层问题,剩下的就是业务侧参数不一致引发的报错。Kafka的安全机制一旦跑通,带来的安全感是实实在在的——你不再需要担心下一个扫到9092端口的脚本把整个topic拖走,也不用在半夜因为一条“连接被拒绝”的报警而反复翻查日志。