Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Kafka mTLS Authentication

Mutual TLS (mTLS) provides certificate-based authentication where both clients and brokers present certificates to verify identity. It eliminates passwords entirely, relying on PKI infrastructure for identity management.


Use CaseRecommendation
Existing PKI infrastructureRecommended
Zero-trust environmentsRecommended
Service-to-service authRecommended
IoT/device authenticationRecommended
User authenticationConsider OAuth/SCRAM
No PKI availableConsider SCRAM
FeatureBenefit
No passwordsEliminates credential management
Strong identityCryptographic verification
Certificate lifecycleAutomated rotation via PKI
Mutual verificationBoth parties authenticated
Network encryptionBuilt into TLS handshake
FeaturemTLSSCRAMOAuth
Password requiredNoYesNo (tokens)
External infrastructurePKI/CANoneIdP
Credential rotationCert renewalManualToken refresh
Identity sourceCertificateBroker configIdP
FeatureKafka Version
SSL/TLS authentication0.9.0+
Per-listener SSL config1.0.0+
PEM format support2.7.0+
Custom principal builder0.10.0+

Certificate authority signing broker and client certificatesCertificate authority signing broker and client certificatesBroker CertificatesClient Certificateskafka1.example.comkafka2.example.comkafka3.example.comproducer-appconsumer-appadmin-toolCertificate Authority (CA)Root CA orIntermediate CACN=kafka1.example.comSAN=kafka1.example.comCN=producer-appOU=applicationssignssignssignssignssignssigns

Certificate requirements:

ComponentCertificate PurposeKey Fields
CATrust anchorRoot or intermediate CA
BrokerServer identityCN or SAN matching hostname
ClientClient identityCN identifying application

1. Create CA:

Terminal window
# Generate CA private key
openssl genrsa -out ca-key.pem 4096
# Create CA certificate
openssl req -new -x509 -days 3650 -key ca-key.pem -out ca-cert.pem \
-subj "/C=US/ST=CA/L=San Francisco/O=MyOrg/CN=Kafka-CA"

2. Create broker certificate:

Terminal window
# Generate broker private key
openssl genrsa -out kafka1-key.pem 2048
# Create certificate signing request (CSR)
openssl req -new -key kafka1-key.pem -out kafka1.csr \
-subj "/C=US/ST=CA/L=San Francisco/O=MyOrg/CN=kafka1.example.com"
# Create SAN extension file
cat > kafka1-san.ext << EOF
subjectAltName=DNS:kafka1.example.com,DNS:localhost,IP:192.168.1.10
EOF
# Sign certificate with CA
openssl x509 -req -days 365 -in kafka1.csr -CA ca-cert.pem -CAkey ca-key.pem \
-CAcreateserial -out kafka1-cert.pem -extfile kafka1-san.ext

3. Create client certificate:

Terminal window
# Generate client private key
openssl genrsa -out client-key.pem 2048
# Create CSR
openssl req -new -key client-key.pem -out client.csr \
-subj "/C=US/ST=CA/L=San Francisco/O=MyOrg/OU=Applications/CN=producer-app"
# Sign with CA
openssl x509 -req -days 365 -in client.csr -CA ca-cert.pem -CAkey ca-key.pem \
-CAcreateserial -out client-cert.pem
Terminal window
# Broker keystore (certificate + private key)
openssl pkcs12 -export -in kafka1-cert.pem -inkey kafka1-key.pem \
-out kafka1.keystore.p12 -name kafka1 \
-CAfile ca-cert.pem -caname root -password pass:keystore-password
# Truststore (CA certificate)
keytool -import -file ca-cert.pem -keystore kafka.truststore.p12 \
-storetype PKCS12 -alias ca -storepass truststore-password -noprompt
# Client keystore
openssl pkcs12 -export -in client-cert.pem -inkey client-key.pem \
-out client.keystore.p12 -name client \
-CAfile ca-cert.pem -caname root -password pass:keystore-password
Terminal window
# Install cfssl
go install github.com/cloudflare/cfssl/cmd/cfssl@latest
go install github.com/cloudflare/cfssl/cmd/cfssljson@latest
# CA config
cat > ca-config.json << EOF
{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"server": {
"usages": ["signing", "key encipherment", "server auth"],
"expiry": "8760h"
},
"client": {
"usages": ["signing", "key encipherment", "client auth"],
"expiry": "8760h"
}
}
}
}
EOF
# Generate CA
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
# Generate broker cert
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json \
-profile=server kafka1-csr.json | cfssljson -bare kafka1
# Generate client cert
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json \
-profile=client client-csr.json | cfssljson -bare client

server.properties
# Listener using SSL protocol (mTLS)
listeners=SSL://0.0.0.0:9093
advertised.listeners=SSL://kafka1.example.com:9093
# Inter-broker communication
security.inter.broker.protocol=SSL
# Require client certificate authentication
ssl.client.auth=required
# Broker keystore (certificate + private key)
ssl.keystore.type=PKCS12
ssl.keystore.location=/etc/kafka/ssl/kafka.keystore.p12
ssl.keystore.password=${KEYSTORE_PASSWORD}
ssl.key.password=${KEY_PASSWORD}
# Truststore (CA certificates)
ssl.truststore.type=PKCS12
ssl.truststore.location=/etc/kafka/ssl/kafka.truststore.p12
ssl.truststore.password=${TRUSTSTORE_PASSWORD}
# TLS protocol settings
ssl.enabled.protocols=TLSv1.3,TLSv1.2
ssl.endpoint.identification.algorithm=HTTPS
ValueBehavior
requiredClient must present valid certificate
requestedClient certificate optional
noneNo client certificate requested
# Use PEM files directly (no keystore)
ssl.keystore.type=PEM
ssl.keystore.certificate.chain=/etc/kafka/ssl/kafka1-cert.pem
ssl.keystore.key=/etc/kafka/ssl/kafka1-key.pem
ssl.truststore.type=PEM
ssl.truststore.certificates=/etc/kafka/ssl/ca-cert.pem

Configure different authentication per listener:

# Define listeners
listeners=INTERNAL://0.0.0.0:9092,MTLS://0.0.0.0:9093
# Map to protocols
listener.security.protocol.map=INTERNAL:PLAINTEXT,MTLS:SSL
# mTLS listener requires client cert
listener.name.mtls.ssl.client.auth=required
# Internal listener (trusted network)
listener.name.internal.ssl.client.auth=none

By default, the client’s Distinguished Name (DN) becomes the Kafka principal:

Certificate DN: CN=producer-app,OU=Applications,O=MyOrg
Kafka Principal: User:CN=producer-app,OU=Applications,O=MyOrg

Extract specific certificate fields:

# Use Common Name only
ssl.principal.mapping.rules=RULE:^CN=([^,]+).*$/$1/
# Examples:
# CN=producer-app,OU=Applications -> producer-app
# CN=admin,OU=Admins,O=MyOrg -> admin
ssl.principal.mapping.rules=\
RULE:^CN=([^,]+),OU=Services.*$/$1/,\
RULE:^CN=([^,]+),OU=Users.*$/User:$1/,\
DEFAULT
RuleInput DNOutput Principal
Rule 1CN=producer,OU=Services,O=MyOrgproducer
Rule 2CN=alice,OU=Users,O=MyOrgUser:alice
DEFAULTCN=other,OU=OtherCN=other,OU=Other

For complex logic, implement a custom builder:

import org.apache.kafka.common.security.auth.KafkaPrincipalBuilder;
import org.apache.kafka.common.security.auth.AuthenticationContext;
import org.apache.kafka.common.security.auth.KafkaPrincipal;
import org.apache.kafka.common.security.auth.SslAuthenticationContext;
import javax.net.ssl.SSLSession;
import java.security.cert.X509Certificate;
public class CustomPrincipalBuilder implements KafkaPrincipalBuilder {
@Override
public KafkaPrincipal build(AuthenticationContext context) {
if (context instanceof SslAuthenticationContext) {
SSLSession session = ((SslAuthenticationContext) context).session();
try {
X509Certificate cert = (X509Certificate) session.getPeerCertificates()[0];
String cn = extractCN(cert.getSubjectDN().getName());
return new KafkaPrincipal(KafkaPrincipal.USER_TYPE, cn);
} catch (Exception e) {
return KafkaPrincipal.ANONYMOUS;
}
}
return KafkaPrincipal.ANONYMOUS;
}
private String extractCN(String dn) {
// Extract CN from DN
for (String part : dn.split(",")) {
if (part.trim().startsWith("CN=")) {
return part.trim().substring(3);
}
}
return dn;
}
}
# Register custom builder
principal.builder.class=com.example.CustomPrincipalBuilder

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import java.util.Properties;
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1:9093,kafka2:9093");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
// SSL/mTLS configuration
props.put("security.protocol", "SSL");
// Client keystore (client certificate + private key)
props.put("ssl.keystore.type", "PKCS12");
props.put("ssl.keystore.location", "/etc/kafka/ssl/client.keystore.p12");
props.put("ssl.keystore.password", "keystore-password");
props.put("ssl.key.password", "key-password");
// Truststore (CA certificate)
props.put("ssl.truststore.type", "PKCS12");
props.put("ssl.truststore.location", "/etc/kafka/ssl/client.truststore.p12");
props.put("ssl.truststore.password", "truststore-password");
// Verify broker hostname
props.put("ssl.endpoint.identification.algorithm", "HTTPS");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);

application.yml:

spring:
kafka:
bootstrap-servers: kafka1:9093,kafka2:9093
properties:
security.protocol: SSL
ssl.endpoint.identification.algorithm: HTTPS
ssl:
key-store-location: classpath:client.keystore.p12
key-store-password: ${KEYSTORE_PASSWORD}
key-store-type: PKCS12
key-password: ${KEY_PASSWORD}
trust-store-location: classpath:truststore.p12
trust-store-password: ${TRUSTSTORE_PASSWORD}
trust-store-type: PKCS12
from confluent_kafka import Producer
config = {
'bootstrap.servers': 'kafka1:9093,kafka2:9093',
'security.protocol': 'SSL',
# Client certificate and key
'ssl.certificate.location': '/etc/kafka/ssl/client-cert.pem',
'ssl.key.location': '/etc/kafka/ssl/client-key.pem',
'ssl.key.password': 'key-password',
# CA certificate
'ssl.ca.location': '/etc/kafka/ssl/ca-cert.pem',
# Verify broker hostname
'ssl.endpoint.identification.algorithm': 'https',
}
producer = Producer(config)
import (
"github.com/confluentinc/confluent-kafka-go/v2/kafka"
)
producer, err := kafka.NewProducer(&kafka.ConfigMap{
"bootstrap.servers": "kafka1:9093,kafka2:9093",
"security.protocol": "SSL",
"ssl.certificate.location": "/etc/kafka/ssl/client-cert.pem",
"ssl.key.location": "/etc/kafka/ssl/client-key.pem",
"ssl.key.password": "key-password",
"ssl.ca.location": "/etc/kafka/ssl/ca-cert.pem",
"ssl.endpoint.identification.algorithm": "https",
})

client-ssl.properties:

security.protocol=SSL
ssl.keystore.type=PKCS12
ssl.keystore.location=/etc/kafka/ssl/client.keystore.p12
ssl.keystore.password=keystore-password
ssl.key.password=key-password
ssl.truststore.type=PKCS12
ssl.truststore.location=/etc/kafka/ssl/client.truststore.p12
ssl.truststore.password=truststore-password
ssl.endpoint.identification.algorithm=HTTPS
Terminal window
# List topics
kafka-topics.sh --bootstrap-server kafka:9093 \
--command-config client-ssl.properties \
--list
# Produce messages
kafka-console-producer.sh --bootstrap-server kafka:9093 \
--topic my-topic \
--producer.config client-ssl.properties
# Consume messages
kafka-console-consumer.sh --bootstrap-server kafka:9093 \
--topic my-topic \
--consumer.config client-ssl.properties \
--from-beginning

  1. Generate new certificates signed by same CA
  2. Update keystore on each broker
  3. Rolling restart brokers
Terminal window
# Verify new certificate before deployment
openssl x509 -in new-kafka1-cert.pem -text -noout
# Update keystore
openssl pkcs12 -export -in new-kafka1-cert.pem -inkey new-kafka1-key.pem \
-out kafka1.keystore.p12 -name kafka1 \
-password pass:keystore-password
# Copy to broker and restart
scp kafka1.keystore.p12 kafka1:/etc/kafka/ssl/
ssh kafka1 'systemctl restart kafka'

Rotating the CA requires more care:

  1. Add new CA to all truststores (alongside old CA)
  2. Rolling restart all brokers and clients
  3. Issue new certificates signed by new CA
  4. Deploy new certificates with rolling restarts
  5. Remove old CA from truststores
  6. Final rolling restart
# Kubernetes cert-manager Certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: kafka-broker
spec:
secretName: kafka-broker-tls
issuerRef:
name: kafka-ca-issuer
kind: ClusterIssuer
commonName: kafka1.example.com
dnsNames:
- kafka1.example.com
- kafka1
duration: 8760h # 1 year
renewBefore: 720h # 30 days
privateKey:
algorithm: RSA
size: 2048

RequirementRecommendation
Key sizeRSA 2048+ or ECDSA P-256+
Validity period1 year for services, shorter for rotation
SAN extensionInclude all hostnames/IPs
Key usageServer auth for brokers, client auth for clients
Terminal window
# Restrict keystore access
chmod 400 /etc/kafka/ssl/*.keystore.p12
chown kafka:kafka /etc/kafka/ssl/*.keystore.p12
# Private keys
chmod 400 /etc/kafka/ssl/*-key.pem
chown kafka:kafka /etc/kafka/ssl/*-key.pem
# Always verify broker hostname
ssl.endpoint.identification.algorithm=HTTPS
# Use TLS 1.2 or higher
ssl.enabled.protocols=TLSv1.3,TLSv1.2
# Strong cipher suites (example)
ssl.cipher.suites=TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256

ErrorCauseSolution
PKIX path building failedCA not in truststoreAdd CA to truststore
Certificate unknownClient cert not trustedSign with trusted CA
Hostname verification failedSAN doesn’t match hostnameAdd hostname to SAN
No cipher suites in commonProtocol/cipher mismatchCheck ssl.enabled.protocols
Keystore password incorrectWrong passwordVerify password
Terminal window
# JVM debug parameter
-Djavax.net.debug=ssl:handshake
# Or more verbose
-Djavax.net.debug=all
Terminal window
# Check certificate details
openssl x509 -in kafka1-cert.pem -text -noout
# Verify certificate chain
openssl verify -CAfile ca-cert.pem kafka1-cert.pem
# Check keystore contents
keytool -list -v -keystore kafka.keystore.p12 -storepass password
# Test SSL connection
openssl s_client -connect kafka1:9093 -CAfile ca-cert.pem \
-cert client-cert.pem -key client-key.pem
Terminal window
# Test broker connection
kafka-broker-api-versions.sh --bootstrap-server kafka:9093 \
--command-config client-ssl.properties
# Check broker logs
grep -i "ssl\|tls\|certificate" /var/log/kafka/server.log | tail -50

Use mTLS for transport security and SASL for authentication:

# Broker: SASL authentication over TLS
listeners=SASL_SSL://0.0.0.0:9093
# Client cert optional (SASL provides auth)
ssl.client.auth=requested
# SASL mechanism
sasl.enabled.mechanisms=SCRAM-SHA-512

This provides:

  • TLS encryption and optional client certificate verification
  • SASL-based identity for authorization