Pulsar’s JWT and TLS authentication are often seen as two separate, independent security mechanisms, but they’re actually deeply intertwined, with TLS acting as the secure channel that makes JWT validation robust and trustworthy.
Let’s see Pulsar in action with both configured. Imagine a client application trying to connect to a Pulsar cluster.
# First, start a Pulsar broker and BookKeeper ensemble (simplified)
# ... (Assume these are already running and configured for TLS)
# Now, create a JWT token. This requires a private key that matches the broker's public key.
# Let's say our private key is 'broker.key.pem' and the token is signed by this key.
# For demonstration, we'll manually construct a simplified token payload.
# In a real scenario, this would be generated by an authentication service.
# Example JWT payload (JSON):
# {
# "sub": "my-pulsar-client",
# "iss": "my-auth-server",
# "aud": "pulsar-broker.example.com",
# "exp": 1678886400, # A future timestamp
# "iat": 1678800000 # A past timestamp
# }
# Let's assume we have a tool to sign this payload with our private key.
# The resulting token might look something like:
JWT_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1wdWxzYXItY2xpZW50IiwiaXNzIjoibXktYXV0aC1zZXJ2ZXIiLCJhYWQiOiJwdWxzYXItYnJva2VyLmV4YW1wbGUuY29tIiwiZXhwIjoxNjc4ODg2NDAwLCJpYXQiOjE2Nzg4MDAwMDB9.signature_part"
# Now, configure the Pulsar client to use TLS and present the JWT.
# This is typically done in the client's configuration file or programmatically.
# Example client configuration (client.conf):
# webServiceUrl=https://pulsar-broker.example.com:8080
# authPlugin=org.apache.pulsar.client.impl.auth.AuthenticationToken
# authParams=token:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJteS1wdWxzYXItY2xpZW50IiwiaXNzIjoibXktYXV0aC1zZXJ2ZXIiLCJhYWQiOiJwdWxzYXItYnJva2VyLmV4YW1wbGUuY29tIiwiZXhwIjoxNjc4ODg2NDAwLCJpYXQiOjE2Nzg4MDAwMDB9.signature_part
# tlsAllowInsecureConnection=false
# tlsTrustCertsFilePath=/path/to/ca.cert.pem
The core problem Pulsar’s JWT and TLS authentication solve is establishing a secure and authenticated connection between clients and brokers, and between brokers themselves. TLS handles the encryption of the communication channel, preventing eavesdropping and man-in-the-middle attacks. JWT, when used with TLS, provides a verifiable identity for the client, ensuring that only authorized entities can access Pulsar resources.
Here’s how it breaks down internally:
-
TLS Handshake: When a client (or broker) initiates a connection to a Pulsar broker, the first step is a TLS handshake. The client presents its certificate (if client authentication is configured) or requests the broker’s certificate. The broker validates the client’s certificate against its trust store (or vice-versa). This establishes a secure, encrypted tunnel. The
tlsAllowInsecureConnection=falsesetting is crucial here, forcing the client to validate the broker’s certificate against thetlsTrustCertsFilePath. -
Authentication Plugin: Once the TLS tunnel is established, the Pulsar client’s configured
authPlugin(in this case,AuthenticationToken) takes over. It reads theauthParams, which contains the JWT token. -
Token Presentation: The client sends the JWT token to the broker over the already encrypted TLS channel.
-
Broker JWT Validation: The Pulsar broker receives the token. It then performs several checks:
- Signature Verification: It uses its configured public key (or a JWKS endpoint) to verify the signature of the JWT. This ensures the token hasn’t been tampered with and was issued by a trusted party (the entity holding the corresponding private key).
- Expiration Check: It verifies that the token has not expired (
expclaim). - Audience Check: It checks if the token is intended for this broker (
audclaim). - Issuer Check: It verifies the issuer of the token (
issclaim). - Subject Identification: The
subclaim identifies the client’s principal (e.g., a username or service account ID).
-
Authorization: After successful authentication, the broker consults its authorization policies to determine if the authenticated client (identified by its
subclaim) has permission to perform the requested operation (e.g., publish to a topic, subscribe to a subscription).
The magic of using JWT over TLS is that the JWT itself is sent through an encrypted channel. If you were to send a JWT over an unencrypted HTTP connection, an attacker could intercept the token, potentially steal it, and impersonate the client. TLS makes this interception useless because even if captured, the token is encrypted. Furthermore, the broker’s public key used for JWT signature verification is often distributed securely or fetched over TLS, ensuring its authenticity.
The most surprising thing most people don’t realize is that the tlsTrustCertsFilePath on the client is not just for trusting the broker’s identity; it’s also the mechanism by which the client can securely obtain the public key needed to verify the JWT signature if the broker is configured to serve its JWKS endpoint over TLS. In many setups, the broker’s TLS certificate itself is signed by a CA, and the client trusts that CA. The broker can then use its private key (which it never shares) to sign JWTs, and clients can derive the public key from the broker’s certificate or a trusted JWKS endpoint to verify those signatures, all within the secure TLS context.
The next hurdle you’ll likely face is configuring granular authorization policies based on the authenticated JWT subject.