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

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.

config.toml
[[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 PLAIN or AMQPLAIN, whatever Authentication.Enable is 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 EXTERNAL whose 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\.?" "^$"
the same entry in Connectors.Amqp.Credentials
{
  "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.

OperationCheck(s), in order
queue.declare, activeconfigure on the queue
queue.declare, passiveany 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.unbindwrite on the queue, then read on the exchange
queue.purgeread on the queue
queue.deleteconfigure on the queue
exchange.declare, activeconfigure on the exchange
exchange.declare, passiveany 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.unbindwrite on the destination, read on the source
exchange.deleteconfigure on the exchange
basic.publishwrite on the exchange (the default exchange "" is reported as amq.default)
basic.get / basic.consumeread on the queue
basic.consume on amq.rabbitmq.reply-tono 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 q1 grants q1, aq1b and q10, never Q1. 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 reserved amq. 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 exact Vhost entry always wins over the wildcard for the same user.
  • The default vhost is always written "/", never its mapped DefaultVhost name — a client can only ever open it by naming /. An entry naming a vhost connection.open could never resolve (one containing ., a reserved channel character, or whitespace) is refused at boot.
  • A user with Permissions entries but none matching the vhost it opens is refused at connection.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_permissions has no equivalent.
  • Platform-token logins keep the platform's two-verb model. Permissions governs only a store user. A platform token, and EXTERNAL with no store configured, are governed by platform (Casbin) authorization alone, which has only Read and Write: there queue.purge and queue.delete both need Write, 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:

DerivationSelected whenSubject
Credential storeConnectors.Amqp.Credentials has at least one entryamqp-_user- + the encoded username
SASL EXTERNALSslCertLogin = true and a verified client certificateamqp-_cert- + the encoded certificate identity
Platform tokenno store and Authentication.Enable = truethe token's client id, verbatim and unprefixed
Anonymousno store and Authentication.Enable = falseamqp- + 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 UsernameThe 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.

Was this page helpful?

On this page