Kafka再探3A

本篇博文主要内容为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 客户端+服务端证书
    • 那么问题来了,客户端证书、服务端证书的区别是什么?
      1. 客户端证书只用于签名校验身份,服务端证书还可以用于加密key密钥
      2. 证书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
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

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

你可能感兴趣的:(Kafka再探3A)