资讯详情

资讯详情

ZooKeeper开启SSL/TLS全攻略:从证书生成到双向认证配置

1. 项目概述1.1 为什么要给ZooKeeper加SSLZooKeeper作为分布式协调服务在不少公司的核心链路里承担着注册中心、配置中心、分布式锁这类关键角色。以前很多团队在内部网络里跑ZK默认不开启认证客户端一连接就能读写数据。但在多租户环境、跨机房调用、或者有等保合规要求的时候明文传输和弱认证就成了两个绕不过去的隐患。明文传输意味着什么只要有人能在网络链路上抓包就能直接看到客户端和ZK之间传输的数据内容包括znode里的配置信息、临时节点的心跳数据等等。如果ZK上存的是数据库连接串、业务开关配置这类敏感信息明文暴露的风险就非常直接。弱认证则是指ZooKeeper默认的Auth机制比较简陋本质上是服务端仅凭客户端传入的认证信息做一次校验认证信息在网络上是明文传输的。在可信内网里这种方案勉强够用但只要网络环境稍微复杂一点就很容易被伪造或重放。SSL/TLS认证可以同时解决这两个问题一方面通过证书握手建立双向信任另一方面用加密通道保证传输内容不被窃听。我在实际项目中就遇到过这样的场景某监控系统需要跨部门读ZK上的拓扑数据但网络策略不允许明文端口互通最后就是靠开启SSL才把链路打通。这也是为什么很多公司要求ZK强制开启TLS认证才能接入。1.2 这篇攻略适合谁如果你是运维工程师、平台开发工程师或者公司的微服务基础设施归你管那这篇内容就是为你准备的。我从“证书准备”到“ZK配置文件修改”再到“Java客户端和服务端的TLS对接”整理了完整的操作路径也把我在实际部署中踩过的坑一并写出来。需要说明的是不同版本的ZooKeeper在SSL配置项的命名上有差异。我会以比较常用的ZooKeeper 3.5.x和3.6.x为主来写这两个版本的配置方式基本一致3.7.x开始引入了ssl.enable这类新命名但核心思路不变。如果你用的是3.4.x那需要先升级或者考虑其他方案因为3.4.x对TLS的支持非常有限。2. 方案选型与整体设计思路2.1 先想清楚到底要单向认证还是双向认证TLS认证分为单向TLS和双向TLSmTLS。单向TLS指客户端验证服务端证书保证连接到的确实是目标服务器服务器不验证客户端身份。双向TLS则是在此基础上客户端也出示自己的证书服务端验证客户端身份双方都确认对方可信。ZooKeeper官方文档里明确提到如果要实现可靠的认证建议直接上mTLS也就是client.secure连接方式配合客户端的ssl.clientAuth配置。只开启单向TLS的话ZK对外能防窃听但任何持有信任证书的客户端都能连接认证意义上等于没设防。所以大多数生产环境我推荐直接做双向认证一步到位。我的实际经验是在微服务集群内部ZK一般部署3节点或5节点所有客户端都是自己可控的服务双向认证并没有太大的接入成本。反而是在跨组织或跨部门场景下如果客户端证书由对方部门自行签发那服务端要维护的信任列表就需要动态更新这种情况下可以折中采用单向TLS加应用层认证。2.2 证书方案怎么选要给ZK开启SSL第一步是解决证书来源。有三种常见选择第一种是使用企业内部自建的私有CA签发证书。适合公司内部有统一证书管理平台的情况所有服务都可以申请到合规的证书客户端内嵌信任的CA根证书服务端和客户端各自持有签发后的证书及私钥。这种方案最正规适合等保要求比较严格的场景。第二种是使用公网CA签发的证书比如Lets Encrypt之类的。这种情况适合ZK节点暴露在公网或半公网环境的场景不过实践中很少见因为ZK一般不会直接暴露公网而且公网CA证书通常有有效期限制需要自动续期ZK集群频繁替换证书不是很好操作。第三种是使用自签名证书。这是我在本地开发环境和测试环境最常用的方式。自己充当CA签发服务端和客户端证书然后把自签的CA证书导入各端的信任库即可。自签名证书在跨团队复现问题、本地调试时特别方便缺点是证书的分发和管理都要手动做一旦节点多起来就容易乱。我的建议是生产环境优先走内部私有CA测试环境用自签名证书尽量不要用过期的公网CA证书去凑合。证书有效期的问题在ZK上很容易被忽略有人配好TLS之后一跑就是两年证书到期那天整个集群连接全断排查起来相当被动。2.3 理解JKS与PEM的转换关系ZooKeeper是Java生态的组件所以默认的TLS配置走的是JVM的SSLContext体系使用JKS或PKCS12格式的Keystore。大家平时从CA平台下载的证书通常是PEM格式里面包含-----BEGIN CERTIFICATE-----这样的文本块。两者之间需要做格式转换核心工具就是JDK自带的keytool。我在操作前习惯先用openssl确认证书内容是否正确再决定转换路径。整个链路是这样的PEM证书 PEM私钥 → PKCS12 → JKS如果需要。因为PKCS12本身也是Java支持的格式也可以直接把keystore.pkcs12路径配置给ZK不一定非要转成JKS。实际用下来PKCS12格式对PEM的兼容性更好转换过程更少出错。3. 核心配置细节与实操要点3.1 JVM TLS相关基础概念在正式开始前有必要把这几个概念弄清楚否则后面配置很容易混淆。Keystore存储本端的私钥和证书。服务端用它来向客户端证明自己的身份客户端用它来向服务端证明自己的身份。Truststore存储信任的CA证书或对方证书。服务端用Truststore来校验客户端证书客户端用Truststore来校验服务端证书。keytoolJDK自带的密钥和证书管理工具可以创建密钥对、导入导出证书、查看证书信息。PKCS12一种常见的安全存储格式.p12或.pfx结尾。JKSJava特有的密钥库格式.jks结尾不过新版本JDK更推荐PKCS12。有一个非常容易搞混的点是在双向TLS中服务端的Keystore和Truststore必须同时配置服务端的Keystore放的是自己的证书和私钥服务端的Truststore放的是客户端CA的根证书。客户端同理也需要配置自己的Keystore和Truststore。很多第一次配的人只配了Keystore连接时报错说找不到信任证书就是因为没配Truststore。3.2 证书生成与格式转换实操我先演示一套自签名证书的完整生成流程。假设现在有一个ZK服务节点叫zookeeper1一个客户端叫zk-client我给它们分别签发证书。生产环境如果用的私有CA流程类似只是把自建CA换成公司的CA申请流程。先生成CA根证书# 生成CA私钥 openssl genrsa -out ca.key 2048 # 生成CA自签名证书有效期设为3650天 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj /CCN/STBeijing/LBeijing/OExampleOrg/OUIT/CNExample CA然后为ZK服务端生成私钥和证书签名请求# 生成服务端私钥 openssl genrsa -out zookeeper1.key 2048 # 生成证书签名请求 openssl req -new -key zookeeper1.key -out zookeeper1.csr -subj /CCN/STBeijing/LBeijing/OExampleOrg/OUIT/CNzookeeper1 # 使用CA根证书签发服务端证书 openssl x509 -req -in zookeeper1.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out zookeeper1.crt -days 730 -sha256同样地为客户端生成证书openssl genrsa -out zk-client.key 2048 openssl req -new -key zk-client.key -out zk-client.csr -subj /CCN/STBeijing/LBeijing/OExampleOrg/OUIT/CNzk-client openssl x509 -req -in zk-client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out zk-client.crt -days 730 -sha256需要注意一个细节服务端证书的CN一定要写成ZK节点的主机名或者IP地址否则客户端校验服务端域名时可能失败。在IP直连的场景下需要确保CN或Subject Alternative Name里包含对应的IP否则JVM默认的HostnameVerifier会拒绝连接。动手之前一定要确认这一点。证书生成好之后把服务端证书和私钥打包成PKCS12格式openssl pkcs12 -export -in zookeeper1.crt -inkey zookeeper1.key -out zookeeper1.p12 -name zookeeper1 -CAfile ca.crt -caname root -password pass:serverpass123然后把它转成JKS虽然ZK可以直接用PKCS12但很多团队的运维规范里仍然要求JKS格式所以统一转一下也行keytool -importkeystore -deststorepass serverpass123 -destkeystore zookeeper1.jks -deststoretype JKS -srckeystore zookeeper1.p12 -srcstoretype PKCS12 -srcstorepass serverpass123客户端也做同样处理openssl pkcs12 -export -in zk-client.crt -inkey zk-client.key -out zk-client.p12 -name zk-client -CAfile ca.crt -caname root -password pass:clientpass123 keytool -importkeystore -deststorepass clientpass123 -destkeystore zk-client.jks -deststoretype JKS -srckeystore zk-client.p12 -srcstoretype PKCS12 -srcstorepass clientpass123最后需要把CA根证书导入到服务端的Truststore和客户端的Truststore里。这一步很多人会漏漏了之后的表现就是证书链校验失败。keytool -import -trustcacerts -alias root -file ca.crt -keystore truststore.jks -storepass trustpass123 -noprompt到这里服务端需要的zookeeper1.jks、truststore.jks客户端需要的zk-client.jks、truststore.jks都准备好了。如果有多台ZK节点每个节点用自己独立的证书不要所有节点共用同一个证书。3.3 ZK配置项的逐项说明有了证书之后进入ZooKeeper服务端的配置环节。在zoo.cfg中增加以下内容secureClientPort2281 ssl.keyStore.location/path/to/zookeeper1.jks ssl.keyStore.passwordserverpass123 ssl.trustStore.location/path/to/truststore.jks ssl.trustStore.passwordtrustpass123 ssl.clientAuthtrue逐项解释secureClientPort2281指定TLS端口。通常不用默认的2181而是单独开一个端口这样可以让明文端口和TLS端口共存方便灰度迁移。ssl.keyStore.location服务端Keystore文件路径。ssl.keyStore.passwordKeystore的访问密码。ssl.trustStore.location服务端Truststore文件路径。ssl.trustStore.passwordTruststore的访问密码。ssl.clientAuthtrue开启客户端认证。只有这个参数为true时才真正算双向认证服务端才会去校验客户端证书。ZooKeeper 3.6及之前版本使用的是ssl.*这样的配置前缀3.7开始官方推荐了新的前缀比如ssl.keyStore.location变为zookeeper.ssl.keyStore.location等。如果你的版本比较新建议以官方文档为准但核心逻辑一致。这里提醒一个常见误区client.secure才走TLS端口普通客户端如果连接的是secureClientPort指定的端口报错信息里可能不会直接提示“TLS required”而是各种握手失败。所以在测试时要明确用TLS客户端去连TLS端口不要拿旧的非TLS连接方式去试TLS端口。3.4 四字母命令与TLS限制配置好之后大家习惯用echo stat | nc 127.0.0.1 2281这种方式查看ZK状态但这在TLS端口上是不通的。四字母命令依赖明文TCP协议而TLS端口要求先完成TLS握手。想通过命令行查看TLS端口的状态需要用openssl s_client工具来验证openssl s_client -connect 127.0.0.1:2281 -CAfile ca.crt -cert zk-client.crt -key zk-client.key如果看到Verify return code: 0说明TLS握手成功服务端证书校验通过。接下来可以手动发送四字母命令stat这样能看到ZooKeeper的基本状态信息。实际项目里监控系统如果接了ZK建议直接走JMX或者客户端API不要依赖四字母命令。3.5 服务端启停与日志验证配置结束后从重启开始验证。启动ZK的命令和以前一样zkServer.sh start但此时需要确认端口监听状态。检查是否在2281端口上正常监听ss -lntp | grep 2281正常情况下应该能看到Java进程监听该端口。然后看日志会有类似这样的输出ZooKeeper audit logging is enabled binding to port 0.0.0.0/2281如果SSL配置错误启动阶段可能不报错而是等到客户端连接时才在日志里刷异常。所以仅仅看“启动成功”并不代表TLS生效必须要用客户端实际跑一次握手才能确认。这个问题我在刚开始调TLS时踩过后来就把“启动成功”和“TLS握手成功”两件事严格分开验证。4. 客户端如何连接TLS端口4.1 Java原生产客户端配置Java应用通过ZooKeeper原生客户端连接TLS时需要把以下JVM参数加进去-Dzookeeper.client.securetrue -Dzookeeper.clientCnxnSocketorg.apache.zookeeper.ClientCnxnSocketNetty -Dzookeeper.ssl.keyStore.location/path/to/zk-client.jks -Dzookeeper.ssl.keyStore.passwordclientpass123 -Dzookeeper.ssl.trustStore.location/path/to/truststore.jks -Dzookeeper.ssl.trustStore.passwordtrustpass123同时需要把连接地址指向TLS端口例如127.0.0.1:2281。这里特别说明一下zookeeper.clientCnxnSocket必须设置为ClientCnxnSocketNetty因为原生的ClientCnxnSocket不直接支持SSL。这是ZooKeeper客户端TLS接入的一个硬性配置漏掉它会看到连接被服务器直接关闭。另外如果是Spring Boot项目很多团队会使用CuratorFramework来操作ZK。Curator底层也是复用ZooKeeper原生客户端所以配置方式一样只需要在你的配置类中把系统属性的设置放到CuratorFramework创建之前即可。一个常见的错误是在CuratorFramework已经构建之后再设置系统属性这时属性可能不生效导致连不上TLS端口。4.2 其他语言客户端的选择如果客户端不是Java语言写的常见选择有Python可以使用kazoo库但它对TLS的支持相对有限通常需要借助pyOpenSSL或自定义socket包装。实际上kazoo在较新版本里支持KazooClient(hosts127.0.0.1:2281, use_sslTrue, certfile..., keyfile..., ca...)需要你的kazoo版本在2.9以上并且依赖版本正确。Go常用go-zookeeper库它支持通过WithTLSServerName和WithDialer方式定制TLS配置。GitHub上有很多示例代码核心思路是把tls.Config传给连接器。C/C可以使用zookeeper-c-client在zoo_set_ssl_ctx接口中传入SSL上下文。我的经验是如果是全新项目优先选择官方或活跃社区维护的客户端库如果是老项目必须确认所依赖的客户端库是否支持TLS不要想当然。曾经遇到过一个项目从ZK迁移到开启了TLS的ZK后发现底层用的客户端版本太老根本不支持TLS最后被迫升级版本才解决。4.3 常见Java库适配细节以Spring Cloud Zookeeper为例正常情况下只需要在配置文件中添加spring.cloud.zookeeper.connect-string127.0.0.1:2281然后配合上一节提到的JVM参数即可。注意spring.cloud.zookeeper.connect-string必须指向TLS端口否则即使JVM参数都配了也会去连接明文端口。如果使用curator-x-discovery做服务发现那还需要注意CuratorFramework的连接参数要和TLS配置匹配。有个比较隐蔽的问题是org.apache.curator.framework.CuratorFrameworkFactory默认的connectionTimeoutMs是60000但在TLS握手失败场景下报错可能不会准时出现在日志里排查时容易误判为网络超时。建议在调试阶段把连接超时时间调大一些比如300000看清楚日志再收窄。5. 实操过程与全链路验证5.1 环境准备清单在动手之前先把环境梳理清楚。我这里列了一个清单避免做到一半发现缺少工具工具/组件版本建议用途JDK1.8.0_2xx及以上推荐OpenJDK 8/11/17运行ZK及keytool工具ZooKeeper3.5.x或3.6.x开启TLS推荐的稳定版本OpenSSL1.1.1及以上生成证书、校验握手ZK客户端对应语言最新稳定版验证连接如果你是本机测试可以直接在/opt/zookeeper下完成所有操作。生产环境建议证书目录单独维护并把日志和安全授权做好。5.2 完整操作全流程我把从零开始到验证通过的完整命令串一遍。第一步准备证书目录mkdir -p /data/ssl/zookeeper cd /data/ssl/zookeeper第二步生成CAopenssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj /CCN/STBeijing/OExampleOrg/OUIT/CNExample CA第三步生成服务端证书openssl genrsa -out zookeeper1.key 2048 openssl req -new -key zookeeper1.key -out zookeeper1.csr -subj /CCN/STBeijing/OExampleOrg/OUIT/CNzookeeper1 openssl x509 -req -in zookeeper1.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out zookeeper1.crt -days 730 -sha256第四步生成客户端证书openssl genrsa -out zk-client.key 2048 openssl req -new -key zk-client.key -out zk-client.csr -subj /CCN/STBeijing/OExampleOrg/OUIT/CNzk-client openssl x509 -req -in zk-client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out zk-client.crt -days 730 -sha256第五步转换服务端证书库openssl pkcs12 -export -in zookeeper1.crt -inkey zookeeper1.key -out zookeeper1.p12 -name zookeeper1 -CAfile ca.crt -caname root -password pass:serverpass123 keytool -importkeystore -deststorepass serverpass123 -destkeystore zookeeper1.jks -deststoretype JKS -srckeystore zookeeper1.p12 -srcstoretype PKCS12 -srcstorepass serverpass123第六步转换客户端证书库openssl pkcs12 -export -in zk-client.crt -inkey zk-client.key -out zk-client.p12 -name zk-client -CAfile ca.crt -caname root -password pass:clientpass123 keytool -importkeystore -deststorepass clientpass123 -destkeystore zk-client.jks -deststoretype JKS -srckeystore zk-client.p12 -srcstoretype PKCS12 -srcstorepass clientpass123第七步创建Truststorekeytool -import -trustcacerts -alias root -file ca.crt -keystore truststore.jks -storepass trustpass123 -noprompt第八步修改zoo.cfgsecureClientPort2281 ssl.keyStore.location/data/ssl/zookeeper/zookeeper1.jks ssl.keyStore.passwordserverpass123 ssl.trustStore.location/data/ssl/zookeeper/truststore.jks ssl.trustStore.passwordtrustpass123 ssl.clientAuthtrue第九步用openssl s_client验证openssl s_client -connect 127.0.0.1:2281 -CAfile ca.crt -cert zk-client.crt -key zk-client.key如果没看到Verify return code: 0就需要回到证书配置上排查。常见报错是self-signed certificate in certificate chain这通常是因为服务端没有把中间CA或根CA证书链完整带回但我们的测试里服务端证书由自建CA直接签发一般不会有中间CA问题。真正容易犯的错是证书生成时忘记加SAN字段导致域名校验不过。5.3 生产环境多节点场景的特殊处理真实生产环境通常不止一个ZK节点。三节点集群里每个节点都需要自己的证书不能三个节点共用同一个zookeeper1.jks。否则节点间做节点认证时客户端对所有节点都看到同一张证书虽然能用但安全上不合理且未来做证书轮换时也会很麻烦。我建议按节点名来分配证书别名和文件名比如zookeeper1.jks、zookeeper2.jks、zookeeper3.jks。然后把所有节点的证书都导入同一份Truststore因为节点间通信也需要相互信任。多节点集群还要注意一个叫sslQuorum的配置。在ZooKeeper中如果只开secureClientPort那只是客户端访问走TLS节点与节点之间的Quorum通信仍然是明文。为了保证全链路加密需要额外增加sslQuorumtrue ssl.quorum.keyStore.location/data/ssl/zookeeper/zookeeper1.jks ssl.quorum.keyStore.passwordserverpass123 ssl.quorum.trustStore.location/data/ssl/zookeeper/truststore.jks ssl.quorum.trustStore.passwordtrustpass123对于sslQuorum需要特别小心。一旦开启所有节点的Quorum通信都必须使用TLS端口。ZooKeeper在3.5版本里配置了sslQuorumtrue之后节点间通信会用3888对应的TLS端口或指定的sslQuorumPort。具体端口需要根据集群配置明确定义。而且开启sslQuorum时所有节点都要同时配置并且证书互相信任否则会出现节点反复连接不上、leader选举超时的问题。三节点集群的完整配置可以参考以下模板# 每个节点独立 tickTime2000 initLimit10 syncLimit5 dataDir/data/zookeeper clientPort2181 secureClientPort2281 # 每个节点不同对应自己的IP和端口 server.1zookeeper1:2888:3888 server.2zookeeper2:2888:3888 server.3zookeeper3:2888:3888 # 客户端SSL ssl.keyStore.location/data/ssl/zookeeper/zookeeper1.jks ssl.keyStore.passwordserverpass123 ssl.trustStore.location/data/ssl/zookeeper/truststore.jks ssl.trustStore.passwordtrustpass123 ssl.clientAuthtrue # Quorum SSL sslQuorumtrue ssl.quorum.keyStore.location/data/ssl/zookeeper/zookeeper1.jks ssl.quorum.keyStore.passwordserverpass123 ssl.quorum.trustStore.location/data/ssl/zookeeper/truststore.jks ssl.quorum.trustStore.passwordtrustpass123如果一开始就从明文集群迁移到TLS集群建议先单节点开启TLS验证无误后再逐个节点滚动升级。不要一次性把三个节点的Quorum TLS全改掉否则一旦配置不同步整个集群可能直接脑裂。5.4 平滑迁移从明文到TLS的过渡方案很多团队不会一上来就直接关闭明文端口毕竟线上客户端太多不可能全部同时改造。一个稳妥的方案是让clientPort2181和secureClientPort2281并存。并存期间可以逐步引导客户端切换到TLS端口。等所有客户端都切换完毕后再用防火墙或安全组策略禁用明文端口完成最终的收敛。这个过程中务必关注两点第一不要让明文端口长期开放。如果TLS已经全面启用明文端口却还开着等于给攻击者留了一扇后门。安全审计的时候明文端口的存在会直接被判不合规。第二并存期间所有新增客户端必须要求走TLS不要再允许明文接入。否则旧的明文客户端永远清理不干净迁移计划就会无限延期。我在一次内部迁移中就是这么做的先开TLS端口让新服务全部走TLS再排计划把存量服务分批切换切换一批就关闭一批对应的明文连接白名单。整个迁移花了两个迭代版本最后关闭明文端口的时候只影响到了两个没有及时改造的旧服务比自己预想的情况好很多。6. 常见问题与排查技巧实录6.1 无法启动KeyStore被篡改或密码错误如果ZK启动直接失败日志里出现java.io.IOException: keystore password was incorrect那说明Keystore密码和配置里的ssl.keyStore.password不一致。这个问题通常在证书转换阶段就埋下了。排查思路用keytool -list -keystore zookeeper1.jks -storepass serverpass123确认密码是否正确。确认zoo.cfg里没有多余空格。比如ssl.keyStore.password serverpass123这种写法密码会变成带一个前导空格很容易忽略。顺便提醒ZK的配置解析是原样读取的密码前后不要有意外空格不然会非常隐蔽。6.2 客户端连接时报证书链验证失败客户端日志里出现javax.net.ssl.SSLHandshakeException: PKIX path building failed或unable to find valid certification path to requested target这基本就是Truststore里没有对应的CA证书。验证方法keytool -list -keystore truststore.jks -storepass trustpass123看看truststore.jks中是否存在root别名。如果不存在导入CA证书的操作没有生效重新执行keytool -import -trustcacerts -alias root -file ca.crt -keystore truststore.jks -storepass trustpass123 -noprompt然后重启服务端或客户端再次尝试。6.3 客户端连接被拒绝或证书类型不匹配如果客户端已经配置TLS但连过去直接被断开日志中反复出现handshake alert: certificate_unknown或Received fatal alert: certificate_unknown有可能是证书扩展密钥用法里没有clientAuth。在生产CA环境下签发的客户端证书需要显式带上Extended Key Usage中的TLS Web Client Authentication否则服务端会拒绝。自签名测试环境可能没这个限制但生产环境经常遇到。检查方法openssl x509 -in zk-client.crt -text -noout | grep -A 1 Extended Key Usage如果输出里只有TLS Web Server Authentication那这张证书不能当客户端证书用。需要向CA重新申请客户端证书。6.4 域名校验失败的常见场景连接已建立但立刻被关闭日志中有No subject alternative names present这通常是因为客户端连接的地址和证书的CN不匹配。解决办法有两个方向让ZK服务端证书重新签发时加上Subject Alternative Name把IP和域名都加进去。在客户端JVM参数里设置-Dzookeeper.ssl.hostnameVerificationfalse。注意这么做会降低安全性不推荐在生产环境使用。只是解决测试环境连不上问题时可以临时这么干。我在自签名证书本地调试时经常用第二种方式临时跳过主机名校验线上一定要走正规证书。6.5 排查问题时的核心方法论遇到TLS连接类问题时先分级排查。按层级从底层到上层层级验证手段预期结果网络链路telnet 127.0.0.1 2281TCP端口通TLS握手openssl s_client -connect 127.0.0.1:2281看到Verify return code: 0ZK认证启动一个干净的Java客户端连接能创建ZooKeeper实例业务读写执行create/get操作正常返回先把网络和TLS握手这层确认通过再往上查客户端配置效率会高很多。直接扎到客户端代码里调试往往事倍功半。6.6 关于性能损耗的实测体会ZooKeeper开启TLS之后最关心的问题往往是性能损耗。我曾在测试环境做了一个简单的读写压测结论是TLS握手只发生在连接建立阶段对短连接场景影响明显对长连接场景影响很小。ZK客户端和服务端之间通常是长连接握手只有一次。因此开启TLS后读写的吞吐量差距大概在10%以内。连接频繁重建的场景下TLS握手开销会被放大比如某些客户端每次操作都重新创建连接这种用法本身就不推荐遇到性能问题时应先优化客户端连接复用而不是关TLS。6.7 证书轮换时的注意事项证书到期前一定要做轮换。轮换流程建议尽量平滑生成新证书并导入原Keystore。先更新客户端信任库保证客户端信任新证书。再更新服务端Keystore。重启ZK节点逐步滚动。如果直接从服务端换证书而客户端信任库没更新就会出现旧连接仍然可用、新连接全部失败的现象。因为旧连接在TLS会话缓存未过期前仍可继续新连接要完成新的TLS握手就会校验新证书。所以轮换证书时最好在低峰期操作并且准备好快速回滚方案。我在某个测试环境里曾经做过一次证书轮换因为只更新了服务端证书客户端没同步更新Truststore导致所有新连接全部握手失败。后来我把更新顺序调整为“先客户端后服务端”并且分批灰度验证就再没有出现过连接中断的问题。7. 补充ZK ACL与TLS的关系7.1 TLS不是ACL的替代品有同学会认为开启了TLS客户端认证就不需要配置ZooKeeper的ACL权限了。这种理解是错的。TLS解决的是“网络传输安全”和“连接双方身份认证”的问题ZK自身存储的数据仍然需要通过setAcl来做细粒度权限控制。换句话说TLS只能保证连上来的客户端是证书对应的客户端但它一旦建立连接如果znode的ACL为world:anyone:all那它还是可以读写所有节点。之前就有一个真实案例某服务开了TLS但某个znode的ACL是默认的world:anyone:all结果另一个拥有合法证书但原本不应访问该数据的服务还是能直接读到敏感数据。所以合理的组合是TLS负责传输层加密和身份标识确认ACL负责数据层的读写权限控制。两者同时启用才算是一个相对完整的加固方案。7.2 SASL与TLS的配合使用如果环境里有Kerberos或者需要更细粒度的认证ZooKeeper支持SASL认证。SASL与TLS并不冲突可以叠加使用。常见做法是先用TLS建立加密通道。再在TLS通道之上启用SASL来做用户级认证。这样既能保证数据在传输过程中不泄露又能做到用户粒度的权限控制。配置SASL需要在zoo.cfg中加入authProvider相关配置同时在客户端传入java.security.auth.login.config参数。叠加使用会稍微增加排查复杂度但等保场景下经常是必须的。从我自己的实践经验来看如果公司已有Kerberos基础设施SASL的接入成本不算太高如果没有现成Kerberos为了简化维护可以先做TLS加ACL等业务规模扩大后再引入SASL。不必一开始就追求最复杂的组合。7.3 用JMX监控TLS状态开启TLS后运维监控上也要做相应调整。ZooKeeper的JMX指标中客户端连接数、TLS握手成功次数等可以通过JMX导出。如果想监控TLS相关指标可以在JVM启动参数里加入JMX端口然后通过监控系统采集。一个常见的检查项是确认TLS端口的连接数是否正常。确认有没有大量的SSLHandshakeException异常被打到日志。确认TLS握手耗时是否突然飙高。如果发现TLS握手异常持续增加大概率是证书问题或者客户端配置问题及时排查比事后补救要好得多。8. 我的一些经验总结做ZK的TLS改造前后经手过好几个环境从本地单节点到跨机房集群都走了一遍。这里说几个通用的结论。第一证书管理一定要提前设计好。让每个ZK节点拥有独立证书统一由内部CA签发并约定好证书有效期和轮换周期。否则证书到期时连接全断那种场景下想快速救火是非常痛苦的。第二客户端的TLS配置要和生产环境对齐。很多人本地测试时把hostnameVerification关掉才能连上这掩盖了证书里SAN没配置的问题。正确做法是让证书完善到位而不是在客户端放松校验。线上如果把主机名校验关了TLS防中间人攻击的能力就大打折扣。第三迁移过程要灰度。先开TLS端口共存逐步切换客户端最终关闭明文端口。一次性切换所有连接风险系数太高尤其ZK集群内的Quorum通信一旦开启TLS所有节点必须同步更新否则可能出现集群不可用。第四遇到TLS报错时养成逐层验证的习惯。不要盲目改配置。网络层不通就先解决网络证书链有问题就先修Truststore域名校验失败就先看SAN和CN。每一层都有独立的验证方式逐层排查能大幅缩短定位时间。如果你正准备给自己负责的ZK集群开SSL建议先从单节点测试环境起步用这篇文章里的自签名证书方案跑通全流程再逐步推广到生产环境。等你把证书生成、服务端配置、客户端对接这几个问题全部解决后会发现ZK的TLS并没有想象中那么复杂多花进去的维护成本换来的是链路安全和认证合规上的确定性保障。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →