How to Enable Username/Password Authentication (SASL/PLAIN) in Kafka 4.0 with HAProxy SSL Termination

In the previous post, How to Install Kafka 4.0 on a Single Ubuntu 24.04 Node with HAProxy SSL Termination, we set up a Kafka 4.0 broker in KRaft mode with:

  • Kafka EXTERNAL listener on 127.0.0.1:9093 (PLAINTEXT)
  • Kafka CONTROLLER listener on 127.0.0.1:9094 (PLAINTEXT)
  • HAProxy listening on 192.168.0.73:9093 (TLS) and forwarding to 127.0.0.1:9093

That setup used TLS (via HAProxy) but allowed unauthenticated access to Kafka.

In this post, we’ll extend that configuration by enabling username/password authentication using SASL/PLAIN on the Kafka EXTERNAL listener. Clients will connect with SASL_SSL to HAProxy, and Kafka will enforce SASL_PLAINTEXT behind HAProxy.


Architecture Recap

After enabling SASL/PLAIN, the architecture looks like this:

ComponentAddress / HostProtocolPurpose
Kafka EXTERNAL127.0.0.1:9093SASL_PLAINTEXTBroker listener behind HAProxy
Kafka CONTROLLER127.0.0.1:9094PLAINTEXTKRaft controller listener
HAProxy frontend192.168.0.73:9093SSL (public)TLS termination, forwards to 127.0.0.1:9093
Clientkafka.maksonlee.com:9093SASL_SSLAuthenticated + encrypted client access
  • Clients → HAProxy: TLS + SASL (SASL_SSL)
  • HAProxy → Kafka: Plain TCP, but Kafka requires SASL on the EXTERNAL listener (SASL_PLAINTEXT)
  • Controller listener stays PLAINTEXT and unauthenticated (internal use only)

  1. Enable SASL/PLAIN on Kafka Broker

Edit the KRaft server configuration:

sudo vi /opt/kafka/config/kraft/server.properties

Apply the following changes:

# 1. Protocol mapping
- listener.security.protocol.map=EXTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT
+ listener.security.protocol.map=EXTERNAL:SASL_PLAINTEXT,CONTROLLER:PLAINTEXT

# 2. SASL mechanism
+ sasl.enabled.mechanisms=PLAIN
+ sasl.mechanism.inter.broker.protocol=PLAIN

# 3. JAAS configuration (inline)
+ listener.name.external.plain.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \
+   username="admin" \
+   password="admin-secret" \
+   user_admin="admin-secret" \
+   user_user1="password1";

That’s all you need to add/change for SASL/PLAIN.


  1. Restart Kafka

Apply the configuration changes by restarting Kafka:

sudo systemctl restart kafka

Verify that Kafka starts without errors using:

journalctl -u kafka -f

  1. No Changes Needed to HAProxy

You do not need to touch /etc/haproxy/haproxy.cfg.

From HAProxy’s perspective, nothing changed:

  • It still terminates TLS on 192.168.0.73:9093
  • It still forwards raw TCP to 127.0.0.1:9093

From Kafka’s perspective:

  • Incoming connections on EXTERNAL must now authenticate with SASL/PLAIN.
  • The connection between HAProxy and Kafka is still unencrypted (loopback), but SASL_PLAINTEXT is enforced.

Clients must now:

  • Use security.protocol=sasl_ssl
  • Provide the correct SASL username/password

  1. Test with Python (confluent_kafka)

produce_kafka.py

from confluent_kafka import Producer

conf = {
    'bootstrap.servers': 'kafka.maksonlee.com:9093',
    'security.protocol': 'sasl_ssl',
    'sasl.mechanism': 'PLAIN',
    'sasl.username': 'user1',
    'sasl.password': 'password1',
}

producer = Producer(conf)

def delivery_report(err, msg):
    if err:
        print('Delivery failed:', err)
    else:
        print(f'Message delivered to {msg.topic()} [{msg.partition()}]')

producer.produce('test-topic', key='key1', value='Hello Kafka!', callback=delivery_report)
producer.flush()

consume_kafka.py

from confluent_kafka import Consumer

conf = {
    'bootstrap.servers': 'kafka.maksonlee.com:9093',
    'security.protocol': 'sasl_ssl',
    'sasl.mechanism': 'PLAIN',
    'sasl.username': 'user1',
    'sasl.password': 'password1',
    'group.id': 'test-group',
    'auto.offset.reset': 'earliest'
}

consumer = Consumer(conf)
consumer.subscribe(['test-topic'])

print('Waiting for messages...')
msg = consumer.poll(10.0)

if msg is None:
    print("No message received.")
elif msg.error():
    print("Error:", msg.error())
else:
    print(f'Received: {msg.value().decode("utf-8")}')

consumer.close()

Summary

You’ve now added simple username/password authentication to your Kafka 4.0 setup with HAProxy SSL termination. This ensures that only valid clients with credentials can publish or consume messages.

If you want more secure password storage or LDAP integration in the future, consider using SCRAM-SHA-256 or delegating auth to external identity providers.

Did this guide save you time?

Support this site

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top