From 2924fdeb39f95738782cf198e73593bc688b4e61 Mon Sep 17 00:00:00 2001 From: Vishwanath Hiremath Date: Wed, 7 Jan 2026 09:48:29 -0800 Subject: [PATCH 1/5] recipe-digest Signed-off-by: Vishwanath Hiremath --- recipe/2025-12-recipe-digest.md | 249 ++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 recipe/2025-12-recipe-digest.md diff --git a/recipe/2025-12-recipe-digest.md b/recipe/2025-12-recipe-digest.md new file mode 100644 index 00000000..2a3643d8 --- /dev/null +++ b/recipe/2025-12-recipe-digest.md @@ -0,0 +1,249 @@ +# Title + +* **Author**: Your name (@YourGitHubUserName) + +## Overview + +Teams consume Bicep recipes from OCI registries to provision shared infrastructure via Radius recipe packs. Today, registration stores a registry URL with a tag (e.g., v1.4.2 or latest) and the recipe type, and at deployment we pull and execute whatever artifact the registry currently serves for that tag. Because tags are mutable, an attacker or any accidental republish could retarget a tag to different contents without Radius detecting the change. This proposal introduces digest pinning and tag→digest verification so deployments are anchored to the exact artifact that was reviewed and registered, preventing silent drift and supply‑chain tampering. + +## Terms and definitions + +Recipe pack: Radius resource that groups recipes and metadata for provisioning. +Recipe digest: OCI content hash (e.g., sha256:…) uniquely identifying an image manifest. +Mutable tag: Registry tag that can be moved to point at a different digest (latest, semantic versions). +Dependency confusion: Pulling a similarly named artifact from an unintended origin. (Context in template.) + +## Objectives +### Goals + +- Allow recipe authors/platform teams to pin Bicep recipes to a specific digest inside recipe packs. +- Validate the digest at registration and deployment (fail closed on mismatch). +- Preserve ergonomics: tag remains visible for discoverability; digest is the source of truth. +- Maintain backward compatibility with existing packs, with a path to stricter policy by environment. + +### Non goals +- No automatic patching of running apps if digests change; updates are explicit via recipe pack changes. +- No signing (Cosign/Notary) or registry allowlist in v1—those are future enhancements that can layer on top. + + +### User scenarios (optional) + +#### User story 1 +App references ghcr.io/org/recipes/secrets:latest. Registration resolves/stores digest sha256:X. Later, the tag moves to sha256:Y. Deployment fails with an actionable digest mismatch. +#### User story 2 +Security‑conscious team uses v1.4.2 with pinned digest. When upgrading to v1.4.3, they update tag and digest in a PR; Radius validates the new digest and proceeds. +## User Experience (if applicable) + +- publish a bicep recipe +```diff + rad bicep publish --file redis-recipe.bicep --target br:vishwaradius.azurecr.io/redis:1.0 + + Building /Users/vishwa/Documents/RadiusProject/radius/test/testrecipes/test-bicep-recipes/corerp-redis-recipe.bicep... + Pushed to test.azurecr.io:redis@sha256:c6a1…eabb + Successfully published Bicep file "/Users/vishwa/Documents/RadiusProject/radius/test/testrecipes/test-bicep-recipes/corerp-redis-recipe.bicep" to "vishwaradius.azurecr.io/redis:1.0" ++ Copy the digest (sha256:c6a1…eabb) into your recipe pack to pin the artifact immutably. + +``` +- Registering a recipe pack +```diff +resource redisPack 'Radius.Core/recipePacks@2025-05-01-preview' = { + name: 'redisPack' + properties: { + recipes: { + 'Radius.Cache/redis': { + recipeKind: 'bicep' + recipeLocation: 'vishwaradius.azurecr.io/redis:1.0' ++ digest: 'sha256:c6a1…eabb' + } + } + } +} +``` + + +**Sample Input:** + + +**Sample Output:** + + +**Sample Recipe Contract:** + + +## Design + +### High Level Design +High Level Design + +Registration: When a recipe pack is applied, the controller resolves recipeLocation tag → digest via OCI manifest. If the spec already includes digest, the controller compares and rejects on mismatch; otherwise it persists the resolved digest in status. +Deployment: The Bicep engine reads the pack entry and pulls by stored digest. Before pull, it re‑resolves the tag to check for retargeting; if tag now points elsewhere, it fails early. +Updates: Any change of digest requires a recipe pack update/PR; controller re‑validates and persists the new digest. + +### Architecture Diagram + + +### Detailed Design + + + +#### Advantages (of each option considered) + + +#### Disadvantages (of each option considered) + + +#### Proposed Option + + +### API design (if applicable) + + + +### CLI Design (if applicable) + + +### Implementation Details + + +#### UCP (if applicable) +#### Bicep (if applicable) +#### Deployment Engine (if applicable) +#### Core RP (if applicable) +#### Portable Resources / Recipes RP (if applicable) + +### Error Handling + + +## Test plan + + + +## Security + + + +## Compatibility (optional) + + + +## Monitoring and Logging + + + +## Development plan + + + +## Open Questions + + + +## Alternatives considered + + + +## Design Review Notes + + \ No newline at end of file From 3e8f115b8971b4a2e2bc95cc641859906061c907 Mon Sep 17 00:00:00 2001 From: Vishwanath Hiremath Date: Wed, 7 Jan 2026 09:51:24 -0800 Subject: [PATCH 2/5] update Signed-off-by: Vishwanath Hiremath --- recipe/2025-12-recipe-digest.md | 328 +++++++++++++++++--------------- 1 file changed, 172 insertions(+), 156 deletions(-) diff --git a/recipe/2025-12-recipe-digest.md b/recipe/2025-12-recipe-digest.md index 2a3643d8..7012da24 100644 --- a/recipe/2025-12-recipe-digest.md +++ b/recipe/2025-12-recipe-digest.md @@ -1,10 +1,24 @@ # Title -* **Author**: Your name (@YourGitHubUserName) +* **Author**: Vishwanath Hiremath (@vishwahiremat) ## Overview -Teams consume Bicep recipes from OCI registries to provision shared infrastructure via Radius recipe packs. Today, registration stores a registry URL with a tag (e.g., v1.4.2 or latest) and the recipe type, and at deployment we pull and execute whatever artifact the registry currently serves for that tag. Because tags are mutable, an attacker or any accidental republish could retarget a tag to different contents without Radius detecting the change. This proposal introduces digest pinning and tag→digest verification so deployments are anchored to the exact artifact that was reviewed and registered, preventing silent drift and supply‑chain tampering. +Radius recipes enable consistent, reusable provisioning of infrastructure by allowing applications to reference +externally stored infrastructure definitions, such as Bicep templates or Terraform modules, through recipe +packs. These recipes are sourced from external systems including registries and repositories and are later +executed as part of application deployment. + +In the current model, recipe references typically rely on version identifiers that may be mutable, such as tags +or unpinned versions. If the underlying artifact changes without the reference changing, Radius has no built‑in +way to detect the modification. This can result in unintended infrastructure changes and exposes Radius +deployments to supply‑chain risks. + +This proposal introduces a consistent approach to **recipe source integrity**, ensuring that recipes executed +during deployment are identical to those originally intended. By validating artifact identity at both +registration and deployment time, Radius can detect unexpected changes and prevent execution of modified +content. + ## Terms and definitions @@ -17,34 +31,40 @@ Dependency confusion: Pulling a similarly named artifact from an unintended orig ### Goals - Allow recipe authors/platform teams to pin Bicep recipes to a specific digest inside recipe packs. -- Validate the digest at registration and deployment (fail closed on mismatch). -- Preserve ergonomics: tag remains visible for discoverability; digest is the source of truth. -- Maintain backward compatibility with existing packs, with a path to stricter policy by environment. +- Ensure that recipes executed during deployment are identical to the artifacts originally published and intended +- Prevent unexpected recipe drift by validating artifact identity at deployment time. +- Maintain backward compatibility for existing recipes that do not specify a digest. ### Non goals - No automatic patching of running apps if digests change; updates are explicit via recipe pack changes. -- No signing (Cosign/Notary) or registry allowlist in v1—those are future enhancements that can layer on top. - +- Terraform recipe integrity for http/s3 based module sources. ### User scenarios (optional) - #### User story 1 -App references ghcr.io/org/recipes/secrets:latest. Registration resolves/stores digest sha256:X. Later, the tag moves to sha256:Y. Deployment fails with an actionable digest mismatch. +As a platform engineer, I want to pin a recipe to an immutable digest when I register it in a recipe pack, so that any deployment using that recipe executes exactly the artifact I reviewed and approved. + #### User story 2 -Security‑conscious team uses v1.4.2 with pinned digest. When upgrading to v1.4.3, they update tag and digest in a PR; Radius validates the new digest and proceeds. +As a user of Radius recipes, I want Radius to execute the same recipe artifact that I originally published and intended during deployment, so that hidden registry changes cannot cause unexpected infrastructure behavior. + ## User Experience (if applicable) +This proposal introduces minor changes to the recipe publishing and registration experience to make recipe immutability explicit and easy to use. The overall workflow remains the same, with an additional step to surface and persist the recipe digest for bicep recipes. + +#### Publishing a Bicep recipe + +When publishing a Bicep recipe to an OCI registry, the CLI surfaces the resolved digest in the output. This makes the immutable identifier of the artifact immediately available for use during recipe pack registration. -- publish a bicep recipe ```diff - rad bicep publish --file redis-recipe.bicep --target br:vishwaradius.azurecr.io/redis:1.0 + $ rad bicep publish --file redis-recipe.bicep --target br:vishwaradius.azurecr.io/redis:1.0 - Building /Users/vishwa/Documents/RadiusProject/radius/test/testrecipes/test-bicep-recipes/corerp-redis-recipe.bicep... + Building redis-recipe.bicep... Pushed to test.azurecr.io:redis@sha256:c6a1…eabb - Successfully published Bicep file "/Users/vishwa/Documents/RadiusProject/radius/test/testrecipes/test-bicep-recipes/corerp-redis-recipe.bicep" to "vishwaradius.azurecr.io/redis:1.0" + Successfully published Bicep file "redis-recipe.bicep" to "test.acr.io/redis:1.0" + Copy the digest (sha256:c6a1…eabb) into your recipe pack to pin the artifact immutably. ``` -- Registering a recipe pack +#### Registering a recipe pack +When registering a recipe pack, users can optionally specify the digest of the recipe artifact. When provided, the digest becomes the authoritative identifier used for deployment. + ```diff resource redisPack 'Radius.Core/recipePacks@2025-05-01-preview' = { name: 'redisPack' @@ -59,100 +79,128 @@ resource redisPack 'Radius.Core/recipePacks@2025-05-01-preview' = { } } ``` +**Sample Recipe Contract:** +recipepacks.tsp +```diff +@doc("Recipe definition for a specific resource type") +model RecipeDefinition { + @doc("The type of recipe (e.g., Terraform, Bicep)") + recipeKind: RecipeKind; + @doc("Connect to the location using HTTP (not HTTPS). This should be used when the location is known not to support HTTPS, for example in a locally hosted registry for Bicep recipes. Defaults to false (use HTTPS/TLS)") + plainHttp?: boolean; -**Sample Input:** - + @doc("URL path to the recipe") + recipeLocation: string; -**Sample Output:** - + @doc("Parameters to pass to the recipe") + parameters?: Record; -**Sample Recipe Contract:** - ++ @doc("Immutable content digest (sha256:...) for Bicep recipes) ++ digest?: string; +} +``` ## Design ### High Level Design -High Level Design +This design ensures the integrity of Bicep recipes by binding recipe execution to an immutable OCI digest instead of a mutable version tag. -Registration: When a recipe pack is applied, the controller resolves recipeLocation tag → digest via OCI manifest. If the spec already includes digest, the controller compares and rejects on mismatch; otherwise it persists the resolved digest in status. -Deployment: The Bicep engine reads the pack entry and pulls by stored digest. Before pull, it re‑resolves the tag to check for retargeting; if tag now points elsewhere, it fails early. -Updates: Any change of digest requires a recipe pack update/PR; controller re‑validates and persists the new digest. +The core idea is : +- A recipe is registered with both a tag and a digest +- The digest represents the exact recipe artifact that is allowed to execute +- During deployment, recipes are pulled by digest, not by tag -### Architecture Diagram - +This guarantees that the infrastructure deployed by Radius is always derived from the same recipe artifact that was originally published and registered.and persists the new digest. ### Detailed Design - +#### Recipe Registration: Digest Handling +To bind recipe execution to an immutable artifact, the recipe pack must record a digest that uniquely identifies the intended recipe contents. This section evaluates two possible approaches for how the digest is introduced during recipe registration. -#### Advantages (of each option considered) - +##### Option 1: User‑Provided Digest at Registration Time +In this approach, the digest is explicitly supplied by the user when creating or updating a recipe pack. The digest is typically obtained from the output of the recipe publish command and added as part of the recipe definition. +Workflow +- The user publishes a Bicep recipe to an OCI registry. +- The publish command surfaces the resolved digest. +- The user includes this digest in the recipe pack definition during registration. +- Receipe is created with digest details. -#### Disadvantages (of each option considered) - +Advantages: +- **Strong integrity guarantee**: Protects against scenarios where the recipe is modified in the registry between publish and registration. +- **Explicit user intent**: The user declares exactly which artifact is trusted and allowed to execute. +- **Simple implementation**: Leverages existing registry metadata resolution with minimal additional logic. +Clear trust boundary: Digest is treated as an explicit, trusted input rather than derived implicitly. + +Disadvantages: +- **Manual step required**: Users must copy the digest from publish output into the recipe pack definition. + +##### Option 2: Controller‑Computed Digest During Registration +In this approach, the recipe pack controller resolves and records the digest automatically during recipe registration. The user supplies only the recipe location and version, and the system derives the digest from the registry. + +Workflow +- The user publishes a Bicep recipe to an OCI registry. +- The user registers a recipe pack using a tag‑based recipe location. +- During registration, the controller queries the registry for the current digest associated with the tag. +- The resolved digest is stored as part of the recipe definition. + +Advantages +- **No user experience change**: Users are not required to handle digests explicitly. +- Simplifies recipe pack authoring. + +Disadvantages + +- **Weaker integrity guarantees**:If a recipe is modified—maliciously or accidentally—after publish but before recipe registration, Radius will silently register and trust the modified artifact. +- **Implicit trust in registry state**: The system assumes the registry contents at registration time are correct, which reintroduces supply‑chain risk. +- The absence of an explicit digest in the recipe definition obscures which artifact was intended at authoring time. #### Proposed Option - +Option 1 is recommended. While Option 2 offers a slightly smoother authoring experience, it can result in the recipe pack being bound to a different artifact than the one originally published. Option 1 requires the digest to be explicitly provided by the user, making the intended recipe artifact clear, stable, and auditable. The additional manual step is acceptable for infrastructure recipes and aligns with established digest‑pinning practices used for OCI artifacts. + +#### Recipe Execution +During recipe execution, the recipe driver uses the digest information recorded in the recipe definition to ensure that the correct recipe artifact is retrieved and executed. +When a recipe includes a digest: +- The recipe driver retrieves the digest field from the recipe definition stored in the recipe pack. +- Instead of pulling the recipe using a version tag, the driver constructs a digest‑qualified OCI reference. +- The recipe artifact is fetched using ORAS with the digest reference, for example: +``` +oras pull test.acr.io/redis@sha256:c6a1...eabb +``` -### API design (if applicable) +Because OCI digests are immutable, this pull operation deterministically retrieves the exact recipe artifact that was originally published and registered. + +### Integrity Enforcement for Terraform Recipes +This proposal focuses more on digest‑based integrity enforcement for Bicep recipes, where Radius directly pulls and executes recipe artifacts. Terraform recipes differ in execution model and include several native integrity mechanisms. This section outlines how similar integrity guarantees can be achieved for Terraform recipes and clarifies how the principles introduced in this design apply in that context. + +Terraform is different as recipes are executed by invoking `terraform init` and `terraform apply`, and Terraform itself provides built‑in integrity mechanisms: +- Git sources can be pinned to immutable commit SHAs. +- Terraform Registry modules are versioned and protected by checksums recorded in .terraform.lock.hcl. +- Provider binaries are checksum‑verified during init. + +However, these guarantees are post‑selection: Terraform validates integrity after a module source has been chosen. Terraform does not prevent a malicious or accidental change to a module before the first initialization if Radius passes a mutable or unpinned source to Terraform. + +Terraform recipes require source‑specific immutability rules that align with Terraform’s supported module sources. However, this can be achieved for most module sources, but plain HTTPS or S3 needs explicit versioning or checksums. + +- **Git/Mercurial Based Modules** : Pin module sources using immutable commit SHAs (ref=) +- **Terraform Registry Modules** : Registry enforces immutability of published versions. +- **S3/HTTP URL based modules** : S3 or HTTP URLs are mutable and do not provide first‑download integrity guarantees. - ### CLI Design (if applicable) - +Adding digest details to `rad recipe-pack show` command. + +```diff +$ rad recipe-pack show computeRecipePack +RECIPE PACK GROUP +computeRecipePack default + +RECIPES: +Radius.Compute/containers + Kind: bicep + Location: test.acr.io/computepack/recipe:1.0 ++ Digest: sha256:c6a1…eabb +``` + ### Implementation Details -#### UCP (if applicable) -#### Bicep (if applicable) -#### Deployment Engine (if applicable) #### Core RP (if applicable) #### Portable Resources / Recipes RP (if applicable) +Bicep Driver: +- Adding + ### Error Handling - +- Digest not found (registration/deploy): `Recipe digest not found: /@sha256: (404 from registry)` +- Tag retargeted (deploy re-resolve): `Tag digest changed: : now points to sha256:; expected sha256:. Deployment aborted.` ## Test plan - - +**Unit** +- Resolve tag → digest (happy path; plainHttp=false/true). +- Mismatch detection: provided digest vs registry digest. +- Not-found digest. +- Deployment pull-by-digest path; fallback pull-by-tag when digest missing. +- TSP/schema: `digest?: string` serialization/deserialization. + +**E2E / Functional ** +- publish, register with digest, deploy; verify recipe pulled by digest. +- pack without digest +- Retargeted tag: detect and block at deploy. ## Security - - -## Compatibility (optional) - - - -## Monitoring and Logging - - +- Undetected recipe drift +Changes to recipe contents occur without any modification to recipe pack configuration, leading to unexpected infrastructure changes at deployment time. ## Development plan - - -## Open Questions +**Phase 1: Schema & UX** +- Add `digest?: string` to recipe pack schema (Bicep only); update TSP and conversions. +- CLI: ensure `rad bicep publish` output highlights digest; add `rad recipe-pack show` to display stored digest. - +**Phase 2: Bicep engine (deployment)** +- Pull recipes by stored digest (`repo@sha256:...`); tag used only for readability/re-resolve checks. +- Fall back to tag pull only when no digest exists (back-compat), and record resolved digest. -## Alternatives considered +**Phase 3: Tests** +- Unit: resolve/compare logic, error paths (mismatch, not found, auth). +- Integration: publish → register with digest; register without digest (auto-resolve); deploy with retargeted tag (fail); deploy without digest (legacy). +- E2E/func: happy path and negative paths against a test ACR. - +**Phase 4: Docs** +- Update authoring guide: copy digest from publish output into recipe pack. +- Add docs for terraform recipe integrity. +## Open Questions ## Design Review Notes - - \ No newline at end of file From b62d2fa109cd8d603a0b97287acdce1be339a1a5 Mon Sep 17 00:00:00 2001 From: Vishwanath Hiremath Date: Wed, 7 Jan 2026 10:27:27 -0800 Subject: [PATCH 3/5] update Signed-off-by: Vishwanath Hiremath --- recipe/2025-12-recipe-digest.md | 30 +++--------------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/recipe/2025-12-recipe-digest.md b/recipe/2025-12-recipe-digest.md index 7012da24..4ffc3421 100644 --- a/recipe/2025-12-recipe-digest.md +++ b/recipe/2025-12-recipe-digest.md @@ -4,20 +4,11 @@ ## Overview -Radius recipes enable consistent, reusable provisioning of infrastructure by allowing applications to reference -externally stored infrastructure definitions, such as Bicep templates or Terraform modules, through recipe -packs. These recipes are sourced from external systems including registries and repositories and are later -executed as part of application deployment. +Radius recipes enable consistent, reusable provisioning of infrastructure by allowing applications to reference externally stored infrastructure definitions, such as Bicep templates or Terraform modules, through recipe packs. These recipes are sourced from external systems including registries and repositories and are later executed as part of application deployment. -In the current model, recipe references typically rely on version identifiers that may be mutable, such as tags -or unpinned versions. If the underlying artifact changes without the reference changing, Radius has no built‑in -way to detect the modification. This can result in unintended infrastructure changes and exposes Radius -deployments to supply‑chain risks. +In the current model, recipe references typically rely on version identifiers that may be mutable, such as tags or unpinned versions. If the underlying artifact changes without the reference changing, Radius has no built‑in way to detect the modification. This can result in unintended infrastructure changes. -This proposal introduces a consistent approach to **recipe source integrity**, ensuring that recipes executed -during deployment are identical to those originally intended. By validating artifact identity at both -registration and deployment time, Radius can detect unexpected changes and prevent execution of modified -content. +This proposal introduces a consistent approach to **recipe source integrity**, ensuring that recipes executed during deployment are identical to those originally intended. By validating artifact identity deployment time, Radius can detect unexpected changes and prevent execution of modified content. ## Terms and definitions @@ -25,7 +16,6 @@ content. Recipe pack: Radius resource that groups recipes and metadata for provisioning. Recipe digest: OCI content hash (e.g., sha256:…) uniquely identifying an image manifest. Mutable tag: Registry tag that can be moved to point at a different digest (latest, semantic versions). -Dependency confusion: Pulling a similarly named artifact from an unintended origin. (Context in template.) ## Objectives ### Goals @@ -130,7 +120,6 @@ Advantages: - **Strong integrity guarantee**: Protects against scenarios where the recipe is modified in the registry between publish and registration. - **Explicit user intent**: The user declares exactly which artifact is trusted and allowed to execute. - **Simple implementation**: Leverages existing registry metadata resolution with minimal additional logic. -Clear trust boundary: Digest is treated as an explicit, trusted input rather than derived implicitly. Disadvantages: - **Manual step required**: Users must copy the digest from publish output into the recipe pack definition. @@ -202,19 +191,6 @@ Radius.Compute/containers ``` -### Implementation Details - - -#### Core RP (if applicable) -#### Portable Resources / Recipes RP (if applicable) -Bicep Driver: -- Adding - - ### Error Handling - Digest not found (registration/deploy): `Recipe digest not found: /@sha256: (404 from registry)` - Tag retargeted (deploy re-resolve): `Tag digest changed: : now points to sha256:; expected sha256:. Deployment aborted.` From e4742fde803fdabd8a51c20d90d2146d2442515b Mon Sep 17 00:00:00 2001 From: Vishwanath Hiremath Date: Wed, 14 Jan 2026 09:40:12 -0800 Subject: [PATCH 4/5] addressing feedback Signed-off-by: Vishwanath Hiremath --- recipe/2025-12-recipe-digest.md | 161 +++++++++++++++++++------------- 1 file changed, 96 insertions(+), 65 deletions(-) diff --git a/recipe/2025-12-recipe-digest.md b/recipe/2025-12-recipe-digest.md index 4ffc3421..aedd94c6 100644 --- a/recipe/2025-12-recipe-digest.md +++ b/recipe/2025-12-recipe-digest.md @@ -1,4 +1,4 @@ -# Title +# Enhance recipe-engine to support digests * **Author**: Vishwanath Hiremath (@vishwahiremat) @@ -13,9 +13,12 @@ This proposal introduces a consistent approach to **recipe source integrity**, e ## Terms and definitions -Recipe pack: Radius resource that groups recipes and metadata for provisioning. -Recipe digest: OCI content hash (e.g., sha256:…) uniquely identifying an image manifest. -Mutable tag: Registry tag that can be moved to point at a different digest (latest, semantic versions). + +| Term | Definition | +| --------------- | ---------------------------------------------------------------------------------------------------- | +| Recipe pack | Radius resource that groups recipes and metadata for provisioning. | +| Recipe digest | OCI content hash (e.g., `sha256:…`) uniquely identifying an image manifest. Most OCI registries currently support only `sha256` digests.| +| Mutable tag | Registry tag that can be moved to point at a different digest (e.g., `latest`, semantic versions). | ## Objectives ### Goals @@ -37,7 +40,7 @@ As a platform engineer, I want to pin a recipe to an immutable digest when I reg As a user of Radius recipes, I want Radius to execute the same recipe artifact that I originally published and intended during deployment, so that hidden registry changes cannot cause unexpected infrastructure behavior. ## User Experience (if applicable) -This proposal introduces minor changes to the recipe publishing and registration experience to make recipe immutability explicit and easy to use. The overall workflow remains the same, with an additional step to surface and persist the recipe digest for bicep recipes. +This proposal introduces minor, intentional changes to how recipes are registered, while preserving the existing publishing experience. Recipe immutability is expressed using OCI‑native references, allowing users to choose between tag‑based and digest‑based recipe resolution #### Publishing a Bicep recipe @@ -53,8 +56,20 @@ When publishing a Bicep recipe to an OCI registry, the CLI surfaces the resolved ``` #### Registering a recipe pack -When registering a recipe pack, users can optionally specify the digest of the recipe artifact. When provided, the digest becomes the authoritative identifier used for deployment. +When registering a recipe pack, users specify how the recipe should be resolved at deployment time by choosing the appropriate recipeLocation format. + +##### Tag‑based recipe reference (mutable): +To continue using tag‑based resolution, users specify a tag as they do today: +``` +recipeLocation: 'vishwaradius.azurecr.io/redis:1.0' +``` +In this mode: +- The recipe is resolved using the tag. +- The recipe contents may change if the tag is retargeted. +- This preserves existing behavior for backward compatibility. +##### Digest‑based recipe reference (immutable): +To pin a recipe to an immutable artifact, users specify a digest‑qualified OCI reference : ```diff resource redisPack 'Radius.Core/recipePacks@2025-05-01-preview' = { name: 'redisPack' @@ -62,46 +77,30 @@ resource redisPack 'Radius.Core/recipePacks@2025-05-01-preview' = { recipes: { 'Radius.Cache/redis': { recipeKind: 'bicep' - recipeLocation: 'vishwaradius.azurecr.io/redis:1.0' -+ digest: 'sha256:c6a1…eabb' ++ recipeLocation: 'vishwaradius.azurecr.io/redis@sha256:c6a1…eabb' } } } } ``` -**Sample Recipe Contract:** -recipepacks.tsp -```diff -@doc("Recipe definition for a specific resource type") -model RecipeDefinition { - @doc("The type of recipe (e.g., Terraform, Bicep)") - recipeKind: RecipeKind; - - @doc("Connect to the location using HTTP (not HTTPS). This should be used when the location is known not to support HTTPS, for example in a locally hosted registry for Bicep recipes. Defaults to false (use HTTPS/TLS)") - plainHttp?: boolean; - @doc("URL path to the recipe") - recipeLocation: string; - - @doc("Parameters to pass to the recipe") - parameters?: Record; - -+ @doc("Immutable content digest (sha256:...) for Bicep recipes) -+ digest?: string; -} -``` +In this mode: +- The digest uniquely identifies the recipe artifact. +- Radius always pulls and executes the recipe by digest. +- Mutable tags are not used for content resolution. +- The recipe executed during deployment is guaranteed to be the exact artifact originally published and intended. ## Design ### High Level Design -This design ensures the integrity of Bicep recipes by binding recipe execution to an immutable OCI digest instead of a mutable version tag. +This design enforces recipe integrity by binding execution to an immutable OCI digest rather than a mutable version tag. The goal is to ensure that the deployed infrastructure always originates from the exact recipe artifact that was published and registered. The core idea is : -- A recipe is registered with both a tag and a digest +- A recipe is registered with a digest. - The digest represents the exact recipe artifact that is allowed to execute -- During deployment, recipes are pulled by digest, not by tag +- During deployment, recipes are pulled by digest. -This guarantees that the infrastructure deployed by Radius is always derived from the same recipe artifact that was originally published and registered.and persists the new digest. +This guarantees that the infrastructure deployed by Radius is always derived from the same recipe artifact that was originally published and registered. ### Detailed Design @@ -109,12 +108,34 @@ This guarantees that the infrastructure deployed by Radius is always derived fro To bind recipe execution to an immutable artifact, the recipe pack must record a digest that uniquely identifies the intended recipe contents. This section evaluates two possible approaches for how the digest is introduced during recipe registration. ##### Option 1: User‑Provided Digest at Registration Time -In this approach, the digest is explicitly supplied by the user when creating or updating a recipe pack. The digest is typically obtained from the output of the recipe publish command and added as part of the recipe definition. +In this approach, the digest is explicitly provided by the user when creating or updating a recipe pack. The digest is typically obtained from the output of the recipe publish command and added as part of the recipe definition. + Workflow - The user publishes a Bicep recipe to an OCI registry. -- The publish command surfaces the resolved digest. + ```diff + $ rad bicep publish --file redis-recipe.bicep --target br:vishwaradius.azurecr.io/redis:1.0 + + Building redis-recipe.bicep... + Pushed to test.azurecr.io:redis@sha256:c6a1…eabb + Successfully published Bicep file "redis-recipe.bicep" to "test.acr.io/redis:1.0" + + Copy the digest (sha256:c6a1…eabb) into your recipe pack to pin the artifact immutably. + ``` +- The publish command surfaces the resolved digest. (e.g: sha256:c6a1…eabb) - The user includes this digest in the recipe pack definition during registration. -- Receipe is created with digest details. + ```diff + resource redisPack 'Radius.Core/recipePacks@2025-05-01-preview' = { + name: 'redisPack' + properties: { + recipes: { + 'Radius.Cache/redis': { + recipeKind: 'bicep' + + recipeLocation: 'vishwaradius.azurecr.io/redis@sha256:c6a1…eabb' + } + } + } + } + ``` +- Recipe is created with digest details. Advantages: - **Strong integrity guarantee**: Protects against scenarios where the recipe is modified in the registry between publish and registration. @@ -147,14 +168,20 @@ Disadvantages Option 1 is recommended. While Option 2 offers a slightly smoother authoring experience, it can result in the recipe pack being bound to a different artifact than the one originally published. Option 1 requires the digest to be explicitly provided by the user, making the intended recipe artifact clear, stable, and auditable. The additional manual step is acceptable for infrastructure recipes and aligns with established digest‑pinning practices used for OCI artifacts. #### Recipe Execution -During recipe execution, the recipe driver uses the digest information recorded in the recipe definition to ensure that the correct recipe artifact is retrieved and executed. -When a recipe includes a digest: -- The recipe driver retrieves the digest field from the recipe definition stored in the recipe pack. -- Instead of pulling the recipe using a version tag, the driver constructs a digest‑qualified OCI reference. -- The recipe artifact is fetched using ORAS with the digest reference, for example: -``` -oras pull test.acr.io/redis@sha256:c6a1...eabb -``` +During recipe execution, the recipe driver uses the digest information recorded as part of the recipeLocation in the recipe definition. + +When a recipeLocation is defined with a digest: +- The recipe driver retrieves the digest from the recipeLocation. +- Pull the recipe artifact directly from the OCI registry using the digest reference. example: + ``` + oras pull test.acr.io/redis@sha256:c6a1...eabb + + # instead of + + oras pull test.acr.io/redis:1.0 + ``` +- Execute the retrieved recipe artifact. + Because OCI digests are immutable, this pull operation deterministically retrieves the exact recipe artifact that was originally published and registered. @@ -171,12 +198,25 @@ However, these guarantees are post‑selection: Terraform validates integrity af Terraform recipes require source‑specific immutability rules that align with Terraform’s supported module sources. However, this can be achieved for most module sources, but plain HTTPS or S3 needs explicit versioning or checksums. - **Git/Mercurial Based Modules** : Pin module sources using immutable commit SHAs (ref=) -- **Terraform Registry Modules** : Registry enforces immutability of published versions. + ```diff + resource redisPack 'Radius.Core/recipePacks@2025-05-01-preview' = { + name: 'redisPack' + properties: { + recipes: { + 'Radius.Cache/redis': { + recipeKind: 'terraform' + + recipeLocation: 'git::https://github.com/recipes/test-module.git?ref=9f3c2e1a4b6d7c8e9f0123456789abcd12345678' + } + } + } + } + ``` +- **Terraform Registry Modules** : Module versions in the Terraform Registry are not inherently immutable, but can be effectively pinned by using immutable Git tags or releases in the backing repository. - **S3/HTTP URL based modules** : S3 or HTTP URLs are mutable and do not provide first‑download integrity guarantees. ### CLI Design (if applicable) -Adding digest details to `rad recipe-pack show` command. +Adding digest details to `rad recipe-pack show` command output. ```diff $ rad recipe-pack show computeRecipePack @@ -186,27 +226,21 @@ computeRecipePack default RECIPES: Radius.Compute/containers Kind: bicep - Location: test.acr.io/computepack/recipe:1.0 -+ Digest: sha256:c6a1…eabb ++ Location: test.acr.io/computepack/recipe@sha256:c6a1…eabb ``` - ### Error Handling - Digest not found (registration/deploy): `Recipe digest not found: /@sha256: (404 from registry)` -- Tag retargeted (deploy re-resolve): `Tag digest changed: : now points to sha256:; expected sha256:. Deployment aborted.` +- Invalid digest format : Returned when the provided digest does not conform to the expected `sha256:` format. ## Test plan **Unit** - Resolve tag → digest (happy path; plainHttp=false/true). -- Mismatch detection: provided digest vs registry digest. - Not-found digest. -- Deployment pull-by-digest path; fallback pull-by-tag when digest missing. -- TSP/schema: `digest?: string` serialization/deserialization. +- Deployment pull-by-digest path. **E2E / Functional ** -- publish, register with digest, deploy; verify recipe pulled by digest. -- pack without digest -- Retargeted tag: detect and block at deploy. +- publish, register with digest, update the recipe for the current tag, verify recipe pulled by digest. ## Security @@ -220,18 +254,15 @@ Changes to recipe contents occur without any modification to recipe pack configu ## Development plan -**Phase 1: Schema & UX** -- Add `digest?: string` to recipe pack schema (Bicep only); update TSP and conversions. +**Phase 1: UX and refactoring** - CLI: ensure `rad bicep publish` output highlights digest; add `rad recipe-pack show` to display stored digest. +- refactor the code for version/tag check from the recipeLocation where ever needed. -**Phase 2: Bicep engine (deployment)** -- Pull recipes by stored digest (`repo@sha256:...`); tag used only for readability/re-resolve checks. -- Fall back to tag pull only when no digest exists (back-compat), and record resolved digest. - -**Phase 3: Tests** -- Unit: resolve/compare logic, error paths (mismatch, not found, auth). -- Integration: publish → register with digest; register without digest (auto-resolve); deploy with retargeted tag (fail); deploy without digest (legacy). -- E2E/func: happy path and negative paths against a test ACR. +**Phase 2: Bicep engine (deployment) and Tests** +- Pull recipes by specified digest (`repo@sha256:...`). +- Unit Tests: resolve/compare logic, error paths (mismatch, not found, auth). +- Integration Tests: publish → register with digest; register without digest (auto-resolve); deploy with retargeted tag ; deploy without digest. +- E2E/functional Tests: happy path and negative paths against a test ACR. **Phase 4: Docs** - Update authoring guide: copy digest from publish output into recipe pack. From 0b0fff52250404acd79bc7cae7c5ae217c927b60 Mon Sep 17 00:00:00 2001 From: Vishwanath Hiremath Date: Wed, 14 Jan 2026 10:43:04 -0800 Subject: [PATCH 5/5] update Signed-off-by: Vishwanath Hiremath --- recipe/2025-12-recipe-digest.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/recipe/2025-12-recipe-digest.md b/recipe/2025-12-recipe-digest.md index aedd94c6..fc60621b 100644 --- a/recipe/2025-12-recipe-digest.md +++ b/recipe/2025-12-recipe-digest.md @@ -25,7 +25,6 @@ This proposal introduces a consistent approach to **recipe source integrity**, e - Allow recipe authors/platform teams to pin Bicep recipes to a specific digest inside recipe packs. - Ensure that recipes executed during deployment are identical to the artifacts originally published and intended -- Prevent unexpected recipe drift by validating artifact identity at deployment time. - Maintain backward compatibility for existing recipes that do not specify a digest. ### Non goals @@ -37,7 +36,7 @@ This proposal introduces a consistent approach to **recipe source integrity**, e As a platform engineer, I want to pin a recipe to an immutable digest when I register it in a recipe pack, so that any deployment using that recipe executes exactly the artifact I reviewed and approved. #### User story 2 -As a user of Radius recipes, I want Radius to execute the same recipe artifact that I originally published and intended during deployment, so that hidden registry changes cannot cause unexpected infrastructure behavior. +As a user of Radius recipes, I want Radius to execute the same recipe artifact that I originally published and intended during deployment, so that changes such as a registry tag being retargeted to a different artifact cannot cause unexpected infrastructure behavior. ## User Experience (if applicable) This proposal introduces minor, intentional changes to how recipes are registered, while preserving the existing publishing experience. Recipe immutability is expressed using OCI‑native references, allowing users to choose between tag‑based and digest‑based recipe resolution @@ -168,7 +167,7 @@ Disadvantages Option 1 is recommended. While Option 2 offers a slightly smoother authoring experience, it can result in the recipe pack being bound to a different artifact than the one originally published. Option 1 requires the digest to be explicitly provided by the user, making the intended recipe artifact clear, stable, and auditable. The additional manual step is acceptable for infrastructure recipes and aligns with established digest‑pinning practices used for OCI artifacts. #### Recipe Execution -During recipe execution, the recipe driver uses the digest information recorded as part of the recipeLocation in the recipe definition. +During recipe execution, the recipe driver uses the digest information recorded as part of the `recipeLocation` in the recipe definition. When a recipeLocation is defined with a digest: - The recipe driver retrieves the digest from the recipeLocation. @@ -229,6 +228,16 @@ Radius.Compute/containers + Location: test.acr.io/computepack/recipe@sha256:c6a1…eabb ``` +Adding digest info to the `rad bicep publish` command output +```diff + $ rad bicep publish --file redis-recipe.bicep --target br:vishwaradius.azurecr.io/redis:1.0 + + Building redis-recipe.bicep... + Pushed to test.azurecr.io:redis@sha256:c6a1…eabb + Successfully published Bicep file "redis-recipe.bicep" to "test.acr.io/redis:1.0" ++ Copy the digest (sha256:c6a1…eabb) into your recipe pack to pin the artifact immutably. +``` + ### Error Handling - Digest not found (registration/deploy): `Recipe digest not found: /@sha256: (404 from registry)` - Invalid digest format : Returned when the provided digest does not conform to the expected `sha256:` format.