KubeMQ
ConnectorsRabbitMQ (AMQP 0-9-1)How-to guides

Authentication

How a RabbitMQ (AMQP 0-9-1) client authenticates to KubeMQ — SASL PLAIN with the password as a KubeMQ JWT, the accept-any dev default, and Casbin authorization.

The KubeMQ RabbitMQ connector authenticates over SASL PLAIN only. The non-obvious part: the password carries a KubeMQ JWT, and the username is cosmetic. When authentication is disabled (the dev default), any credentials are accepted, so the examples clone-and-run with guest:guest.

On a stock dev broker, authentication is off and any credentials are accepted — guest:guest completes a full round-trip. SASL PLAIN with a KubeMQ JWT in the password is the one credentialed form that also runs on a stock broker. To encrypt the JWT on the wire, use the TLS listener (amqps://:5671) — see TLS and mTLS.

SASL PLAIN is the only mechanism

The connector advertises and accepts only the PLAIN mechanism. A connection.start-ok carrying any other mechanism (AMQPLAIN, EXTERNAL, …) is rejected with 503 command-invalid ("unsupported SASL mechanism … only PLAIN is supported").

The PLAIN response is parsed as the standard triple authzid \x00 authcid \x00 passwd.

Password = KubeMQ JWT, username = cosmetic

The username slot is informational; the password slot carries the credential. Most AMQP 0-9-1 clients accept a (username, password) pair in the connection URL — put the JWT in the password:

amqp://<username-ignored>:<KubeMQ-JWT>@host:5672/<vhost>
        └─ cosmetic ─┘  └─ authenticated ─┘
FieldRole
UsernameCosmetic. Recorded for audit and user-id checks; ignored for authorization.
PasswordThe KubeMQ JWT — passed to the auth service.
IdentityClientID from the JWT's claims. This single ClientID covers both connector-level and channel-level (Casbin) authorization.

The JWT is validated at connect time only — there is no mid-connection expiry enforcement. On auth failure the connector sends connection.close(403) and emits an auth.failure audit event.

The JWT travels in the SASL PLAIN password in cleartext at the AMQP layer. Production deployments that use authentication MUST use the TLS listener (amqps://:5671), otherwise the JWT is exposed on the wire. See TLS and mTLS and Auth & security.

Put the JWT in the password slot regardless of client library:

// amqp091-go — username is cosmetic, password is the KubeMQ JWT.
conn, err := amqp.Dial(fmt.Sprintf(
    "amqp://audit-user:%s@broker:5672/", os.Getenv("KUBEMQ_AMQP_JWT")))
if err != nil {
    log.Fatalf("dial (bad/expired JWT? auth-disabled broker?): %v", err)
}
defer conn.Close()
# pika — credentials: username audit-only, password is the KubeMQ JWT.
creds = pika.PlainCredentials("audit-user", os.environ["KUBEMQ_AMQP_JWT"])
params = pika.ConnectionParameters(host="broker", port=5672, credentials=creds)
conn = pika.BlockingConnection(params)
// amqp-client — setUsername is cosmetic; setPassword carries the KubeMQ JWT.
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("broker");
factory.setPort(5672);
factory.setUsername("audit-user");
factory.setPassword(System.getenv("KUBEMQ_AMQP_JWT"));
Connection connection = factory.newConnection();
// amqplib — username audit-only, password is the KubeMQ JWT.
const connection = await amqp.connect(
  `amqp://audit-user:${process.env.KUBEMQ_AMQP_JWT}@broker:5672/`);
// RabbitMQ.Client — UserName is cosmetic; Password carries the KubeMQ JWT.
var factory = new ConnectionFactory
{
    HostName = "broker",
    Port = 5672,
    UserName = "audit-user",
    Password = Environment.GetEnvironmentVariable("KUBEMQ_AMQP_JWT"),
};
using var connection = factory.CreateConnection();
# bunny — username audit-only, password is the KubeMQ JWT.
conn = Bunny.new(
  host: "broker", port: 5672,
  user: "audit-user", password: ENV.fetch("KUBEMQ_AMQP_JWT"))
conn.start
// lapin — username audit-only, password is the KubeMQ JWT.
let uri = format!("amqp://audit-user:{}@broker:5672/",
    std::env::var("KUBEMQ_AMQP_JWT")?);
let conn = Connection::connect(&uri, ConnectionProperties::default()).await?;

Auth disabled — the dev default

When authentication is disabled (the default on a dev broker), any credentials are acceptedguest:guest completes a full round-trip. The ClientID is then derived from the client's sanitized connection_name:

  • amqp-{connection_name} if a connection name was provided, otherwise
  • amqp-{uuid8}.

The user-id property

When authentication is enabled and the user-id property is set on basic.publish, it MUST equal the JWT's ClientID; a mismatch is rejected with 406 precondition-failed. When auth is disabled, user-id passes through unvalidated.

Authorization (Casbin)

When auth is enabled, every channel operation is checked per-channel against a Casbin policy using the connection's ClientID:

OperationCasbin permissionOn denial
PublishWrite, per routed queueThe denied queue is silently removed from the routed set + an amqp.publish.denied audit. The publish does not error.
ConsumeRead403 access-refused.
queue.declare / queue.delete / bindWrite403 access-refused.

When authorization is disabled, all operations are allowed.

Publish denial is silent. Because a denied queue is removed from the routed set rather than rejected, a publish to a partially-denied fanout still succeeds for the allowed queues. Use mandatory=true if you need a 312 NO_ROUTE when nothing routed — see Reliability.

Example policy

{"ClientID":"amqp-authz-allowed","Channel":"amqp.default.*","Read":true,"Write":true}

A client with this policy can declare, publish, and consume under amqp.default.*. A client without Read on a channel gets 403 on consume; a client without Write has its publishes silently dropped for that queue.

Quick decision guide

You want…Do this
Clone-and-run on a stock dev brokerConnect with any credentials (guest:guest)
Authenticate with a KubeMQ identitySASL PLAIN, JWT in the password slot; username is audit-only
Encrypt the JWT on the wireUse amqps://:5671 — see TLS and mTLS
Diagnose a permission failureA 403 access-refused on consume means no Read; silently-dropped publishes mean no Write

Was this page helpful?

On this page