本篇博文主要内容为Kafka中SSL相关加密认证配置
实验环境延续上篇初探中的compose,配置同样写在环境变量中
Easyrsa进行CA管理
安装部署
- easyrsa是openvpn项目研发的CA管理工具,简化了用openssl签发管理证书的过程
- keytool是jdk自带的密钥证书处理工具,主要用来处理java程序常用的证书格式jks
- 一个jks文件中可以存储私钥、对应证书和CA证书
- 本文使用的ca管理容器镜像如下
FROM alpine:3.10RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \apk add easy-rsa openjdk10 bash && \mkdir /pki && echo "alias easyrsa=/usr/share/easy-rsa/easyrsa" > /root/.bashrcWORKDIR /pkiCMD sleep 10000
- 主要内容除了安装jdk和easyrsa外,还有建立别名,因为把easyrsa二进制程序拷贝到其他的地方,或者软链到其他地方,使用起来会有找不到各种文件的问题
- 如
Easy-RSA error: Failed to update /etc/openvpn/pki/safessl-easyrsa.cnf Easy-RSA error: Unknown cert type 'server'
- 如
- 容器的编排同样通过compose
- 通过volume把pki管理的根目录传出容器,从而被其他容器挂载,从而利用其中签发证书与私钥
services:keytool:build:context: ./keytool/volumes:- "./keytool/pki:/pki"networks:ninestates: {}省略其他service
运行使用
pem格式部分
- 进入容器
docker exec -it infra_keytool_1 bash - 初始化pki文件夹
easyrsa init-pki - 创建新的CA名为caroot
echo caroot | easyrsa build-ca nopass - 创建私钥、创建签发请求、签发证书一条龙
easyrsa --batch --subject-alt-name="DNS:broker1.kafka.cluster,IP:172.26.0.2" build-server-full broker1 nopass
*build-server-full服务端证书build-client-full客户端证书build-serverClient-full客户端+服务端证书- 那么问题来了,客户端证书、服务端证书的区别是什么?
- 客户端证书只用于签名校验身份,服务端证书还可以用于加密key密钥
- 证书extension中的Key Usage字段会写明此证书用于客户端还是服务端
- 上面的一条龙还可以分解为:
- 创建私钥、创建签发请求:
easyrsa --batch --subject-alt-name="DNS:broker1.kafka.cluster,IP:172.26.0.2" --req-cn="broker1" gen-req broker1 nopass - 签发证书:
easyrsa --subject-alt-name="DNS:broker1.kafka.cluster,IP:172.26.0.2" sign-req server broker1
- 创建私钥、创建签发请求:
- 创建签发请求时的参数
- —req-cn 或者 nopass前面的字符串 都代表着证书的CN
- subject-alt-name简写SAN,是x509证书的扩展,其中可以逗号分隔容纳很多个域名或者IP,对于拥有多个域名的场景非常方便
- 查看得到的csr、crt相关的命令
- 查看csr
openssl req -in pki/reqs/broker1.req -text -noouteasyrsa show-req broker1
- 查看csr
- 查看crt
openssl x509 -in pki/issued/broker1.crt -text -noouteasyrsa show-cert broker1
jks格式部分
- 要让java能够使用easyrsa管理的证书,需要进行证书的格式转换
- ca.crt.pem 转换为 truststore.jks
keytool -import -noprompt -alias caroot -file pki/ca.crt -keystore jks/truststore.jks -storepass 123456- 可以看出truststore.jks中仅储存CA的根证书,用于对其信任并校验其他由其签发的证书
- server.crt&key 转换为 server.jks
- server.jks包含三个实体:私钥、私钥对应的证书、CA的根证书
- 但是jdk的keytool的import命令只能把证书,不能把私钥,导入进jks格式文件
- 然而keytool可以把pkcs12格式(可以包含多个证书、私钥)转化为jks
- 所以就有了以下过程
NAME=client1openssl pkcs12 -export -name key -in pki/issued/$NAME.crt -inkey pki/private/$NAME.key -out jks/$NAME.p12 -passout pass:somepwd#or easyrsa export-p12 $NAMEkeytool -importkeystore -srckeystore jks/$NAME.p12 -srcstoretype PKCS12 -destkeystore jks/$NAME.jks -srcstorepass somepwd -deststorepass 123456 --alias keykeytool -import -noprompt -alias caroot -file pki/ca.crt -keystore jks/$NAME.jks -storepass 123456keytool -import -noprompt -alias crt -file pki/issued/$NAME.crt -keystore jks/$NAME.jks -storepass 123456
- 承上
- importkeystore把pkcs12格式(p12后缀)中的私钥转化为jks格式的私钥
- 注意importkeystore命令仅导入p12中的私钥,尽管easyrsa export-p12中三个实体内容是都有的(openssl转换的仅包含私钥)
- 所以需要后面的两个import把证书导入进去
- 第一条import导入了ca证书
- 第二条import导入了私钥对应的证书
- 最后查看生成好的jks证书,应该有三条实体,一个PriviateKey,两个Certificate
keytool --list -keystore jks/broker1.jks -storepass 123456- 结果应类似这样
Keystore type: PKCS12Keystore provider: SUNYour keystore contains 3 entrieskey, Oct 12, 2020, PrivateKeyEntry,Certificate fingerprint (SHA-256): 9C:DC:2A:06:C0:7E:10:9E:9E:0B:70:4F:6A:0B:A9:8Ccaroot, Oct 13, 2020, trustedCertEntry,Certificate fingerprint (SHA-256): 9D:D2:8D:24:BE:59:FC:0E:FE:5F:BC:68:E4:ED:A8:52crt, Oct 13, 2020, trustedCertEntry,Certificate fingerprint (SHA-256): 9C:DC:2A:06:C0:7E:10:9E:9E:0B:70:4F:6A:0B:A9:8C
- 附查看p12格式证书的命令
openssl pkcs12 -in jks/broker1.p12 -info
SSL仅加密 & 不认证
- 在CA创建好并签发管理证书后,可以开始对kafka服务端和客户端进行配置进行实验
- 注意kafka客户端会检查服务端证书中的SAN字段并校验,不通过会停止自己的访问
- 如果 不需要 客户端对服务端证书进行校验的话,在签发证书的时候把SAN字段去掉就可以
- 但会面临MITM攻击的风险
- 服务端配置公共部分,与上一篇初探博文中的配置相同
KAFKA_BROKER_ID: 1KAFKA_CREATE_TOPICS: "test:1:1"KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181KAFKA_ADVERTISED_LISTENERS: INSIDE://172.26.0.2:9092,OUTSIDE://172.26.0.2:9093KAFKA_LISTENERS: INSIDE://:9092,OUTSIDE://:9093KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
- 服务端配置特殊部分
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:SSLKAFKA_SSL_KEYSTORE_LOCATION: /etc/pki/jks/broker1.jksKAFKA_SSL_KEYSTORE_PASSWORD: 123456KAFKA_SSL_KEY_PASSWORD: 123456KAFKA_SSL_TRUSTSTORE_LOCATION: /etc/pki/jks/truststore.jksKAFKA_SSL_TRUSTSTORE_PASSWORD: 123456KAFKA_SSL_SECURE_RANDOM_IMPLEMENTATION: SHA1PRNGKAFKA_SSL_CLIENT_AUTH: none
- 配置解释:
- truststore.location与keystore.location是两个jks证书的位置
- keystore.password与key.password的区别是
- 前者是jks文件的密码,后者是jks文件中密钥的密码
- importstore时指定这两个密码不同的话会报错
Warning: Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified -destkeypass value. - 因此使用keytool创建的jks文件这两个密码是必须相同的,也就省略了私钥密码
- 所以在kafka的配置中这两个参数的value是一样的
- secure.random是调用SSL时使用的随机数生成器,官方文档推荐SHA1PRNG
- ssl.client.auth是服务端对客户端的证书要求
- none:不要求
- required:要求客户端必须提供证书
- requested:客户端可选提供证书
- 客户端配置
kafka_client_ssl.properties:group.id=test-groupsecurity.protocol=SSLssl.truststore.location=/etc/pki/jks/truststore.jksssl.truststore.password=123456
- 测试命令
- 消费者
./kafka-console-consumer.sh --bootstrap-server 172.26.0.2:9093 --topic test --from-beginning --consumer.config /etc/jaas/kafka_client_ssl.properties - 生产者
./kafka-console-producer.sh --bootstrap-server 172.26.0.2:9093 --topic test --producer.config /etc/jaas/kafka_client_ssl.properties - 这时候在docker网桥上tcpdump抓包已经看不到明文内容了
- 消费者
SSL加密 & SSL认证
对服务端SSL认证
- 如前文所述,在签发证书时加上SAN字段即可
对客户端SSL认证
- 服务端配置仅需改动KAFKA_SSL_CLIENT_AUTH为required
- 客户端配置需改动为如下,添加了给客户端生成的jks
group.id=test-groupsecurity.protocol=SSLssl.truststore.location=/etc/pki/jks/truststore.jksssl.truststore.password=123456ssl.keystore.location=/etc/pki/jks/client1.jksssl.keystore.password=123456ssl.key.password=123456
- 测试命令相同
- 若此时若客户端所访问的kafka服务的IP不在服务端的SAN中,如127.0.0.1,会报错
java.security.cert.CertificateException: No subject alternative names matching IP address 127.0.0.1 found - 若此时客户端不提供客户端证书,则会报错
org.apache.kafka.common.errors.SslAuthenticationException: SSL handshake failed
- 若此时若客户端所访问的kafka服务的IP不在服务端的SAN中,如127.0.0.1,会报错
SSl加密 & SASL认证
对服务端SSL认证
- 同上一小节
对客户端SASL认证
- 服务端配置改为
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:SASL_SSLKAFKA_SSL_KEYSTORE_LOCATION: /etc/pki/jks/broker1.jksKAFKA_SSL_KEYSTORE_PASSWORD: 123456KAFKA_SSL_KEY_PASSWORD: 123456KAFKA_SSL_TRUSTSTORE_LOCATION: /etc/pki/jks/truststore.jksKAFKA_SSL_TRUSTSTORE_PASSWORD: 123456KAFKA_SSL_SECURE_RANDOM_IMPLEMENTATION: SHA1PRNGKAFKA_SSL_CLIENT_AUTH: noneKAFKA_OPTS: "-Djava.security.auth.login.config=/etc/jaas/kafka_server_jaas.conf"KAFKA_SASL_MECHANISM: PLAINKAFKA_SASL_ENABLED_MECHANISMS: PLAINKAFKA_SUPER_USERS: "User:admin;User:ANONYMOUS"
- 主要修改了security的map,指定SASL_SSL,代表传输层SSL,认证使用SASL
- 其次SASL配置与初探中的SASL配置相同,jaas配置文件也与上篇文章中相同
- 客户端配置修改为
security.protocol=SASL_SSLsasl.mechanism=PLAINsasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="5699923";group.id=test-groupssl.truststore.location=/etc/pki/jks/truststore.jksssl.truststore.password=123456
- 同样也是SASL配置和传输层配置SSL的结合
- 测试命令相同
- 若客户端提供的密码不对,会报错
org.apache.kafka.common.errors.SaslAuthenticationException: Authentication failed: Invalid username or password
