- 消息队列
- 后端
- 流处理
【免费下载链接】pulsar
Apache Pulsar - distributed pub-sub messaging system
TLS 证书认证是 Apache Pulsar 在 TLS 传输加密基础上构建的客户端身份认证机制:不仅服务端持有证书供客户端验证,客户端也持有一张由同一证书权威(CA)签发的证书,其 Common Name(CN)即代表客户端的“角色令牌”(role token),Broker 以此完成身份识别。本文基于 Pulsar 2.3.0 版本文档 security-tls-authentication.md 展开,完整覆盖客户端证书生成的 openssl 操作流程、Broker/Proxy 侧配置,以及 CLI、Java、Python、C++、Node.js、C# 六种客户端接入方式,并结合当前仓库源码(如 AuthenticationProviderTls、AuthenticationTls)剖析“CN 如何变成角色名”的底层实现,帮助你既能照做落地、又知其所以然。
TLS 认证概述:与 TLS 传输加密的关系
TLS 认证(TLS authentication)是 TLS 传输加密(TLS transport encryption)的扩展。两者的关键区别在于证书校验的方向:
- TLS 传输加密:只有服务端持有密钥和证书,客户端用 CA 证书验证服务端身份;
- TLS 认证:客户端同样持有密钥和证书,服务端用 CA 证书验证客户端身份,实现双向 TLS(mTLS)式的身份认证。
前置条件很明确:必须先完成集群的 TLS 传输加密配置,本文假定你已按 TLS 传输加密文档 完成了 Broker 端 TLS 证书(tlsCertificateFilePath、tlsKeyFilePath、tlsTrustCertsFilePath)的准备。
两个关键差异点值得注意:
- 客户端证书的 CN 就是角色令牌:客户端证书与服务器证书都由同一个 CA 签发,但客户端证书在生成 CSR 时,Common Name 填写的不是主机名,而是希望该客户端以其身份认证的角色名(role token),例如
admin。 - Broker 必须开启客户端证书强制校验:需要设置
tlsRequireTrustedClientCertOnConnect=true。从源码 ServiceConfiguration.java#L1347-L1351 可以看到该参数的定义与默认值:
@FieldContext( category = CATEGORY_TLS, doc = "Specify whether Client certificates are required for TLS Reject.\n" + "the Connection if the Client Certificate is not trusted") private boolean tlsRequireTrustedClientCertOnConnect = false;其语义是:要求客户端必须携带受信任的证书建立 TLS 连接,否则拒绝该连接——这正是 TLS 认证能“拒绝未认证客户端”的开关。
另外,Pulsar 的 TLS 相关密码套件和算法由Bouncy Castle Provider提供。如果你的环境要求 FIPS 合规版本,可参考文档同目录下的 Bouncy Castle 相关章节(security-bouncy-castle 一节的链接索引中提及,该文档在当前版本目录中需以仓库实际存在的 security 系列文档为准)。
创建客户端证书(openssl 完整流程)
客户端证书生成流程共四步:生成私钥 → 转换为 PKCS 8 → 生成 CSR → 用 CA 签名。以下命令与原文档完全一致,可直接照抄执行(将/path/my-ca/替换为你的 CA 目录,将admin替换为目标角色名)。
第一步:生成 RSA 私钥
$ openssl genrsa -out admin.key.pem 2048与 Broker 侧证书相同,客户端同样期望私钥为 PKCS 8 格式,因此需要转换:
$ openssl pkcs8 -topk8 -inform PEM -outform PEM \ -in admin.key.pem -out admin.key-pk8.pem -nocrypt注意:后续配置中引用的私钥文件是转换后的
admin.key-pk8.pem,而不是原始的admin.key.pem。
第二步:生成证书签名请求(CSR)
$ openssl req -config openssl.cnf \ -key admin.key.pem -new -sha256 -out admin.csr.pem执行时 openssl 会依次询问组织、城市、Common Name等信息。这里最关键的一步是:Common Name 必须填写你希望该密钥对认证为哪个角色令牌(例如admin),因为这就是之后 Pulsar 中客户端的“身份名”。
如果手头没有openssl.cnf,请参照 TLS 传输加密文档中的“Certificate authority”一节 获取标准配置。
第三步:使用 CA 签名客户端证书
$ openssl ca -config openssl.cnf -extensions usr_cert \ -days 1000 -notext -md sha256 \ -in admin.csr.pem -out admin.cert.pem注意-extensions usr_cert参数:客户端证书使用usr_cert扩展(对应 extendedKeyUsage 中的 clientAuth),使该证书可被用于客户端认证场景——这是客户端证书与服务器证书在扩展用途上的核心区别。
执行成功后得到两个产物:
- 证书:
admin.cert.pem - 密钥:
admin.key-pk8.pem
客户端凭此证书 + 密钥 + CA 证书(ca.cert.pem),即可向 Broker 和 Proxy 证明自己是角色admin。
排错:CA 私钥缺失
如果签名步骤报unable to load CA private key,且原因是No such file or directory: /etc/pki/CA/private/cakey.pem,可执行以下命令生成cakey.pem:
$ cd /etc/pki/tls/misc/CA $ ./CA -newca源码深读:Broker 如何把 CN 变成角色令牌
配置项只是“外壳”,真正执行认证逻辑的是 Broker 侧的认证 Provider。当前仓库中对应实现为 AuthenticationProviderTls.java,其authenticate方法(#L52-L105)的核心逻辑与证书生成流程一一对应:
Certificate[] certs = authData.getTlsCertificates(); if (null == certs) { errorCode = ErrorCode.INVALID_CERTS; throw new AuthenticationException("Failed to get TLS certificates from client"); } String distinguishedName = ((X509Certificate) certs[0]).getSubjectX500Principal().getName(); for (String keyValueStr : distinguishedName.split(",")) { String[] keyValue = keyValueStr.split("=", 2); if (keyValue.length == 2 && "CN".equals(keyValue[0]) && !keyValue[1].isEmpty()) { commonName = keyValue[1]; break; } } // ... return commonName; // 返回的字符串即客户端的角色令牌要点解析:
- Provider 从 TLS 层拿到的客户端证书链中取第一张证书(
certs[0]),即客户端自证书; - 解析其 X.500 主体名(Distinguished Name,RFC 2253 格式,形如
CN=admin,O=...),逐项split(",")后匹配CN=前缀; - 方法最终返回 CN 值作为角色名,交由后续授权(authorization)流程判定该角色是否有权限。这也解释了为什么生成 CSR 时 CN 必须填角色令牌——Broker 端并不做任何“CN 到角色”的映射表,CN 就是角色;
- 失败路径会区分
INVALID_CERTS(未拿到证书)与INVALID_CN(证书中无有效 CN)两类错误码并计入AuthenticationMetrics指标,便于通过监控排查认证失败原因。
对应地,客户端侧的认证插件实现为 AuthenticationTls.java#L40-L121。它接受tlsCertFile和tlsKeyFile两个参数(见 #L118-L121):
private void setAuthParams(Map<String, String> authParams) { certFilePath = authParams.get("tlsCertFile"); keyFilePath = authParams.get("tlsKeyFile"); }其configure方法(#L93-L105)先尝试按 JSON 解析参数串,失败则回退到k1:v1,k2:v2的旧式格式解析——这就是下文 broker.conf 中brokerClientAuthenticationParameters既能用 JSON 字符串、CLIclient.conf中又用逗号分隔格式的原因。
在 Broker 上启用 TLS 认证
在broker.conf中,于既有 TLS 传输加密配置基础上追加以下参数(完整参数说明见 TLS broker 配置):
# Configuration to enable authentication authenticationEnabled=true authenticationProviders=org.apache.pulsar.broker.authentication.AuthenticationProviderTls # operations and publish/consume from all topics superUserRoles=admin # Authentication settings of the broker itself. Used when the broker connects to other brokers, either in same or other clusters brokerClientTlsEnabled=true brokerClientAuthenticationPlugin=org.apache.pulsar.client.impl.auth.AuthenticationTls brokerClientAuthenticationParameters={"tlsCertFile":"/path/my-ca/admin.cert.pem","tlsKeyFile":"/path/my-ca/admin.key-pk8.pem"} brokerClientTrustCertsFilePath=/path/my-ca/certs/ca.cert.pem逐项解读:
| 参数 | 作用 |
|---|---|
authenticationEnabled=true | 开启认证总开关(见 ServiceConfiguration.java#L1358) |
authenticationProviders=...AuthenticationProviderTls | 指定认证 Provider 为 TLS 证书 Provider;Provider 为类名列表 |
superUserRoles=admin | 将角色admin设为超级用户,可执行所有管理操作并在所有主题上发布/消费;若不开启 authorization,则至少需要一个 super user 才能管理集群 |
brokerClientTlsEnabled=true | Broker 作为客户端连接其他 Broker(同集群或跨集群)时使用 TLS |
brokerClientAuthenticationPlugin | Broker 间连接使用的客户端认证插件,即AuthenticationTls |
brokerClientAuthenticationParameters | Broker 身份所用的证书/密钥路径(JSON 格式);Broker 之间互连时,其 CN 同样充当角色令牌 |
brokerClientTrustCertsFilePath | Broker 互连时信任的 CA 证书路径 |
同时别忘了前文提到的前置开关:
# 要求客户端必须携带受信任证书,否则拒绝 TLS 连接 tlsRequireTrustedClientCertOnConnect=true # 以及 TLS 传输加密本身的三项:tlsCertificateFilePath / tlsKeyFilePath / tlsTrustCertsFilePath这三项在仓库自带的 conf/broker.conf 中均以注释形式预置,例如tlsTrustCertsFilePath=附近的注释说明了“只有该文件中的证书才被允许连接服务端”。
在 Proxy 上启用 TLS 认证
Proxy 同时扮演两个角色:面向客户端的服务端(验证客户端证书)、面向 Broker 的客户端(出示自己的证书)。因此 Proxy 的证书对需要在Broker 侧登记为proxyRoles。从 ServiceConfiguration.java#L1390-L1395 可以看到该参数的语义定义:
@FieldContext( category = CATEGORY_AUTHORIZATION, doc = "Role names that are treated as `proxy roles`. \n\nIf the broker sees" + " a request with role as proxyRoles - it will demand to see the original" + " client role or certificate.") private Set<String> proxyRoles = new TreeSet<>();即:当 Broker 看到来自proxyRoles中某角色的请求时,会进一步要求出示原始客户端的角色或证书,从而防止 Proxy 自身身份被冒充。授权细节见 授权文档。
在proxy.conf中追加以下参数(同样叠加在 TLS 传输加密的 proxy 配置之上):
# For clients connecting to the proxy authenticationEnabled=true authenticationProviders=org.apache.pulsar.broker.authentication.AuthenticationProviderTls # For the proxy to connect to brokers brokerClientAuthenticationPlugin=org.apache.pulsar.client.impl.auth.AuthenticationTls brokerClientAuthenticationParameters=tlsCertFile:/path/to/proxy.cert.pem,tlsKeyFile:/path/to/proxy.key-pk8.pem这里proxy.cert.pem的 CN 是 Proxy 自己的角色令牌(如proxy),该令牌必须出现在 Broker 的proxyRoles配置中。仓库自带的 conf/proxy.conf 同样预置了tlsTrustCertsFilePath=等 TLS 相关参数可供参考。
客户端配置
启用 TLS 认证后,客户端一律走 TLS 传输。URL 约定如下:
- Web 服务 URL:
https://+8443端口 - Broker 服务 URL:
pulsar+ssl://+6651端口
CLI 工具(pulsar-admin / pulsar-perf / pulsar-client)
pulsar-admin、pulsar-perf、pulsar-client等命令行工具读取 Pulsar 安装目录下的conf/client.conf配置文件。仓库自带的 conf/client.conf 中已预留了认证相关注释行(如authPlugin=org.apache.pulsar.client.impl.auth.AuthenticationTls、authParams=tlsCertFile:/path/to/client-cert.pem,tlsKeyFile:/path/to/client-key.pem)。在该文件中追加:
webServiceUrl=https://broker.example.com:8443/ brokerServiceUrl=pulsar+ssl://broker.example.com:6651/ useTls=true tlsAllowInsecureConnection=false tlsTrustCertsFilePath=/path/to/ca.cert.pem authPlugin=org.apache.pulsar.client.impl.auth.AuthenticationTls authParams=tlsCertFile:/path/to/my-role.cert.pem,tlsKeyFile:/path/to/my-role.key-pk8.pem其中tlsAllowInsecureConnection=false表示强制校验 Broker 证书,生产环境不建议改为true。
Java 客户端
import org.apache.pulsar.client.api.PulsarClient; PulsarClient client = PulsarClient.builder() .serviceUrl("pulsar+ssl://broker.example.com:6651/") .enableTls(true) .tlsTrustCertsFilePath("/path/to/ca.cert.pem") .authentication("org.apache.pulsar.client.impl.auth.AuthenticationTls", "tlsCertFile:/path/to/my-role.cert.pem,tlsKeyFile:/path/to/my-role.key-pk8.pem") .build();这里authentication(插件类名, 参数串)最终会走到 AuthenticationTls#configure,按k:v,k:v格式解析出tlsCertFile/tlsKeyFile。
Python 客户端
from pulsar import Client, AuthenticationTLS auth = AuthenticationTLS("/path/to/my-role.cert.pem", "/path/to/my-role.key-pk8.pem") client = Client("pulsar+ssl://broker.example.com:6651/", tls_trust_certs_file_path="/path/to/ca.cert.pem", tls_allow_insecure_connection=False, authentication=auth)C++ 客户端
#include <pulsar/Client.h> pulsar::ClientConfiguration config; config.setUseTls(true); config.setTlsTrustCertsFilePath("/path/to/ca.cert.pem"); config.setTlsAllowInsecureConnection(false); pulsar::AuthenticationPtr auth = pulsar::AuthTls::create("/path/to/my-role.cert.pem", "/path/to/my-role.key-pk8.pem"); config.setAuth(auth); pulsar::Client client("pulsar+ssl://broker.example.com:6651/", config);C++ 客户端源码位于 pulsar-client-cpp 目录,AuthenticationTls对应的插件实现可参考 pulsar-client-cpp/lib/auth 目录。
Node.js 客户端
const Pulsar = require('pulsar-client'); (async () => { const auth = new Pulsar.AuthenticationTls({ certificatePath: '/path/to/my-role.cert.pem', privateKeyPath: '/path/to/my-role.key-pk8.pem', }); const client = new Pulsar.Client({ serviceUrl: 'pulsar+ssl://broker.example.com:6651/', authentication: auth, tlsTrustCertsFilePath: '/path/to/ca.cert.pem', }); })();C# 客户端
var clientCertificate = new X509Certificate2("admin.pfx"); var client = PulsarClient.Builder() .AuthenticateUsingClientCertificate(clientCertificate) .Build();注意 C# 客户端使用 PFX 格式的证书文件(admin.pfx),而非 PEM 证书 + PKCS8 密钥的组合。
验证与测试参考
仓库中的集成测试 ClientAuthenticationTlsTest.java 完整演练了上述配置:它先为 Broker 设置tlsKeyFilePath/tlsCertificateFilePath/tlsTrustCertsFilePath,再设置setBrokerClientAuthenticationPlugin(AuthenticationTls.class.getName())与setBrokerClientTrustCertsFilePath(...)(#L58-L71),然后验证:
- 带信任证书 + TLS 认证构建的
PulsarAdmin可正常执行管理操作(testAdminWithFull); - 只配了客户端证书密钥、缺少
tlsTrustCertsFilePath时,请求会因证书链校验失败抛出包含PKIX path的异常(testAdminWithCertAndKey)——这提醒我们:客户端信任 Broker 的 CA 与 Broker 信任客户端的 CA 是两条独立的信任链,缺一即失败; - 完全不启用 TLS 的客户端访问 TLS 端点同样被拒绝(
testAdminWithoutTls)。
此外,AdminApiTlsAuthTest.java、BrokerAdminClientTlsAuthTest.java 以及 Proxy 侧的 AuthedAdminProxyHandlerTest.java、ProxyAuthenticatedProducerConsumerTest.java 分别覆盖了管理 API 与 Proxy 链路的 TLS 认证场景,可作为自测脚本的参照。
小结
TLS 认证链路的完整闭环是:CA 签发 CN 为角色令牌的客户端证书(usr_cert扩展)→ Broker 开启tlsRequireTrustedClientCertOnConnect+AuthenticationProviderTls→ Provider 从客户端证书中提取 CN 作为角色 → 授权流程按superUserRoles/proxyRoles判定权限。落地时常见的三个坑:私钥忘记转 PKCS 8、CSR 的 CN 误填主机名、Proxy 角色未登记进 Broker 的proxyRoles。按本文的配置清单逐项核对,即可在当前仓库版本上完整跑通 TLS 客户端认证。
- 消息队列
- 后端
- 流处理
【免费下载链接】pulsar
Apache Pulsar - distributed pub-sub messaging system
相关推荐
如何用 export_model.py 把 happy-llm 第五章训练出的模型 checkpoint 导出为 HuggingFace 格式
如何用 export_model.py 把 happy llm 第五章训练出的模型 checkpoint 导出为 HuggingFace 格式 在 happy
消息队列后端流处理Apache Pulsar 基于 TLS 的认证配置指南:从客户端证书签发到多语言客户端接入
Apache Pulsar 基于 TLS 的认证配置指南:从客户端证书签发到多语言客户端接入 导读 TLS 认证(TLS Authentication)是 Ap
消息队列后端流处理Apache Pulsar客户端连接验证:TLS证书配置检查
Apache Pulsar客户端连接验证:TLS证书配置检查 你是否在使用Apache Pulsar时遇到过客户端连接失败的问题?是否曾因为TLS(传输层安全协
消息队列后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考