Securely flow developer secrets into direct-module recipe parameters#12249
Securely flow developer secrets into direct-module recipe parameters#12249willdavsmith wants to merge 3 commits into
Conversation
Implements Phase 1 of #12244: securely flow developer-authored secrets into direct-module recipe parameters. The engine enriches secret-typed connections with secret material, the param resolver resolves whole-value secret references and tags them, and the Bicep and Terraform drivers route secret values through @secure() / sensitive variables. Docs are a planned follow-up. Signed-off-by: willdavsmith <[email protected]>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
There was a problem hiding this comment.
Pull request overview
This PR implements Phase 1 of #12244 by introducing an in-memory (non-serialized) “secret lane” for connected resources, enabling {{context.resource.connections.<name>.secrets.<key>}} expressions to resolve developer-authored secret values into direct-module recipe parameters, and propagating “secure” taint metadata to drivers so Terraform can route those values via sensitive = true variables.
Changes:
- Add
ConnectedResource.Secrets(json:"-") and enrich secret-typed connections in the recipe engine via the existingSecretsLoader. - Extend the parameter resolver to (a) resolve secret expressions from a separate secret lookup, (b) reject secret interpolation into surrounding strings, and (c) return
{Values, SecureKeys}plus an error. - Update Terraform direct-module execution to use
SecureKeysand emit sensitive variables, while Bicep continues passing resolved parameters through ARM.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| pkg/recipes/types.go | Adds a non-serialized Secrets map to ConnectedResource for secret material. |
| pkg/recipes/engine/engine.go | Best-effort enrichment of secret-typed connections via SecretsLoader. |
| pkg/recipes/engine/engine_test.go | Tests secret enrichment behavior (typed-only, non-fatal errors, nil-loader). |
| pkg/recipes/paramresolver/resolver.go | Returns (ResolvedParameters, error) and adds secret resolution + interpolation rejection. |
| pkg/recipes/paramresolver/resolver_test.go | Adds Test_SecretExpressions and updates existing tests for new signature. |
| pkg/recipes/driver/bicep/bicep.go | Updates for new resolver signature; passes resolved values into ARM parameters. |
| pkg/recipes/terraform/execute.go | Uses resolver {Values, SecureKeys} and calls tfConfig.SetModuleParams(...). |
| pkg/recipes/terraform/config/types.go | Adds TerraformConfig.Variable for declaring sensitive input variables. |
| pkg/recipes/terraform/config/config.go | Implements SetModuleParams to route secure params via sensitive Terraform variables. |
| pkg/recipes/terraform/config/config_test.go | Adds unit tests for SetModuleParams secure/non-secure routing. |
| cfg.Variable[varName] = map[string]any{ | ||
| "type": "string", | ||
| "sensitive": true, | ||
| "default": v, | ||
| } |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #12249 +/- ##
==========================================
+ Coverage 52.88% 52.95% +0.06%
==========================================
Files 751 751
Lines 48353 48440 +87
==========================================
+ Hits 25570 25649 +79
- Misses 20385 20391 +6
- Partials 2398 2400 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Extends Phase 1 of #12244 so a developer-authored Radius.Security/secrets resource referenced by a resource property (x-radius-secret-reference, e.g. secretName) flows its real cleartext into direct-module recipe parameters via the context.resource.secrets.<key> expression, tagged for secure routing (ARM @secure() / Terraform sensitive = true). Because a secret's sensitive data is redacted from the database before recipe execution, the new dynamic-RP secrets loader reads cleartext on demand from the secret's deployed Kubernetes Secret via status.outputResources (never the redacted Properties.Data), scoped to Kubernetes-backed secrets. Enrichment is fail-closed: a referenced secret that cannot be loaded fails the deployment rather than passing a literal placeholder to the module. Docs are a planned follow-up. Signed-off-by: willdavsmith <[email protected]>
…ters" This reverts commit 3876f0a. The #12244 owner has parked the secretName / Radius.Security/secrets path pending reconciliation of its scope (Phase 1 vs deferred Phase 2 reference passthrough, the latter potentially needing security-owner sign-off). This restores PR #12249 to the merge-ready connection-based Phase 1 deliverable. The reverted work remains in history at 3876f0a and can be re-applied when the owner picks it back up. Signed-off-by: willdavsmith <[email protected]>
Radius functional test overviewClick here to see the test run details
Test Status⌛ Building Radius and pushing container images for functional tests... |
| cfg.Variable[varName] = map[string]any{ | ||
| "type": "string", | ||
| "sensitive": true, | ||
| "default": v, | ||
| } |
| t.Run("routes secure params through sensitive variables and sets others inline", func(t *testing.T) { | ||
| tfconfig, err := New(context.Background(), testRecipeName, &envRecipe, &resourceRecipe) | ||
| require.NoError(t, err) | ||
|
|
||
| params := RecipeParams{ |
Description
Implements Phase 1 of #12244: securely flow developer-authored secrets into the parameters of direct-module recipes. Secret material is loaded for secret-typed connections, resolved into recipe parameters only as whole values, tagged as secure, and routed through each driver's native secret-handling mechanism.
Changes by layer
pkg/recipes/types.go): adds a taintedSecrets map[string]string(json:"-") lane torecipes.ConnectedResource, so secret material is never serialized alongside regular connection data.pkg/recipes/engine):enrichConnectionSecretsloads secret material for secret-typed connections (Applications.Core/secretStores,Radius.Security/secrets) using the engine's existingSecretsLoader. Loading is best-effort and non-fatal.pkg/recipes/paramresolver):ResolveParameterExpressionsnow returnsResolvedParameters{Values, SecureKeys}, error. It resolvescontext.resource.connections.<name>.secrets.<key>from a separate secret lookup, enforces whole-value-only usage (a secret interpolated into a surrounding string is rejected with an error), and records secret-sourced parameters inSecureKeys.pkg/recipes/driver/bicep): passes the resolved values to ARM, relying on@secure()scrubbing; Radius never logs the parameter map.pkg/recipes/terraform): routesSecureKeysthrough asensitive = trueTerraform variable referenced as${var.<name>}.This change is rebased onto main's recursive nested-ternary implementation (#12239) and preserves all of its ternary helpers.
Testing
Test_SecretExpressions): whole-value resolution + secure tagging, surrounding-whitespace handling, nested-object tagging, non-secret params untagged, interpolation-into-a-string rejection, and missing-key passthrough.Test_enrichConnectionSecrets): secrets populated for secret-typed connections only, non-fatal load error, and nil-loader no-op.Test_SetModuleParams: secure params routed through sensitive variables while others are set inline, plus an unknown-module no-op.Test_TernaryExpressions) continue to pass.Verified locally:
gofmt -l(clean),go build ./...,go vet ./pkg/recipes/..., andgo test ./pkg/recipes/...all pass.Out of scope / hard to test
@secure()/ Terraformsensitiveparameter needs a real secret store plus a cloud deploy; existing direct-module cloud tests are already manual/deferred.@secure()and Terraformsensitiveredaction are provider behaviors and aren't assertable in unit tests.main.tf.jsonworking directory is inherent to Phase 1; Phase 2 (reference passthrough) avoids it.Addresses #12244 (Phase 2 reference-passthrough remains open, so this does not close the issue).