Users and permissions
RabbitMQ-style users on the KubeMQ RabbitMQ connector — the credential store, per-vhost configure/write/read permissions, tags, and the rabbitmqctl mapping.
A migrating RabbitMQ deployment usually has its own users, each with a per-vhost
configure/write/read permission triple and a set of tags. The KubeMQ RabbitMQ connector can be
given the same thing through Connectors.Amqp.Credentials — a connector-local user store
consulted by SASL PLAIN/AMQPLAIN logins, by certificate logins over EXTERNAL, and by the
management HTTP API. This guide covers how to
configure it and what changes the moment you do.
Configuring users changes the authorization subject of every AMQP connection. With no users
configured, an authenticated client's subject is its platform token's client id. With at least
one user configured, every PLAIN/AMQPLAIN connection's subject becomes its encoded
username under the amqp-_user- prefix instead — and every existing authorization policy
written against the token's client id stops matching, silently. Update the policy file in the
same change. See Which subject a policy must name.
The credential store
Connectors.Amqp.Credentials is a list of entries, each with a Username, a Password, an
optional Permissions list and an optional Tags list. It is file or structured
configuration only — there is no environment variable, matching the Kafka and AWS connectors'
credential lists.
[[Connectors.Amqp.Credentials]]
Username = "orders_producer"
Password = "s3cret"
Tags = ["management"]
[[Connectors.Amqp.Credentials.Permissions]]
Vhost = "/"
Configure = "^$"
Write = "^orders\\.?"
Read = "^$"
[[Connectors.Amqp.Credentials]]
Username = "admin"
Password = "adminpass"
Tags = ["administrator"]
[[Connectors.Amqp.Credentials.Permissions]]
Vhost = "" # wildcard: every vhost (this connector's own extension)
Configure = ".*"
Write = ".*"
Read = ".*"What the store changes:
- Not configured (the default): nothing changes. The SASL password is a KubeMQ platform token when authentication is on, and the username is recorded but never checked.
- Configured: both the username and the password are checked against this store, over
PLAINorAMQPLAIN, whateverAuthentication.Enableis set to — the store is consulted before the platform flag is looked at, and the platform token is not consulted at all for a connection authenticating this way. - Certificate logins consult it too. With a store configured, a certificate over
EXTERNALwhose derived identity names no configured user is refused, so turning on certificate login does not create a second population of principals the store does not know about.
Credential checks are constant-time, and the three ways to fail are indistinguishable on the
wire. A wrong password, an unknown username, and no matching entry all close the connection
with the identical connection.close(403) / ACCESS_REFUSED - authentication failed, matching
RabbitMQ 4.3.4. The distinguishing reason goes to the audit log only.
Validation (at boot, and on every settings save even when the connector is off): every
Username and Password must be non-empty, usernames must not repeat, and no username may end
in the reserved kubemq-dashboard identity. Every non-empty permission pattern must compile as
an RE2 regular expression, no two entries for one user may name the same vhost, and no tag may
be an empty string.
Permissions
A RabbitMQ user carries rabbitmqctl set_permissions -p <vhost> <user> <conf> <write> <read> —
a configure, a write and a read regular expression, per vhost. A credential can carry the same
triple as Permissions, a list of {Vhost, Configure, Write, Read} entries, enforced by the
connector before platform authorization runs.
A set_permissions invocation maps directly onto one entry:
# RabbitMQ
rabbitmqctl set_permissions -p / orders_producer "^$" "^orders\.?" "^$"{
"Username": "orders_producer",
"Password": "...",
"Permissions": [
{ "Vhost": "/", "Configure": "^$", "Write": "^orders\\.?", "Read": "^$" }
]
}A definitions export's permissions array maps the same way, field for field — each entry's
vhost, configure, write and read become one Permissions entry.
What each verb governs
The operation → verb table, in check order. A passive declare needs any one of the three
verbs, and its denial always names configure — RabbitMQ's own reply.
| Operation | Check(s), in order |
|---|---|
queue.declare, active | configure on the queue |
queue.declare, passive | any one of configure/write/read on the queue |
queue.declare with x-dead-letter-exchange (new queue) | configure on the queue; then read on the queue and write on the dead-letter exchange |
queue.bind / queue.unbind | write on the queue, then read on the exchange |
queue.purge | read on the queue |
queue.delete | configure on the queue |
exchange.declare, active | configure on the exchange |
exchange.declare, passive | any one of configure/write/read on the exchange |
exchange.declare with alternate-exchange (new exchange) | configure on the exchange; then read on the exchange and write on the alternate |
exchange.bind / exchange.unbind | write on the destination, read on the source |
exchange.delete | configure on the exchange |
basic.publish | write on the exchange (the default exchange "" is reported as amq.default) |
basic.get / basic.consume | read on the queue |
basic.consume on amq.rabbitmq.reply-to | no permission check — every user may consume it |
A denial closes the channel with channel.close(403) and RabbitMQ's own text:
ACCESS_REFUSED - <verb> access to <queue|exchange> '<name>' in vhost '<vhost>' refused for user '<username>'Both the connector check and platform authorization run before the operation is evaluated for existence, so a denied caller cannot use the reply to learn whether a queue or exchange exists.
Rules to plan for
- Patterns are an unanchored, case-sensitive search, as in RabbitMQ. A pattern
q1grantsq1,aq1bandq10, neverQ1. Lead every pattern with^…$unless the unanchored behavior is actually wanted.""grants nothing for that verb — an entry with every verb""denies everything in that vhost. - RE2, not PCRE. A PCRE-only construct such as a negative lookahead (
^(?!amq\.).*, a common way to deny the reservedamq.prefix) is refused by name at boot and on every save, rather than silently accepted and then never matching. Write the positive-list alternative instead:^(orders|billing)\..*$in place of a deny-amq.-prefix rule. Vhost: ""is a wildcard matching any vhost — this connector's own extension. RabbitMQ permissions are always scoped to one vhost, and a definitions export never produces a cross-vhost entry. An exactVhostentry always wins over the wildcard for the same user.- The default vhost is always written
"/", never its mappedDefaultVhostname — a client can only ever open it by naming/. An entry naming a vhostconnection.opencould never resolve (one containing., a reserved channel character, or whitespace) is refused at boot. - A user with
Permissionsentries but none matching the vhost it opens is refused atconnection.open:connection.close(530) NOT_ALLOWED - access to vhost '<vhost>' refused for user '<username>'. Add a wildcard entry, or an entry naming that vhost. A user with no entries at all is never subject to this check — no connector-level check runs for it and platform authorization alone decides. - Topic permissions are not supported. RabbitMQ's
set_topic_permissionshas no equivalent. - Platform-token logins keep the platform's two-verb model.
Permissionsgoverns only a store user. A platform token, andEXTERNALwith no store configured, are governed by platform (Casbin) authorization alone, which has onlyReadandWrite: therequeue.purgeandqueue.deleteboth needWrite, and a policy that lets a client declare a queue also lets it delete and publish to it.
Tags
Tags is RabbitMQ's own user-tag list (rabbitmqctl set_user_tags). The
management HTTP API is the only consumer: any
one of management, policymaker, monitoring or administrator admits a store user to that
listener at all, and administrator additionally admits GET/POST /api/definitions and
widens visibility to every vhost. Any other value is stored and ignored, so a RabbitMQ
definitions.json export's tags round-trip through import without being rejected. Tags play no
part on the AMQP wire itself.
Which subject a policy must name
When platform authorization is enabled, the subject it sees depends on how the connection authenticated — and the credential store is consulted first:
| Derivation | Selected when | Subject |
|---|---|---|
| Credential store | Connectors.Amqp.Credentials has at least one entry | amqp-_user- + the encoded username |
SASL EXTERNAL | SslCertLogin = true and a verified client certificate | amqp-_cert- + the encoded certificate identity |
| Platform token | no store and Authentication.Enable = true | the token's client id, verbatim and unprefixed |
| Anonymous | no store and Authentication.Enable = false | amqp- + 8 random characters per connection — no policy can name it |
The encoder keeps a-z A-Z 0-9 - unchanged, doubles _, and writes every other byte as _x
plus two lowercase hex digits:
Configured Username | The ClientID pattern that matches it |
|---|---|
orders-producer | ^amqp-_user-orders-producer$ |
orders_producer | ^amqp-_user-orders__producer$ |
svc.orders | ^amqp-_user-svc_x2eorders$ |
alice@corp.example | ^amqp-_user-alice_x40corp_x2eexample$ |
A policy naming the raw username matches nothing, and that user is then denied every operation.
Do not sanity-check the rule with a name like alice — it passes through unchanged and teaches
the wrong rule. Over-long names carry a digest instead of the full body; read the subject out of
the audit log's auth.success event rather than deriving it by hand.
Never write ^amqp-.*$ as a policy subject. Every configured user and every certificate
identity shares that namespace, so one such rule grants them all everything — and with no store
and authentication off it also matches every anonymous connection. Write one anchored, encoded
literal per user.
Refreshing and expiring credentials
A connection authenticated with a platform token is closed at the token's exp with
connection.close(320) CONNECTION_FORCED - credential expired, and can refresh itself in place
with connection.update-secret — see Authentication.
A connection authenticated against this store holds no token: it is never evicted for age,
and connection.update-secret is refused 530 NOT_ALLOWED - Secret update failed whatever the
new secret says. Changing a user's password in configuration takes effect for new logins on
the next settings save or restart; existing connections are not re-authenticated.
Kubernetes and the operator
On an operator-managed Kubernetes cluster there is no shipped way to set this store today.
Connectors.Amqp.Credentials has no environment variable for the operator's
spec.envFromSecrets to supply, and the settings page refuses every save on a server the
operator manages. Plan such a cluster's PLAIN/AMQPLAIN clients around the paths that need
no store: a platform token as the password when authentication is on, and any credentials when
it is off. Certificate login over EXTERNAL needs no store either — spec.tls supplies the
certificate, key and CA that mutual TLS needs, and spec.env sets
CONNECTORS_AMQP_SSL_CERT_LOGIN — but that CA turns on mutual TLS for every TLS listener on
the server, not only AMQP.
Planned for v1.1.0 — not shipped. A Kubernetes path is planned: a bootstrap user supplied
through CONNECTORS_AMQP_DEFAULT_USER and CONNECTORS_AMQP_DEFAULT_PASS, and a definitions file
loaded at startup through CONNECTORS_AMQP_LOAD_DEFINITIONS, both settable from
spec.envFromSecrets. Until it ships, none of those variables does anything.
Related
Authentication
The four login paths, the JWT-in-password rule, certificate login, and token expiry and refresh.
Management HTTP API
The listener that consumes tags — endpoints, visibility scoping, and definitions import.
Migrating from RabbitMQ
The policy examples for both identity modes and the definitions export/import recipe.
Configuration reference
The Credentials field shape, validation rules, and every other Connectors.Amqp.* setting.
Was this page helpful?
Authentication
How a RabbitMQ (AMQP 0-9-1) client authenticates to KubeMQ — PLAIN/AMQPLAIN with a JWT or a connector user, certificate login, token expiry, and authorization.
Pub/Sub (Fanout)
Broadcast every message to all subscribers over AMQP 0-9-1 — a fanout exchange copies to exclusive queues, each backed by its own KubeMQ Queue channel.