Dubbo从2.7.5开始支持TLS,但是有关于这方面的资料非常少,官方文档也非常简单。所以专门记录下来自己的踩坑之路,如果对你有所帮忙,麻烦老板给我点个赞!
前置知识
在使用Dubbo TLS 协议之前,最好先了解一下TLS相关的知识,主要包括两方面:
- 各种证书相关
- TLS认证流程
以下内容是看了各类资料后自己的理解,有不对的还请指出!
证书相关概念
- CA:颁发证书的权威机构
- 根证书:暂时就叫CA证书吧,由CA颁发,即这个证书代表了某一方CA自己,因为CA是权威的,所以等价于该CA证书也是可信任的,浏览器里已经默认嵌入了一些权威机构的根证书
- 数字证书:基于根证书签发的证书(我们可以理解为二级证书或下一层证书)。如果站点是对外使用,这个证书一般需我们去买购买
- 自签发证书:自己签发给自己的证书,此时该证书的签发者和被签发者相同,比如根证书
证书内容
一个证书应该包括哪些内容?
- 该证书对应的公钥:每个证书都需要有一对公私钥,公钥作为证书里面的内容,对外公开,私钥自己保存
- 签发者:是谁签发的。对于根证书而言,签发者和被签发者都是它自己
- 被签发者:签发给谁
- 有效期
- 其它信息
证书签发
如何签发一个证书?
- 首先基于某种非对称加密算法生成一对公私
- 基于某种hash算法对证书明文进行hash计算, 得到 H1,
- 使用签发者的私钥对 H1 进行加密,得到 P1 , 这个步骤称为签名
- 证书的明文 + P1 就是证书的完整内容。这个数字证书里面包括了: 证书基本信息、该证书对应的公钥、签名信息
证书校验
浏览器收到一个证书,该如何校验这是不是一个有效的证书?
- 基于某种hash算法对证书明文进行hash计算, 得到 H2
- 使用签发者的公钥对 该证书的签名信息进行解密,得到 H3 。 浏览器为啥有签发者的公钥? 前面不是说了浏览器内嵌了一些CA根证书吗?还有一种情况是自己手动添加的
- 判断 H2 和 H3 是否相同。如果相同,说明证书内容没有被串改,因为签名信息你改不了,签发者的私钥只有CA权威机构自己才有
TLS交互流程
这里说的是单向认证,即浏览器要确保访问的站点是安全的,即 浏览器要认证server端,这主要是保护浏览器一端
- 浏览器访问某站点,该站点返回浏览器一个数字证书
- 浏览器对这个数字证书进行校验
- 校验通过后,浏览器生成一个随即数,然后使用该数字证书的公钥对其加密,返回给server端
- Server收到请求后用证书的私钥随机数进行解密
- 后续两者就用这个随机数作为对称加密的密钥来加密传输的数据
整合过程
证书准备
假设在开始之前,我们已经准备好了如下证书:
- CA证书:
ca.crt
- 数字证书对1:
servcer.crt
server.key
- 数字证书对2:
client.crt
client.key
单向认证
即只要认证一方,比如在请求之前,consumer
要认证provider
或者 provider
要认证consumer
,此时就是单向认证
被认证方
即谁需要被认证,在dubbo中指 consumer
或 provider
。在单向认证中,被认证方
需要提供数字证书
和该数字证书对应的密钥
。这里用的是server密钥对
,其实用client密钥对
也是一样的
// 首先通过ProtocolConfig开启TLS协议
@Bean
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(7798);
protocolConfig.setThreadpool("fixed");
protocolConfig.setThreads(200);
protocolConfig.setDispatcher("message");
protocolConfig.setSslEnabled(true);
return protocolConfig;
}
@Bean
public SslConfig sslConfig() {
SslConfig sslConfig = new SslConfig();
// 如果被认证方是provider, 设置下面两个
sslConfig.setServerKeyCertChainPath("classpath:certs/servcer.crt");
sslConfig.setServerPrivateKeyPath("classpath:certs/servcer.key");
// 如果被认证方是consumer, 设置下面两个
sslConfig.setClientKeyCertChainPath("classpath:certs/servcer.crt");
sslConfig.setClientPrivateKeyPath("classpath:certs/servcer.key");
return sslConfig;
}
复制代码
认证方
即谁需要被认证,在dubbo中指 consumer
或 provider
。在单向认证中,认证方
需要提供被认证方对应的证书
或者ca证书
@Bean
public SslConfig sslConfig() {
SslConfig sslConfig = new SslConfig();
// 如果认证方是consumer, 设置下面这个
sslConfig.setClientTrustCertCollectionPath("classpath:certs/servcer.crt");
// 如果认证方是provider, 设置下面这个
sslConfig.setServerTrustCertCollectionPath("classpath:certs/servcer.crt");
return sslConfig;
}
复制代码
双向认证
provider
// 首先通过ProtocolConfig开启TLS协议
@Bean
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
......
protocolConfig.setSslEnabled(true);
return protocolConfig;
}
@Bean
public SslConfig sslConfig() {
SslConfig sslConfig = new SslConfig();
sslConfig.setServerKeyCertChainPath("classpath:certs/servcer.crt");
sslConfig.setServerPrivateKeyPath("classpath:certs/servcer.crt");
// 下面代表双向认证 这里代表服务端也要认证消费端
sslConfig.setServerTrustCertCollectionPath("classpath:certs/ca.crt");
return sslConfig;
}
复制代码
consumer
// 首先通过ProtocolConfig开启TLS协议
@Bean
public ProtocolConfig protocolConfig() {
ProtocolConfig protocolConfig = new ProtocolConfig();
......
protocolConfig.setSslEnabled(true);
return protocolConfig;
}
@Bean
public SslConfig sslConfig() {
SslConfig sslConfig = new SslConfig();
sslConfig.setClientKeyCertChainPath("classpath:certs/client.crt");
sslConfig.setClientPrivateKeyPath("classpath:certs/client.crt");
// 下面代表双向认证 这里代表服务端也要认证消费端
sslConfig.setClientTrustCertCollectionPath("classpath:certs/ca.crt");
return sslConfig;
}
复制代码
案例分析
- Dubbo版本: 2.7.8
- 确认证书没有问题
- 在本地新建了两个Dubbo客户端,单向认证、双向认证 都可以测试通过;将这两个客户端部署到K8s集群之后,TLS认证失败。此时本地和开发环境的配置项都是从开发环境的apollo配置中心拉取的,按理应该是一样的
问题重现
- 本地开启TLS测试, 不论单向 双向认证 都可以正常访问
- 将这两个应用部署到K8s之后,观察启动日志: client 先和 server 建立连接,然后马上断开连接, 同时server端报
shakehand time out
异常,但是马上 client 和 server 又建立连接,一直重复这个动作 - 本地启动 server 和 client ,通过抓包工具
wireshark
观察 server端口,确认走了TLS认证 - 从K8s上下载对应的starter.jar, 启动并通过抓包工具
wireshark
观察,发现根本没走TLS认证。我就纳闷了,难道是打包有问题吗?或者就是配置问题,此时本地和开发环境的配置项都是从开发环境的apollo配置中心拉取的,按理应该是一样的 - 重新弄了两个新的Dubbo客户端,还是遇到相同的问题
问题原因
最后发现,本地的应用连的是本地的ZK;K8s上的应用,连的是开发环境的ZK。开发环境开启了Dubbo的全局配置,配置项存放在ZK中,这里面的配置项会对每个客户端生效,并且全局配置中开启了简化URL。 简化URL开启之后,导致TLS相关的配置在应用启动时无法获取,从而导致TLS不生效,而本地ZK没有开启dubbo全局配置,所以不会有影响。
开启简化URL: 可以通过Dubbo全局化配置开启,此时所有Dubbo客户端都生效;也可以在某个客户端单独开启,此时只有这个客户端生效。相关配置:dubbo.registry.simplified=true
解决方案
Dubbo提供了一个配置项,在开启简化URL的情况下,可以配置一些属性,让其不被简化,如下:
dubbo.registry.extra-keys=ssl-enabled
复制代码
因为我们已经开启了Dubbo全局化配置,所以在将配置项添加到全局化配置项中,此时所有Dubbo客户端就都可以生效了。