Skip to content

Commit cf57799

Browse files
authored
Adopt the recipe secret-output pattern for secret-bearing resource types (#226)
## Summary Adopts the recipe **secret-output** pattern for the seven secret-bearing resource types, so their recipe-generated secrets are no longer stored on the owning resource. Today these types expose their secret (connection string / URL / API key / account key) as a **plain read-only property**, which is written onto the resource and surfaced on reads, in logs, and in the application graph. This PR moves each secret into a managed `Radius.Security/secrets` resource and exposes only a read-only reference on the owner. ## What changed (per type) For **kafka, rabbitMQ, mongoDatabases, redisCaches, models, search, objectStorage**: - **Type schema (`<type>.yaml`)** — replace the plain secret property with a single read-only `secrets` block: a reserved `name` sub-property (the reference to the managed secret) plus the secret key(s). The block is intentionally **not** marked `readOnly` (forward-compat for future secret *inputs*); each sub-property's own `readOnly` flag is the output/input discriminator. Descriptions updated to consume the secret via `secretKeyRef`. - **Recipe pack (`recipepack/azure/aks-recipepack.bicep`)** — nest the secret module outputs under `outputs.secrets`. Also adds the `objectStorage` `connectionString` mapping (declared on the type but previously missing from the pack). - **`test/app.bicep`** — bind the secret into a container env var via `valueFrom.secretKeyRef` using `<resource>.properties.secrets.name`. - **README** — reflect the `secrets` reference + `secretKeyRef` consumption. | Type | Secret key(s) | Non-secret (still connection-injected) | |---|---|---| | Messaging/kafka | `connectionString` | `host` | | Messaging/rabbitMQ | `connectionString` | `host` | | Data/mongoDatabases | `connectionString` | `endpoint` | | Data/redisCaches | `url` | `host`, `port` | | AI/models | `apiKey` | `endpoint` | | AI/search | `apiKey` | `endpoint` | | Storage/objectStorage | `connectionString`, `accountKey` | `endpoint`, `accountName` | ## Decisions - **Secret key names preserved** — redis keeps `url`, storage keeps `accountKey`. This is a secret-handling *mechanism* change, not a rename, so existing consumers of the key names are unaffected (the value now arrives via the managed secret instead of a resource property). - **SQL types out of scope** — `mySqlDatabases`, `postgreSqlDatabases`, `sqlServerDatabases` use secret *inputs*, not outputs, and are unchanged. - **Testing/CI stays in `resource-types-verification`** — this PR only updates the type definitions, recipe pack, and `test/app.bicep`; no CI workflows are moved here. ## Dependency Requires the engine support in **radius-project/radius#12344** (fold the secret reference into the `secrets` block as `secrets.name`; read recipe secret mappings from nested `outputs.secrets`). The `validate-resource-types` CI runs against `rad edge`, so it will not go green until #12344 is merged and released to `edge`. The same pattern is validated end-to-end on real Azure across all seven types in `resource-types-verification` (7 base + 7 app workflows green). Kept as a **draft** pending the engine release. Signed-off-by: willdavsmith <willdavsmith@gmail.com>
1 parent ab9722a commit cf57799

22 files changed

Lines changed: 294 additions & 62 deletions

File tree

AI/models/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Developer documentation is embedded in the resource type definition YAML file an
1414
| `application` | string | Optional | The Radius Application ID. |
1515
| `model` | string | Optional | The model deployment to provision. Defaults to `gpt-5-mini`. |
1616
| `endpoint` | string | Read only | The base URL used to call the model inference endpoint. Set from the Recipe module's output. |
17-
| `apiKey` | string | Read only | The API key used to call the model inference endpoint. Set from the Recipe module's output. |
17+
| `secrets` | object | Read only | Recipe secrets. `secrets.name` references the managed `Radius.Security/secrets` resource; `secrets.apiKey` is the secret key (delivered via that managed secret, never stored on the resource). |
1818

1919
## Recipe Packs
2020

@@ -26,4 +26,4 @@ Recipes for this resource type are provided through the platform Recipe Packs at
2626

2727
## Using the resource type
2828

29-
Add a `models` resource to your application and connect a container to it. Radius injects the model's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_LLM_MODEL`, `CONNECTION_LLM_ENDPOINT`, and `CONNECTION_LLM_APIKEY`). See [`test/app.bicep`](test/app.bicep) for a complete example.
29+
Add a `models` resource to your application and connect a container to it. Radius injects the model's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_LLM_MODEL` and `CONNECTION_LLM_ENDPOINT`). The `apiKey` secret is not injected — bind it from the managed `Radius.Security/secrets` resource with a container `secretKeyRef` using `model.properties.secrets.name`. See [`test/app.bicep`](test/app.bicep) for a complete example.

AI/models/models.yaml

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ types:
4646
4747
- CONNECTION_LLM_MODEL
4848
- CONNECTION_LLM_ENDPOINT
49-
- CONNECTION_LLM_APIKEY
49+
50+
The `apiKey` secret is NOT injected via the connection — it is materialized
51+
into a managed `Radius.Security/secrets` resource. Bind it into a container
52+
env var with a `secretKeyRef`, using `model.properties.secrets.name` as the
53+
`secretName` and key `apiKey` (see the `secrets` property).
5054
5155
apiVersions:
5256
'2025-08-01-preview':
@@ -67,8 +71,21 @@ types:
6771
type: string
6872
description: "(Read Only) The base URL used to call the model inference endpoint. Mapped from the recipe module's output."
6973
readOnly: true
70-
apiKey:
71-
type: string
72-
description: "(Read Only) The API key used to call the model inference endpoint. Mapped from the recipe module's `primaryKey` output."
73-
readOnly: true
74+
secrets:
75+
type: object
76+
description: >-
77+
(Read-only) Recipe secrets. The reserved `name` sub-property references the managed
78+
Radius.Security/secrets resource Radius materializes from the recipe's `outputs.secrets`;
79+
the other sub-properties declare secret keys whose values are written only into that
80+
managed secret (never onto this resource). Consumers bind a key into a container env var
81+
via `secretKeyRef`, using `<resource>.properties.secrets.name` as `secretName`.
82+
properties:
83+
name:
84+
type: string
85+
readOnly: true
86+
description: (Reserved) Name of the managed Radius.Security/secrets resource. Use as `secretName` in a container `secretKeyRef`.
87+
apiKey:
88+
type: string
89+
readOnly: true
90+
description: "(Read Only) The API key used to call the model inference endpoint. Mapped from the recipe module's `primaryKey` output; delivered via the managed secret."
7491
required: [environment]

AI/models/test/app.bicep

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ resource democontainer 'Radius.Compute/containers@2025-08-01-preview' = {
2727
containers: {
2828
demo: {
2929
image: 'ghcr.io/radius-project/samples/demo:latest'
30+
// The recipe's secret output(s) are materialized into a managed
31+
// Radius.Security/secrets resource and consumed here BY REFERENCE via
32+
// secretKeyRef — the value never lands on model state.
33+
env: {
34+
MODEL_APIKEY: {
35+
valueFrom: {
36+
secretKeyRef: {
37+
secretName: model.properties.secrets.name
38+
key: 'apiKey'
39+
}
40+
}
41+
}
42+
}
3043
ports: {
3144
web: {
3245
containerPort: 3000

AI/search/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Developer documentation is embedded in the resource type definition YAML file an
1313
| `environment` | string | Required | The Radius Environment ID. Typically set by the `rad` CLI. |
1414
| `application` | string | Optional | The Radius Application ID. |
1515
| `endpoint` | string | Read only | The endpoint used to connect to the search service. Set from the Recipe module's output. |
16-
| `apiKey` | string | Read only | The admin API key used to connect to the search service. Set from the Recipe module's output. |
16+
| `secrets` | object | Read only | Recipe secrets. `secrets.name` references the managed `Radius.Security/secrets` resource; `secrets.apiKey` is the secret key (delivered via that managed secret, never stored on the resource). |
1717

1818
## Recipe Packs
1919

@@ -25,4 +25,4 @@ Recipes for this resource type are provided through the platform Recipe Packs at
2525

2626
## Using the resource type
2727

28-
Add a `search` resource to your application and connect a container to it. Radius injects the search service's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_SEARCH_ENDPOINT` and `CONNECTION_SEARCH_APIKEY`). See [`test/app.bicep`](test/app.bicep) for a complete example.
28+
Add a `search` resource to your application and connect a container to it. Radius injects the search service's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_SEARCH_ENDPOINT`). The `apiKey` secret is not injected — bind it from the managed `Radius.Security/secrets` resource with a container `secretKeyRef` using `search.properties.secrets.name`. See [`test/app.bicep`](test/app.bicep) for a complete example.

AI/search/search.yaml

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ types:
4646
example the connection name is `search` so the environment variables will be:
4747
4848
- CONNECTION_SEARCH_ENDPOINT
49-
- CONNECTION_SEARCH_APIKEY
49+
50+
The `apiKey` secret is NOT injected via the connection — it is materialized
51+
into a managed `Radius.Security/secrets` resource. Bind it into a container
52+
env var with a `secretKeyRef`, using `search.properties.secrets.name` as the
53+
`secretName` and key `apiKey` (see the `secrets` property).
5054
5155
apiVersions:
5256
'2025-08-01-preview':
@@ -63,8 +67,21 @@ types:
6367
type: string
6468
description: The endpoint used to connect to the search service. Mapped from the recipe module's output.
6569
readOnly: true
66-
apiKey:
67-
type: string
68-
description: The admin API key used to connect to the search service. Mapped from the recipe module's output.
69-
readOnly: true
70+
secrets:
71+
type: object
72+
description: >-
73+
(Read-only) Recipe secrets. The reserved `name` sub-property references the managed
74+
Radius.Security/secrets resource Radius materializes from the recipe's `outputs.secrets`;
75+
the other sub-properties declare secret keys whose values are written only into that
76+
managed secret (never onto this resource). Consumers bind a key into a container env var
77+
via `secretKeyRef`, using `<resource>.properties.secrets.name` as `secretName`.
78+
properties:
79+
name:
80+
type: string
81+
readOnly: true
82+
description: (Reserved) Name of the managed Radius.Security/secrets resource. Use as `secretName` in a container `secretKeyRef`.
83+
apiKey:
84+
type: string
85+
readOnly: true
86+
description: The admin API key used to connect to the search service. Mapped from the recipe module's output; delivered via the managed secret.
7087
required: [environment]

AI/search/test/app.bicep

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ resource democontainer 'Radius.Compute/containers@2025-08-01-preview' = {
2626
containers: {
2727
demo: {
2828
image: 'ghcr.io/radius-project/samples/demo:latest'
29+
// The recipe's secret output(s) are materialized into a managed
30+
// Radius.Security/secrets resource and consumed here BY REFERENCE via
31+
// secretKeyRef — the value never lands on searchService state.
32+
env: {
33+
SEARCH_APIKEY: {
34+
valueFrom: {
35+
secretKeyRef: {
36+
secretName: searchService.properties.secrets.name
37+
key: 'apiKey'
38+
}
39+
}
40+
}
41+
}
2942
ports: {
3043
web: {
3144
containerPort: 3000

Data/mongoDatabases/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Developer documentation is embedded in the resource type definition YAML file an
1414
| `application` | string | Optional | The Radius Application ID. |
1515
| `database` | string | Optional | The Mongo database name. Defaults to `mongo_db`. |
1616
| `endpoint` | string | Read only | The endpoint used to connect to the database. Set from the Recipe module's output. |
17-
| `connectionString` | string | Read only | The connection string used to connect to the database. Set from the Recipe module's output. |
17+
| `secrets` | object | Read only | Recipe secrets. `secrets.name` references the managed `Radius.Security/secrets` resource; `secrets.connectionString` is the secret key (delivered via that managed secret, never stored on the resource). |
1818

1919
## Recipe Packs
2020

@@ -26,4 +26,4 @@ Recipes for this resource type are provided through the platform Recipe Packs at
2626

2727
## Using the resource type
2828

29-
Add a `mongoDatabases` resource to your application and connect a container to it. Radius injects the database's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_MONGODB_DATABASE`, `CONNECTION_MONGODB_ENDPOINT`, and `CONNECTION_MONGODB_CONNECTIONSTRING`). See [`test/app.bicep`](test/app.bicep) for a complete example.
29+
Add a `mongoDatabases` resource to your application and connect a container to it. Radius injects the database's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_MONGODB_DATABASE` and `CONNECTION_MONGODB_ENDPOINT`). The `connectionString` secret is not injected — bind it from the managed `Radius.Security/secrets` resource with a container `secretKeyRef` using `mongo.properties.secrets.name`. See [`test/app.bicep`](test/app.bicep) for a complete example.

Data/mongoDatabases/mongoDatabases.yaml

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ types:
4545
4646
- CONNECTION_MONGO_DATABASE
4747
- CONNECTION_MONGO_ENDPOINT
48-
- CONNECTION_MONGO_CONNECTIONSTRING
48+
49+
The `connectionString` secret is NOT injected via the connection — it is
50+
materialized into a managed `Radius.Security/secrets` resource. Bind it into
51+
a container env var with a `secretKeyRef`, using `mongo.properties.secrets.name`
52+
as the `secretName` and key `connectionString` (see the `secrets` property).
4953
5054
Portability note: this schema is platform-neutral so the same resource
5155
type works with Azure AVM, AWS Terraform modules, and Kubernetes recipes.
@@ -71,8 +75,21 @@ types:
7175
type: string
7276
description: The endpoint used to connect to the database. Mapped from the recipe module's output.
7377
readOnly: true
74-
connectionString:
75-
type: string
76-
description: The connection string used to connect to the database. Mapped from the recipe module's output.
77-
readOnly: true
78+
secrets:
79+
type: object
80+
description: >-
81+
(Read-only) Recipe secrets. The reserved `name` sub-property references the managed
82+
Radius.Security/secrets resource Radius materializes from the recipe's `outputs.secrets`;
83+
the other sub-properties declare secret keys whose values are written only into that
84+
managed secret (never onto this resource). Consumers bind a key into a container env var
85+
via `secretKeyRef`, using `<resource>.properties.secrets.name` as `secretName`.
86+
properties:
87+
name:
88+
type: string
89+
readOnly: true
90+
description: (Reserved) Name of the managed Radius.Security/secrets resource. Use as `secretName` in a container `secretKeyRef`.
91+
connectionString:
92+
type: string
93+
readOnly: true
94+
description: The connection string used to connect to the database. Mapped from the recipe module's output; delivered via the managed secret.
7895
required: [environment]

Data/mongoDatabases/test/app.bicep

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ resource democontainer 'Radius.Compute/containers@2025-08-01-preview' = {
2929
containers: {
3030
demo: {
3131
image: 'ghcr.io/radius-project/samples/demo:latest'
32+
// The recipe's secret output(s) are materialized into a managed
33+
// Radius.Security/secrets resource and consumed here BY REFERENCE via
34+
// secretKeyRef — the value never lands on mongo state.
35+
env: {
36+
MONGODB_CONNECTIONSTRING: {
37+
valueFrom: {
38+
secretKeyRef: {
39+
secretName: mongo.properties.secrets.name
40+
key: 'connectionString'
41+
}
42+
}
43+
}
44+
}
3245
ports: {
3346
web: {
3447
containerPort: 3000

Data/redisCaches/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Developer documentation is embedded in the resource type definition YAML file an
1515
| `size` | string (`S`, `M`, `L`) | Optional | The size of the Redis cache. Defaults to `S`. The Recipe maps the size onto a concrete cloud SKU. |
1616
| `host` | string | Read only | The host name used to connect to the cache. Set from the Recipe module's output. |
1717
| `port` | integer | Read only | The TLS port number used to connect to the cache. Set from the Recipe module's output. |
18-
| `url` | string | Read only | The full TLS connection URL (`rediss://:<access-key>@<host>:<port>`), including the access key. Set from the Recipe module's output. |
18+
| `secrets` | object | Read only | Recipe secrets. `secrets.name` references the managed `Radius.Security/secrets` resource; `secrets.url` is the secret key (delivered via that managed secret, never stored on the resource). |
1919

2020
## Recipe Packs
2121

@@ -27,4 +27,4 @@ Recipes for this resource type are provided through the platform Recipe Packs at
2727

2828
## Using the resource type
2929

30-
Add a `redisCaches` resource to your application and connect a container to it. Radius injects the cache's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_REDIS_HOST`, `CONNECTION_REDIS_PORT`, and `CONNECTION_REDIS_URL`). See [`test/app.bicep`](test/app.bicep) for a complete example.
30+
Add a `redisCaches` resource to your application and connect a container to it. Radius injects the cache's connection properties into the container as environment variables named `CONNECTION_<CONNECTION-NAME>_<PROPERTY-NAME>` (for example `CONNECTION_REDIS_HOST` and `CONNECTION_REDIS_PORT`). The `url` secret is not injected — bind it from the managed `Radius.Security/secrets` resource with a container `secretKeyRef` using `redis.properties.secrets.name`. See [`test/app.bicep`](test/app.bicep) for a complete example.

0 commit comments

Comments
 (0)