Skip to main content
You can configure JSON Web Token (JWT) authentication to a Pulsar cluster using asymmetric (RS256) signing. A private key signs tokens and a public key verifies them on the brokers and proxies, so the signing credential never needs to be distributed to cluster components.

Before you begin

  • Install the following tools.

Keep my-private.key strictly controlled. Anyone who holds the private key can issue valid tokens for any subject, including broker-admin and proxy-admin. Treat it with the same care as a root credential:
  • Store it outside the Kubernetes cluster.
  • Consider storing it in a secrets manager (for example, AWS Secrets Manager, HashiCorp Vault) and only retrieving it when issuing new tokens.

Generate the RSA256 key pair and tokens

Use pulsarctl to generate an RSA256 key pair:
pulsarctl token create-key-pair \
  --output-private-key my-private.key \
  --output-public-key my-public.key
Issue tokens for the broker-admin and proxy-admin subjects using the private key:
pulsarctl token create -a RS256 \
  --private-key-file my-private.key \
  --subject broker-admin
pulsarctl token create -a RS256 \
  --private-key-file my-private.key \
  --subject proxy-admin
Issue a token for the client subject:
pulsarctl token create -a RS256 \
  --private-key-file my-private.key \
  --subject client

Create Kubernetes Secrets for keys and tokens

Only the public key goes into Kubernetes. The private key must never be stored as a cluster Secret.
  • Create the public key Secret:
    kubectl create secret generic token-public-key \
      --from-file=my-public.key \
      -n pulsar
    
  • Create the broker-admin token Secret:
    kubectl create secret generic broker-admin \
      --from-literal=token=<broker-admin-token> \
      -n pulsar
    
  • Create the proxy-admin token Secret:
    kubectl create secret generic proxy-admin \
      --from-literal=token=<proxy-admin-token> \
      -n pulsar
    
Replace <broker-admin-token> and <proxy-admin-token> with the tokens generated in the previous step.

Enable JWT authentication for the Pulsar cluster

Add the following configurations to the PulsarBroker object:
spec:
  config:
    clientAuth:
      jwt:
        secret: broker-admin
    custom:
      authenticationEnabled: 'true'
      authenticateOriginalAuthData: 'true'
      authenticationProviders: 'org.apache.pulsar.broker.authentication.AuthenticationProviderToken'
      brokerClientAuthenticationPlugin: 'org.apache.pulsar.client.impl.auth.AuthenticationToken'
      superUserRoles: 'broker-admin, proxy-admin'
      proxyRoles: 'proxy-admin'
      authorizationEnabled: 'true'
      authorizationProvider: 'org.apache.pulsar.broker.authorization.PulsarAuthorizationProvider'
  pod:
    secretRefs:
      - mountPath: /mnt/secrets
        secretName: token-public-key
    vars:
      - name: brokerClientAuthenticationParameters
        valueFrom:
          secretKeyRef:
            name: broker-admin
            key: token
      - name: tokenPublicKey
        value: 'file:///mnt/secrets/my-public.key'
  • config.clientAuth: configures the toolset to mount and use the broker-admin token automatically.
  • config.custom: sets Pulsar authentication and authorization properties.
  • pod.secretRefs: mounts the public key Secret as a file inside the pod.
  • pod.vars: exposes the public key path and broker-admin token as environment variables for Pulsar configuration.
On the PulsarProxy object, add the following configurations:
spec:
  config:
    custom:
      authenticationEnabled: 'true'
      authenticateOriginalAuthData: 'true'
      forwardAuthorizationCredentials: 'true'
      authenticationProviders: 'org.apache.pulsar.broker.authentication.AuthenticationProviderToken'
      brokerClientAuthenticationPlugin: 'org.apache.pulsar.client.impl.auth.AuthenticationToken'
      superUserRoles: 'proxy-admin'
  pod:
    secretRefs:
      - mountPath: /mnt/secrets
        secretName: token-public-key
    vars:
      - name: brokerClientAuthenticationParameters
        valueFrom:
          secretKeyRef:
            name: proxy-admin
            key: token
      - name: tokenPublicKey
        value: 'file:///mnt/secrets/my-public.key'
  • config.custom: sets Pulsar authentication and authorization properties.
  • pod.secretRefs: mounts the public key Secret as a file inside the pod.
  • pod.vars: exposes the public key path and broker-admin token as environment variables for Pulsar configuration.

Connect clients to Pulsar

  • Create authorization for the client subject using the broker-admin token:
    pulsarctl --admin-service-url http://<Your PulsarProxy Endpoint>:8080 \
      --token "<broker-admin-token>" \
      namespaces grant-permission \
      --role client \
      --actions produce \
      --actions consume \
      public/default
    
    Expected output:
    Grant permissions [produce consume] to the client role client to access the namespace public/default successfully
    
    If you are running this command from the toolset pod, pulsarctl reads /pulsar/conf/client.conf which already has the admin service URL and the broker-admin token configured. You can omit the --admin-service-url and --token flags:
    pulsarctl namespaces grant-permission \
      --role client \
      --actions produce \
      --actions consume \
      public/default
    
  • Produce messages with the client token:
    bin/pulsar-client \
      --url pulsar://<Your PulsarProxy Endpoint>:6650 \
      --auth-plugin org.apache.pulsar.client.impl.auth.AuthenticationToken \
      --auth-params "token:<client-token>" \
      produce public/default/test -m "test" -n 10
    
  • Consume messages with the client token:
    bin/pulsar-client \
      --url pulsar://<Your PulsarProxy Endpoint>:6650 \
      --auth-plugin org.apache.pulsar.client.impl.auth.AuthenticationToken \
      --auth-params "token:<client-token>" \
      consume public/default/test -s my-subscription -n 10
    

Verify the configuration

After the pods restart, confirm the broker is using the public key and not a shared secret:
kubectl exec -n pulsar <broker-pod> -- \
  grep -E 'tokenPublicKey|tokenSecretKey' /pulsar/conf/broker.conf
Expected output:
tokenSecretKey=
tokenPublicKey=file:///mnt/secrets/my-public.key
tokenSecretKey must be empty. If it is set, the broker is using symmetric auth instead of RS256.

Configure Pulsar console

The Console CRD does not have a native plain-JWT field. Authentication is configured by injecting environment variables directly via pod.vars and mounting the public key via pod.secretRefs. A single dedicated service account (consoleservice) is used for both the Console backend connection to the broker and the web login. This subject must be in superUserRoles on the broker so it can perform all admin operations, including listing clusters.

Issue the Console service account token

Issue a dedicated RS256 token for the consoleservice subject:
pulsarctl token create -a RS256 \
  --private-key-file my-private.key \
  --subject consoleservice
Create a Kubernetes Secret that contains both the token and the public key:
kubectl create secret generic consoleservice \
  --from-literal=token=<consoleservice-token> \
  --from-file=my-public.key \
  -n pulsar
Replace <consoleservice-token> with the token generated above.

Add the Console service account to the broker

Update the PulsarBroker object to include consoleservice in superUserRoles:
spec:
  config:
    custom:
      superUserRoles: 'broker-admin, proxy-admin, consoleservice'

Configure the Console CRD

Add the following to the Console object:
spec:
  pod:
    secretRefs:
      - mountPath: /pulsar-manager/keys
        secretName: consoleservice
    vars:
      - name: AUTHENTICATION_NAME
        value: pulsar-jwt
      - name: AUTHENTICATION_PROVIDER
        value: org.apache.pulsar.manager.authentication.PulsarJWTAuthenticationProvider
      - name: AUTHENTICATION_CUSTOM_CLAIM
        value: sub
      - name: SERVICE_ACCOUNT_NAME
        value: jwt
      - name: SERVICE_ACCOUNT_PROVIDER
        value: org.apache.pulsar.manager.serviceAccount.JWTProvider
      - name: AUTH_METHOD_NAME
        value: pulsar-jwt
      - name: AUTH_METHOD_PROVIDER
        value: org.apache.pulsar.manager.authMethod.PulsarJWTAuthMethodProvider
      - name: AUTH_METHOD_CUSTOM_CLAIM
        value: sub
      - name: USERNAME_CLAIM
        value: sub
      - name: JWT_BROKER_TOKEN_MODE
        value: PRIVATE
      - name: JWT_BROKER_PUBLIC_KEY_PATH
        value: /pulsar-manager/keys/my-public.key
      - name: TOKEN
        valueFrom:
          secretKeyRef:
            name: consoleservice
            key: token
      - name: BACKEND_DEFAULT_SUPER_USER_ROLE
        value: consoleservice