Skip to content

Container Apps RP rewrites env-var secretRef to non-existent name capp-<appname> when materializing revisions, regardless of input path (CLI / YAML / REST PATCH) #1705

@joranderAtos

Description

@joranderAtos

Container Apps RP rewrites env-var secretRef to non-existent name capp-<appname> when materializing revisions, regardless of input path (CLI / YAML / REST PATCH)

Summary

When creating or updating a Container App with KV-backed secrets bound to env vars, every emitted revision has its env-var secretRef values silently rewritten by the resource provider to a single grouped name capp-<appname> that does not exist in configuration.secrets[]. The container-app desired-state (visible via az containerapp show -o yaml) reports the original secretRef names correctly. The configuration.secrets[] itself retains the user-specified names. Only the emitted revision spec is mangled — and only the env block within it.

This was verified to be RP-side (not CLI-side) by sending a hand-constructed PATCH body via az rest --method PATCH directly to the management plane and observing the same rewriting in the resulting revision retrieved via az rest --method GET. The same rewriting occurs whether the secret's identity is system-assigned or user-assigned — it is independent of the identity binding.

Severity

Security-relevant. The control plane stores the user-specified secretRef names in configuration.secrets[] AND reports them correctly in the desired-state view, but the RP materializes a revision spec where every KV-backed env-var secretRef is replaced with a single grouped name capp-<appname> that has no corresponding entry. If at any point a real secret named capp-<appname> is created (manually, accidentally, or by a future RP feature), env vars would silently start resolving to it. Two distinct env vars (DATABASE_URL and JWT_SECRET in our case) are collapsed to point at the same phantom secret name — a credential-confusion vector if that name ever materializes a value.

Repro

CLI: azure-cli 2.86.0 on Linux 6.6.87.2-microsoft-standard-WSL2. Region: eastus2. Workload profile: Consumption. API version tested: 2024-03-01.

  1. Create two KV-backed secrets and wire them to env vars by name:
az containerapp create \
  --name myapp --resource-group myrg --environment myenv \
  --image myacr.azurecr.io/myapp:v1 \
  --registry-identity system --target-port 3001 --ingress external \
  --secrets \
    "database-url=keyvaultref:https://mykv.vault.azure.net/secrets/database-url,identityref:system" \
    "jwt-secret=keyvaultref:https://mykv.vault.azure.net/secrets/jwt-secret,identityref:system" \
  --env-vars \
    "DATABASE_URL=secretref:database-url" \
    "JWT_SECRET=secretref:jwt-secret"
  1. Verify secrets are correctly created:
$ az containerapp secret list --name myapp --resource-group myrg -o json
[
  { "name": "database-url", "keyVaultUrl": "https://mykv.vault.azure.net/secrets/database-url", "identity": "system" },
  { "name": "jwt-secret",   "keyVaultUrl": "https://mykv.vault.azure.net/secrets/jwt-secret",   "identity": "system" }
]
  1. Verify the desired-state template reports the bindings correctly:
$ az containerapp show --name myapp --resource-group myrg -o yaml | grep -A1 "DATABASE_URL\|JWT_SECRET"
- name: DATABASE_URL
  secretRef: database-url
- name: JWT_SECRET
  secretRef: jwt-secret
  1. Every emitted revision has secretRef rewritten to capp-<appname>:
$ az containerapp revision show --name myapp --resource-group myrg --revision myapp--<suffix> \
    --query "properties.template.containers[0].env" -o json
[
  { "name": "NODE_ENV", "value": "production" },
  { "name": "PORT", "value": "3001" },
  { "name": "DATABASE_URL", "secretRef": "capp-myapp" },
  { "name": "JWT_SECRET",   "secretRef": "capp-myapp" }
]
  1. Same result via direct REST PATCH — this isolates the rewriting to the resource provider:

PATCH body sent directly to the RP:

$ cat patch.json
{
  "properties": {
    "configuration": {
      "secrets": [
        { "name": "database-url", "identity": "<uami-resource-id>",
          "keyVaultUrl": "https://mykv.vault.azure.net/secrets/database-url" },
        { "name": "jwt-secret", "identity": "<uami-resource-id>",
          "keyVaultUrl": "https://mykv.vault.azure.net/secrets/jwt-secret" }
      ]
    },
    "template": {
      "containers": [{
        "image": "myacr.azurecr.io/myapp:v1",
        "name": "myapp",
        "env": [
          { "name": "DATABASE_URL", "secretRef": "database-url" },
          { "name": "JWT_SECRET",   "secretRef": "jwt-secret" }
        ],
        "resources": { "cpu": 0.25, "memory": "0.5Gi" }
      }],
      "scale": { "minReplicas": 1, "maxReplicas": 3 }
    }
  }
}

$ az rest --method PATCH \
    --url "https://management.azure.com/subscriptions/<sub>/resourceGroups/myrg/providers/Microsoft.App/containerApps/myapp?api-version=2024-03-01" \
    --body @patch.json
(exit 0)

GET on the resulting revision via REST:

$ az rest --method GET \
    --url ".../containerApps/myapp/revisions/<latest>?api-version=2024-03-01" \
    --query "properties.template.containers[0].env" -o json
[
  ...
  { "name": "DATABASE_URL", "secretRef": "capp-myapp" },   ← rewritten
  { "name": "JWT_SECRET",   "secretRef": "capp-myapp" }    ← rewritten
]

The CLI is not in the data path. The RP itself rewrites.

  1. Identity choice does not affect the bug. Both identityref:system and a user-assigned managed identity (UAMI, attached separately) produce the same rewriting. We tested by creating a UAMI, granting it Key Vault Secrets User on the KV, attaching it to the container app, and PATCHing configuration.secrets[].identity to the UAMI resource ID. The secrets[].identity field updated to UAMI as expected; the env-var secretRef was rewritten anyway.

  2. There is no capp-<appname> entry in secrets[]:

$ az containerapp secret list --name myapp --resource-group myrg --query "[?name=='capp-myapp']"
[]

What we observed at runtime (not inferred)

The replica record exists with replicas: 1 in metadata, but runningStateDetails reports WorkLoad Profile Full. 0/1 replicas ready. and the replica's containers array is empty:

$ az containerapp replica list --name myapp --resource-group myrg --revision myapp--<suffix> -o json
[{
  "properties": {
    "containers": [],
    "createdTime": "...",
    "runningState": "NotRunning",
    "runningStateDetails": ""
  }
}]

External HTTP requests to the FQDN return HTTP 504. We did not observe runtime env-var resolution behavior because no container ever started.

(The WorkLoad Profile Full situation is an independent issue in the same environment that masks downstream observation. We cannot attest to what the runtime would do when given the rewritten secretRef because the container does not reach that code path. The rewriting is verified at the spec/control-plane level only.)

What we tried (none of which fixed the rewriting)

Input path Result
az containerapp create --secrets ... --env-vars "VAR=secretref:NAME" Emitted revision has secretRef: capp-<appname>
az containerapp update --set-env-vars "VAR=secretref:NAME" Desired-state template appears updated, but no new revision is emitted from this command alone, and subsequently emitted revisions still have the rewritten name
az containerapp update --image ... --revision-suffix <suffix> New revision materialized; secretRef rewritten
az containerapp update --yaml /path/spec.yaml (YAML literally containing secretRef: database-url) New revision materialized; secretRef rewritten
az rest --method PATCH with hand-constructed JSON body containing literal secretRef values New revision materialized; secretRef rewritten — RP-side confirmation
Switching from identityref:system to a UAMI on secrets[].identity secrets[].identity updates correctly to UAMI; env-var secretRef rewriting unchanged

Additional anomaly

After the rewriting has occurred, az containerapp identity assign --user-assigned <UAMI> returns:

ERROR: (ContainerAppSecretRefNotFound) SecretRef 'database-url' defined for container 'myapp' not found.

The error message is misleading — secret list clearly shows database-url exists in configuration.secrets[]. The validator appears to be reading the (rewritten) revision spec and failing to find capp-<appname> in configuration.secrets[], but it reports the original (un-rewritten) name in the error. We could not determine whether the identity-assign actually attached the UAMI or partially failed; a subsequent REST PATCH that referenced UAMI in secrets[].identity succeeded.

Suggested fix

Either (a) the RP should honor the literal secretRef value in env-vars verbatim during revision materialization, or (b) emit a synchronous error on revision creation when env-var secretRef references a name not present in configuration.secrets[], naming both the user-supplied value and any rewritten value the RP intends to apply.

A silent rewrite to a non-existent name with no warning, no logged event, and no asynchronous error is the most surprising part — the operator has no signal that anything is wrong until containers fail to start, and even then the failure manifests as the unrelated WorkLoad Profile Full error if the env happens to be at capacity.

Environment

  • azure-cli 2.86.0 on Linux 6.6.87.2-microsoft-standard-WSL2
  • Region: eastus2
  • Container Apps environment workload profile: Consumption (no Dedicated profile)
  • Container Apps environment resource type: Microsoft.App/managedEnvironments
  • API version tested: 2024-03-01
  • Identity types tested: SystemAssigned, UserAssigned (single UAMI), and SystemAssigned + UserAssigned simultaneously

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs: InvestigationIssue has been assigned and tagged, pending them to process itProvisioningRelated to deployment issues, revision provisioning, etc.bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions