Kafka 如何给集群配置SSL认证

14 篇文章 7 订阅
5 篇文章 0 订阅

前言

上一篇【Kafka 如何给集群配置Scram账户认证】我们说了如何给连接Kafka的客户端设置账户名和密码,但是对于一个企业使用的安全验证显然还是不够的,因此本篇就接着上篇继续,在使用SASL的方式下再加一个SSL的连接认证方式,做一个双保险。更多内容请点击【Apache Kafka API AdminClient 目录】

Kafka-SSL官方支持

Kafka如何进行SSL认证的官方参考见链接【security_ssl】,包括如何配置,如何申请CA Key,如何对证书签名等等都有详细的介绍,这里就不多说了,有兴趣的可以自己研究,这里我们直说怎么用。因此我们已经有了一对CA认证签名做好的.jks文件service.keystore.jksclient.truststore.jks以及它们的密码abcdefg

Zookeeper 配置

由于这篇博客算是接着上一篇来的,因此我们就直接使用上一篇的Zookeeper配置,最终会搭建一个既有SASL认证,也有SSL认证的Kafka集群。当然SSL和SASL两种安全机制是独立的,单独使用SASL或者SSL都是可以的。

tickTime=2000
initLimit=10
syncLimit=5
dataDir=/home/data/zookeeper_data
dataLogDir=/home/data/zookeeper_log   #这里可以不配置
clientPort=2181
# 下面两行仅仅是SASL支持,不需要可以删了
authProvider.1=org.apache.zookeeper.server.auth.SASLAuthenticationProvider
requireClientAuthScheme=sasl
server.1=192.168.33.101:2887:3887
server.2=192.168.33.102:2887:3887
server.3=192.168.33.103:2887:3887
server.4=192.168.33.104:2887:3887
server.5=192.168.33.105:2887:3887

Kafka配置

在正常启动Zookeeper以后,我们在上一篇Kafka的service.properties文件中添加新的SSL认证参数。此步骤的前提同样也是建立在上篇SASL认证基础上的,如果不需要,可以把这部分删除。

############################     基础配置如下    ##############################
broker.id=1
#默认监控端口,设置9092使用SASL_SSL协议,设置9093使用SASL_PLAINTEXT协议,此时使用9092端口就需要jks+SASL认证才能够连接了
#如果只要SSL认证,则配置“listeners=SSL://192.168.33.101:9092”
listeners=SASL_SSL://192.168.33.101:9092,SASL_PLAINTEXT://192.168.33.101:9093
#advertised.listeners控制生产者与消费者接入的端口,如果不设置默认都用listeners,设置9092使用SASL_SSL协议,设置9093使用SASL_PLAINTEXT协议
#如果只要SSL认证,则配置“advertised.listeners=SSL://192.168.33.101:9092”
advertised.listeners=SASL_SSL://192.168.33.101:9092,SASL_PLAINTEXT://192.168.33.101:9093
log.dirs=/home/data/kafka-logs
zookeeper.connect=192.168.33.101:2181,192.168.33.102:2181,192.168.33.103:2181,192.168.33.104:2181,192.168.33.105:2181/kafka

############################     SSL相关配置如下    ##############################
#设置客户端的SSL认证文件,需要提前将client.truststore.jks文件提前Copy到该目录
ssl.truststore.location=/usr/local/client.truststore.jks
#配置客户端truststore认证密码
ssl.truststore.password=abcdefg
#设置服务器的SSL认证文件,需要提前将service.keystore.jks文件提前Copy到该目录
ssl.keystore.location=/usr/local/service.keystore.jks
#配置服务端KeyStore认证密码
ssl.keystore.password=abcdefg
#配置key认证密码
ssl.key.password=abcdefg
#配置启动的权限,这里把所有的认证协议都加上了
ssl.enabled.protocols=TLSv1.2,TLSv1.1,TLSv1
#强制连接需要
ssl.client.auth=required
#配置文件认证类型
ssl.keystore.type=JKS
ssl.truststore.type=JKS

############################     SASL/SCRAM相关配置如下,不需要可以删除    ##############################
#Broker内部联络使用的security协议
#security.inter.broker.protocol=SSL 只用SSL认证这样配置
security.inter.broker.protocol=SASL_PLAINTEXT
#Broker内部联络使用的sasl协议,这里也可以配置多个,比如SCRAM-SHA-512,SCRAM-SHA-256并列使用
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512
#Broker允许使用的sasl协议,这里也可以配多个PLAIN,SCRAM-SHA-512,SCRAM-SHA-256
sasl.enabled.mechanisms=PLAIN,SCRAM-SHA-512

#设置zookeeper是否使用ACL
zookeeper.set.acl=true
#设置ACL类(低于 2.4.0 版本推荐使用 SimpleAclAuthorizer)
#authorizer.class.name=kafka.security.auth.SimpleAclAuthorizer
#设置ACL类(高于 2.4.0 版本推荐使用 AclAuthorizer)
authorizer.class.name=kafka.security.authorizer.AclAuthorizer
#设置Kafka超级用户账号,这两个分别对应zookeeper_jaas.conf中的user_super="super1234"和user_kafka="kafka1234";
super.users=User:admin;User:kafka

########################    其他辅助配置,笔者推荐的重要配置   #######################
#每条最大消息设置为3MB,超过此size会报错,可以自由调整
replica.fetch.max.bytes=3145728
message.max.bytes=3145728
#默认的备份数量,可以自由调整
default.replication.factor=2
#默认的partion数量,可以自由调整
num.partitions=3
#是否允许彻底删除topic,低版本这里设置为false则是隐藏topic
delete.topic.enable=true
#如果topic不存在,是否允许创建一个新的。这里特别推荐设置为false,否则可能会因为手滑多出很多奇奇怪怪的topic出来
auto.create.topics.enable=false

listeners和advertised.listeners

还是要说下listenersadvertised.listeners这两个参数配置的内容,可以看到这次9092端口使用的是SASL_SSL认证,因此外部客户端连接9092端口必须使用.jks文件和相应的密码去连接Kafka集群。但是9093端口使用的依然是SASL_PLAINTEXT认证也就是说,当程序连接9093端口的时候只需要有SASL认证而不需要SSL认证。可以通过这点做一个内外网的区分,让内部更快的通过认证连接Kafka,但是这样就必须做好文档的安全管理了。如果仅仅想用SSL,可以使用listeners=SSL://192.168.33.101:9092这样的配置,而不配置SASL_SSL认证。由于证书会和hostnameip等信息绑定,随便拿一个证书是无法使用的,因此笔者这里也不得不配置内部认证是SASL_PLAINTEXT,否则集群的内部联通都会报SSL handshake failed异常。如果公司里给Kafka 集群服务器机器域名申请了相应的证书,就不会出现这种问题。


security.inter.broker.protocol

这个参数也有必要再说下,其实Kafka官网推荐使用的是SASL_SSL,但是笔者这里改为了SASL_PLAINTEXT是由于笔者认为Broker内部通信不需要使用十分复杂的认证。如果只是用SSL认证则需要配置为security.inter.broker.protocol=SSL,因为不这样配置已经没有选择了,除了SSL协议就剩PLAINTEXT协议可以选择。之前已经解释过PLAINTEXT协议属于不设防状态,极度不安全,因此要配置为SSL协议。
还有一点要再次提醒一下,这个参数的值必须在listeners或者advertised.listeners中配置过。笔者这里同时配置了SASL_SSLSASL_PLAINTEXT,因此这个参数配置SASL_PLAINTEXT或者SASL_SSL任意一个都没有问题。如果listeners或者advertised.listeners只配置了SASL_SSL一个监听端口,则必须使用SASL_SSL协议。如果只配置了SSL一个监听端口,则必须使用SSL协议。如果配置了没有使用的协议,如listeners=SASL_SSL://192.168.33.101:9092security.inter.broker.protocol=SASL_PLAINTEXT就会报下面的错误。

[2021-02-23 15:40:26,792] ERROR Exiting Kafka due to fatal exception (kafka.Kafka$)
        java.lang.IllegalArgumentException: requirement failed: inter.broker.listener.name must be a listener name defined in advertised.listeners. The valid options based on currently configured listeners are SASL_SSL
        at kafka.server.KafkaConfig.validateValues(KafkaConfig.scala:1781)
        at kafka.server.KafkaConfig.<init>(KafkaConfig.scala:1756)
        at kafka.server.KafkaConfig.<init>(KafkaConfig.scala:1312)
        at kafka.server.KafkaServerStartable$.fromProps(KafkaServerStartable.scala:34)
        at kafka.Kafka$.main(Kafka.scala:68)
        at kafka.Kafka.main(Kafka.scala)

Kafka集群启动

把上述配置在所有的Kafka集群机器里做做一遍,然后就可以启动了。如果只用了SSL认证,直接使用自带kafka-server-start.sh脚本启动就好了。如果带了SASL协议认证就需要把把创建的JAAS认证文件带上启动,可以参考上篇内容,不再赘述。

Kafka集群自带验证

启动完以后就可以进入验证使用的环节了,如果使用自带的console验证必须还要去/config里面配置生产者与消费者的配置文件。当然也不建议用这样的方法验证,谁也不会用这种方法实际操作Kafka,因此后面笔者还有Java程序作为验证,这里就是提一笔。

producer.properties

配是上SSL以后,我们进行验证的方法也必须带上.jks文件才可以,因此如果想要用console必须加上下面的配置。

# 单独使用SSL配置producer.security.protocol=SSL
producer.security.protocol=SASL_SSL
producer.ssl.truststore.location=/usr/local/client.truststore.jks
producer.ssl.truststore.password=abcdefg
#这里为啥什么都不配下面有解释
producer.endpoint.identification.algorithm=  

# 单独使用SSL可以忽略以下配置
producer.sasl.mechanism=SCRAM-SHA-512
producer.sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username="account" password="password";

consumer.properties

Consumer也是一样。

# 单独使用SSL配置consumer.security.protocol=SSL
consumer.security.protocol=SASL_SSL
consumer.ssl.truststore.location=/usr/local/client.truststore.jks
consumer.ssl.truststore.password=abcdefg
#这里为啥什么都不配下面有解释
consumer.endpoint.identification.algorithm=   

# 单独使用SSL可以忽略以下配置
consumer.sasl.mechanism=SCRAM-SHA-512
consumer.sasl.jaas.config=org.apache.kafka.common.security.scram.ScramLoginModule required username=" account" password=" password";

Kafka集群Java程序验证

下面的程序中有一个参数ssl.endpoint.identification.algorithm值得详细说下。这个参数的作用是设置一个终端域名认证算法的,也就是用来检测是否客户端连接的是允许连接的域名。为什么设置为空呢?是为了规避认证文件中已经做过认证的域名。相当于只要有.jks文件和账号密码都可以连接,而不需要进行域名验证。由于笔者不打算为了这个帖子再申请一个新的证书,所以直接拿一个已经废弃了证书做了一个例子,但是这个废弃证书是有域名签名的,因此这里只能将其规避,否则会报SSL handshake failed

Producer 连接:

public class SSLProducer {
   public static void main(String[] args) {
      Properties props = new Properties();
      props.put("bootstrap.servers", "192.168.33.101:9092");
      props.put("acks", "1");
      props.put("retries", 3);
      props.put("batch.size", 16384);
      props.put("linger.ms", 1);
      props.put("buffer.memory", 33554432);
      props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
      props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
      //取消域名绑定检查
      props.put("ssl.endpoint.identification.algorithm", "");
      //设置连接安全协议
      props.put("security.protocol", "SASL_SSL");
      //设置.jks认证文件路径
      props.put("ssl.truststore.location", " D:/client.truststore.jks");
      //设置认证文件密码
      props.put("ssl.truststore.password", "abcdefg");
      //单独使用SSL下面两个参数不需要配置,这两个是SASL用的
      props.put("sasl.mechanism", "SCRAM-SHA-512");
      props.put("sasl.jaas.config",
            "org.apache.kafka.common.security.scram.ScramLoginModule required username='easy' password='easy1234';");
      /*props.put("sasl.jaas.config",
            "org.apache.kafka.common.security.scram.ScramLoginModule required username='admin' password='admin1234';");*/
      KafkaProducer<String, String> producer = new KafkaProducer<>(props);
      for (int i = 0; i < 5; i++) {
         ProducerRecord<String, String> record = new ProducerRecord<>("my-topic", "topic_" + i, "topic_:" + i);
         Future<RecordMetadata> metadataFuture = producer.send(record);
         RecordMetadata recordMetadata = null;
         try {
            recordMetadata = metadataFuture.get();
            System.out.println("发送成功!topic:" + recordMetadata.topic()+" partition:" + recordMetadata.partition()+" offset:" + recordMetadata.offset());
         } catch (Exception e) {
            System.out.println("发送失败!");
            e.printStackTrace();
         }
      }
      producer.flush();
      producer.close();
   }
}

Consumer 连接:

public class SSLConsumer {
   public static void main(String[] args) {
      Properties props = new Properties();
      props.put("bootstrap.servers", "192.168.33.101:9092");
      props.put("group.id", "aaa");
      props.put("enable.auto.commit", "false");
      props.put("auto.offset.reset", "earliest");
      props.put("auto.commit.interval.ms", "1000");
      props.put("session.timeout.ms", "30000");
      props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
      props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
      props.put("ssl.endpoint.identification.algorithm", "");
      props.put("security.protocol", "SASL_SSL");
      props.put("ssl.truststore.location", "D:/client.truststore.jks");
      props.put("ssl.truststore.password", "abcdefg");
      //单独使用SSL下面两个参数不需要配置,这两个是SASL用的
      props.put("sasl.mechanism", "SCRAM-SHA-512");
      /*props.put("sasl.jaas.config",
            "org.apache.kafka.common.security.scram.ScramLoginModule required username='easy' password='easy1234';");*/
      props.put("sasl.jaas.config",
            "org.apache.kafka.common.security.scram.ScramLoginModule required username='admin' password='admin1234';");
      KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props);
      consumer.subscribe(Arrays.asList("my-topic"));
      while (true) {
         ConsumerRecords<String, String> records = consumer.poll(100);
         for (ConsumerRecord<String, String> record : records) {
            System.out.printf("partition= %d, offset = %d, key = %s, value = %s\n", record.partition(),
                  record.offset(), record.key(), record.value());
         }
      }
   }
}

如果一切正常,上面两个程序应该可以正常读写Kafka集群,到此Kafka 给集群配置SSL认证结束。

附:认证报错

Error 1: SSL handshake failed

如果认证没有通过(比如没有配置.jks文件)就会报SSL handshake failed的错误。

java.util.concurrent.ExecutionException: org.apache.kafka.common.errors.SslAuthenticationException: SSL handshake failed
	at org.apache.kafka.clients.producer.KafkaProducer$FutureFailure.<init>(KafkaProducer.java:1317)
	at org.apache.kafka.clients.producer.KafkaProducer.doSend(KafkaProducer.java:986)
	at org.apache.kafka.clients.producer.KafkaProducer.send(KafkaProducer.java:886)
	at org.apache.kafka.clients.producer.KafkaProducer.send(KafkaProducer.java:774)
	at com.api.EasySSLProducer.main(EasySSLProducer.java:32)
Caused by: org.apache.kafka.common.errors.SslAuthenticationException: SSL handshake failed
Caused by: javax.net.ssl.SSLHandshakeException: General SSLEngine problem
	at sun.security.ssl.Handshaker.checkThrown(Handshaker.java:1529)
	at sun.security.ssl.SSLEngineImpl.checkTaskThrown(SSLEngineImpl.java:535)
	at sun.security.ssl.SSLEngineImpl.writeAppRecord(SSLEngineImpl.java:1214)
	at sun.security.ssl.SSLEngineImpl.wrap(SSLEngineImpl.java:1186)
	at javax.net.ssl.SSLEngine.wrap(SSLEngine.java:469)
	at org.apache.kafka.common.network.SslTransportLayer.handshakeWrap(SslTransportLayer.java:478)
	at org.apache.kafka.common.network.SslTransportLayer.doHandshake(SslTransportLayer.java:341)
	at org.apache.kafka.common.network.SslTransportLayer.handshake(SslTransportLayer.java:291)
	at org.apache.kafka.common.network.KafkaChannel.prepare(KafkaChannel.java:173)
	at org.apache.kafka.common.network.Selector.pollSelectionKeys(Selector.java:543)
	at org.apache.kafka.common.network.Selector.poll(Selector.java:481)
	at org.apache.kafka.clients.NetworkClient.poll(NetworkClient.java:561)
	at org.apache.kafka.clients.producer.internals.Sender.runOnce(Sender.java:325)
	at org.apache.kafka.clients.producer.internals.Sender.run(Sender.java:240)
	at java.lang.Thread.run(Thread.java:748)
Caused by: javax.net.ssl.SSLHandshakeException: General SSLEngine problem
	at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
	at sun.security.ssl.SSLEngineImpl.fatal(SSLEngineImpl.java:1728)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:330)
	at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:322)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1614)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1052)
	at sun.security.ssl.Handshaker$1.run(Handshaker.java:992)
	at sun.security.ssl.Handshaker$1.run(Handshaker.java:989)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.security.ssl.Handshaker$DelegatedTask.run(Handshaker.java:1467)
	at org.apache.kafka.common.network.SslTransportLayer.runDelegatedTasks(SslTransportLayer.java:430)
	at org.apache.kafka.common.network.SslTransportLayer.handshakeUnwrap(SslTransportLayer.java:514)
	at org.apache.kafka.common.network.SslTransportLayer.doHandshake(SslTransportLayer.java:368)
	... 8 more
Caused by: java.security.cert.CertificateException: No subject alternative names present
	at sun.security.util.HostnameChecker.matchIP(HostnameChecker.java:145)
	at sun.security.util.HostnameChecker.match(HostnameChecker.java:94)
	at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:455)
	at sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:436)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:252)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:136)
	at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1601)
	... 17 more

Error 2: Keystore was tampered with, or password was incorrect

如果证书的认证密码错误,则会报密码不正确的错误。

Caused by: java.io.IOException: Keystore was tampered with, or password was incorrect
	at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:780)
	at sun.security.provider.JavaKeyStore$JKS.engineLoad(JavaKeyStore.java:56)
	at sun.security.provider.KeyStoreDelegator.engineLoad(KeyStoreDelegator.java:224)
	at sun.security.provider.JavaKeyStore$DualFormatJKS.engineLoad(JavaKeyStore.java:70)
	at java.security.KeyStore.load(KeyStore.java:1445)
	at org.apache.kafka.common.security.ssl.DefaultSslEngineFactory$FileBasedStore.load(DefaultSslEngineFactory.java:374)
	... 13 more
Caused by: java.security.UnrecoverableKeyException: Password verification failed
	at sun.security.provider.JavaKeyStore.engineLoad(JavaKeyStore.java:778)
	... 18 more
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值