Cloud Security Wire
AWS Azure GCP RSS
Azure Misconfiguration high

Azure Container Apps Security: Ingress, Dapr, and Managed Identity Misconfigurations

Azure Container Apps abstracts Kubernetes complexity but introduces its own security pitfalls: publicly reachable ingress by default, Dapr secrets accessible to any component in the environment, and managed identities scoped too broadly. This guide covers each pattern with az CLI remediation.

By Cloud Security Wire · ·
#Azure#Container-Apps#Dapr#managed-identity#ingress#misconfiguration#Kubernetes#service-mesh#secrets#IAM#CSPM
High Severity

This issue has been assessed as high severity. Review affected configurations immediately.

Azure Container Apps (ACA) is Microsoft’s serverless container platform built on top of Kubernetes and Kubernetes-native tools including Dapr, KEDA, and Envoy. It removes the need to manage cluster infrastructure while retaining support for microservices patterns. What it doesn’t remove are the security decisions — it just makes some of them less visible, which is where misconfigurations emerge.

Three patterns appear consistently in ACA environments in the wild: ingress enabled where it shouldn’t be, Dapr component secrets reachable by any app in the environment, and managed identities granted permissions far beyond what any single container application needs. Each is addressable with specific configuration changes.

Misconfiguration 1: Public Ingress on Internal Services

ACA ingress controls whether your container app is reachable over HTTP/HTTPS. It has three settings:

  • Disabled — no HTTP endpoint, traffic via Dapr service invocation or direct container-to-container only
  • Internal — reachable only within the ACA environment (VNet-integrated)
  • External — reachable from the public internet

The default for new Container Apps created via the Azure portal or quick-start templates is External. This means a service that should only be reachable by other microservices in the same environment is reachable from the public internet with only your application’s own auth (if any) standing in the way.

Check your current ingress configuration:

az containerapp show \
  --name myapp \
  --resource-group myresourcegroup \
  --query "properties.configuration.ingress" \
  --output json

A correctly configured internal service returns:

{
  "external": false,
  "targetPort": 3000,
  "transport": "auto"
}

If "external": true appears on a service that has no business receiving public traffic, restrict it:

az containerapp ingress disable \
  --name myapp \
  --resource-group myresourcegroup

Or switch to internal-only:

az containerapp ingress enable \
  --name myapp \
  --resource-group myresourcegroup \
  --target-port 3000 \
  --type internal

Audit all apps in an environment for external ingress:

az containerapp list \
  --resource-group myresourcegroup \
  --query "[].{Name:name, External:properties.configuration.ingress.external, FQDN:properties.configuration.ingress.fqdn}" \
  --output table

Any service returning True in the External column that doesn’t serve end-user traffic is a candidate for review.

Misconfiguration 2: Dapr Component Secrets Scoped to the Entire Environment

Dapr provides a secrets building block that lets your container apps reference secrets from Azure Key Vault, Kubernetes secrets, or other stores without hardcoding credentials. Dapr components in ACA are configured at the environment level, which means by default every application in that environment can access every Dapr component — including secrets stores.

If you configure a Dapr secrets component pointing at a Key Vault containing your payment service credentials, your frontend app and your logging service can also query that secrets store through Dapr’s API:

# From inside any container app in the environment with Dapr enabled:
curl http://localhost:3500/v1.0/secrets/my-keyvault-component/payment-service-api-key

No additional authorization required.

Scope Dapr components to specific apps using the scopes field in the component definition:

# component.yaml
componentType: secretstores.azure.keyvault
version: v1
metadata:
  - name: vaultName
    value: "mykeyvault"
scopes:
  - payment-service

Deploy with scope:

az containerapp env dapr-component set \
  --name my-environment \
  --resource-group myresourcegroup \
  --dapr-component-name my-keyvault \
  --yaml component.yaml

Without scopes, the component is available to every app. With scopes, only payment-service can access it.

Audit existing Dapr components for missing scopes:

az containerapp env dapr-component list \
  --name my-environment \
  --resource-group myresourcegroup \
  --query "[].{Name:name, Scopes:scopes}" \
  --output table

Any component returning None or an empty scopes list is globally accessible.

Misconfiguration 3: Overprivileged Managed Identities

Managed identities are the correct way to give Container Apps access to Azure resources — no credentials to store or rotate, Azure handles the token lifecycle. The problem is the scope of permissions attached to those identities.

Common patterns that emerge over time:

  • A single managed identity assigned to multiple container apps with different privilege needs
  • Managed identities with Contributor on the resource group rather than specific roles on specific resources
  • User-assigned managed identities shared across microservices, meaning a compromise of any one service inherits the permissions of all

Check what role assignments a managed identity holds:

# Get the managed identity principal ID
PRINCIPAL_ID=$(az containerapp show \
  --name myapp \
  --resource-group myresourcegroup \
  --query "identity.principalId" \
  --output tsv)

# List all role assignments for this identity
az role assignment list \
  --assignee $PRINCIPAL_ID \
  --all \
  --output table

Look for assignments on scopes higher than necessary — resource group or subscription scope when resource scope would suffice.

Right-size permissions to the specific resource:

# Get the Key Vault resource ID
KEYVAULT_ID=$(az keyvault show \
  --name mykeyvault \
  --resource-group myresourcegroup \
  --query id \
  --output tsv)

# Assign only Key Vault Secrets User (read-only) on the specific vault
az role assignment create \
  --assignee $PRINCIPAL_ID \
  --role "Key Vault Secrets User" \
  --scope $KEYVAULT_ID

Instead of a single identity for all services, provision a separate user-assigned managed identity per container app:

# Create app-specific identity
az identity create \
  --name payment-service-identity \
  --resource-group myresourcegroup

# Assign to the specific app
az containerapp identity assign \
  --name payment-service \
  --resource-group myresourcegroup \
  --user-assigned payment-service-identity

This limits blast radius if one service is compromised — its identity has only the permissions that service requires.

Misconfiguration 4: Environment Variables as Secrets

ACA supports secrets as a distinct concept from environment variables, with values stored encrypted and not visible in the portal or ARM template output. Despite this, teams frequently set sensitive values directly as environment variables — API keys, connection strings, and tokens exposed in plain text in any deployment manifest or portal view.

Wrong — credential as environment variable:

az containerapp create \
  --name myapp \
  --env-vars DATABASE_PASSWORD=supersecret123  # ← Exposed in logs, portal, ARM exports

Correct — use ACA secrets:

# Register the secret
az containerapp secret set \
  --name myapp \
  --resource-group myresourcegroup \
  --secrets db-password=supersecret123

# Reference it as an environment variable
az containerapp update \
  --name myapp \
  --resource-group myresourcegroup \
  --set-env-vars DATABASE_PASSWORD=secretref:db-password

For production, point ACA secrets at Azure Key Vault rather than storing values in ACA itself:

az containerapp secret set \
  --name myapp \
  --resource-group myresourcegroup \
  --secrets "db-password=keyvaultref:https://mykeyvault.vault.azure.net/secrets/db-password,identityref:/subscriptions/.../managedIdentities/myapp-identity"

Audit Checklist

  • All container apps not serving public traffic have external: false ingress
  • All Dapr components have explicit scopes limiting access to specific apps
  • Each container app has its own managed identity, not a shared identity
  • No managed identity holds Contributor or Owner at resource group scope
  • Sensitive configuration is stored in ACA secrets or Key Vault, not environment variables
  • ACA environment is VNet-integrated and not using the default public infrastructure subnet
  • Container registry images are pulled from ACR with managed identity — no registry credentials in environment variables
← All Analysis Subscribe via RSS