Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugin/skills/azure-prepare/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: "Prepare Azure apps for deployment (infra Bicep/Terraform, azure.ya
license: MIT
metadata:
author: Microsoft
version: "1.1.1"
version: "1.1.2"
---

# Azure Prepare
Expand Down
8 changes: 7 additions & 1 deletion plugin/skills/azure-prepare/references/analyze.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,10 @@ Converting an existing application to run on Azure.

> ⚠️ **Critical**: The Functions `bicep.md` and `terraform.md` files are **REFERENCE DOCUMENTATION**, not templates to copy. Hand-writing infrastructure from these patterns results in missing RBAC, incorrect managed identity configuration, and security vulnerabilities.

For other compute targets (Container Apps, App Service, Static Web Apps), load their respective README files in `services/` for guidance.
For **Container Apps**, load the composition rules the same way:

1. Load `services/container-apps/templates/selection.md` — decision tree for base template + recipe
2. Load `services/container-apps/templates/recipes/composition.md` — the exact algorithm to follow
3. Use `azd init -t <template>` to generate proven IaC — **NEVER hand-write Bicep/Terraform**

For other compute targets (App Service, Static Web Apps), load their respective README files in `services/` for guidance.
4 changes: 3 additions & 1 deletion plugin/skills/azure-prepare/references/research.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ For each selected service, load the README.md first, then load specific files as
Selected: Container Apps, Cosmos DB, Key Vault

→ Load: services/container-apps/README.md (overview)
→ If need templates: services/container-apps/templates/selection.md
→ Follow: services/container-apps/templates/recipes/composition.md
→ If need Bicep: services/container-apps/bicep.md
→ If need scaling: services/container-apps/scaling.md
→ If need health probes: services/container-apps/health-probes.md
Expand Down Expand Up @@ -103,7 +105,7 @@ Add research findings to `.azure/deployment-plan.md` under a `## Research Summar

### Web Application + API + Database

1. Load: [services/container-apps/README.md](services/container-apps/README.md) → [bicep.md](services/container-apps/bicep.md), [scaling.md](services/container-apps/scaling.md)
1. Load: [services/container-apps/README.md](services/container-apps/README.md) → [templates/selection.md](services/container-apps/templates/selection.md), [bicep.md](services/container-apps/bicep.md), [scaling.md](services/container-apps/scaling.md)
2. Load: [services/cosmos-db/README.md](services/cosmos-db/README.md) → [partitioning.md](services/cosmos-db/partitioning.md)
3. Load: [services/key-vault/README.md](services/key-vault/README.md)
4. Invoke: `azure-observability` (monitoring setup)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ services:
| Background Worker | None | 0 (scale to zero) | Queue-based |
| Web Application | External | 1 | HTTP-based |

## Templates & Recipes

- **[Selection Guide](templates/selection.md)** — Start here: decision tree for base template + recipe
- **[Composition Algorithm](templates/recipes/composition.md)** — How to fetch and compose templates
- [AZD Templates](templates/recipes/README.md) — Recipe overview

## References

- [Bicep Patterns](bicep.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ resource api 'Microsoft.App/containerApps@2024-03-01' = {
allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
allowedHeaders: ['*']
}
// ⚠️ Replace '*' with specific origins for production
}
registries: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ resource funcApp 'Microsoft.App/containerApps@2024-03-01' = {
name: 'AzureWebJobsStorage__credential'
value: 'managedidentity'
}
{
name: 'AzureWebJobsStorage__clientId'
value: uamiClientId // Required for UAMI — runtime defaults to system MI without this
}
Comment on lines +112 to +115

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uamiClientId is referenced for AzureWebJobsStorage__clientId, but this Bicep snippet doesn't declare a uamiClientId parameter (or otherwise define it). As written, the template won't compile; add a param uamiClientId string (consistent with other templates) or derive the clientId from the UAMI resource ID being passed in.

Copilot uses AI. Check for mistakes.
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
Expand Down Expand Up @@ -155,6 +159,7 @@ scale: {
namespace: 'my-sb-namespace'
messageCount: '5'
}
identity: userAssignedIdentityId
}
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,22 @@ resource eventJob 'Microsoft.App/jobs@2024-03-01' = {
name: '${name}-event'
location: location
tags: tags
identity: {
type: 'UserAssigned'
userAssignedIdentities: { '${userAssignedIdentityId}': {} }
}
properties: {
environmentId: envId
configuration: {
triggerType: 'Event'
replicaTimeout: 600
replicaRetryLimit: 2
registries: [
{
server: '${containerRegistryName}.azurecr.io'
identity: userAssignedIdentityId
}
]
eventTriggerConfig: {
parallelism: 1
replicaCompletionCount: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ services:
param name string
param location string = resourceGroup().location
param tags object = {}
param logAnalyticsWorkspaceId string
param logAnalyticsSharedKey string

// logAnalytics resource must be declared or passed as parameter
// Shared Container Apps Environment
resource env 'Microsoft.App/managedEnvironments@2024-03-01' = {
name: '${name}-env'
Expand All @@ -63,8 +66,8 @@ resource env 'Microsoft.App/managedEnvironments@2024-03-01' = {
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: logAnalytics.properties.customerId
sharedKey: logAnalytics.listKeys().primarySharedKey
customerId: logAnalyticsWorkspaceId
sharedKey: logAnalyticsSharedKey
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ IF recipes detected:
name: name
location: location
tags: tags
containerAppPrincipalId: app.outputs.principalId
principalId: app.outputs.principalId
}
}
```
Expand Down Expand Up @@ -78,16 +78,13 @@ Read the recipe's `README.md` for required env vars. Add them to the container a
Each recipe defines required RBAC roles. Use exact role definition GUIDs from recipe docs.

```bicep
resource cosmosRbac 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(cosmos.id, uami.id, cosmosDataContributor)
scope: cosmos
resource cosmosRbac 'Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments@2024-05-15' = {
parent: cosmos
name: guid(cosmos.id, uami.id, 'data-contributor')
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'00000000-0000-0000-0000-000000000002' // from recipe
)
roleDefinitionId: '${cosmos.id}/sqlRoleDefinitions/00000000-0000-0000-0000-000000000002'
principalId: uami.outputs.principalId
principalType: 'ServicePrincipal'
scope: cosmos.id
Comment on lines +81 to +87

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This RBAC example mixes incompatible shapes for uami: it uses uami.outputs.principalId (module output) but also uami.id (resource property). Bicep modules don't have .id, and resources don't have .outputs, so the snippet won't compile as-is. Pick one representation (module vs resource/param) and update all uami.* references consistently (including the guid(...) inputs).

Copilot uses AI. Check for mistakes.
}
}
```
Expand Down Expand Up @@ -132,7 +129,7 @@ Base (web-app)
## Critical Rules

1. **Never synthesize IaC from scratch** — always extend base template
2. **Never modify base IaC files** — only ADD recipe modules alongside them
2. **Don't replace or remove base IaC resources** — extend base files only by adding module references and additive resources
3. **Always use recipe RBAC role GUIDs** — never let the LLM guess role IDs
4. **Always use UAMI** — never use connection strings or access keys
5. **Always use `--no-prompt`** with azd commands
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ resource cosmos 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = {
kind: 'GlobalDocumentDB'
properties: {
databaseAccountOfferType: 'Standard'
disableLocalAuthentication: true
disableLocalAuth: true
locations: [
{ locationName: location, failoverPriority: 0 }
]
Expand Down Expand Up @@ -101,4 +101,4 @@ const client = new CosmosClient({
});
```

> ⚠️ **Always set `disableLocalAuthentication: true`** — use RBAC only, never keys.
> ⚠️ **Always set `disableLocalAuth: true`** — use RBAC only, never keys.
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ resource redis 'Microsoft.Cache/redis@2024-03-01' = {
}
}

// RBAC — Redis Cache Contributor
// RBAC — Redis Cache Data Owner
resource rbac 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(redis.id, principalId, 'e0f68234-74aa-48ed-b826-c38b57376e17')
name: guid(redis.id, principalId, 'e12a10f1-dcd0-4ee7-abb0-0b2e24e345e7')
scope: redis
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'e0f68234-74aa-48ed-b826-c38b57376e17'
'e12a10f1-dcd0-4ee7-abb0-0b2e24e345e7'
)
principalId: principalId
principalType: 'ServicePrincipal'
Expand All @@ -67,8 +67,8 @@ env: [

| Role | GUID | Access |
|------|------|--------|
| Redis Cache Contributor | `e0f68234-74aa-48ed-b826-c38b57376e17` | Manage cache + data |
| Redis Cache Data Access | Custom role | Data plane operations |
| Redis Cache Data Owner | `e12a10f1-dcd0-4ee7-abb0-0b2e24e345e7` | Read + write data |
| Redis Cache Data Contributor | `e12a10f1-dcd0-4ee7-abb0-0b2e24e345c2` | Read-only data |

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the RBAC roles table, “Redis Cache Data Contributor” is described as “Read-only data”, which conflicts with the role name and is inconsistent with the Cosmos recipe using “Data Reader” for read-only access. Please verify the intended built-in role and GUID, and either change the access description or switch to the appropriate read-only role name/GUID to avoid misleading guidance.

Suggested change
| Redis Cache Data Contributor | `e12a10f1-dcd0-4ee7-abb0-0b2e24e345c2` | Read-only data |
| Redis Cache Data Contributor | `e12a10f1-dcd0-4ee7-abb0-0b2e24e345c2` | Read + write data |

Copilot uses AI. Check for mistakes.

## SDK Connection (Node.js Example)

Expand All @@ -80,10 +80,16 @@ const credential = new DefaultAzureCredential({
managedIdentityClientId: process.env.AZURE_CLIENT_ID,
});

const client = createClient({
url: `rediss://${process.env.REDIS_HOSTNAME}:${process.env.REDIS_PORT}`,
credential,
});
async function createRedisClient() {
const { token } = await credential.getToken("https://redis.azure.com/.default");
const client = createClient({
url: `rediss://${process.env.REDIS_HOSTNAME}:${process.env.REDIS_PORT}`,
username: process.env.AZURE_CLIENT_ID,
password: token,
});
await client.connect();
return client;
}
```

## Dapr State Store
Expand All @@ -101,6 +107,7 @@ resource stateStore 'Microsoft.App/managedEnvironments/daprComponents@2024-03-01
{ name: 'redisHost', value: '${redis.outputs.hostName}:${redis.outputs.sslPort}' }
{ name: 'enableTLS', value: 'true' }
{ name: 'azureClientId', value: uamiClientId }
{ name: 'useEntraID', value: 'true' }
]
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,15 @@ scale: {
queueName: sb.outputs.queueName
messageCount: '5'
}
auth: [
{
secretRef: 'sb-identity'
triggerParameter: 'connection'
}
]
identity: managedIdentityResourceId
}
}
]
}
```

> 💡 **Tip:** Set `identity` to the UAMI resource ID to authenticate the KEDA scaler via managed identity instead of connection strings.

## RBAC Roles

| Role | GUID | Access |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ param envId string
param containerRegistryName string
param imageName string
param userAssignedIdentityId string
param uamiClientId string
param serviceBusNamespace string = ''
param queueName string = ''

Expand Down Expand Up @@ -82,7 +83,7 @@ resource worker 'Microsoft.App/containerApps@2024-03-01' = {
{ name: 'QUEUE_NAME', value: queueName }
{
name: 'AZURE_CLIENT_ID'
value: '' // Set to UAMI client ID
value: uamiClientId
}
]
}
Expand All @@ -100,12 +101,7 @@ resource worker 'Microsoft.App/containerApps@2024-03-01' = {
queueName: queueName
messageCount: '5'
}

Copilot AI Apr 23, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main worker example uses managed identity auth for the Service Bus scaler (identity: userAssignedIdentityId), but the later “Service Bus Queue Scaling (KEDA)” snippet in the same file still omits identity entirely. That inconsistency makes it easy to copy the wrong pattern; update the scaling-pattern snippet to include identity (or call out that it’s required for MI auth).

Suggested change
}
}
// Required when the Service Bus scaler uses managed identity authentication.

Copilot uses AI. Check for mistakes.
auth: [
{
secretRef: 'sb-connection'
triggerParameter: 'connection'
}
]
identity: userAssignedIdentityId
}
}
]
Expand Down Expand Up @@ -146,9 +142,12 @@ rules: [
custom: {
type: 'azure-eventhub'
metadata: {
namespace: '<namespace>'
eventHubName: '<eventhub>'
consumerGroup: '$Default'
unprocessedEventThreshold: '64'
}
identity: userAssignedIdentityId
}
}
]
Expand All @@ -163,9 +162,11 @@ rules: [
custom: {
type: 'azure-queue'
metadata: {
accountName: '<storage-account>'
queueName: '<queue>'
queueLength: '5'
}
identity: userAssignedIdentityId
}
}
]
Expand Down
Loading