Authentication
How an AMQP 1.0 client authenticates to KubeMQ — SASL PLAIN with a KubeMQ JWT, SASL EXTERNAL with mTLS, ANONYMOUS for dev, and Casbin authorization.
This guide explains how a native AMQP 1.0 client proves who it is to the KubeMQ AMQP 1.0
connector (authentication / SASL) and what it may do once attached (authorization). It covers
the three SASL mechanisms, how the client identity (ClientID) is derived, why container-id
matters, and the audit events the connector emits.
On a stock dev broker, authentication is off and clients connect ANONYMOUS — so the examples clone-and-run with no credentials. SASL PLAIN with a KubeMQ JWT is the one credentialed mechanism that also runs on a stock broker; SASL EXTERNAL requires mTLS (see TLS and mTLS).
The three SASL mechanisms
The connector computes the SASL mechanism list per connection from the auth and TLS context and offers it in a fixed order: EXTERNAL → PLAIN → ANONYMOUS.
| Mechanism | Offered when | Credential | Identity (ClientID) becomes |
|---|---|---|---|
PLAIN | always | RFC 4616 authzid\x00authcid\x00passwd; the password is the KubeMQ JWT | auth on → ClientID from the JWT; auth off → sanitized SASL username, else the container-id |
ANONYMOUS | only when the auth service is disabled | none | sanitized container-id |
EXTERNAL | only on an mTLS connection with a verified client certificate | the certificate itself (no JWT) | the cert Subject CN, sanitized |
PLAIN — the documented contract
PLAIN is the mechanism to use when authentication is enabled. The credential layout is the
standard RFC 4616 triple authzid \x00 authcid \x00 passwd:
passwd(password) = the KubeMQ JWT. It is validated server-side; on success the connector takes theClientIDfrom the JWT's claims.authcid(username) is audit-only / informational when auth is on. It does not become the identity — the JWT does. (When auth is off, the username is used as a convenience identity; see precedence below.)- The SASL initial-response binary is capped at 16 KiB. A larger response is rejected as a malformed/hostile peer. A KubeMQ JWT fits comfortably.
- No SCRAM-SHA-256, GSSAPI, or Azure CBS. PLAIN is the only credentialed mechanism.
Most clients accept a (username, password) pair — put the JWT in the password slot:
// Azure/go-amqp — SASL PLAIN: username is audit-only, password is the KubeMQ JWT.
conn, err := amqp.Dial(ctx, "amqp://broker:5672", &amqp.ConnOptions{
SASLType: amqp.SASLTypePlain("audit-username", os.Getenv("KUBEMQ_AMQP_JWT")),
})
if err != nil {
log.Fatalf("dial (bad/expired JWT? auth-disabled broker?): %v", err)
}
defer conn.Close()# python-qpid-proton — SASL PLAIN: username audit-only, password is the KubeMQ JWT.
# allow_insecure_mechs permits PLAIN over plaintext amqp://; use amqps:// in production.
conn = BlockingConnection(
"amqp://broker:5672",
user="audit-username",
password=os.environ["KUBEMQ_AMQP_JWT"],
allowed_mechs="PLAIN",
allow_insecure_mechs=True,
)// qpid-jms — the username/password passed to createConnection become the SASL
// PLAIN authcid/password (password = the KubeMQ JWT). Pin PLAIN on the URI.
String url = "amqp://broker:5672?amqp.saslMechanisms=PLAIN";
JmsConnectionFactory factory = new JmsConnectionFactory(url);
Connection connection = factory.createConnection(
"audit-username", System.getenv("KUBEMQ_AMQP_JWT"));
connection.start();// AMQPNetLite — an Address carrying User + Password makes the client negotiate PLAIN
// (password = the KubeMQ JWT). The host/port/user/password ctor avoids URL-encoding it.
var baseAddress = new Address("amqp://broker:5672");
var connectAddress = new Address(
baseAddress.Host, baseAddress.Port,
"audit-username", Environment.GetEnvironmentVariable("KUBEMQ_AMQP_JWT"),
"/", baseAddress.Scheme);
var connection = await Connection.Factory.CreateAsync(connectAddress);// rhea / rhea-promise — setting both username + password makes rhea select PLAIN
// (password = the KubeMQ JWT).
const connection = await container.connect({
host: "broker",
port: 5672,
container_id: `kubemq-amqp10-js-${process.pid}`,
username: "audit-username",
password: process.env.KUBEMQ_AMQP_JWT,
});// fe2o3-amqp — SASL PLAIN: username audit-only, password is the KubeMQ JWT.
let mut connection = Connection::builder()
.container_id("amqp10-client")
.sasl_profile(SaslProfile::Plain {
username: "audit-username".to_string(),
password: std::env::var("KUBEMQ_AMQP_JWT")?,
})
.open("amqp://broker:5672")
.await?;ANONYMOUS — the auth-off default
When the broker's authentication service is disabled, the connector offers ANONYMOUS. This is
the mechanism the runnable examples rely on: no credential is sent and the identity falls back to
the container-id. ANONYMOUS is only offered when auth is off — requesting it against an
auth-enabled broker is rejected as an auth failure (it was never advertised).
EXTERNAL — mTLS, cert CN → ClientID
EXTERNAL is offered only when the TLS handshake presented a client certificate that the
listener verified (the mTLS listener). The identity is the client certificate's Subject
CN, sanitized to a valid ClientID; no JWT is needed. An empty CN is rejected. Because EXTERNAL
depends on mTLS, it is covered alongside TLS — see
TLS and mTLS.
Identity precedence
The connector resolves the client identity in this order, depending on the negotiated mechanism and whether auth is enabled:
- PLAIN + auth on → the JWT's
ClientIDclaim. - PLAIN + auth off → the sanitized SASL username (
authcid); if empty, thecontainer-id. - EXTERNAL → the client certificate's Subject CN (sanitized).
- ANONYMOUS → the sanitized
container-id. - bare (no SASL) → the sanitized
container-id; if empty, a generatedamqp10-<uuid8>.
Sanitization caps the value at 256 characters and maps every character outside
[a-zA-Z0-9_-] to _.
container-id is required — and must be stable for durable subscribers
Every AMQP 1.0 OPEN must carry a non-empty container-id. An empty value is rejected with
CLOSE(amqp:invalid-field, "open.container-id is required"). The value is sanitized
([a-zA-Z0-9_-], ≤ 256).
container-id matters for two reasons beyond identity:
- It becomes the
ClientIDwhenever there is no SASL identity (ANONYMOUS / bare / auth-off PLAIN-with-empty-username). - It is half of the durable-subscription identity. A durable events-store subscriber that
reconnects with a different
container-idwill not resume its old position — it becomes a different durable identity.
Set a stable container-id for any durable subscriber. Because the container-id is half the
durable identity, a subscriber that lets its container-id drift across reconnects (e.g. a
randomly generated one) will never resume — it creates a new subscription each time. See
Reliability.
Authorization — enforced at attach, per resource
Once authenticated, the connector enforces a Casbin policy at ATTACH time, against the
ClientID, the resolved pattern (mapped to a resource name), the channel, and the link role. If
no authorizer is wired (auth off), nothing is enforced.
| Link the client attaches | Server role | Permission enforced |
|---|---|---|
Receiver from <pattern>/<ch> (client consumes) | server-sender | Read on (ClientID, resource, channel) |
Sender to a fixed <pattern>/<ch> (client produces) | server-receiver | Write on (ClientID, resource, channel) |
| Sender with a null (anonymous) target | server-receiver | deferred — each message is authorized per-message with Write on its properties.to |
/responses/<RequestID> (RPC reply token) | server-receiver | not enforced — connection-scoped reply token |
- The resource name maps the pattern:
events-store→events_store;queues/events/commands/queriesmap to themselves. - Anonymous-terminus links cannot be checked at attach because there is no fixed channel yet.
Instead, the transfer layer authorizes each message with a Write check on the message's
to, backed by a short-lived LRU cache. See Addressing.
A denied authorization closes the link with DETACH(amqp:unauthorized-access) and a generic
description. No policy internals leak. Your client should surface the
amqp:unauthorized-access condition and treat it as a permission error, not retry it as
transient.
Audit events — exactly two
The connector's audit surface for AMQP 1.0 lives entirely in the SASL layer and emits only two event types:
| Audit event | Emitted on | Fields |
|---|---|---|
auth.success | successful SASL authentication | ClientID, Transport: "amqp10", SourceIP, Metadata{mechanism} |
auth.failure | failed/rejected SASL authentication | ClientID, Transport: "amqp10", SourceIP, Error (sanitized), Metadata{mechanism} |
The AMQP 1.0 connector does not emit client.connected or client.disconnected audit
events. Do not build alerting, dashboards, or compliance reporting that depends on
connection-lifecycle audit events from this connector — they are not produced. The only audit
signal is authentication success/failure (with the SASL mechanism and source IP). For
connection/link visibility, use the dashboard API and Prometheus metrics — see
Connections endpoint.
Quick decision guide
| You want… | Do this |
|---|---|
| Clone-and-run on a stock dev broker | Connect ANONYMOUS (no credentials) |
| Authenticate with KubeMQ identity | SASL PLAIN, JWT in the password slot; username is audit-only |
| Authenticate with a client certificate | mTLS + SASL EXTERNAL (cert CN → ClientID) — see TLS and mTLS |
| Resume a durable events-store subscription | Set a stable container-id (it is half the durable identity) |
| Diagnose a permission failure | Look for DETACH(amqp:unauthorized-access); check the Read/Write policy for that channel |
Related
TLS and mTLS
amqps:// on 5671, mutual TLS, and SASL EXTERNAL where the certificate CN becomes the ClientID.
Auth & security
Shared JWT model, public routes, CORS, origin validation, and TLS/mTLS across KubeMQ connectors.
Addressing
The address grammar and the per-message Write check on an anonymous sender's properties.to.
Was this page helpful?
Getting Started
Connect a stock AMQP 1.0 client to KubeMQ and run a send-and-receive round-trip through a KubeMQ Queue in minutes — no KubeMQ SDK required.
Flow Control
AMQP 1.0 link credit on KubeMQ — who grants credit on produce vs consume links, prefetch and MaxUnsettledPerLink, drain, and pre-settled data-loss footguns.