RocketMQ 安全加固的核心只有两件事:堵住匿名访问,把链路从明文换成 TLS。默认安装的 NameServer 监听 9876、Broker 监听 10911,两者都不做身份校验,任何能连通端口的机器都能投递消息、消费消息,甚至调用管理接口下发删除 Topic 的指令。本文按可复现的顺序,把 ACL 账号体系、Topic 权限位和 Broker 原生 TLS 一次配到位,并在最后给出验证与回滚方法。
适用场景
- RocketMQ 4.9.x 或 5.x 单机、主从集群,Broker 端口对办公网或公网可达。
- 等保测评或内部审计要求「消息队列必须身份认证 + 传输加密」。
- 多业务共用一套集群,需要按应用维度隔离生产与消费权限,避免 A 业务误删 B 业务的 Topic。
- Broker 曾被扫描器打满连接,或出现过陌生 Topic 被自动创建的情况。
前置条件
- RocketMQ 4.9.4 及以上(Broker 原生 TLS 从该版本开始提供),JDK 8u261+ 或 JDK 11。
- Broker 与 NameServer 的配置文件可写,允许滚动重启;不需要停机。
- 一台可以签发证书的机器(openssl 1.1.1+),以及一个内部域名,例如 mq.internal.example.com。
- 客户端使用官方 Java SDK,且允许附加 JVM 系统属性。
- 已备份
conf/broker.conf与conf/plain_acl.yml。
原理说明
RocketMQ 的访问控制分两层。第一层是 ACL:Broker 启动时加载 plain_acl.yml,按 accessKey 找到账号,再用 secretKey 校验请求签名;账号可以被限定来源 IP、是否管理员,以及每个 Topic 的权限位(DENY / PUB / SUB / PUB|SUB)。第二层是 传输层 TLS:Broker 用服务端证书与私钥响应握手,客户端通过 trustStore 校验服务端身份;配置客户端证书后升级为 mTLS,把「知道密钥」变成「持有证书」。
两层不能互相替代。只开 ACL 不开 TLS,签名仍然跑在明文连接里,中间人可以抓包重放;只开 TLS 不开 ACL,任何客户端都能匿名投递。生产环境应同时开启,并把 autoCreateTopicEnable 关掉,避免匿名写入顺手把 Topic 也建出来。
操作步骤
第一步:关闭自动建 Topic 并打开 ACL
编辑 Broker 配置,追加以下内容。aclEnable 必须为 true,否则 plain_acl.yml 完全不会生效:
# conf/broker.conf
brokerClusterName = DefaultCluster
brokerName = broker-a
brokerId = 0
namesrvAddr = 127.0.0.1:9876
brokerIP1 = 10.0.0.11
listenPort = 10911
# 禁止客户端自动创建 Topic 与订阅组,防止匿名写入污染元数据
autoCreateTopicEnable = false
autoCreateSubscriptionGroup = false
# 开启 ACL,账号定义在同目录 plain_acl.yml
aclEnable = true
如果集群前面没有负载均衡,管理端口 10912 不要对业务网开放,运维需要时走 SSH 隧道,而不是在防火墙放行一段 IP。
第二步:编写账号与 Topic 权限
在 $ROCKETMQ_HOME/conf/plain_acl.yml 中定义账号。全局白名单只放运维与监控网段,不要写 0.0.0.0/0:
globalWhiteRemoteAddresses:
- 10.0.0.0/8
- 127.0.0.1
accounts:
- accessKey: admin-ops
secretKey: 9f3c1a7e5b2d48c6a0f1e7d3b9c5a2f4
whiteRemoteAddress: 10.0.0.0/8
admin: true
defaultTopicPerm: DENY
defaultGroupPerm: SUB
topicPerms:
- TopicTest=PUB|SUB
groupPerms:
- oms_consumer=SUB
- accessKey: app-order
secretKey: 2b7e4d1c8a5f9036e2c7b4a1d8f5c2e9
whiteRemoteAddress: 10.20.0.0/16
admin: false
defaultTopicPerm: DENY
defaultGroupPerm: DENY
topicPerms:
- ORDER_CREATE=PUB
- ORDER_PAY=PUB
groupPerms:
- order_consumer=SUB
三个关键点:defaultTopicPerm 设为 DENY,未显式授权的 Topic 一律拒绝,这样新增业务必须走变更流程;whiteRemoteAddress 用网段而不是单 IP,扩容时不用改配置;业务账号一律 admin: false,管理员账号只允许从运维网段登录。
第三步:签发证书并启用 Broker TLS
先用 openssl 建一条内部 CA,再签发 Broker 与客户端证书。SAN 必须与客户端连接时使用的主机名一致,否则 Java 端会报主机名不匹配:
# 1) 生成内部 CA
openssl req -x509 -nodes -newkey rsa:2048 -days 3650 \
-keyout ca.key -out ca.crt \
-subj "/C=CN/O=Example/CN=Example MQ Internal CA"
# 2) 生成 Broker 私钥与 CSR
openssl req -nodes -newkey rsa:2048 \
-keyout broker.key -out broker.csr \
-subj "/C=CN/O=Example/CN=mq.internal.example.com"
# 3) 用 CA 签发服务端证书,附加 SAN
openssl x509 -req -in broker.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out broker.crt -days 825 -sha256 \
-extfile <(printf "subjectAltName=DNS:mq.internal.example.com,IP:10.0.0.11")
# 4) 生成客户端证书,mTLS 时使用
openssl req -nodes -newkey rsa:2048 \
-keyout client.key -out client.csr \
-subj "/C=CN/O=Example/CN=app-order"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 825 -sha256
# 5) 证书权限收紧,私钥只允许运行 Broker 的用户读取
chmod 600 broker.key ca.key client.key
chown rocketmq:rocketmq broker.key broker.crt ca.crt
然后在 broker.conf 中追加 TLS 段,参数名以所用版本的 conf 模板为准,4.9.4 与 5.x 基本一致:
tlsEnabled = true
tlsTestModeEnable = false
tlsKeyPath = /opt/rocketmq/certs/broker.key
tlsCertPath = /opt/rocketmq/certs/broker.crt
tlsTrustCertPath = /opt/rocketmq/certs/ca.crt
tlsTestModeEnable 必须设为 false,为 true 时 Broker 会跳过证书校验,等于把加密做成了摆设。NameServer 侧如需同样加密,在 namesrv.conf 中加入 tlsEnabled = true 与同一组证书路径,并让 Broker 与客户端都信任这条 CA。
第四步:客户端接入
TLS 由客户端 JVM 属性开启,ACL 由 RPCHook 注入,两者互不影响:
# 启动参数
-Dtls.enable=true
-Dtls.client.authServer=true
-Dtls.trustStorePath=/opt/app/certs/ca.jks
-Dtls.trustStorePassword=changeit
-Dtls.client.certPath=/opt/app/certs/client.crt
-Dtls.client.keyPath=/opt/app/certs/client.key
// 生产端:ACL 凭据通过 AclClientRPCHook 注入
DefaultMQProducer producer = new DefaultMQProducer("order_producer");
producer.setNamesrvAddr("mq.internal.example.com:9876");
producer.setRPCHook(new AclClientRPCHook(
new SessionCredentials("app-order",
"2b7e4d1c8a5f9036e2c7b4a1d8f5c2e9", "")));
producer.start();
Message msg = new Message("ORDER_CREATE", "order-20261030-001",
"{\"orderId\":\"20261030001\"}".getBytes(StandardCharsets.UTF_8));
SendResult r = producer.send(msg);
producer.shutdown();
注意 accessKey 只给 PUB 权限,这段代码就无法消费同名 Topic,权限边界由 Broker 侧强制,不依赖客户端自律。
配置验证
- 端口与进程:
ss -lntp | grep -E "9876|10911",确认监听地址是内网 IP 而非 0.0.0.0。 - 握手与证书链:
openssl s_client -connect 10.0.0.11:10911 -CAfile ca.crt -cert client.crt -key client.key -servername mq.internal.example.com,输出中应出现 Verify return code: 0 (ok) 与 SAN 为 mq.internal.example.com 的服务端证书。 - 匿名投递应失败:不带 RPCHook 的客户端发起 send,Broker 返回账号不存在或签名校验失败,消息不落盘;若仍能发送成功,说明 aclEnable 没生效或配置未被加载。
- 越权消费应失败:用 app-order 账号订阅 ORDER_CREATE,应返回权限不足;这条最能验证 topicPerms 是否正确。
- 日志侧:Broker 启动日志应出现 ACL 配置加载成功的记录,且不再输出明文连接的握手告警。
验证通过后建议补两项非功能性检查。一是 性能:TLS 握手会占用 CPU,10911 的 QPS 高时优先用主从多副本分摊,并确认 JVM 已开启 AES-NI(java -XshowSettings:properties -version 2>&1 | grep -i aes),否则软件实现会明显抬高单条消息延迟。二是 回滚路径:把改前的 broker.conf 与 plain_acl.yml 一并留存,回滚只需把 aclEnable 与 tlsEnabled 置回 false 并重启对应节点,客户端同步去掉 -Dtls.enable 即可,不要在两处不同步的状态下长时间运行。生产环境建议先在预发集群跑一轮完整收发,再进入正式集群。
常见问题(FAQ)
FAQ 1:开启 TLS 后客户端一直连不上,报握手超时怎么办?
先确认两端模式一致。RocketMQ 的 TLS 是整条连接加密,服务端开了 TLS 而客户端没加 -Dtls.enable=true,表现就是 TCP 能连上但协议解析失败或直接超时。其次检查 SAN:客户端用域名连接就必须有 DNS 类型的 SAN,用 IP 连接就必须有 IP 类型的 SAN,两者都不写时只能靠关闭 tls.client.authServer 绕过,但那样就失去了服务端身份校验。最后确认 10911 的证书路径对 rocketmq 用户可读,私钥 600 权限下换用户启动会直接加载失败。
FAQ 2:改了 plain_acl.yml 为什么没有立即生效?
RocketMQ 只在 Broker 启动时加载 ACL 文件,运行期修改需要重启该 Broker。为减少影响,应按「先停从节点、再停主节点」的顺序滚动重启,每次确认从节点同步追上再动下一台。另外 aclEnable 写在 broker.conf 里,如果用了 -c 指定其他配置文件,改错文件同样不会生效,可用启动参数核对实际加载路径。
FAQ 3:只开 ACL 不开 TLS,风险到底有多大?
ACL 的签名算法把 secretKey 参与运算,但请求体与签名本身仍在明文连接上传输。攻击者拿到一条完整请求后可原样重放到 Broker,在签名有效期窗口内等效于持有凭据;更常见的是直接从流量里提取 Topic 名与业务字段,做数据面情报。因此对公网或跨机房链路,TLS 与 ACL 必须成对出现。
总结
RocketMQ 加固可以拆成一条清晰的检查链:autoCreateTopicEnable 与 autoCreateSubscriptionGroup 关闭,把匿名写入的通道掐掉;aclEnable 打开并配 defaultTopicPerm: DENY,让权限默认拒绝、显式放行;tlsEnabled 打开且 tlsTestModeEnable 为 false,保证加密真实生效;客户端用 AclClientRPCHook 带凭据、用 JVM 属性带证书。四步做完,再按本文的验证清单逐条打勾,一套默认裸奔的集群就收敛成了可审计的内部服务。