Kafka安全机制解析及重构(一)
许久不写文章,主要是心情不佳,而且恰逢项目方向变动,学习和研究都放下了不少。最近开始主攻Kafka,在了解Kafka的功能后,要把Kafka构建成一个企业级的消息平台,首先需要完善的就是安全机制。
Kafka官方提供的客户端与服务端的安全机制有两种,官方的文档上写的很清楚:
- SSL协议,一般来说单向SSL协议就够了,双向需要在新增用户时修改服务端的证书还要重启服务器,比较麻烦。这个和ActiveMQ的证书制作类似,可以参考ActiveMQ使用SSL单向认证。
- SASL认证机制:主要有三种方式(1)基于Kerberos的GSSAPI(2)PLAINTEXT(3)SCRAM。
撇开SSL协议不谈,对SASL认证来说,没有使用Kerberos的公司就别考虑用(1)了,(2)和(3)的区别在于PLAINTEXT模式的用户密码是明文保存在服务端的JAAS配置文件中的,且客户端使用PLAIN时,密码是明文传输(Kafka官方建议通过SSL协议加密);而SCRAM的用户密码是保存在ZOOKEEPER上的,可通过脚本动态增减用户,客户端使用SCRAM模式认证时,密码会经过SHA-256的不可逆加密而后传输到服务端。其实归根结底,我们在做KAFKA安全模块时,需要考虑的是以下几个问题(适用于所有应用):
- 密码如果在服务端保存,需要密文保存
- 每次新增用户最好不要重启Broker
- 客户端密码需要密文传输
业界已有的Kafka平台我看了看,像阿里的消息服务Kafka,腾讯的Ckafka,华为的消息服务DMS也有Kafka。这些互联网大厂都有自己的Kafka安全机制,我主要参考了下阿里和华为的鉴权构件,都是基于Kafka开放出来的鉴权API自己写了一套。所以我也自己试了试,尝试自己重构了一套安全机制。
在讲解重构前,我们需要先了解一下KAFKA的SASL认证模式。我大略画了个图。
客户端与服务端的鉴权流程
基本的实现逻辑在kafka的clients的包里,是用java写的。客户端主要在类org.apache.kafka.clients common.security.auth.SaslClientAuthenticator中,服务端在org.apache.kafka.clients common.security.auth.SaslServerAuthenticator类中。其中被写死的步骤是1、2、3三步,其他4,5,6都可以通过重写SaslClient和SaslServer来实现自定义请求交互,需要注意的是实现SaslServer的时候务必要解决一下第3步中INITAL阶段往SERVER发的空包。(都是泪)
可以把3理解成客户端告知服务端鉴权可以开始了,接着第4步服务端返回一个server token,告诉客户端准备好了,第5步客户端发送包含用户密码的token给服务端,在服务端进行校验,如果校验通过,服务端返回一个空包,告诉客户端通过校验,如果校验不通过,直接在服务端抛出异常,连接就会断开。
可供我们自己实现的步骤有4,5,6,那么我们可以实现以下几个功能:
- 在第4步返回server token的时候,带上server信息,客户端收到token以后如果不满足要求可以直接断开。或将token放到加密串里增加密文复杂度。
- 在客户端构造密文时(第5步前),可以实现自定义的加密模式,比如加盐。我研究了下阿里和华为的源码,他们的做法是构造一个明文串(串中有用户名,还能加上时间戳随机数等花样),然后把明文加上密文一起加密,再将明文串放在加密后的串前,构造出鉴权报文。服务端收到后先通过分割报文解析出明文,然后加上服务端获取到的密码,和明文串一起加密后,再比对客户端传上的密文和自己生成的密文,一致则校验通过。
- 服务端的校验可以自己实现,因此我们可以自己实现用户密码的加载,比如去用户中心获取用户信息,或者自己写一个用户密码文件,然后实例化一个单例线程,让其监控文件变化并重新加载,从而解决需要重启broker的问题。
- 第6步发送的空包类似于校验的终止符。如果觉得一次校验不够犀利,可以像SCRAM那样多次校验。客户端只要不收到空包,都会继续尝试发送报文。具体可参考SCRAM源码。
今天就到这里吧,下一篇直接上代码,讲解如何重构一个比PLAIN模式更复杂的Kafka安全认证框架。