Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Messaging/rabbitMQ/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ Developer documentation is embedded in the resource type definition YAML file an
| `application` | string | Optional | The Radius Application ID. |
| `queue` | string | Optional | The name of the queue to pre-provision on the broker. The Recipe creates this durable queue when the broker starts. Defaults to `jobs`. |
| `username` | string | Optional | The username the broker is provisioned with. Defaults to `radius`. Avoid `guest` (loopback-only in RabbitMQ). Not sensitive; exposed as a read-only connection value. |
| `password` | string | Required | The resource ID of the `Radius.Security/secrets` resource that holds the broker password under key `password` (set to `<secretResource>.id`). The Recipe mounts the password into the broker via `secretKeyRef`, so the plaintext password never lands in the pod spec or on this resource. |
| `password` | string | Optional | The resource ID of a `Radius.Security/secrets` resource containing the broker password under key `password`. If omitted, the Kubernetes Recipe generates a random password and returns it through a managed secret. |
| `host` | string | Read only | The host name used to connect to the broker. Set from the Recipe's Service DNS name. |
| `port` | integer | Read only | The port used to connect to the broker over AMQP 0-9-1 (5672). Set from the Recipe's output. |
| `secrets` | object | Read only | Managed Recipe secrets created when `password` is omitted. Use `secrets.name` as the `secretName` and `password` as the key in a container `secretKeyRef`. |

## Recipe Packs

Expand All @@ -29,4 +30,4 @@ Recipes for this resource type are provided through the platform Recipe Packs at

## Using the resource type

Create a `Radius.Security/secrets` resource holding the broker `password` (from a `@secure()` parameter), add a `rabbitMQ` resource that references it via `password`, and connect a container to the broker. Radius injects the queue's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_RABBITMQ_HOST`, `CONNECTION_RABBITMQ_PORT`, `CONNECTION_RABBITMQ_USERNAME`). The password is not emitted by this resource — read it from the same `Radius.Security/secrets` resource with a container `secretKeyRef` (`secretName: rabbitmqSecret.name`, key `password`). See [`test/app.bicep`](test/app.bicep) for a complete example.
Add a `rabbitMQ` resource and connect a container to it. You can provide the ID of a `Radius.Security/secrets` resource through `password`; the broker uses its materialized Kubernetes Secret directly. If you omit `password`, the Kubernetes Recipe creates broker credentials with a random fallback and Radius materializes the same value into a managed `Radius.Security/secrets` resource. Bind the supplied or generated secret with a container `secretKeyRef` using key `password`. Radius injects the queue's non-secret connection properties as `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` variables. See [`test/app.bicep`](test/app.bicep) for a complete fallback example.
38 changes: 26 additions & 12 deletions Messaging/rabbitMQ/rabbitMQ.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ types:
that speaks AMQP 0-9-1. It allows developers to create and connect to a queue
as part of their Radius applications.

Provision the broker password by creating a `Radius.Security/secrets` resource
(with the value passed to `rad deploy` as a `@secure()` parameter) and pass its
resource ID on the `password` property. The Recipe references the
materialized Kubernetes Secret by name and mounts the password into the broker
via `secretKeyRef`, so the plaintext password is never written into the pod spec
or onto this resource.
You can provision the broker password by creating a `Radius.Security/secrets`
resource and passing its resource ID on the optional `password` property. If
omitted, the Kubernetes Recipe generates a random password and returns it
through the resource's managed secrets. In both cases, the password is mounted
into the broker via `secretKeyRef`.
```bicep
@secure()
param password string
Expand Down Expand Up @@ -55,10 +54,10 @@ types:
- CONNECTION_RABBITMQ_PORT
- CONNECTION_RABBITMQ_USERNAME

The password is NOT emitted by this resource. Read it from the same
`Radius.Security/secrets` resource you created above by binding it into a
container env var with a `secretKeyRef`, using `rabbitmqSecret.name` as the
`secretName` and key `password`.
When `password` is supplied, bind the same `Radius.Security/secrets` resource
into the workload. When it is omitted, bind the Recipe-generated managed
secret using `queue.properties.secrets.name` as the `secretName`. In both cases,
use key `password`.

apiVersions:
'2025-08-01-preview':
Expand All @@ -81,7 +80,7 @@ types:
description: "(Optional) The username the broker is provisioned with and that clients authenticate as. Defaults to `radius` if not provided. Avoid `guest`, which RabbitMQ restricts to loopback connections. The username is not sensitive and is exposed as a read-only connection value."
password:
type: string
description: "(Required) The resource ID of the `Radius.Security/secrets` resource that holds the broker password under the data key `password`. Set to `<secretResource>.id`. The Kubernetes Recipe references the materialized Kubernetes Secret by name and mounts the password into the broker via `secretKeyRef`, so the plaintext password is never written into the pod spec or onto this resource."
description: "(Optional) The resource ID of the `Radius.Security/secrets` resource that holds the broker password under the data key `password`. Set to `<secretResource>.id`. If omitted, the Kubernetes Recipe generates a random password and returns it through the managed `secrets.password` output."
host:
type: string
description: (Read Only) The host name used to connect to the broker. Mapped from the recipe's Service DNS name.
Expand All @@ -90,4 +89,19 @@ types:
type: integer
description: (Read Only) The port used to connect to the broker over AMQP 0-9-1 (5672). Mapped from the recipe's output.
readOnly: true
required: [environment, password]
secrets:
type: object
description: >-
(Read Only) Recipe-generated secrets. The reserved `name` sub-property references
the managed Radius.Security/secrets resource Radius materializes from the Recipe's
`outputs.secrets`. Consumers bind a key into a container env var via `secretKeyRef`.
properties:
name:
type: string
readOnly: true
description: (Read Only) Name of the managed Radius.Security/secrets resource. Use as `secretName` in a container `secretKeyRef`.
password:
type: string
readOnly: true
description: (Read Only) The Recipe-generated fallback password, delivered through the managed secret when the `password` input is omitted.
required: [environment]
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ extension kubernetes with {

@description('Information about what resource is calling this Recipe. Generated by Radius.')
param context object

@description('Random fallback password used when the resource does not reference a password secret.')
@secure()
param generatedPassword string = newGuid()

var resourceName = context.resource.name
// A unique per-resource name so two rabbitMQ resources that share a display name
// (or the same name across recreates) never collide on their Kubernetes objects.
Expand All @@ -30,18 +35,15 @@ var port = 5672
// running in another Pod cannot authenticate as `guest`. A non-`guest` user is not
// loopback-restricted, so the broker accepts AMQP connections from workload Pods.
// The username is not sensitive and comes from the resource properties (default
// `radius`). The password is NOT passed to the Recipe in plaintext: the developer
// supplies it via a Radius.Security/secrets resource and references that resource's
// ID on the `password` property. The Recipe derives the materialized Kubernetes
// Secret name from that ID and mounts the password into the broker via
// `secretKeyRef`, so the plaintext password is never written into the pod spec.
// `radius`). When supplied, `password` references a Radius.Security/secrets resource.
// Otherwise, the Recipe generates a random password and returns it through Radius
// managed secrets. Both paths mount the password through secretKeyRef.
var username = context.resource.properties.?username ?? 'radius'

// Resource ID of the developer-supplied Radius.Security/secrets resource. The
// Kubernetes secrets Recipe names the materialized Secret after the resource name,
// which is the last segment of the resource ID.
var passwordSecretId = context.resource.properties.password
var credentialsSecretName = last(split(passwordSecretId, '/'))
var passwordSecretId = context.resource.properties.?password ?? ''
var usesSuppliedPassword = !empty(passwordSecretId)
var fallbackCredentialsSecretName = '${uniqueName}-credentials'
var credentialsSecretName = usesSuppliedPassword ? last(split(passwordSecretId, '/')) : fallbackCredentialsSecretName

// The queue is pre-provisioned on the broker (see the definitions ConfigMap and the
// init container below) so the named queue exists as soon as the broker is ready,
Expand All @@ -56,6 +58,17 @@ var labels = {
'radapp.io/resource-group': resourceGroupName
}

resource fallbackCredentials 'core/Secret@v1' = if (!usesSuppliedPassword) {
metadata: {
name: fallbackCredentialsSecretName
namespace: namespace
labels: labels
}
stringData: {
password: generatedPassword
}
}

//////////////////////////////////////////
// RabbitMQ definitions import config
//
Expand Down Expand Up @@ -104,14 +117,18 @@ resource rabbitmq 'apps/Deployment@v1' = {
template: {
metadata: {
labels: labels
annotations: !usesSuppliedPassword ? {
// A new generated password changes the Pod template so the broker restarts
// and reads the updated Secret during deployment.
'radapp.io/generated-password-hash': uniqueString(generatedPassword)
} : {}
}
spec: {
// Generate the broker definitions file (vhost, user, permissions, and the
// pre-provisioned queue) into a shared volume before the broker starts.
// rabbitmqctl hash_password computes the password hash offline. The plaintext
// password is injected only at runtime from the developer-supplied
// Radius.Security/secrets Kubernetes Secret via secretKeyRef; it is never
// baked into the pod spec, the definitions file, or the image.
// password is injected only at runtime from a Kubernetes Secret via
// secretKeyRef; it is never baked into the pod spec, definitions, or image.
initContainers: [
{
name: 'generate-definitions'
Expand All @@ -127,8 +144,8 @@ resource rabbitmq 'apps/Deployment@v1' = {
value: username
}
{
// Sourced from the developer-supplied Radius.Security/secrets
// Kubernetes Secret — never a literal value in the pod spec.
// Sourced from either the supplied or Recipe-created Kubernetes
// Secret — never a literal value in the pod spec.
name: 'RABBITMQ_PASSWORD'
valueFrom: {
secretKeyRef: {
Expand Down Expand Up @@ -228,18 +245,21 @@ resource svc 'core/Service@v1' = {
var host = '${svc.metadata.name}.${svc.metadata.namespace}.svc.cluster.local'

output result object = {
resources: [
resources: concat(!usesSuppliedPassword ? [
'/planes/kubernetes/local/namespaces/${namespace}/providers/core/Secret/${fallbackCredentialsSecretName}'
] : [], [
'/planes/kubernetes/local/namespaces/${brokerConfig.metadata.namespace}/providers/core/ConfigMap/${brokerConfig.metadata.name}'
'/planes/kubernetes/local/namespaces/${svc.metadata.namespace}/providers/core/Service/${svc.metadata.name}'
'/planes/kubernetes/local/namespaces/${rabbitmq.metadata.namespace}/providers/apps/Deployment/${rabbitmq.metadata.name}'
]
])
values: {
// Non-secret connection values. Clients build their AMQP 0-9-1 connection from
// these plus the password, which they read directly from the same
// Radius.Security/secrets resource (via secretKeyRef) — the Recipe never sees
// or emits the plaintext password.
// Non-secret connection values. Clients combine these with either their
// supplied password secret or the Recipe-generated managed secret.
host: host
port: port
username: username
}
secrets: usesSuppliedPassword ? {} : {
password: generatedPassword
}
}
14 changes: 7 additions & 7 deletions Messaging/rabbitMQ/test/app.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ extension radius
@description('The ID of your Radius Environment. Set automatically by the rad CLI.')
param environment string

@description('The broker password. Passed to rad deploy as a secure parameter, stored in a Radius.Security/secrets resource, and mounted into the broker via secretKeyRef so it is never written into the pod spec.')
@description('The broker password. Passed to rad deploy as a secure parameter and stored in a Radius.Security/secrets resource.')
@secure()
param password string

Expand All @@ -14,9 +14,10 @@ resource app 'Radius.Core/applications@2025-08-01-preview' = {
}
}

// The developer supplies the broker password via a Radius.Security/secrets resource
// rather than a plaintext property. The rabbitMQ Recipe references the materialized
// Kubernetes Secret by name and mounts the password via secretKeyRef.
// The broker password is supplied via a Radius.Security/secrets resource and its ID
// is passed on the rabbitMQ `password` property. When `password` is omitted, the
// Recipe instead generates a random fallback and returns it through the resource's
// own managed Radius.Security/secrets resource (queue.properties.secrets).
resource rabbitmqSecret 'Radius.Security/secrets@2025-08-01-preview' = {
name: 'rabbitmq-credentials'
properties: {
Expand Down Expand Up @@ -50,9 +51,8 @@ resource democontainer 'Radius.Compute/containers@2025-08-01-preview' = {
demo: {
image: 'ghcr.io/radius-project/samples/demo:latest'
// host/port/username arrive via the connection below as CONNECTION_RABBITMQ_*.
// The password is read directly from the shared Radius.Security/secrets
// resource via secretKeyRef, so it never lands on the rabbitMQ resource or
// its pod spec.
// The password is read from the same Radius.Security/secrets resource that
// was passed to the broker via secretKeyRef.
env: {
RABBITMQ_PASSWORD: {
valueFrom: {
Expand Down