本篇博文主要内容为Kafka中SSL相关加密认证配置
实验环境延续上篇初探中的compose,配置同样写在环境变量中
Easyrsa进行CA管理
安装部署
- easyrsa是openvpn项目研发的CA管理工具,简化了用openssl签发管理证书的过程
- keytool是jdk自带的密钥证书处理工具,主要用来处理java程序常用的证书格式jks
- 一个jks文件中可以存储私钥、对应证书和CA证书
- 本文使用的ca管理容器镜像如下
FROM alpine:3.10
RUN 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/.bashrc
WORKDIR /pki
CMD 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 -noout
easyrsa show-req broker1
- 查看crt
openssl x509 -in pki/issued/broker1.crt -text -noout
easyrsa show-cert broker1
- 查看csr
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=client1
openssl 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 $NAME
keytool -importkeystore -srckeystore jks/$NAME.p12 -srcstoretype PKCS12 -destkeystore jks/$NAME.jks -srcstorepass somepwd -deststorepass 123456 --alias key
keytool -import -noprompt -alias caroot -file pki/ca.crt -keystore jks/$NAME.jks -storepass 123456
keytool -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: PKCS12
Keystore provider: SUN
Your keystore contains 3 entries
key, Oct 12, 2020, PrivateKeyEntry,
Certificate fingerprint (SHA-256): 9C:DC:2A:06:C0:7E:10:9E:9E:0B:70:4F:6A:0B:A9:8C
caroot, Oct 13, 2020, trustedCertEntry,
Certificate fingerprint (SHA-256): 9D:D2:8D:24:BE:59:FC:0E:FE:5F:BC:68:E4:ED:A8:52
crt, 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: 1
KAFKA_CREATE_TOPICS: "test:1:1"
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: INSIDE://172.26.0.2:9092,OUTSIDE://172.26.0.2:9093
KAFKA_LISTENERS: INSIDE://:9092,OUTSIDE://:9093
KAFKA_INTER_BROKER_LISTENER_NAME: INSIDE
- 服务端配置特殊部分
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INSIDE:PLAINTEXT,OUTSIDE:SSL
KAFKA_SSL_KEYSTORE_LOCATION: /etc/pki/jks/broker1.jks
KAFKA_SSL_KEYSTORE_PASSWORD: 123456
KAFKA_SSL_KEY_PASSWORD: 123456
KAFKA_SSL_TRUSTSTORE_LOCATION: /etc/pki/jks/truststore.jks
KAFKA_SSL_TRUSTSTORE_PASSWORD: 123456
KAFKA_SSL_SECURE_RANDOM_IMPLEMENTATION: SHA1PRNG
KAFKA_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-group
security.protocol=SSL
ssl.truststore.location=/etc/pki/jks/truststore.jks
ssl.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-group
security.protocol=SSL
ssl.truststore.location=/etc/pki/jks/truststore.jks
ssl.truststore.password=123456
ssl.keystore.location=/etc/pki/jks/client1.jks
ssl.keystore.password=123456
ssl.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_SSL
KAFKA_SSL_KEYSTORE_LOCATION: /etc/pki/jks/broker1.jks
KAFKA_SSL_KEYSTORE_PASSWORD: 123456
KAFKA_SSL_KEY_PASSWORD: 123456
KAFKA_SSL_TRUSTSTORE_LOCATION: /etc/pki/jks/truststore.jks
KAFKA_SSL_TRUSTSTORE_PASSWORD: 123456
KAFKA_SSL_SECURE_RANDOM_IMPLEMENTATION: SHA1PRNG
KAFKA_SSL_CLIENT_AUTH: none
KAFKA_OPTS: "-Djava.security.auth.login.config=/etc/jaas/kafka_server_jaas.conf"
KAFKA_SASL_MECHANISM: PLAIN
KAFKA_SASL_ENABLED_MECHANISMS: PLAIN
KAFKA_SUPER_USERS: "User:admin;User:ANONYMOUS"
主要修改了security的map,指定SASL_SSL,代表传输层SSL,认证使用SASL
其次SASL配置与初探中的SASL配置相同,jaas配置文件也与上篇文章中相同
客户端配置修改为
security.protocol=SASL_SSL
sasl.mechanism=PLAIN
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="admin" password="5699923";
group.id=test-group
ssl.truststore.location=/etc/pki/jks/truststore.jks
ssl.truststore.password=123456
- 同样也是SASL配置和传输层配置SSL的结合
- 测试命令相同
- 若客户端提供的密码不对,会报错
org.apache.kafka.common.errors.SaslAuthenticationException: Authentication failed: Invalid username or password