TLS Encryption

TLS overview

Transport Layer Security (TLS) is a form of public key cryptography. By default, Pulsar clients communicate with Pulsar services in plain text. This means that all data is sent in the clear. You can use TLS to encrypt this traffic to protect the traffic from the snooping of a man-in-the-middle attacker.

This section introduces how to configure TLS encryption in Pulsar. For how to configure mTLS authentication in Pulsar, refer to mTLS authentication. Alternatively, you can use another Athenz authentication on top of TLS transport encryption.

TLS Encryption - 图1note

Enabling TLS encryption may impact the performance due to encryption overhead.

TLS certificates

TLS certificates include the following three types. Each certificate (key pair) contains both a public key that encrypts messages and a private key that decrypts messages.

  • Certificate Authority (CA)
    • CA private key is distributed to all parties involved.
    • CA public key (trust cert) is used for signing a certificate for either broker or clients.
  • Server key pairs
  • Client key pairs (for mutual TLS)

For both server and client certificates, the private key with a certificate request is generated first, and the public key (the certificate) is generated after the trust cert signs the certificate request. When mTLS authentication is enabled, the server uses the trust cert to verify that the client has a key pair that the certificate authority signs. The Common Name (CN) of a client certificate is used as the client’s role token, while the Subject Alternative Name (SAN) of a server certificate is used for Hostname verification.

TLS Encryption - 图2note

The validity of these certificates is 365 days. It’s highly recommended to use sha256 or sha512 as the signature algorithm, while sha1 is not supported.

Certificate formats

You can use either one of the following certificate formats to configure TLS encryption:

Hostname verification

Hostname verification is a TLS security feature whereby a client can refuse to connect to a server if the Subject Alternative Name (SAN) does not match the hostname that the hostname is connecting to.

By default, Pulsar clients disable hostname verification, as it requires that each broker has a DNS record and a unique cert.

One scenario where you may want to enable hostname verification is where you have multiple proxy nodes behind a VIP, and the VIP has a DNS record, for example, pulsar.mycompany.com. In this case, you can generate a TLS cert with pulsar.mycompany.com as the SAN, and then enable hostname verification on the client.

To enable hostname verification in Pulsar, ensure that SAN exactly matches the fully qualified domain name (FQDN) of the server. The client compares the SAN with the DNS domain name to ensure that it is connecting to the desired server. See Configure clients for more details.

Moreover, as the administrator has full control of the CA, a bad actor is unlikely to be able to pull off a man-in-the-middle attack. allowInsecureConnection allows the client to connect to servers whose cert has not been signed by an approved CA. The client disables allowInsecureConnection by default, and you should always disable allowInsecureConnection in production environments. As long as you disable allowInsecureConnection, a man-in-the-middle attack requires that the attacker has access to the CA.

Configure mTLS encryption with PEM

By default, Pulsar uses netty-tcnative. It includes two implementations, OpenSSL (default) and JDK. When OpenSSL is unavailable, JDK is used.

Create TLS certificates

Creating TLS certificates involves creating a certificate authority, a server certificate, and a client certificate.

Create a certificate authority

You can use a certificate authority (CA) to sign both server and client certificates. This ensures that each party trusts the others. Store CA in a very secure location (ideally completely disconnected from networks, air-gapped, and fully encrypted).

Use the following command to create a CA.

  1. openssl genrsa -out ca.key.pem 2048
  2. openssl req -x509 -new -nodes -key ca.key.pem -subj "/CN=CARoot" -days 365 -out ca.cert.pem

TLS Encryption - 图3note

The default openssl on macOS doesn’t work for the commands above. You need to upgrade openssl via Homebrew:

  1. brew install openssl
  2. export PATH="/usr/local/Cellar/openssl@3/3.0.1/bin:$PATH"

Use the actual path from the output of the brew install command. Note that version number 3.0.1 might change.

Create a server certificate

Once you have created a CA, you can create certificate requests and sign them with the CA.

  1. Generate the server’s private key.

    1. openssl genrsa -out broker.key.pem 2048

    The broker expects the key to be in PKCS 8 format. Enter the following command to convert it.

    1. openssl pkcs8 -topk8 -inform PEM -outform PEM -in broker.key.pem -out broker.key-pk8.pem -nocrypt
  2. Create a broker.conf file with the following content:

    1. [ req ]
    2. default_bits = 2048
    3. prompt = no
    4. default_md = sha256
    5. distinguished_name = dn
    6. [ v3_ext ]
    7. authorityKeyIdentifier=keyid,issuer:always
    8. basicConstraints=CA:FALSE
    9. keyUsage=critical, digitalSignature, keyEncipherment
    10. extendedKeyUsage=serverAuth
    11. subjectAltName=@alt_names
    12. [ dn ]
    13. CN = broker
    14. [ alt_names ]
    15. DNS.1 = pulsar
    16. DNS.2 = pulsar.default
    17. IP.1 = 127.0.0.1
    18. IP.2 = 192.168.1.2

    TLS Encryption - 图4tip

    To configure hostname verification, you need to enter the hostname of the broker in alt_names as the Subject Alternative Name (SAN). To ensure that multiple machines can reuse the same certificate, you can also use a wildcard to match a group of broker hostnames, for example, *.broker.usw.example.com.

  3. Generate the certificate request.

    1. openssl req -new -config broker.conf -key broker.key.pem -out broker.csr.pem -sha256
  4. Sign the certificate with the CA.

    1. openssl x509 -req -in broker.csr.pem -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -out broker.cert.pem -days 365 -extensions v3_ext -extfile broker.conf -sha256

At this point, you have a cert, broker.cert.pem, and a key, broker.key-pk8.pem, which you can use along with ca.cert.pem to configure TLS encryption for your brokers and proxies.

Create a client certificate

  1. Generate the client’s private key.

    1. openssl genrsa -out client.key.pem 2048

    The client expects the key to be in PKCS 8 format. Enter the following command to convert it.

    1. openssl pkcs8 -topk8 -inform PEM -outform PEM -in client.key.pem -out client.key-pk8.pem -nocrypt
  2. Generate the certificate request. Note that the value of CN is used as the client’s role token.

    1. openssl req -new -subj "/CN=client" -key client.key.pem -out client.csr.pem -sha256
  3. Sign the certificate with the CA.

    1. openssl x509 -req -in client.csr.pem -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -out client.cert.pem -days 365 -sha256

At this point, you have a cert client.cert.pem and a key client.key-pk8.pem, which you can use along with ca.cert.pem to configure TLS encryption for your clients.

Configure brokers

To configure a Pulsar broker to use TLS encryption, you need to add these values to broker.conf in the conf directory of your Pulsar installation. Substitute the appropriate certificate paths where necessary.

  1. # configure TLS ports
  2. brokerServicePortTls=6651
  3. webServicePortTls=8081
  4. # configure CA certificate
  5. tlsTrustCertsFilePath=/path/to/ca.cert.pem
  6. # configure server certificate
  7. tlsCertificateFilePath=/path/to/broker.cert.pem
  8. # configure server's priviate key
  9. tlsKeyFilePath=/path/to/broker.key-pk8.pem
  10. # enable mTLS
  11. tlsRequireTrustedClientCertOnConnect=true
  12. # configure mTLS for the internal client
  13. brokerClientTlsEnabled=true
  14. brokerClientTrustCertsFilePath=/path/to/ca.cert.pem
  15. brokerClientCertificateFilePath=/path/to/client.cert.pem
  16. brokerClientKeyFilePath=/path/to/client.key-pk8.pem

Configure TLS Protocol Version and Cipher

To configure the broker (and proxy) to require specific TLS protocol versions and ciphers for TLS negotiation, you can use the TLS protocol versions and ciphers to stop clients from requesting downgraded TLS protocol versions or ciphers that may have weaknesses.

By default, Pulsar uses OpenSSL when it is available, otherwise, Pulsar defaults back to the JDK implementation. OpenSSL currently supports TLSv1.1, TLSv1.2 and TLSv1.3. You can acquire a list of supported ciphers from the OpenSSL ciphers command, i.e. openssl ciphers -tls1_3.

Both the TLS protocol versions and cipher properties can take multiple values, separated by commas. The possible values for protocol versions and ciphers depend on the TLS provider that you are using.

  1. tlsProtocols=TLSv1.3,TLSv1.2
  2. tlsCiphers=TLS_DH_RSA_WITH_AES_256_GCM_SHA384,TLS_DH_RSA_WITH_AES_256_CBC_SHA
  • tlsProtocols=TLSv1.3,TLSv1.2: List out the TLS protocols that you are going to accept from clients. By default, it is not set.
  • tlsCiphers=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: A cipher suite is a named combination of authentication, encryption, MAC and key exchange algorithm used to negotiate the security settings for a network connection using TLS network protocol. By default, it is null. See OpenSSL Ciphers and JDK Ciphers for more details.

For JDK 11, you can obtain a list of supported values from the documentation:

Configure proxies

Configuring mTLS on proxies includes two directions of connections, from clients to proxies, and from proxies to brokers.

  1. # configure TLS ports
  2. servicePortTls=6651
  3. webServicePortTls=8081
  4. # configure certificates for clients to connect proxy
  5. tlsCertificateFilePath=/path/to/broker.cert.pem
  6. tlsKeyFilePath=/path/to/broker.key-pk8.pem
  7. tlsTrustCertsFilePath=/path/to/ca.cert.pem
  8. # enable mTLS
  9. tlsRequireTrustedClientCertOnConnect=true
  10. # configure TLS for proxy to connect brokers
  11. tlsEnabledWithBroker=true
  12. brokerClientTrustCertsFilePath=/path/to/ca.cert.pem
  13. brokerClientCertificateFilePath=/path/to/client.cert.pem
  14. brokerClientKeyFilePath=/path/to/client.key-pk8.pem

Configure clients

To enable TLS encryption, you need to configure the clients to use https:// with port 8443 for the web service URL, and pulsar+ssl:// with port 6651 for the broker service URL.

As the server certificate that you generated above does not belong to any of the default trust chains, you also need to either specify the path of the trust cert (recommended) or enable the clients to allow untrusted server certs.

The following examples show how to configure TLS encryption for Java/Python/C++/Node.js/C#/WebSocket clients.

  • Java
  • Python
  • C++
  • Node.js
  • C#
  • WebSocket API
  1. import org.apache.pulsar.client.api.PulsarClient;
  2. PulsarClient client = PulsarClient.builder()
  3. .serviceUrl("pulsar+ssl://broker.example.com:6651/")
  4. .tlsKeyFilePath("/path/to/client.key-pk8.pem")
  5. .tlsCertificateFilePath("/path/to/client.cert.pem")
  6. .tlsTrustCertsFilePath("/path/to/ca.cert.pem")
  7. .enableTlsHostnameVerification(false) // false by default, in any case
  8. .allowTlsInsecureConnection(false) // false by default, in any case
  9. .build();
  1. from pulsar import Client
  2. client = Client("pulsar+ssl://broker.example.com:6651/",
  3. tls_hostname_verification=False,
  4. tls_trust_certs_file_path="/path/to/ca.cert.pem",
  5. tls_allow_insecure_connection=False) // defaults to false from v2.2.0 onwards
  1. #include <pulsar/Client.h>
  2. ClientConfiguration config = ClientConfiguration();
  3. config.setUseTls(true); // shouldn't be needed soon
  4. config.setTlsTrustCertsFilePath(caPath);
  5. config.setTlsAllowInsecureConnection(false);
  6. config.setAuth(pulsar::AuthTls::create(clientPublicKeyPath, clientPrivateKeyPath));
  7. config.setValidateHostName(false);
  1. const Pulsar = require('pulsar-client');
  2. (async () => {
  3. const client = new Pulsar.Client({
  4. serviceUrl: 'pulsar+ssl://broker.example.com:6651/',
  5. tlsTrustCertsFilePath: '/path/to/ca.cert.pem',
  6. useTls: true,
  7. tlsValidateHostname: false,
  8. tlsAllowInsecureConnection: false,
  9. });
  10. })();
  1. var certificate = new X509Certificate2("ca.cert.pem");
  2. var client = PulsarClient.Builder()
  3. .TrustedCertificateAuthority(certificate) //If the CA is not trusted on the host, you can add it explicitly.
  4. .VerifyCertificateAuthority(true) //Default is 'true'
  5. .VerifyCertificateName(false) //Default is 'false'
  6. .Build();

TLS Encryption - 图5note

VerifyCertificateName refers to the configuration of hostname verification in the C# client.

  1. import websockets
  2. import asyncio
  3. import base64
  4. import json
  5. import ssl
  6. import pathlib
  7. ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
  8. client_cert_pem = pathlib.Path(__file__).with_name("client.cert.pem")
  9. client_key_pem = pathlib.Path(__file__).with_name("client.key.pem")
  10. ca_cert_pem = pathlib.Path(__file__).with_name("ca.cert.pem")
  11. ssl_context.load_cert_chain(certfile=client_cert_pem, keyfile=client_key_pem)
  12. ssl_context.load_verify_locations(ca_cert_pem)
  13. # websocket producer uri wss, not ws
  14. uri = "wss://localhost:8080/ws/v2/producer/persistent/public/default/testtopic"
  15. client_pem = pathlib.Path(__file__).with_name("pulsar_client.pem")
  16. ssl_context.load_verify_locations(client_pem)
  17. # websocket producer uri wss, not ws
  18. uri = "wss://localhost:8080/ws/v2/producer/persistent/public/default/testtopic"
  19. # encode message
  20. s = "Hello World"
  21. firstEncoded = s.encode("UTF-8")
  22. binaryEncoded = base64.b64encode(firstEncoded)
  23. payloadString = binaryEncoded.decode('UTF-8')
  24. async def producer_handler(websocket):
  25. await websocket.send(json.dumps({
  26. 'payload' : payloadString,
  27. 'properties': {
  28. 'key1' : 'value1',
  29. 'key2' : 'value2'
  30. },
  31. 'context' : 5
  32. }))
  33. async def test():
  34. async with websockets.connect(uri) as websocket:
  35. await producer_handler(websocket)
  36. message = await websocket.recv()
  37. print(f"< {message}")
  38. asyncio.run(test())

TLS Encryption - 图6note

In addition to the required configurations in the conf/client.conf file, you need to configure more parameters in the conf/broker.conf file to enable TLS encryption on WebSocket service. For more details, see security settings for WebSocket.

Configure CLI tools

Command-line tools like pulsar-admin, pulsar-perf, and pulsar-client use the conf/client.conf config file in a Pulsar installation.

To use mTLS encryption with Pulsar CLI tools, you need to add the following parameters to the conf/client.conf file.

  1. webServiceUrl=https://localhost:8081/
  2. brokerServiceUrl=pulsar+ssl://localhost:6651/
  3. authPlugin=org.apache.pulsar.client.impl.auth.AuthenticationTls
  4. authParams=tlsCertFile:/path/to/client.cert.pem,tlsKeyFile:/path/to/client.key-pk8.pem

Configure mTLS encryption with KeyStore

By default, Pulsar uses Conscrypt for both broker service and Web service.

Generate JKS certificate

You can use Java’s keytool utility to generate the key and certificate for each machine in the cluster.

  1. DAYS=365
  2. CLIENT_COMMON_PARAMS="-storetype JKS -storepass clientpw -keypass clientpw -noprompt"
  3. BROKER_COMMON_PARAMS="-storetype JKS -storepass brokerpw -keypass brokerpw -noprompt"
  4. # create keystore
  5. keytool -genkeypair -keystore broker.keystore.jks ${BROKER_COMMON_PARAMS} -keyalg RSA -keysize 2048 -alias broker -validity $DAYS \
  6. -dname 'CN=broker,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown'
  7. keytool -genkeypair -keystore client.keystore.jks ${CLIENT_COMMON_PARAMS} -keyalg RSA -keysize 2048 -alias client -validity $DAYS \
  8. -dname 'CN=client,OU=Unknown,O=Unknown,L=Unknown,ST=Unknown,C=Unknown'
  9. # export certificate
  10. keytool -exportcert -keystore broker.keystore.jks ${BROKER_COMMON_PARAMS} -file broker.cer -alias broker
  11. keytool -exportcert -keystore client.keystore.jks ${CLIENT_COMMON_PARAMS} -file client.cer -alias client
  12. # generate truststore
  13. keytool -importcert -keystore client.truststore.jks ${CLIENT_COMMON_PARAMS} -file broker.cer -alias truststore
  14. keytool -importcert -keystore broker.truststore.jks ${BROKER_COMMON_PARAMS} -file client.cer -alias truststore

TLS Encryption - 图7note

To configure hostname verification, you need to append -ext SAN=IP:127.0.0.1,IP:192.168.20.2,DNS:broker.example.com to the value of BROKER_COMMON_PARAMS as the Subject Alternative Name (SAN).

Configure brokers

Configure the following parameters in the conf/broker.conf file and restrict access to the store files via filesystem permissions.

  1. brokerServicePortTls=6651
  2. webServicePortTls=8081
  3. # Trusted client certificates are required to connect TLS
  4. # Reject the Connection if the Client Certificate is not trusted.
  5. # In effect, this requires that all connecting clients perform TLS client
  6. # authentication.
  7. tlsRequireTrustedClientCertOnConnect=true
  8. tlsEnabledWithKeyStore=true
  9. # key store
  10. tlsKeyStoreType=JKS
  11. tlsKeyStore=/var/private/tls/broker.keystore.jks
  12. tlsKeyStorePassword=brokerpw
  13. # trust store
  14. tlsTrustStoreType=JKS
  15. tlsTrustStore=/var/private/tls/broker.truststore.jks
  16. tlsTrustStorePassword=brokerpw
  17. # internal client/admin-client config
  18. brokerClientTlsEnabled=true
  19. brokerClientTlsEnabledWithKeyStore=true
  20. brokerClientTlsTrustStoreType=JKS
  21. brokerClientTlsTrustStore=/var/private/tls/client.truststore.jks
  22. brokerClientTlsTrustStorePassword=clientpw
  23. brokerClientTlsKeyStoreType=JKS
  24. brokerClientTlsKeyStore=/var/private/tls/client.keystore.jks
  25. brokerClientTlsKeyStorePassword=clientpw

To disable non-TLS ports, you need to set the values of brokerServicePort and webServicePort to empty.

TLS Encryption - 图8note

The default value of tlsRequireTrustedClientCertOnConnect is false, which represents one-way TLS. When it’s set to true (mutual TLS is enabled), brokers/proxies require trusted client certificates; otherwise, brokers/proxies reject connection requests from clients.

Configure proxies

Configuring mTLS on proxies includes two directions of connections, from clients to proxies, and from proxies to brokers.

  1. servicePortTls=6651
  2. webServicePortTls=8081
  3. tlsRequireTrustedClientCertOnConnect=true
  4. # keystore
  5. tlsKeyStoreType=JKS
  6. tlsKeyStore=/var/private/tls/proxy.keystore.jks
  7. tlsKeyStorePassword=brokerpw
  8. # truststore
  9. tlsTrustStoreType=JKS
  10. tlsTrustStore=/var/private/tls/proxy.truststore.jks
  11. tlsTrustStorePassword=brokerpw
  12. # internal client/admin-client config
  13. tlsEnabledWithKeyStore=true
  14. brokerClientTlsEnabled=true
  15. brokerClientTlsEnabledWithKeyStore=true
  16. brokerClientTlsTrustStoreType=JKS
  17. brokerClientTlsTrustStore=/var/private/tls/client.truststore.jks
  18. brokerClientTlsTrustStorePassword=clientpw
  19. brokerClientTlsKeyStoreType=JKS
  20. brokerClientTlsKeyStore=/var/private/tls/client.keystore.jks
  21. brokerClientTlsKeyStorePassword=clientpw

Configure clients

Similar to Configure mTLS encryption with PEM, you need to provide the TrustStore information for a minimal configuration.

The following is an example.

  • Java client
  • Java admin client
  1. import org.apache.pulsar.client.api.PulsarClient;
  2. PulsarClient client = PulsarClient.builder()
  3. .serviceUrl("pulsar+ssl://broker.example.com:6651/")
  4. .useKeyStoreTls(true)
  5. .tlsTrustStoreType("JKS")
  6. .tlsTrustStorePath("/var/private/tls/client.truststore.jks")
  7. .tlsTrustStorePassword("clientpw")
  8. .tlsKeyStoreType("JKS")
  9. .tlsKeyStorePath("/var/private/tls/client.keystore.jks")
  10. .tlsKeyStorePassword("clientpw")
  11. .enableTlsHostnameVerification(false) // false by default, in any case
  12. .allowTlsInsecureConnection(false) // false by default, in any case
  13. .build();

TLS Encryption - 图9note

If you set useKeyStoreTls to true, be sure to configure tlsTrustStorePath.

  1. PulsarAdmin amdin = PulsarAdmin.builder().serviceHttpUrl("https://broker.example.com:8443")
  2. .tlsTrustStoreType("JKS")
  3. .tlsTrustStorePath("/var/private/tls/client.truststore.jks")
  4. .tlsTrustStorePassword("clientpw")
  5. .tlsKeyStoreType("JKS")
  6. .tlsKeyStorePath("/var/private/tls/client.keystore.jks")
  7. .tlsKeyStorePassword("clientpw")
  8. .enableTlsHostnameVerification(false) // false by default, in any case
  9. .allowTlsInsecureConnection(false) // false by default, in any case
  10. .build();

Configure CLI tools

For Command-line tools like pulsar-admin, pulsar-perf, and pulsar-client, use the conf/client.conf config file in a Pulsar installation.

  1. authPlugin=org.apache.pulsar.client.impl.auth.AuthenticationKeyStoreTls
  2. authParams={"keyStoreType":"JKS","keyStorePath":"/var/private/tls/client.keystore.jks","keyStorePassword":"clientpw"}

Enable TLS Logging

You can enable TLS debug logging at the JVM level by starting the brokers and/or clients with javax.net.debug system property. For example:

  1. -Djavax.net.debug=all

For more details, see Oracle documentation.