──────
Build and Push Docker Images / detect-changes (push) Successful in 11s
Build and Push Docker Images / build-auth-api (push) Successful in 1m0s
Build and Push Docker Images / build-employee-api (push) Successful in 1m2s
Build and Push Docker Images / build-transit-api (push) Successful in 1m1s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m5s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m13s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m15s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
Build and Push Docker Images / detect-changes (push) Successful in 11s
Build and Push Docker Images / build-auth-api (push) Successful in 1m0s
Build and Push Docker Images / build-employee-api (push) Successful in 1m2s
Build and Push Docker Images / build-transit-api (push) Successful in 1m1s
Build and Push Docker Images / build-error-log-api (push) Successful in 1m5s
Build and Push Docker Images / build-admin-ui (push) Successful in 1m13s
Build and Push Docker Images / build-monitoring-ui (push) Successful in 1m15s
Build and Push Docker Images / deploy-test-node (push) Successful in 3s
### Summary of Changes
1. Documentation Consolidation:
• Relocated the docs/ folder and all repository .md files (README.md, DEPLOYMENT.md, DOCKER_README.md,
DOMAINS_README.md, GETTING_STARTED.md, CI_CD_PORTAINER_GUIDE.md, ADMIN_DEPLOYMENT_SUMMARY.md,
SECURITY_PRIVATE_FILES.md, handoff.md, azure-project-plan.md, plus service-specific READMEs) into .
• Updated document links in README.md.
2. AI Agent Project Map:
• Created PROJECT_MAP.md in the project root containing complete architecture diagrams, service specs, ports, DB
schemas, environment variables, and developer workflows.
• Added PROJECT_MAP.md to .gitignore.
3. Monitoring UI & Infrastructure Integration:
• Added monitoring_ui service to docker-compose.yml (port 5175).
• Configured Gitea workflow docker-build.yml for monitoring_ui builds.
• Added CORS middleware headers in index.js.
• Added nginx.conf copy step in Dockerfile.
──────
### Recommended Commit Message
docs: consolidate project documentation into monitoring_ui and add agent project map
- Move all markdown documentation files and docs/ folder into monitoring_ui/docs/
- Update documentation hub index (monitoring_ui/docs/README.md) with relative paths
- Generate comprehensive AI agent reference map (PROJECT_MAP.md) in repository root
- Add PROJECT_MAP.md and monitoring_ui build output/env overrides to .gitignore
- Integrate monitoring_ui service into docker-compose.yml and Gitea CI workflow
- Configure CORS handling in employee_api for monitoring dashboard access
──────
### Alternative Short Commit Message
refactor(docs): move docs to monitoring_ui and create AI agent PROJECT_MAP.md
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Bicep — App Service Patterns
|
||||
|
||||
App Service-specific Bicep patterns. For shared patterns (skeleton, naming, tags, security defaults, data modules), see [bicep-patterns.md](bicep-patterns.md).
|
||||
|
||||
> ⛔ **Azure Functions on Flex Consumption (the default Functions floor) is NOT an App Service — do NOT use this module for it.** Flex uses a different resource shape (`functionAppConfig`) and a different deploy channel; see [bicep-functions-flex.md](bicep-functions-flex.md). This module applies to App Service web apps and to Functions only on **Premium (EP1)**.
|
||||
|
||||
## Module Template
|
||||
|
||||
Standard App Service module with managed identity and SCM/FTP auth disabled. Use this as the base for ALL App Service resources.
|
||||
|
||||
```bicep
|
||||
param location string
|
||||
param tags object
|
||||
param appServicePlanId string
|
||||
param appServiceName string
|
||||
|
||||
resource appService 'Microsoft.Web/sites@2023-12-01' = {
|
||||
name: appServiceName
|
||||
location: location
|
||||
tags: tags
|
||||
identity: {
|
||||
type: 'SystemAssigned'
|
||||
}
|
||||
properties: {
|
||||
serverFarmId: appServicePlanId
|
||||
httpsOnly: true
|
||||
siteConfig: {
|
||||
minTlsVersion: '1.2'
|
||||
ftpsState: 'Disabled'
|
||||
// Only when this App Service is an API called by a browser frontend on a DIFFERENT hostname.
|
||||
// Omit entirely for a full-stack app that serves its own UI — that is same-origin and needs no CORS.
|
||||
// See bicep-patterns.md § Cross-Origin (CORS).
|
||||
cors: {
|
||||
allowedOrigins: [ 'https://${staticWebApp.properties.defaultHostname}' ]
|
||||
supportCredentials: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SCM basic auth — enabled in IaC for deploy convenience. Deploy phase re-disables via REST API after code upload.
|
||||
resource scmAuth 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2023-12-01' = {
|
||||
parent: appService
|
||||
name: 'scm'
|
||||
properties: {
|
||||
allow: true
|
||||
}
|
||||
}
|
||||
|
||||
// ⛔ MANDATORY — disable FTP basic auth
|
||||
resource ftpAuth 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2023-12-01' = {
|
||||
parent: appService
|
||||
name: 'ftp'
|
||||
properties: {
|
||||
allow: false
|
||||
}
|
||||
}
|
||||
|
||||
output appServiceId string = appService.id
|
||||
output principalId string = appService.identity.principalId
|
||||
```
|
||||
|
||||
> ⛔ **Every App Service module MUST include:** (1) `identity: { type: 'SystemAssigned' }`, (2) `scm` basicPublishingCredentialsPolicies with `allow: true` (IaC sets enabled for deploy convenience — deploy phase re-disables via REST API after code upload), (3) `ftp` basicPublishingCredentialsPolicies with `allow: false`. Missing any of these → self-review L1 `FLAGGED`.
|
||||
|
||||
## Native Module Deploy Strategy
|
||||
|
||||
When `prepare-plan.json.deployStrategy` exists with `codeDeployPattern: "startup-install"`, apply these patterns to the App Service Bicep:
|
||||
|
||||
```bicep
|
||||
resource appService 'Microsoft.Web/sites@2023-12-01' = {
|
||||
name: appServiceName
|
||||
location: location
|
||||
tags: tags
|
||||
properties: {
|
||||
serverFarmId: appServicePlan.id
|
||||
httpsOnly: true
|
||||
siteConfig: {
|
||||
linuxFxVersion: 'NODE|${nodeVersion}' // ⛔ Use exact version tag (18-lts, 20-lts) — NEVER tilde (~18). Tilde works for WEBSITE_NODE_DEFAULT_VERSION but NOT linuxFxVersion.
|
||||
ftpsState: 'Disabled'
|
||||
minTlsVersion: '1.2'
|
||||
// Startup command: Oryx build is primary, this is the safety-net fallback.
|
||||
// Runs npm install only if node_modules doesn't exist (Oryx missed it).
|
||||
// MUST be inline — never a .sh file (Windows CRLF → bash exit code 2).
|
||||
appCommandLine: '${deployStrategy.startupCommand}'
|
||||
appSettings: [
|
||||
// Primary: tell Oryx to run npm install during zip deploy
|
||||
{ name: 'SCM_DO_BUILD_DURING_DEPLOYMENT', value: 'true' }
|
||||
{ name: 'ENABLE_ORYX_BUILD', value: 'true' }
|
||||
// Extended timeout for native module compilation (max 1800, default 230)
|
||||
{ name: 'WEBSITES_CONTAINER_START_TIME_LIMIT', value: '1800' }
|
||||
// App-specific settings
|
||||
{ name: 'NODE_ENV', value: 'production' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- ⛔ **Inline `appCommandLine` only** — never generate a `.sh` startup script file. Files created on Windows have CRLF line endings → bash exit code 2 on Linux
|
||||
- ⛔ **Entry point from manifest** — read `package.json.scripts.start` or `.main`, never hardcode `index.js`
|
||||
- ⛔ **`WEBSITES_CONTAINER_START_TIME_LIMIT` = 1800** (the maximum). Native compilation takes 2-5 min; Python with scipy can take longer
|
||||
- When `deployStrategy` is absent (no native modules), do NOT set `appCommandLine` — let Oryx use its default startup
|
||||
- ⛔ **Never prefix startup with `cd /home/site/wwwroot`** — Oryx extracts build output to a temp directory and sets the working directory automatically. Hardcoding `cd /home/site/wwwroot` causes `MODULE_NOT_FOUND` / `Could not import` because the app files aren't there
|
||||
|
||||
**Self-review check (L2 Pattern):** If `hasNativeModules == true`, verify Bicep has BOTH `appCommandLine` and `WEBSITES_CONTAINER_START_TIME_LIMIT`. If `prereq-output.json.initCommands[]` has `required: true` entries, verify `appCommandLine` includes them before the app start command. **FLAGGED** if either check fails.
|
||||
|
||||
## Identity Output — SystemAssigned vs UserAssigned
|
||||
|
||||
> ⛔ **`appService.identity.principalId` only exists for `SystemAssigned` identity.** When using `UserAssigned`, output the managed identity MODULE's `principalId` instead — `identity.principalId` is undefined and causes `DeploymentOutputEvaluationFailed`. Every App Service has a managed identity (mandatory — the compute floor is B1, which supports MI; F1/D1/Free are never selected).
|
||||
@@ -0,0 +1,189 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Bicep — Container Apps Patterns
|
||||
|
||||
Container Apps-specific Bicep patterns. For shared patterns (skeleton, naming, tags, security defaults, data modules), see [bicep-patterns.md](bicep-patterns.md).
|
||||
|
||||
## Two-Phase Wiring
|
||||
|
||||
Container Apps + ACR requires two-phase deployment (circular dependency: CA needs ACR image, ACR needs CA identity for AcrPull):
|
||||
|
||||
1. **Phase 1:** Deploy Container App with placeholder image (`mcr.microsoft.com/azuredocs/containerapps-helloworld:latest`). ⛔ **No `registries` block.** The placeholder image is pulled from MCR (public). Use `registries: []`. Native `secrets` (literal values) MAY be set in Phase 1 — they have no RBAC dependency. **The AcrPull role assignment IS created in Phase 1** — it doesn't affect the placeholder deployment and needs 1–2 minutes to propagate before Phase 2.
|
||||
2. **Phase 2:** Build + push app image to ACR, redeploy with real image + `registries`. AcrPull RBAC is already propagated from Phase 1.
|
||||
|
||||
> ⛔ **Placeholder image listens on port 80, not your app's port.** Set `targetPort` conditionally: `var effectivePort = containerImage == 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' ? 80 : appPort`. Mismatched ports cause "Operation expired" (health probe can't reach container).
|
||||
|
||||
> ⛔ **`containerImage` param must exist in BOTH `main.bicep` AND the container app module.** Phase 2 passes `--parameters containerImage='...'` via CLI — if `main.bicep` lacks the param, the override is silently ignored and the placeholder persists.
|
||||
|
||||
```bicep
|
||||
// In main.bicep: thread containerImage to module
|
||||
param containerImage string = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
|
||||
module containerApp './modules/containerapp.bicep' = {
|
||||
params: { containerImage: containerImage /* ...other params... */ }
|
||||
}
|
||||
|
||||
// In containerapp.bicep:
|
||||
param containerImage string = 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
|
||||
var isPlaceholder = containerImage == 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest'
|
||||
|
||||
resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
|
||||
identity: { type: 'SystemAssigned' }
|
||||
properties: {
|
||||
configuration: {
|
||||
ingress: {
|
||||
external: true
|
||||
targetPort: isPlaceholder ? 80 : appPort
|
||||
allowInsecure: false // ⛔ MANDATORY
|
||||
}
|
||||
registries: isPlaceholder ? [] : [{ server: acr.properties.loginServer, identity: 'system' }]
|
||||
secrets: [ { name: 'secret-key', value: secretKey } ] // native CA secrets (no RBAC dep) — NOT KV
|
||||
}
|
||||
template: {
|
||||
containers: [{
|
||||
image: containerImage
|
||||
env: [{ name: 'PORT', value: string(isPlaceholder ? 80 : appPort) }]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> ⛔ **Do NOT set `revisionSuffix`.** Omit it entirely — ARM auto-generates unique revision names. Hardcoding `revisionSuffix: 'v1'` causes Phase 2 redeploy to fail with "revision with suffix v1 already exists."
|
||||
|
||||
### AcrPull Role Assignment
|
||||
|
||||
> ⛔ **AcrPull role GUID: `7f951dda-4ed3-4680-a7ca-43fe172d538d`.** Copy verbatim — wrong GUIDs cause `RoleDefinitionDoesNotExist`.
|
||||
|
||||
```bicep
|
||||
resource acrPullRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
|
||||
name: guid(acr.id, containerApp.id, '7f951dda-4ed3-4680-a7ca-43fe172d538d')
|
||||
scope: acr
|
||||
properties: {
|
||||
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d')
|
||||
principalId: containerApp.identity.principalId
|
||||
principalType: 'ServicePrincipal'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Log Analytics Workspace Key
|
||||
|
||||
> ⛔ **Use `resource.listKeys()`, NOT `reference()`.** `reference()` does not expose `primarySharedKey`.
|
||||
|
||||
```bicep
|
||||
// ✅ Correct
|
||||
var laKey = logAnalyticsWorkspace.listKeys().primarySharedKey
|
||||
|
||||
// ❌ Wrong
|
||||
var laKey = reference(logAnalyticsWorkspace.id, '2023-09-01').primarySharedKey
|
||||
```
|
||||
|
||||
### Log Analytics customerId vs resource ID
|
||||
|
||||
> ⛔ **Output BOTH `id` and `customerId` from the log-analytics module.** Container Apps Environment needs the GUID `customerId`. App Insights needs the ARM resource ID. Do NOT use `split(workspaceId, '/')[8]` — that extracts the workspace name, not the GUID.
|
||||
|
||||
```bicep
|
||||
// log-analytics.bicep outputs:
|
||||
output id string = logAnalyticsWorkspace.id // ARM resource ID
|
||||
output customerId string = logAnalyticsWorkspace.properties.customerId // GUID
|
||||
output sharedKey string = logAnalyticsWorkspace.listKeys().primarySharedKey
|
||||
|
||||
// container-app-environment.bicep:
|
||||
param workspaceCustomerId string // GUID, NOT resource ID
|
||||
// ⛔ MUST nest under appLogsConfiguration.destination='log-analytics' — a bare top-level logAnalyticsConfiguration fails deploy (ManagedEnvironmentInvalidSchema). This nesting is the ONLY valid location at EVERY API version (the flat shape was never valid — NOT version drift, so do not chase API-version pins). This is the CA's only log path (no diagnostic-settings module).
|
||||
properties: {
|
||||
appLogsConfiguration: {
|
||||
destination: 'log-analytics'
|
||||
logAnalyticsConfiguration: {
|
||||
customerId: workspaceCustomerId
|
||||
sharedKey: workspaceSharedKey
|
||||
}
|
||||
}
|
||||
}
|
||||
// ❌ WRONG: customerId: split(workspaceId, '/')[8]
|
||||
```
|
||||
|
||||
## Ingress & Port Mapping
|
||||
|
||||
> ⛔ **Container resource limits:** Use decimal format for memory: `'0.5Gi'`, `'1Gi'`, `'2Gi'` — NOT Kubernetes-style `'512Mi'`. CPU must be type `string`: `'0.25'`, `'0.5'`, `'1'`. Valid combos: `0.25/0.5Gi`, `0.5/1Gi`, `0.75/1.5Gi`, `1/2Gi`, `1.25/2.5Gi`, `1.5/3Gi`, `1.75/3.5Gi`, `2/4Gi`.
|
||||
|
||||
> ⛔ **ACR module:** `retentionPolicy` is **Premium-only**. For Basic/Standard ACR, omit `retentionPolicy` entirely — ARM rejects it.
|
||||
|
||||
## Container Apps Secrets (Native — No Key Vault)
|
||||
|
||||
> ⛔ **No Key Vault.** Container Apps store app-internal secrets in the app's **native** `secrets` array with a literal `value` supplied by an `@secure()` param at deploy time. NEVER use `keyVaultUrl`, `@Microsoft.KeyVault(...)`, or a KV `secrets` child resource.
|
||||
|
||||
> ⛔ **Only app-internal secrets use `secretRef`.** Database, cache, and storage access is managed-identity + token — wire their **connection parameters as plain `env` values** (host, db name, MI username, `sslmode=require`), never a `secretRef`. `secretRef` is for things like `SECRET_KEY`/JWT/third-party API keys.
|
||||
|
||||
> Native secrets have no RBAC dependency, so they don't need the `isPlaceholder` gate that KV secretRefs required (two-phase wiring is still needed for ACR registries + real image — see § Two-Phase Wiring).
|
||||
|
||||
```bicep
|
||||
@secure()
|
||||
param secretKey string // generated at deploy, passed via CLI --parameters, never committed
|
||||
|
||||
resource containerApp 'Microsoft.App/containerApps@2024-03-01' = {
|
||||
identity: {
|
||||
type: 'SystemAssigned'
|
||||
}
|
||||
properties: {
|
||||
configuration: {
|
||||
secrets: [
|
||||
{ name: 'secret-key', value: secretKey } // ⛔ native CA secret — NOT keyVaultUrl
|
||||
]
|
||||
}
|
||||
template: {
|
||||
containers: [{
|
||||
env: [
|
||||
// App-internal secret via secretRef:
|
||||
{ name: 'SECRET_KEY', secretRef: 'secret-key' }
|
||||
// Database via managed identity — plain connection params, NO password/secretRef:
|
||||
{ name: 'PGHOST', value: '${pgName}.postgres.database.azure.com' }
|
||||
{ name: 'PGUSER', value: containerAppName } // the MI's DB principal name
|
||||
{ name: 'PGSSLMODE', value: 'require' }
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> ⛔ **Never use conditional logic (`??`, ternary, `empty()`, `union()`) to mix plain and secret env vars in a single Bicep loop or array.** ARM evaluates ALL property paths in conditional expressions — `envVar.secretRef` errors on items that don't have that property, producing `InvalidTemplate`. Instead, define plain and secret env vars as separate arrays and concatenate:
|
||||
>
|
||||
> ```bicep
|
||||
> env: concat(
|
||||
> [
|
||||
> { name: 'PORT', value: '8000' }
|
||||
> { name: 'NODE_ENV', value: 'production' }
|
||||
> // DB/cache via managed identity — plain connection params, no secretRef:
|
||||
> { name: 'PGHOST', value: '${pgName}.postgres.database.azure.com' }
|
||||
> { name: 'PGUSER', value: containerAppName }
|
||||
> ],
|
||||
> [
|
||||
> { name: 'SECRET_KEY', secretRef: 'secret-key' } // app-internal secret only
|
||||
> ]
|
||||
> )
|
||||
> ```
|
||||
|
||||
> ⛔ **`principalId: containerApp.identity.principalId`** (object ID, NOT clientId) for the AcrPull role assignment. See [rbac-roles.md](rbac-roles.md) for the AcrPull role GUID.
|
||||
|
||||
> ❌ **WRONG:** `principalId: .clientId` (not the object ID) or `identity: containerApp.id` in secrets[] (use `'system'` for system-assigned MI).
|
||||
|
||||
> App-internal secrets are native CA secrets (literal `value` from an `@secure()` param) — there is no Key Vault, no KV role assignment, and no secret seeding into KV.
|
||||
|
||||
## Multi-Container Internal DNS
|
||||
|
||||
Container Apps in the same environment communicate via internal DNS: `http://{container-app-name}`. Set via env vars:
|
||||
|
||||
```bicep
|
||||
env: [
|
||||
{ name: 'API_URL', value: 'http://${apiContainerApp.name}' }
|
||||
{ name: 'WORKER_URL', value: 'http://${workerContainerApp.name}' }
|
||||
]
|
||||
```
|
||||
|
||||
No ingress needed for internal-only services — set `ingress.external: false` or omit ingress entirely.
|
||||
|
||||
## Networking
|
||||
|
||||
> ⛔ **Subnets MUST be defined inline** in VNet `properties.subnets[]`, NOT as separate `Microsoft.Network/virtualNetworks/subnets` child resources. Separate child resources cause `InUseSubnetCannotBeDeleted` on redeploy when NICs are attached.
|
||||
@@ -0,0 +1,161 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Bicep — Azure Functions (Flex Consumption)
|
||||
|
||||
Bicep patterns for **Azure Functions on the Flex Consumption plan** — the default Functions floor in this
|
||||
pipeline (see [sku-matrix.md](../../prepare/references/sku-matrix.md)). For shared patterns (skeleton, naming,
|
||||
tags, storage, RBAC), see [bicep-patterns.md](bicep-patterns.md) and
|
||||
[bicep-patterns-data.md](bicep-patterns-data.md).
|
||||
|
||||
> ⛔ **A Flex Consumption function app is NOT an App Service.** Do **not** reuse
|
||||
> [bicep-app-service.md](bicep-app-service.md) for it. Flex uses a different resource shape
|
||||
> (`functionAppConfig`), a different deploy channel (a blob-container package — see
|
||||
> [code-deployment-functions-flex.md](../../deploy/references/code-deployment-functions-flex.md)), and it
|
||||
> rejects the App Service build/publish settings. Only **Functions on Premium (EP1)** use the App Service
|
||||
> module + SCM/Kudu path.
|
||||
|
||||
## ⛔ Settings that are deprecated / moved on Flex Consumption — NEVER emit them
|
||||
|
||||
Flex Consumption ignores or is broken by these App Service / Consumption settings. Emitting them is the
|
||||
single most common cause of a "provisioned but broken" Flex app. See
|
||||
[Flex Consumption plan deprecations](https://learn.microsoft.com/azure/azure-functions/functions-app-settings#flex-consumption-plan-deprecations).
|
||||
|
||||
| Setting | Why it's wrong on Flex |
|
||||
| --- | --- |
|
||||
| `FUNCTIONS_WORKER_RUNTIME` | Conflicts with `functionAppConfig.runtime`. The runtime is declared **only** in `functionAppConfig.runtime`. |
|
||||
| `FUNCTIONS_EXTENSION_VERSION` | Managed by the platform on Flex. |
|
||||
| `SCM_DO_BUILD_DURING_DEPLOYMENT`, `ENABLE_ORYX_BUILD` | Oryx/Kudu build settings — unsupported. Flex builds via a **remote build** during package deploy (see the deploy reference), not Oryx-on-SCM. |
|
||||
| `WEBSITE_RUN_FROM_PACKAGE` | Flex runs from its deployment blob container automatically. |
|
||||
| `WEBSITE_CONTENTAZUREFILECONNECTIONSTRING`, `WEBSITE_CONTENTSHARE` | Flex has no content share. |
|
||||
| `WEBSITES_CONTAINER_START_TIME_LIMIT`, `WEBSITE_TIME_ZONE`, `TZ` | Not supported on Flex. |
|
||||
|
||||
> ⛔ **Inline `siteConfig.appSettings` on a Flex site are dropped for any of the keys above.** Put runtime in
|
||||
> `functionAppConfig.runtime`, scale/memory in `functionAppConfig.scaleAndConcurrency`, and only genuine
|
||||
> **app** settings (connection info, `APPLICATIONINSIGHTS_CONNECTION_STRING`, `AzureWebJobsStorage__accountName`)
|
||||
> in `siteConfig.appSettings`.
|
||||
|
||||
> ⛔ **Do NOT add `basicPublishingCredentialsPolicies` (`scm`/`ftp`) to a Flex function app.** That is an App
|
||||
> Service / Premium publishing concept. Flex deploys through its deployment storage container, so there is no
|
||||
> SCM basic-auth to enable or re-disable. Adding these does not harden anything and the deploy phase must not
|
||||
> toggle them for Flex (see [code-deployment-functions-flex.md](../../deploy/references/code-deployment-functions-flex.md)).
|
||||
|
||||
## Module Template
|
||||
|
||||
```bicep
|
||||
param location string
|
||||
param tags object
|
||||
param functionAppName string
|
||||
param planName string
|
||||
param storageAccountName string // host + deployment storage (Entra/MI — shared-key disabled)
|
||||
param deploymentContainerName string // blob container that holds the package, e.g. 'app-package'
|
||||
param appInsightsConnectionString string
|
||||
@allowed(['dotnet-isolated', 'node', 'python', 'java', 'powershell', 'go'])
|
||||
param runtimeName string
|
||||
param runtimeVersion string // e.g. '8.0' (dotnet-isolated), '20' (node), '3.11' (python)
|
||||
param instanceMemoryMB int = 2048 // 512 | 2048 | 4096
|
||||
param maximumInstanceCount int = 100
|
||||
|
||||
resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' existing = {
|
||||
name: storageAccountName
|
||||
}
|
||||
|
||||
// Flex Consumption plan — FC1 / FlexConsumption, Linux (reserved).
|
||||
resource flexPlan 'Microsoft.Web/serverfarms@2023-12-01' = {
|
||||
name: planName
|
||||
location: location
|
||||
tags: tags
|
||||
kind: 'functionapp'
|
||||
sku: {
|
||||
tier: 'FlexConsumption'
|
||||
name: 'FC1'
|
||||
}
|
||||
properties: {
|
||||
reserved: true
|
||||
}
|
||||
}
|
||||
|
||||
resource functionApp 'Microsoft.Web/sites@2023-12-01' = {
|
||||
name: functionAppName
|
||||
location: location
|
||||
tags: tags
|
||||
kind: 'functionapp,linux'
|
||||
identity: {
|
||||
type: 'SystemAssigned'
|
||||
}
|
||||
properties: {
|
||||
serverFarmId: flexPlan.id
|
||||
httpsOnly: true
|
||||
siteConfig: {
|
||||
minTlsVersion: '1.2'
|
||||
ftpsState: 'Disabled'
|
||||
// Only when a browser frontend on a DIFFERENT hostname calls this API. Omit the whole `cors`
|
||||
// block when there is no separate frontend — `staticWebApp` must be a real symbolic name in
|
||||
// THIS file, or use the `allowedOrigin` param form for a cross-module frontend.
|
||||
// See bicep-patterns.md § Cross-Origin (CORS).
|
||||
cors: {
|
||||
allowedOrigins: [ 'https://${staticWebApp.properties.defaultHostname}' ]
|
||||
supportCredentials: false
|
||||
}
|
||||
// ⛔ ONLY genuine app settings here — NEVER the deprecated keys listed above.
|
||||
appSettings: [
|
||||
// Identity-based host storage (shared-key access is disabled on the account).
|
||||
{ name: 'AzureWebJobsStorage__accountName', value: storage.name }
|
||||
{ name: 'APPLICATIONINSIGHTS_CONNECTION_STRING', value: appInsightsConnectionString }
|
||||
]
|
||||
}
|
||||
functionAppConfig: {
|
||||
deployment: {
|
||||
storage: {
|
||||
type: 'blobContainer'
|
||||
value: '${storage.properties.primaryEndpoints.blob}${deploymentContainerName}'
|
||||
authentication: {
|
||||
// Managed identity — REQUIRED here because storage shared-key access is disabled.
|
||||
type: 'SystemAssignedIdentity'
|
||||
}
|
||||
}
|
||||
}
|
||||
scaleAndConcurrency: {
|
||||
instanceMemoryMB: instanceMemoryMB
|
||||
maximumInstanceCount: maximumInstanceCount
|
||||
}
|
||||
runtime: {
|
||||
name: runtimeName
|
||||
version: runtimeVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output functionAppId string = functionApp.id
|
||||
output functionAppName string = functionApp.name
|
||||
output principalId string = functionApp.identity.principalId
|
||||
```
|
||||
|
||||
## Required companions (emit these too)
|
||||
|
||||
- **Deployment blob container.** The container named by `deploymentContainerName` MUST exist on the storage
|
||||
account (a `Microsoft.Storage/storageAccounts/blobServices/containers` resource). Flex writes the package
|
||||
there on deploy and mounts it on startup.
|
||||
- **Storage role for the app identity.** Because shared-key access is disabled, grant the function app's
|
||||
managed identity **Storage Blob Data Owner** (or Contributor) on the storage account via a deterministic
|
||||
`Microsoft.Authorization/roleAssignments` — see [rbac-roles.md](rbac-roles.md). Without it the app cannot
|
||||
read its own deployment package and fails to start.
|
||||
- **Application Insights** — same as other compute targets.
|
||||
|
||||
> ⛔ **Region gating.** Flex Consumption is not available in every region. The plan must target a
|
||||
> Flex-supported region (`az functionapp list-flexconsumption-locations`). This is validated in
|
||||
> [sku-quota-validation.md](../../prepare/references/sku-quota-validation.md); if the requested region has no
|
||||
> Flex, surface the region fallback there — do not silently relocate.
|
||||
|
||||
## Self-review (Flex)
|
||||
|
||||
- `functionAppConfig.runtime` present; **no** `FUNCTIONS_WORKER_RUNTIME` anywhere → else `FLAGGED`.
|
||||
- **No** `SCM_DO_BUILD_DURING_DEPLOYMENT` / `ENABLE_ORYX_BUILD` / `WEBSITE_RUN_FROM_PACKAGE` → else `FLAGGED`.
|
||||
- **No** `basicPublishingCredentialsPolicies` on the Flex site → else `FLAGGED`.
|
||||
- `deployment.storage.authentication.type` is an identity type (not a connection string) when shared-key is
|
||||
disabled, and the app identity has a Storage Blob Data role on the account → else `FLAGGED`.
|
||||
- If the plan includes a browser frontend on a different hostname, `siteConfig.cors.allowedOrigins` contains
|
||||
that frontend's origin → else `FLAGGED`. A missing `cors` block deploys cleanly and then fails every
|
||||
browser call with `No 'Access-Control-Allow-Origin' header is present`. See
|
||||
[bicep-patterns.md § Cross-Origin (CORS)](bicep-patterns.md).
|
||||
@@ -0,0 +1,193 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Bicep Patterns — Data Service Modules
|
||||
|
||||
Bicep module templates for database and cache services. Read when the prepare plan includes PostgreSQL, MySQL, or Redis.
|
||||
|
||||
For core patterns (file structure, skeleton, naming, tagging), see [bicep-patterns.md](bicep-patterns.md). For security defaults, see [bicep-patterns-security.md](bicep-patterns-security.md).
|
||||
|
||||
## PostgreSQL Flexible Server Module — Entra-Only
|
||||
|
||||
> ⛔ **Password authentication is disabled.** No `administratorLogin` / `administratorLoginPassword`. The deploying principal is set as the Entra admin so migrations/seeding run token-based; the app's managed identity is granted a DB role as a post-deploy data-plane step (see [database-post-deploy.md](../../deploy/references/database-post-deploy.md)).
|
||||
|
||||
```bicep
|
||||
param pgName string
|
||||
param location string
|
||||
param tags object
|
||||
|
||||
// Entra admin = the deploying principal (so migrations can run token-based). No password params.
|
||||
param entraAdminObjectId string
|
||||
param entraAdminName string
|
||||
@allowed(['User', 'Group', 'ServicePrincipal'])
|
||||
param entraAdminType string = 'User'
|
||||
|
||||
param allowedExtensions string = 'uuid-ossp,pgcrypto,pg_trgm'
|
||||
|
||||
resource pg 'Microsoft.DBforPostgreSQL/flexibleServers@2024-08-01' = {
|
||||
name: pgName
|
||||
location: location
|
||||
tags: tags
|
||||
sku: { name: 'Standard_B1ms', tier: 'Burstable' }
|
||||
properties: {
|
||||
version: '16' // ⛔ use prepare-plan.json.services[].version (capabilities-verified) — do not guess
|
||||
// ⛔ Entra-only: no password auth, no administratorLogin/Password.
|
||||
authConfig: {
|
||||
activeDirectoryAuth: 'Enabled'
|
||||
passwordAuth: 'Disabled'
|
||||
tenantId: subscription().tenantId
|
||||
}
|
||||
storage: { storageSizeGB: 32 }
|
||||
}
|
||||
}
|
||||
|
||||
// Entra administrator (deploying principal) — required for token-based admin + migrations.
|
||||
resource pgAdmin 'Microsoft.DBforPostgreSQL/flexibleServers/administrators@2024-08-01' = {
|
||||
parent: pg
|
||||
name: entraAdminObjectId
|
||||
properties: {
|
||||
principalType: entraAdminType
|
||||
principalName: entraAdminName
|
||||
tenantId: subscription().tenantId
|
||||
}
|
||||
}
|
||||
|
||||
// 0.0.0.0 = all Azure services (intentional) — broad access consented at the Scaffold Gate.
|
||||
resource pgFirewall 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2024-08-01' = {
|
||||
parent: pg
|
||||
name: 'AllowAllAzureServicesAndResourcesWithinAzureIps'
|
||||
properties: { startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
|
||||
}
|
||||
|
||||
// Allow PG extensions (uuid-ossp, pgcrypto, pg_trgm)
|
||||
resource pgExtensions 'Microsoft.DBforPostgreSQL/flexibleServers/configurations@2024-08-01' = {
|
||||
parent: pg
|
||||
name: 'azure.extensions'
|
||||
properties: { value: allowedExtensions, source: 'user-override' }
|
||||
}
|
||||
```
|
||||
|
||||
Wire connection **parameters** (host, db name, MI username, `sslmode=require`) as plain app settings — NOT a Key Vault secret. The driver fetches an Entra access token at runtime as the password. The app's managed identity is granted a DB role post-deploy via `pgaadauth_create_principal` (see [database-post-deploy.md](../../deploy/references/database-post-deploy.md) § Grant the app managed identity a DB role).
|
||||
|
||||
## MySQL Flexible Server Module — Entra-Only
|
||||
|
||||
> ⛔ **Password authentication is disabled.** No `administratorLogin` / `administratorLoginPassword`. The deploying principal is the Entra admin; the app's MI is mapped as an AAD login post-deploy.
|
||||
|
||||
> ⛔ **MySQL Entra admin REQUIRES a user-assigned managed identity (UAMI).** Unlike PostgreSQL, `Microsoft.DBforMySQL/flexibleServers/administrators` will not deploy without a UAMI: the `identityResourceId` property is mandatory (the portal and `az mysql flexible-server ad-admin create --identity` both require it), and the parent server must carry that UAMI in its `identity` block. `identityResourceId: null` fails to deploy. The UAMI is what the server uses to read Microsoft Entra — grant it the **Directory Readers** role (or `User.Read.All` / `GroupMember.Read.All` / `Application.Read.All` app permissions) in Entra ID; emit a `postDeployRecommendation` for that grant since it cannot be assigned from ARM.
|
||||
|
||||
```bicep
|
||||
param mysqlName string
|
||||
param location string
|
||||
param tags object
|
||||
param appDbName string
|
||||
|
||||
// Entra admin = the deploying principal. No password params.
|
||||
param entraAdminObjectId string
|
||||
param entraAdminName string
|
||||
|
||||
// UAMI required for MySQL Entra auth — the server uses it to read Entra.
|
||||
resource mysqlAadIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
|
||||
name: '${mysqlName}-aad-id'
|
||||
location: location
|
||||
tags: tags
|
||||
}
|
||||
|
||||
resource mysql 'Microsoft.DBforMySQL/flexibleServers@2023-12-30' = {
|
||||
name: mysqlName
|
||||
location: location
|
||||
tags: tags
|
||||
sku: { name: 'Standard_B1ms', tier: 'Burstable' }
|
||||
identity: {
|
||||
type: 'UserAssigned'
|
||||
userAssignedIdentities: {
|
||||
'${mysqlAadIdentity.id}': {}
|
||||
}
|
||||
}
|
||||
properties: {
|
||||
version: '8.0.21' // ⛔ use prepare-plan.json.services[].version (capabilities-verified) — major-only '8.0' is rejected by ARM
|
||||
storage: { storageSizeGB: 32 }
|
||||
}
|
||||
}
|
||||
|
||||
// Entra administrator (deploying principal) — enables token-based admin + migrations.
|
||||
resource mysqlAdmin 'Microsoft.DBforMySQL/flexibleServers/administrators@2023-12-30' = {
|
||||
parent: mysql
|
||||
name: 'ActiveDirectory'
|
||||
properties: {
|
||||
administratorType: 'ActiveDirectory'
|
||||
login: entraAdminName
|
||||
sid: entraAdminObjectId
|
||||
tenantId: subscription().tenantId
|
||||
identityResourceId: mysqlAadIdentity.id // ⛔ MANDATORY — MySQL AAD admin will not deploy without a UAMI
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce Entra-only auth (disable native password auth)
|
||||
resource mysqlAadOnly 'Microsoft.DBforMySQL/flexibleServers/configurations@2023-12-30' = {
|
||||
parent: mysql
|
||||
name: 'aad_auth_only'
|
||||
properties: { value: 'ON', source: 'user-override' }
|
||||
dependsOn: [ mysqlAdmin ]
|
||||
}
|
||||
|
||||
// 0.0.0.0 = all Azure services (intentional) — broad access consented at the Scaffold Gate.
|
||||
resource mysqlFirewall 'Microsoft.DBforMySQL/flexibleServers/firewallRules@2023-12-30' = {
|
||||
parent: mysql
|
||||
name: 'AllowAllAzureServicesAndResourcesWithinAzureIps'
|
||||
properties: { startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
|
||||
}
|
||||
|
||||
// Enforce TLS
|
||||
resource mysqlTls 'Microsoft.DBforMySQL/flexibleServers/configurations@2023-12-30' = {
|
||||
parent: mysql
|
||||
name: 'require_secure_transport'
|
||||
properties: { value: 'ON', source: 'user-override' }
|
||||
}
|
||||
|
||||
// App database from compose (e.g. MYSQLDB_DATABASE) — emit so it exists before first boot. Omit if only the default DB is used.
|
||||
resource mysqlDb 'Microsoft.DBforMySQL/flexibleServers/databases@2023-12-30' = {
|
||||
parent: mysql
|
||||
name: appDbName
|
||||
}
|
||||
```
|
||||
|
||||
Wire connection **parameters** as plain app settings (host, db name, MI username, SSL required) — NOT a Key Vault secret. The driver fetches an Entra token at runtime. The app's MI is mapped as an AAD login post-deploy (see [database-post-deploy.md](../../deploy/references/database-post-deploy.md)).
|
||||
|
||||
## Redis Cache Module (Entra-Only)
|
||||
|
||||
> ⛔ **Access-key authentication is disabled** (`disableAccessKeyAuthentication: true`). The app's managed identity is granted data access via an access-policy assignment. Do NOT store a Redis access key in Key Vault. If the app's Redis client library cannot present an Entra token, surface it as a `FLAGGED` finding rather than re-enabling keys.
|
||||
|
||||
```bicep
|
||||
param redisName string
|
||||
param location string
|
||||
param tags object
|
||||
param appPrincipalId string // the app compute MI objectId
|
||||
|
||||
resource redis 'Microsoft.Cache/redis@2024-03-01' = {
|
||||
name: redisName
|
||||
location: location
|
||||
tags: tags
|
||||
properties: {
|
||||
sku: { name: 'Basic', family: 'C', capacity: 0 }
|
||||
enableNonSslPort: false
|
||||
minimumTlsVersion: '1.2'
|
||||
disableAccessKeyAuthentication: true // ⛔ Entra-only
|
||||
redisConfiguration: {
|
||||
'aad-enabled': 'true'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Data-plane access for the app MI (Entra) — no keys.
|
||||
resource redisAccess 'Microsoft.Cache/redis/accessPolicyAssignments@2024-03-01' = {
|
||||
parent: redis
|
||||
name: guid(redis.id, appPrincipalId, 'Data Owner')
|
||||
properties: {
|
||||
accessPolicyName: 'Data Owner'
|
||||
objectId: appPrincipalId
|
||||
objectIdAlias: appPrincipalId
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Wire `redis.properties.hostName` (+ MI object id as the username) as plain app settings. The client obtains an Entra token at runtime — no key in Key Vault.
|
||||
@@ -0,0 +1,187 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Bicep Patterns — Security Defaults
|
||||
|
||||
Mandatory security configuration for all AppOnboard-generated Bicep. Read during IaC generation before writing resource definitions. Apply during scaffold — never defer to deploy.
|
||||
|
||||
For core patterns (file structure, skeleton, naming, tagging), see [bicep-patterns.md](bicep-patterns.md). For data module templates (PostgreSQL, Redis), see [subagent-iac-gen.md](subagent-iac-gen.md) Step 6.
|
||||
|
||||
## No Key Vault — App-Internal Secret Storage
|
||||
|
||||
> ⛔ **Do NOT create a Key Vault.** AppOnboard-generated IaC MUST NOT emit `Microsoft.KeyVault/vaults` (or any KV secret / KV RBAC resource). Azure resource auth (database, cache, storage, Cosmos, queues) is managed-identity + token — there is nothing to store. App-internal secrets that are NOT an Azure resource credential (e.g. `SECRET_KEY`, JWT signing key, third-party API keys) are stored **directly on the compute resource**, never in Key Vault:
|
||||
|
||||
- **App Service / Functions:** pass each secret as an `@secure()` Bicep param → `siteConfig.appSettings`. The platform encrypts app settings at rest. The value is generated at deploy time and passed via CLI (never committed); it does not appear in ARM deployment history because it's `@secure()`.
|
||||
- **Container Apps:** pass each secret as an `@secure()` param → the Container App's **native** `secrets: [{ name, value }]` array → referenced by `secretRef`. This is the Container Apps secret store, NOT Key Vault (`keyVaultUrl` is never used).
|
||||
|
||||
> ⛔ **No `@Microsoft.KeyVault(...)` references, no `keyVaultUrl` secretRefs, no KV role assignments** (`Key Vault Secrets Officer`/`Secrets User`), no `deployerObjectId` KV param.
|
||||
|
||||
## Security Defaults
|
||||
|
||||
> **Source:** Adapted from Azure security best practices. See [Azure security baseline](https://learn.microsoft.com/en-us/security/benchmark/azure/overview) for updates.
|
||||
|
||||
### Identity — Managed Identity Everywhere (MANDATORY, NO EXCEPTIONS)
|
||||
|
||||
> ⛔ **Every compute resource MUST have a managed identity. There is NO SKU, tier, or service combination that is exempt.** Managed identity is the ONLY sanctioned way for app code to authenticate to any Azure resource. Shared keys, admin logins, and connection-string passwords are BLOCKED (see [`bicep-patterns-data.md`](bicep-patterns-data.md) and the conformance gate).
|
||||
>
|
||||
> | Condition | Include MI? |
|
||||
> |-----------|-------------|
|
||||
> | Any compute (App Service, Container Apps, Functions) | **YES — always** |
|
||||
> | Any database, storage, queue, cache, Cosmos, or ACR access | **YES — always** |
|
||||
> | None of the above | **YES** (default secure) |
|
||||
|
||||
- **System-assigned managed identity** for all services (default). User-assigned only when shared identity is explicitly needed.
|
||||
- ⛔ **Compute floor is B1 (App Service) / Standard (Static Web Apps).** F1/D1/Free tiers are NOT offered — the free-tier MI sidecar OOMs and cannot host managed identity, and free SWA lacks the config (CORS, custom auth) these apps need. SKU selection MUST NOT emit F1, D1, or Free. See [sku-matrix.md](../../prepare/references/sku-matrix.md).
|
||||
- ⛔ **Never generate `administratorLogin`, `administratorLoginPassword`, access keys, or connection-string passwords** for ANY data service (SQL, PostgreSQL, MySQL, Redis, Storage, Cosmos DB, Service Bus, Event Hubs) — including inside conditional branches. Use Entra/MI-only auth (see the data-service patterns below and in [`bicep-patterns-data.md`](bicep-patterns-data.md)).
|
||||
- App-to-service auth: managed identity + RBAC role assignments (or data-plane role assignments for Cosmos/Redis). Zero shared-key or password auth to any Azure resource.
|
||||
- **App-internal secrets** that are NOT an Azure resource credential (e.g. Django `SECRET_KEY`, JWT signing key, third-party API keys) are stored **directly on the compute resource** — App Service app settings or Container Apps native secrets, from an `@secure()` param generated at deploy. ⛔ **No Key Vault** (see § No Key Vault — App-Internal Secret Storage).
|
||||
|
||||
```bicep
|
||||
identity: {
|
||||
type: 'SystemAssigned'
|
||||
}
|
||||
```
|
||||
|
||||
### Data Services — Entra / Managed-Identity-Only Authentication
|
||||
|
||||
> ⛔ **Local/shared-key/password authentication MUST be disabled on every data service.** The app authenticates with its managed identity; the deploying principal is granted an Entra admin/data role so migrations and seeding can run token-based. Full module templates (auth config, Entra admin child resources, MI data-plane roles) live in [`bicep-patterns-data.md`](bicep-patterns-data.md).
|
||||
|
||||
| Service | How local auth is disabled | App/MI access |
|
||||
|---------|----------------------------|---------------|
|
||||
| Azure SQL | `azureADOnlyAuthentication: true` (pattern below) | Entra admin + `CREATE USER [<mi>] FROM EXTERNAL PROVIDER` |
|
||||
| PostgreSQL Flexible | `authConfig: { passwordAuth: 'Disabled', activeDirectoryAuth: 'Enabled' }` | Entra admin child resource + MI role via `pgaadauth_create_principal` |
|
||||
| MySQL Flexible | Entra admin child resource, `aad_auth_only` config | MI mapped as an AAD login |
|
||||
| Redis | `disableAccessKeyAuthentication: true` + `accessPolicyAssignments` | Data Owner/Contributor access policy to the MI |
|
||||
| Storage | `allowSharedKeyAccess: false` | Storage Blob/Queue Data role to the MI |
|
||||
| Cosmos DB | `disableLocalAuth: true` | `sqlRoleAssignments` (data plane) — see [rbac-roles.md](rbac-roles.md) |
|
||||
| Service Bus / Event Hubs | `disableLocalAuth: true` | Data Sender/Receiver role to the MI |
|
||||
|
||||
### SQL Server — Entra-Only Authentication
|
||||
|
||||
```bicep
|
||||
param principalId string
|
||||
param principalName string
|
||||
@allowed(['User', 'Group', 'Application'])
|
||||
param principalType string = 'User'
|
||||
|
||||
// Preview API required — azureADOnlyAuthentication via administrators block
|
||||
// is not available in GA API versions (GA path uses a separate child resource).
|
||||
resource sqlServer 'Microsoft.Sql/servers@2024-05-01-preview' = {
|
||||
name: '${resourcePrefix}-sql-${uniqueHash}'
|
||||
location: location
|
||||
properties: {
|
||||
administrators: {
|
||||
administratorType: 'ActiveDirectory'
|
||||
principalType: principalType
|
||||
login: principalName
|
||||
sid: principalId
|
||||
tenantId: subscription().tenantId
|
||||
azureADOnlyAuthentication: true
|
||||
}
|
||||
minimalTlsVersion: '1.2'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ If deploying from CI/CD with a service principal, set `principalType` to `'Application'`. The default `'User'` only works for interactive deployments.
|
||||
|
||||
### Secrets — App-Internal, Stored On-Compute (No Key Vault)
|
||||
|
||||
App-internal secrets (e.g. `SECRET_KEY`, `JWT_SECRET`, `API_KEY`, session secrets) are stored directly on the compute resource. ⛔ **No Key Vault**, no `@Microsoft.KeyVault(...)`, no `keyVaultUrl`.
|
||||
|
||||
> ⛔ **No plaintext/committed secrets.** Values MUST NOT be hardcoded in Bicep or committed to `main.parameters.json` — pass them as `@secure()` params generated at deploy time. Never use `uniqueString()` for secrets (deterministic/predictable). `@secure()` params do NOT appear in ARM deployment history.
|
||||
>
|
||||
> ❌ **NEVER:** `{ name: 'SECRET_KEY', value: 'hard-to-guess-string' }` or `value: 'change-me'`; any `@secure()` password param for a database/cache/storage account (those use managed identity — see § Data Services).
|
||||
|
||||
```bicep
|
||||
// App Service / Functions
|
||||
@secure()
|
||||
param secretKey string // generated at deploy time, passed via CLI --parameters, never committed
|
||||
|
||||
appSettings: [
|
||||
// Database via managed identity — plain connection params, NO password:
|
||||
{ name: 'PGHOST', value: '${pgName}.postgres.database.azure.com' }
|
||||
{ name: 'PGDATABASE', value: appDbName }
|
||||
{ name: 'PGUSER', value: appServiceName } // the MI's DB principal name
|
||||
{ name: 'PGSSLMODE', value: 'require' }
|
||||
// App-internal secret stored directly as an app setting (platform-encrypted at rest):
|
||||
{ name: 'SECRET_KEY', value: secretKey }
|
||||
]
|
||||
```
|
||||
|
||||
For Container Apps, put the secret in the Container App's **native** `secrets: [{ name, value: secretKey }]` array (value from an `@secure()` param) and reference it via `secretRef` — see [bicep-container-apps.md](bicep-container-apps.md). ⛔ **Never** emit a `Microsoft.KeyVault/vaults` resource.
|
||||
|
||||
### App Settings Are Non-Secret Endpoint Configuration Only
|
||||
|
||||
> ⛔ **App settings / container `env` MUST NOT contain any secret or connection string.** With managed identity the app authenticates by token — there is nothing secret to configure. App settings carry ONLY **non-secret endpoint configuration** that tells the app *where* and *as which identity* to connect:
|
||||
> - **Endpoint / host** — e.g. `PGHOST=myserver.postgres.database.azure.com`, `AZURE_STORAGE_BLOB_ENDPOINT=https://myacct.blob.core.windows.net`
|
||||
> - **Resource name** — e.g. database name, container/queue name
|
||||
> - **Identity principal** — the MI's DB principal name as the connection `user` (Postgres/MySQL AAD auth)
|
||||
> - **`AZURE_CLIENT_ID`** — ONLY for a user-assigned MI, so `DefaultAzureCredential` selects the right identity (omit for system-assigned)
|
||||
> - **Non-secret app flags** — e.g. `NODE_ENV`, `PGSSLMODE=require`, CORS origins
|
||||
>
|
||||
> ❌ **NEVER put in app settings / `env`:** a full **connection string** (`DATABASE_URL=postgres://user:pass@host/db`, `AccountKey=...`, `SharedAccessKey=...`), a **password**, an **access key**, a **SAS token**, or any other credential. If a value would authenticate you, it does not belong here — use managed identity instead. The ONLY secret-like value permitted anywhere is a genuinely app-internal secret (e.g. `SECRET_KEY`) that is NOT an Azure resource credential, and even that is passed as an `@secure()` param, never as a plain app-setting literal.
|
||||
|
||||
### Transport — HTTPS Only
|
||||
|
||||
All web-facing resources:
|
||||
|
||||
```bicep
|
||||
// App Service
|
||||
httpsOnly: true
|
||||
siteConfig: {
|
||||
minTlsVersion: '1.2'
|
||||
}
|
||||
|
||||
// Storage
|
||||
supportsHttpsTrafficOnly: true
|
||||
allowBlobPublicAccess: false
|
||||
minimumTlsVersion: 'TLS1_2'
|
||||
```
|
||||
|
||||
### App Service / Premium Functions — Publishing Credential Lockdown
|
||||
|
||||
> ⛔ **Every App Service and every Premium (EP1) Functions app MUST include both `basicPublishingCredentialsPolicies` child resources.** Missing these means deploy cannot toggle SCM auth post-deployment — the REST API call targets a resource that doesn't exist in ARM.
|
||||
>
|
||||
> ⛔ **Exception — Functions on Flex Consumption (the default Functions floor): do NOT add these policies.** Flex has no SCM basic-auth publishing model; it deploys through its deployment storage container. Adding `scm`/`ftp` `basicPublishingCredentialsPolicies` to a Flex site is wrong, and the deploy phase must not toggle them. See [bicep-functions-flex.md](bicep-functions-flex.md).
|
||||
|
||||
```bicep
|
||||
// SCM — allow: true for deploy phase (deploy re-disables via REST API after code upload)
|
||||
resource scmAuth 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2023-12-01' = {
|
||||
parent: appService
|
||||
name: 'scm'
|
||||
properties: {
|
||||
allow: true
|
||||
}
|
||||
}
|
||||
|
||||
// FTP — always disabled
|
||||
resource ftpAuth 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2023-12-01' = {
|
||||
parent: appService
|
||||
name: 'ftp'
|
||||
properties: {
|
||||
allow: false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Deploy lifecycle:** Scaffold sets `scm.allow: true` so `az webapp deploy` works. After code upload + health check, deploy phase runs `az rest --method put .../basicPublishingCredentialsPolicies/scm` with `allow: false` to re-harden. If scaffold omits these resources, deploy's Step 7 SCM re-disable REST API call fails silently.
|
||||
|
||||
### Cosmos DB — Data Plane RBAC
|
||||
|
||||
⛔ Cosmos DB uses its own role system — see [rbac-roles.md](rbac-roles.md) § Cosmos DB for role IDs and behavioral rules. Do NOT use `Microsoft.Authorization/roleAssignments` for Cosmos data access.
|
||||
|
||||
### RBAC — Deterministic Role Assignments
|
||||
|
||||
For the common roles GUID table, see [rbac-roles.md](rbac-roles.md).
|
||||
|
||||
```bicep
|
||||
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
|
||||
name: guid(scopeResourceId, principalId, roleDefinitionId)
|
||||
scope: targetResource
|
||||
properties: {
|
||||
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
|
||||
principalId: managedIdentity.properties.principalId
|
||||
principalType: 'ServicePrincipal' // REQUIRED — prevents AAD graph lookup delays
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Bicep Patterns
|
||||
|
||||
Bicep default-path patterns for AppOnboard scaffold. Used as the primary IaC format. For the alternative Terraform path (existing `.tf` files or user override), scaffold uses `mcp_azure_mcp_azureterraformbestpractices` output patterns.
|
||||
|
||||
> **Source:** Adapted from Azure Bicep best practices. See [Bicep best practices](https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/best-practices) for updates.
|
||||
|
||||
## File Structure
|
||||
|
||||
> ⛔ **Always use `targetScope = 'subscription'`.** Subscription-scope Bicep creates the resource group in IaC with all 5 AppOnboard tags (including `created-at`). Resource-group scope requires `az group create` via CLI, which consistently misses `created-at` because CLI-created resource groups don't receive IaC-managed tags. There are zero benefits to resource-group scope for AppOnboard.
|
||||
|
||||
```
|
||||
infra/
|
||||
├── main.bicep # Entry point (subscription scope)
|
||||
├── main.parameters.json # ARM JSON parameter values (NOT .bicepparam)
|
||||
└── modules/
|
||||
├── container-app.bicep
|
||||
├── app-service.bicep
|
||||
├── sql-database.bicep
|
||||
├── key-vault.bicep
|
||||
├── log-analytics.bicep
|
||||
└── ...
|
||||
```
|
||||
|
||||
Each service gets its own module. `main.bicep` orchestrates resource group creation + module calls.
|
||||
|
||||
## main.bicep Skeleton
|
||||
|
||||
```bicep
|
||||
targetScope = 'subscription'
|
||||
|
||||
@minLength(1)
|
||||
@maxLength(64)
|
||||
param environmentName string
|
||||
|
||||
@minLength(1)
|
||||
param location string
|
||||
|
||||
param sessionId string
|
||||
|
||||
param deployedBy string // resolved via: az ad signed-in-user show --query displayName -o tsv
|
||||
|
||||
// ⛔ createdAt: passed in parameters.json, NOT utcNow() default (crashes Portal blade)
|
||||
param createdAt string
|
||||
|
||||
var tags = {
|
||||
'app-onboard-skill': 'true'
|
||||
'app-onboard-session-id': sessionId
|
||||
'created-at': createdAt
|
||||
environment: environmentName
|
||||
'deployed-by': deployedBy
|
||||
}
|
||||
|
||||
resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = {
|
||||
name: 'rg-${environmentName}'
|
||||
location: location
|
||||
tags: tags
|
||||
}
|
||||
|
||||
// ⛔ scope: rg (symbolic) — creates implicit dependsOn. Do NOT use resourceGroup(name) — it races against RG creation.
|
||||
module resources './modules/resources.bicep' = {
|
||||
name: 'resources'
|
||||
scope: rg
|
||||
params: {
|
||||
location: location
|
||||
environmentName: environmentName
|
||||
tags: tags
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## main.parameters.json
|
||||
|
||||
> ⛔ **ARM JSON only.** Do NOT use `.bicepparam` syntax (`using`, `param`, `readEnvironmentVariable()`). AppOnboard deploys via `az deployment sub create` (subscription-scope default) — not `azd` — and `.bicepparam` requires azd or newer tooling. If the user lacks subscription-level permissions, the deploy phase falls back to `az deployment group create` automatically.
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"environmentName": { "value": "{project}-{env}" },
|
||||
"location": { "value": "{region}" },
|
||||
"sessionId": { "value": "{context.json.sessionId}" },
|
||||
"deployedBy": { "value": "{context.json.azure.userDisplayName}" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Naming Convention (Bicep)
|
||||
|
||||
The prepare phase generates a logical resource prefix in `prepare-plan.json.naming.resourcePrefix` (e.g., `myapp-dev`). Scaffold MUST add a globally unique suffix using Bicep's `uniqueString()` function to prevent cross-deployment name collisions on globally unique Azure resources (App Service, Storage Account, ACR).
|
||||
|
||||
```bicep
|
||||
// main.bicep — derive unique suffix from resource group
|
||||
var nameSuffix = uniqueString(resourceGroup().id)
|
||||
|
||||
// Pass unique names to modules
|
||||
param appName string = 'app-${resourcePrefix}-${take(nameSuffix, 4)}'
|
||||
param storName string = 'st${replace(resourcePrefix, '-', '')}${take(nameSuffix, 4)}'
|
||||
param acrName string = 'cr${replace(resourcePrefix, '-', '')}${take(nameSuffix, 4)}'
|
||||
```
|
||||
|
||||
> ⛔ **Do NOT use `uniqueString()` for secrets** — it is deterministic and predictable. See [bicep-patterns-security.md](bicep-patterns-security.md) § Secrets for correct secret patterns.
|
||||
|
||||
If `prepare-plan.json.naming.resources[]` provides pre-computed names with suffixes, prefer those — but ALWAYS ensure globally unique resources include a `uniqueString()` or equivalent hash in main.bicep as a safety net.
|
||||
|
||||
## Log Analytics Module Output
|
||||
|
||||
> ⛔ **Output the resource ID (`.id`), NOT `.properties.customerId`.** Container Apps Environment requires `workspaceResourceId` (the full ARM resource ID). `.properties.customerId` is the GUID used for queries — passing it as `workspaceResourceId` causes an ARM deploy failure (`BadRequest`). Separate the two outputs:
|
||||
> ```bicep
|
||||
> output workspaceId string = logAnalyticsWorkspace.id // ARM resource ID — for CAE, App Insights
|
||||
> output workspaceCustomerId string = logAnalyticsWorkspace.properties.customerId // GUID — for Log Analytics queries only
|
||||
> ```
|
||||
|
||||
## Compute-Target Patterns
|
||||
|
||||
Read the file(s) matching the service mapping — load only what's needed:
|
||||
- **Functions on Flex Consumption** (the default Functions floor): [bicep-functions-flex.md](bicep-functions-flex.md) — `functionAppConfig`, MI-based deployment storage, forbidden App Service settings. ⛔ Do NOT use the App Service module for a Flex function app.
|
||||
- **App Service (and Functions on Premium/EP1):** [bicep-app-service.md](bicep-app-service.md) — module template, SCM/FTP auth, native module deploy strategy
|
||||
- **Container Apps:** [bicep-container-apps.md](bicep-container-apps.md) — two-phase ACR wiring, ingress, secretRef, image parameter, multi-container DNS
|
||||
- **Static Web Apps:** [bicep-swa.md](bicep-swa.md) — module template, detached deploy rule
|
||||
|
||||
Load multiple only if the plan includes multiple compute targets.
|
||||
|
||||
> ⛔ **F1/D1 SKU: do NOT generate a Dockerfile.** If `prepare-plan.json` specifies F1 or D1 (free/shared tier), use the platform's built-in runtime stack (e.g., `NODE|20-lts` for Node.js, `PYTHON|3.12` for Python). Dockerfiles are for B1+ or Container Apps only.
|
||||
|
||||
> ⛔ **Native module deploy strategy.** If `prepare-plan.json.deployStrategy` exists, read [bicep-app-service.md § Native Module Deploy Strategy](bicep-app-service.md) and apply the startup command + app settings. `deployStrategy.startupCommand` → `appCommandLine`, `deployStrategy.requiredAppSettings` → `appSettings[]`. When no `deployStrategy` exists, do NOT set `appCommandLine`.
|
||||
|
||||
## Cross-Origin (CORS) Between Frontend and Backend
|
||||
|
||||
> ⛔ **If the frontend is served from a different hostname than the API, the API MUST declare the frontend
|
||||
> origin.** A Static Web App calling a Function App / App Service is *always* cross-origin — different
|
||||
> hostname means different origin, so the browser sends a preflight `OPTIONS` and blocks the response
|
||||
> unless the API returns `Access-Control-Allow-Origin`. A backend deployed with no `cors` block fails
|
||||
> **every** browser call with:
|
||||
> `No 'Access-Control-Allow-Origin' header is present on the requested resource`.
|
||||
> The API works fine from `curl` and from the portal test console, so this surfaces only in the browser.
|
||||
|
||||
> ⛔ **Local success proves nothing here.** `local.settings.json` has `"Host": { "CORS": "*" }`, but that
|
||||
> file configures the **local Functions emulator only** — it is git-ignored and never deployed. F5 working
|
||||
> is not evidence that the deployed API allows the deployed frontend.
|
||||
|
||||
Emit `cors` on the backend's `siteConfig`, alongside the frontend resource in the same module:
|
||||
|
||||
```bicep
|
||||
resource functionApp 'Microsoft.Web/sites@2023-12-01' = {
|
||||
properties: {
|
||||
siteConfig: {
|
||||
cors: {
|
||||
// staticSites → `defaultHostname`; the value has no scheme, so add https://
|
||||
allowedOrigins: [ 'https://${staticWebApp.properties.defaultHostname}' ]
|
||||
supportCredentials: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When the frontend and backend are in **different modules**, pass the origin instead of referencing across.
|
||||
Guard with `empty()` so the same module still compiles for an API with no browser frontend:
|
||||
|
||||
```bicep
|
||||
// api module
|
||||
param allowedOrigin string = ''
|
||||
...
|
||||
siteConfig: {
|
||||
cors: {
|
||||
allowedOrigins: empty(allowedOrigin) ? [] : [ allowedOrigin ]
|
||||
supportCredentials: false
|
||||
}
|
||||
}
|
||||
|
||||
// frontend module
|
||||
output defaultHostname string = staticWebApp.properties.defaultHostname
|
||||
|
||||
// main.bicep
|
||||
module api './modules/api.bicep' = {
|
||||
params: { allowedOrigin: 'https://${frontend.outputs.defaultHostname}' }
|
||||
}
|
||||
```
|
||||
|
||||
> ⛔ **A symbolic reference only compiles inside the file that declares it.** `staticWebApp.properties...`
|
||||
> fails `bicep build` with **`BCP057: The name "staticWebApp" does not exist in the current context`**
|
||||
> whenever the SWA lives in another module — or when there is no frontend at all. Use the direct reference
|
||||
> only in the file that declares both; otherwise use the `allowedOrigin` param form above. For an API with
|
||||
> no browser frontend, leave `allowedOrigin` empty (or omit `cors` entirely).
|
||||
|
||||
> ⛔ **Property casing differs by resource type.** `Microsoft.Web/staticSites` exposes
|
||||
> `properties.defaultHostname` (lowercase **n**); `Microsoft.Web/sites` exposes `properties.defaultHostName`
|
||||
> (capital **N**). Using the wrong one fails at `bicep build` — verify against the resource you are actually
|
||||
> referencing when the frontend is an App Service rather than a SWA.
|
||||
|
||||
> ⛔ **`allowedOrigins: ['*']` cannot be combined with `supportCredentials: true`** — Azure rejects the pair.
|
||||
> Always emit the explicit origin; it is required anyway the moment cookie or bearer auth is added, and it
|
||||
> avoids re-work. Include any custom domain as an additional entry when one is configured.
|
||||
|
||||
**No dependency cycle.** The frontend receives the API URL at **build** time (`VITE_API_BASE_URL` /
|
||||
`NEXT_PUBLIC_API_URL`, baked into the bundle from `deploy-result.json.endpoints[]` — see
|
||||
[deploy-checklist-template.md](../../deploy/references/deploy-checklist-template.md)), so the frontend
|
||||
resource never references the backend in Bicep. The backend may therefore reference the frontend hostname
|
||||
freely. Do **not** add the API URL to the SWA resource to "pair" them — that is what creates a cycle.
|
||||
|
||||
**Alternative that removes CORS entirely:** linking an existing Function App as an SWA backend makes the
|
||||
frontend call `/api/*` on its own origin, so no preflight occurs. This is allowed only when
|
||||
`context.json.overrides[]` records explicit user approval for `linkFunctionsToStaticWebApp`. The Function
|
||||
App remains a separate resource and uses the Functions deployment channel. Never move or copy its source
|
||||
into an SWA API directory, emit SWA `buildProperties.apiLocation`, or change the backend's runtime or
|
||||
authentication provider to `azureStaticWebApps`.
|
||||
|
||||
**Self-review check:** If the plan has a browser frontend AND a separate HTTP API resource, verify the API's
|
||||
`siteConfig.cors.allowedOrigins` contains the frontend origin. **FLAGGED** if absent — the app will deploy
|
||||
successfully and then fail on first use.
|
||||
|
||||
## Service Tagging
|
||||
|
||||
> ⛔ **You MUST read [iac-generation-rules.md § Session Tags](iac-generation-rules.md).** All resources MUST include the 5 AppOnboard session tags. Pass `tags` object from `main.bicep` into every module.
|
||||
|
||||
## API Version Policy
|
||||
|
||||
Use the latest stable API version for each resource type. Never use preview APIs unless required for a feature with no GA alternative. Validate via `bicep build` — stale API versions produce warnings.
|
||||
|
||||
## App-Internal Secret Syntax (No Key Vault)
|
||||
|
||||
⛔ **No Key Vault is created.** App-internal secrets (e.g. `SECRET_KEY`) are stored on the compute resource from an `@secure()` param at deploy time. App Service → `siteConfig.appSettings`; Container Apps → native `secrets: [{ name, value }]` + `secretRef` (see [bicep-container-apps.md](bicep-container-apps.md)). Never use `@Microsoft.KeyVault(...)` / `keyVaultUrl`. See [bicep-patterns-security.md](bicep-patterns-security.md) § Secrets. For Terraform, see [terraform-patterns.md](terraform-patterns.md).
|
||||
@@ -0,0 +1,30 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Bicep — Static Web Apps Patterns
|
||||
|
||||
SWA-specific Bicep patterns. For shared patterns (skeleton, naming, tags, security defaults, data modules), see [bicep-patterns.md](bicep-patterns.md).
|
||||
|
||||
## Module Template
|
||||
|
||||
SWA modules for token-based deploys (no GitHub CI/CD):
|
||||
|
||||
```bicep
|
||||
resource staticWebApp 'Microsoft.Web/staticSites@2023-12-01' = {
|
||||
name: swaName
|
||||
location: location
|
||||
tags: tags
|
||||
sku: { name: 'Standard', tier: 'Standard' }
|
||||
properties: {} // ⛔ MUST be empty — no repositoryUrl, no branch, no buildProperties
|
||||
}
|
||||
```
|
||||
|
||||
> ⛔ **Standard SKU minimum — Free SWA is not offered.** Free Static Web Apps lack the configuration these apps need (custom authentication, configurable CORS, SLA, private endpoints, BYO API backends via managed identity). Always emit `sku: { name: 'Standard', tier: 'Standard' }`.
|
||||
|
||||
> ⛔ **The API must allow this SWA's origin.** A SWA calling a Function App or App Service is cross-origin, so
|
||||
> the **backend** needs `siteConfig.cors.allowedOrigins` containing `https://${staticWebApp.properties.defaultHostname}`.
|
||||
> Nothing on the SWA resource itself fixes this. Omitting it deploys cleanly and then fails every browser
|
||||
> call with `No 'Access-Control-Allow-Origin' header is present`. See
|
||||
> [bicep-patterns.md § Cross-Origin (CORS)](bicep-patterns.md).
|
||||
|
||||
> ⛔ **Detached SWA deploy:** Omit `repositoryUrl`, `branch`, and `buildProperties` entirely. These are only for GitHub Actions–connected deployments. Including `repositoryUrl: ''` causes `BadRequest: RepositoryUrl is invalid`.
|
||||
@@ -0,0 +1,8 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# CI/CD Pipeline Patterns
|
||||
|
||||
CI/CD is deferred to v2. Do NOT auto-generate workflow files.
|
||||
|
||||
If the user requests CI/CD guidance, call `mcp_azure_mcp_deploy` → `deploy_pipeline_guidance_get` with `is-azd-project: false`, `pipeline-platform: 'github-actions'`, `deploy-option: 'provision-and-deploy'` and present the guidance.
|
||||
@@ -0,0 +1,77 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Dockerfile Generation
|
||||
|
||||
Generate Dockerfiles for components targeting Container Apps (or B1+ App Service) that have no existing Dockerfile. Read this when `iac-generation-rules.md` Step 6b applies.
|
||||
|
||||
## When to Generate
|
||||
|
||||
- Component targets Container Apps and has NO Dockerfile (and no `Dockerfile.azure`)
|
||||
- Component targets App Service B1+ with `deployStrategy.containerized: true`
|
||||
- ⛔ Do NOT generate for F1/D1 SKUs — use platform runtime stack instead
|
||||
- ⛔ Do NOT overwrite an existing Dockerfile — create `Dockerfile.azure` only if BuildKit stripping is needed
|
||||
|
||||
## Principles
|
||||
|
||||
### Layer ordering
|
||||
|
||||
Copy dependency manifests and install BEFORE copying source (preserves layer cache):
|
||||
|
||||
```
|
||||
COPY {manifest files} ./
|
||||
RUN {install command}
|
||||
COPY . .
|
||||
```
|
||||
|
||||
### Base image selection
|
||||
|
||||
| Principle | Rule |
|
||||
|-----------|------|
|
||||
| Pin version | `node:{major}-slim`, NOT `node:latest` |
|
||||
| Slim variants | `-slim` or `-alpine` for smaller images |
|
||||
| Multi-stage | Go, .NET, Java, Rust: build in SDK image, copy binary to runtime |
|
||||
| Match runtime | Read `engines`, `python_requires`, `go.mod`, `<TargetFramework>` |
|
||||
|
||||
### Port alignment
|
||||
|
||||
`EXPOSE` port, app's listening port, and Container App `targetPort` must all match. Mismatch = silent health probe failure. Read app config for listening port, set `EXPOSE {port}`, add `ENV PORT={port}` if app reads `PORT` from env.
|
||||
|
||||
### Security defaults
|
||||
|
||||
- Non-root user — Debian/`-slim` base: `RUN groupadd -r app && useradd -r -g app app`; Alpine base: `RUN addgroup -S app && adduser -S app -G app`. Then `USER app`
|
||||
- Never `COPY .env` or secrets — use `.dockerignore`
|
||||
- Direct exec: `CMD ["node", "server.js"]` not `CMD ["npm", "start"]`
|
||||
|
||||
### .dockerignore
|
||||
|
||||
Always generate alongside Dockerfile. Exclude: `.git`, `node_modules`, `__pycache__`, `*.pyc`, `.env`, `.env.*`, `.azure`, `.copilot-azure`, `infra`, `*.md`.
|
||||
|
||||
### Common pitfalls
|
||||
|
||||
| Mistake | Fix |
|
||||
|---------|-----|
|
||||
| `COPY . .` before deps | Copy manifests first, install, then source |
|
||||
| `npm install` in prod | `npm ci --omit=dev` |
|
||||
| Wrong EXPOSE port | Read actual listening port from app source |
|
||||
|
||||
### Next.js multi-container build args
|
||||
|
||||
`NEXT_PUBLIC_*` env vars are embedded in the client JS bundle at `npm run build` time — runtime Container App env vars have zero effect on client-side code. For multi-container deploys where a Next.js frontend references another component's API:
|
||||
|
||||
1. Dockerfile MUST include `ARG NEXT_PUBLIC_API_URL` before the `RUN npm run build` step
|
||||
2. Deploy phase passes `--build-arg NEXT_PUBLIC_API_URL=https://{api-fqdn}` to `az acr build`
|
||||
|
||||
Detect from `.env*` files containing `NEXT_PUBLIC_*` pointing to another service (e.g., `NEXT_PUBLIC_API_URL=http://localhost:3001`).
|
||||
|
||||
### ACR Build Compatibility — `Dockerfile.azure` Generation
|
||||
|
||||
⛔ ACR `az acr build` uses the **classic Docker builder — NOT BuildKit**. Do NOT assume ACR supports BuildKit.
|
||||
|
||||
**When to generate `Dockerfile.azure`:** If `buildRequirements.hasBuildKitSyntax == true` OR the existing Dockerfile contains any BuildKit-only syntax, create `{component}/Dockerfile.azure` with all BuildKit syntax removed.
|
||||
|
||||
**BuildKit-only syntax** (strip all): `# syntax=` directives, `RUN --mount=...` (all types: cache, secret, bind, tmpfs), `RUN --network=...`, `RUN --security=...`, `COPY --link`, `COPY --chmod=...`, heredoc syntax (`RUN <<EOF`).
|
||||
|
||||
⛔ **Package manager pinning applies here too.** When stripping BuildKit from an existing Dockerfile, also replace `npm install -g {pm}@latest` with the exact version from the project's `packageManager` field in `package.json` (e.g., `pnpm@9.4.0`). The upstream Dockerfile's `@latest` may pull a version incompatible with the base image's Node.js version.
|
||||
|
||||
⛔ Handle multi-line continuations (`\`) — remove the BuildKit flag but preserve the actual command across all continuation lines. Never leave a bare `RUN` with no command.
|
||||
@@ -0,0 +1,53 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Environment Variables & Secrets — Cross-Cutting Rules
|
||||
|
||||
Applies to ALL compute targets (App Service, Container Apps, Functions). For Container Apps-specific Bicep patterns (secretRef, identity), see [bicep-container-apps.md](bicep-container-apps.md).
|
||||
|
||||
## Environment Variable Value Derivation
|
||||
|
||||
> ⛔ **Never invent env var values — derive from the app's config class.** Cross-reference `.env.example`, `.env.sample`, `docker-compose.yml`, and the app's config module. Verify each value:
|
||||
> 1. **Type validation:** URL-typed fields need valid URLs — not `*` or placeholders
|
||||
> 2. **Defaults:** Use app defaults unless overriding with deployed URL
|
||||
> 3. **Required:** Fields without defaults must be provided
|
||||
>
|
||||
> **Pitfalls:** `CORS_ORIGINS=["*"]` → invalid for strict validators (use actual URLs). `DATABASE_URL=changethis` → use a managed-identity connection (no password; wire plain connection params, driver fetches a token). JSON array env vars need Bicep variable escaping:
|
||||
> ```bicep
|
||||
> var corsOrigins = '["https://${containerApp.properties.configuration.ingress.fqdn}"]'
|
||||
> { name: 'CORS_ORIGINS', value: corsOrigins }
|
||||
> ```
|
||||
|
||||
## App-Internal Secret Storage (No Key Vault)
|
||||
|
||||
> ⛔ **No Key Vault is created.** Database/cache/storage access is managed-identity + token — there is NO connection-string password or access key to store. App-internal secrets that are NOT an Azure resource credential (e.g. `SECRET_KEY`, JWT signing key, third-party API keys) are stored **directly on the compute resource** from an `@secure()` param generated at deploy time.
|
||||
|
||||
**Where app-internal secrets go:**
|
||||
- **App Service / Functions:** `@secure()` param → `siteConfig.appSettings` (platform-encrypted at rest), or set at deploy via `az webapp config appsettings set`.
|
||||
- **Container Apps:** `@secure()` param → the CA's **native** `secrets: [{ name, value }]` → `secretRef`. No `keyVaultUrl`. Native secrets have no RBAC dependency, so they need no `isPlaceholder` gating (two-phase wiring is still needed for ACR registries + real image only).
|
||||
|
||||
```bicep
|
||||
// Container Apps — native secret (NOT keyVaultUrl)
|
||||
secrets: [
|
||||
{ name: 'secret-key', value: secretKey } // value from an @secure() param
|
||||
]
|
||||
```
|
||||
|
||||
> ⛔ **Do NOT hardcode secrets in committed files** — not in `main.parameters.json`, `terraform.tfvars`, env vars, or any generated file. (`@secure()` Bicep params ARE the correct way to pass an **app-internal** secret at deploy time — the ban is on committing the value, not on the parameter. Databases/caches/storage have NO secret param — they use managed identity.) The deploy phase generates each app-internal secret ONCE and passes it as an `@secure()` param — see [code-deployment-appservice.md](../../deploy/references/code-deployment-appservice.md) or [code-deployment-container-apps.md](../../deploy/references/code-deployment-container-apps.md).
|
||||
|
||||
## Azure Managed Service SSL/TLS Requirements
|
||||
|
||||
> ⛔ **Azure managed databases and caches enforce TLS. Local docker-compose configs typically don't.** This mismatch causes container crashes post-deploy.
|
||||
|
||||
Check `prereq-output.json.warnings[]` for warnings with `fixPhase: "scaffold"`. Each warning's `fix` field describes the required IaC change. Read the app's config loader for the actual env var name.
|
||||
|
||||
> ⛔ **Prefer env var override over code change.** Only modify source if no env override path exists AND user approves.
|
||||
> ⛔ **Self-review:** If any `fixPhase: "scaffold"` warning exists and IaC lacks the fix → flag as FLAGGED.
|
||||
|
||||
## App-Internal Secret Naming
|
||||
|
||||
> ⛔ **Container Apps native secret names allow only lowercase alphanumeric characters and hyphens.** Map env var names: `SECRET_KEY` → `secret-key`, `JWT_SECRET` → `jwt-secret`. Do NOT use underscores or uppercase. (App Service app settings keep the original env var name, e.g. `SECRET_KEY`.)
|
||||
|
||||
## Compose → Azure PaaS Credential Mapping (Entra-only)
|
||||
|
||||
> ⛔ **Azure managed databases are provisioned Entra-only — there is no username/password to map.** Docker-compose `POSTGRES_USER`/`POSTGRES_PASSWORD` / `MYSQL_USER`/`MYSQL_PASSWORD` are DROPPED, not translated. The app connects with its **managed identity**: the DB username is the app MI's principal name and the "password" is an Entra token fetched at runtime. Map the compose DB **name** (`POSTGRES_DB`/`MYSQL_DATABASE`) to the app DB; do NOT emit any password env var. See [database-post-deploy.md](../../deploy/references/database-post-deploy.md) for granting the MI a DB role.
|
||||
@@ -0,0 +1,15 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Error Handling — Scaffold Phase
|
||||
|
||||
| Error | Remediation |
|
||||
|-------|-------------|
|
||||
| `prepare-plan.json` missing | Trigger prepare backfill via `azure-app-onboard` orchestrator. Do not generate IaC without a plan. |
|
||||
| Existing Azure IaC (`.bicep`, `azure.yaml`, or `.tf` with `azurerm` provider) | ⛔ Never delete/overwrite; move to `.copilot-azure/sessions/<id>/replaced-files/` (mirror path), tell the user their original was preserved at that backup location, then scaffold. |
|
||||
| Existing non-Azure IaC (`.tf` with GCP/AWS provider) | Generate Azure TF alongside — see [terraform-patterns.md § Non-Azure IaC coexistence](terraform-patterns.md). Do NOT halt. |
|
||||
| MCP tool unavailable | Fall back to reference patterns. Flag generated IaC as "unverified against best practices." |
|
||||
| Self-review finds FLAGGED items | Include in `scaffold-manifest.json.selfReview.findings[]`. Surface at approval gate. |
|
||||
| Self-healing exhausted (3 attempts) | Pause auto-healing. Present diagnosis: (1) explain error pattern, (2) propose specific next fix, (3) ask user: "Yes, try that" / "I have a suggestion" / "Stop." If user continues, auto-heal for 5 more, then ask every 5 thereafter. If user stops, write `validationResult` with `status: "Failed"` and all errors. Do NOT proceed to deploy. |
|
||||
| Schema summary exceeds token limit | Use sub-agent pattern: compress each schema to ≤500 tokens. |
|
||||
| `context.json` malformed | Halt. Report: "Session state corrupted — consider starting a fresh session." |
|
||||
@@ -0,0 +1,70 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# IaC Generation Rules — Steps 5–8
|
||||
|
||||
Rules for generating infrastructure code, Dockerfiles, security verification, and telemetry wiring.
|
||||
|
||||
## Step 5 — Generate IaC
|
||||
|
||||
For each service in `services[]`:
|
||||
|
||||
> **Sub-agent delegation:** Use [subagent-iac-gen.md](subagent-iac-gen.md) template verbatim. Include full `prepare-plan.json`, `ScaffoldManifest` interface, and compute-target patterns. Self-review (Step 9) remains mandatory after sub-agent returns.
|
||||
|
||||
> **PostgreSQL wiring:** Include firewall rule + extension allow-list (see [subagent-iac-gen.md](subagent-iac-gen.md) Step 6 if PostgreSQL in plan). ⛔ **PostgreSQL config resources** (e.g., `require_secure_transport`) must use `source: 'user-override'` — `'system-default'` is read-only and ARM rejects it. ⛔ **Do NOT create `databases/postgres` child resource** — it exists by default and ARM rejects duplicate creation. **BuildKit Dockerfiles:** Generate `Dockerfile.azure` for ACR compatibility — see [code-deployment-container-apps.md § BuildKit](../../deploy/references/code-deployment-container-apps.md).
|
||||
|
||||
> **Env var completeness:** Read `.env.example` (or `.env.sample`, `config.example`) + config/settings files (Pydantic `Settings`, `@t3-oss/env-nextjs`, Django `settings.py`) for each component to enumerate required env vars before generating IaC. Every env var with a placeholder value (not `localhost`) should map to either: (1) a Bicep parameter, (2) a **managed-identity KV reference** for an app-internal secret (NOT an Azure resource credential), or (3) a value derived from other resources. ⛔ **Database/cache/storage access is managed-identity + token — wire connection *parameters* (host, db name, MI username, `sslmode=require`) as plain app settings, never a connection-string password or access key.** ⛔ **Container Apps:** KV `secretRef` entries must be gated behind `isPlaceholder` — Phase 1 = `secrets: []`, Phase 2 activates KV refs. See [bicep-container-apps.md](bicep-container-apps.md). Flag unmapped vars in selfReview as ⚠️ WARN. Missing vars cause container crash loops at deploy time.
|
||||
|
||||
> ⛔ **Set `targetScope = 'subscription'` in `main.bicep`.** Subscription-scope Bicep creates the resource group in IaC with all 5 AppOnboard tags (including `created-at`). Do NOT use default resource-group scope — it requires imperative `az group create` which consistently misses tags. If the user lacks subscription-level permissions, the deploy phase handles fallback to RG-scope automatically.
|
||||
|
||||
> ⛔ **Native module deploy strategy.** If `prepare-plan.json.deployStrategy` exists, read [bicep-app-service.md § Native Module Deploy Strategy](bicep-app-service.md) and apply the startup command + app settings to the App Service Bicep. The `deployStrategy.startupCommand` goes into `appCommandLine`, and `deployStrategy.requiredAppSettings` goes into `appSettings[]`. When no `deployStrategy` exists, do NOT set `appCommandLine` — let Oryx use its default startup.
|
||||
|
||||
### Session Tags — Mandatory on ALL Resources
|
||||
|
||||
⛔ Include the 5 AppOnboard tags on every resource and module. See [bicep-patterns.md § tags](bicep-patterns.md) for Bicep code block or [terraform-patterns.md § tags](terraform-patterns.md) for HCL code block.
|
||||
|
||||
| Tag key | Value source |
|
||||
|---------|-------------|
|
||||
| `app-onboard-skill` | `'true'` |
|
||||
| `app-onboard-session-id` | `sessionId` param |
|
||||
| `created-at` | ISO timestamp (scaffold populates, deploy may override) |
|
||||
| `environment` | `naming.resourcePrefix` |
|
||||
| `deployed-by` | `context.json.azure.userDisplayName` |
|
||||
|
||||
If extending existing IaC, MERGE with existing tags using `union()` / `merge()`.
|
||||
|
||||
> ⛔ **Do NOT generate `azure.yaml`.** Deploy via `az deployment sub create`. See [pipeline-rules.md](../../references/pipeline-rules.md) § azure.yaml prohibition.
|
||||
|
||||
### Verification
|
||||
|
||||
⛔ **API version verification:** Use versions from `apiVersions` input map. If a type is missing from the map, use the latest GA version from reference file examples — no `-preview` suffix. `az bicep build` catches invalid versions at compile time.
|
||||
|
||||
⛔ **Resource property verification:** Training data references deprecated properties. Known traps: `Microsoft.CognitiveServices/accounts/deployments` uses `sku` (name + capacity), NOT `scaleSettings` (deprecated) — omit `raiPolicyName`. ⛔ **Do NOT emit a `Microsoft.KeyVault/vaults` resource at all** (no Key Vault). Fallback: `az bicep build` + `what-if`.
|
||||
|
||||
### Output
|
||||
|
||||
`infra/main.bicep`, `main.parameters.json`, `modules/{service}.bicep` per service.
|
||||
|
||||
### Platform Compatibility
|
||||
|
||||
- **Line endings:** Generated `.bicep` files need LF, not CRLF — Bicep triple-quoted strings pass content literally to ARM, and `\r` bytes crash `/bin/sh` in containers. The validate subagent runs `mcp_bicep_format_bicep_file` (or `bicep-format_bicep_file`) post-generation to enforce this. If the formatter is unavailable, ensure files use LF manually.
|
||||
- **Shell compatibility:** Startup scripts in Bicep multiline strings MUST use `set -eu` (POSIX). Do NOT use `set -euo pipefail` — Container Apps base images use `/bin/sh` (dash), not bash.
|
||||
- **Package manager pinning:** When generating or modifying Dockerfiles, pin package manager versions from the project's `packageManager` field (e.g., `pnpm@9.4.0`). Never use `@latest` — major version drift breaks builds on older Node.js base images.
|
||||
|
||||
### Security Patterns — Apply During Generation
|
||||
|
||||
⛔ Apply ALL patterns from [`bicep-patterns-security.md`](bicep-patterns-security.md) during generation — managed identity, SCM/FTP auth policies, on-compute app-internal secrets (NO Key Vault), least-privilege RBAC.
|
||||
|
||||
## Step 6b — Dockerfile Generation (conditional)
|
||||
|
||||
If `prepare-plan.json.services[]` has any entry with `name` containing "Container Apps" AND that component has no existing Dockerfile → ⛔ **You MUST read [`dockerfile-generation.md`](dockerfile-generation.md)** and generate one. Skip if all Container Apps components already have Dockerfiles, or if no service targets Container Apps.
|
||||
|
||||
## Step 7 — Secure-by-Default Verification
|
||||
|
||||
⛔ Read [`bicep-patterns-security.md`](bicep-patterns-security.md) (or `terraform-patterns.md` § Security Defaults) and verify ALL security patterns from Step 5. If deployment includes compute with MI ↔ resource RBAC, ⛔ read [rbac-roles.md](rbac-roles.md) for GUID table. **If a role is NOT in the table, check [Azure built-in roles docs](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles).** Never guess GUIDs. Skip RBAC for SWA-only.
|
||||
|
||||
Tell the user which patterns were applied: managed identity, KV secrets, least-privilege RBAC, SCM/FTP auth policies, private endpoints.
|
||||
|
||||
## Step 8 — Wire Telemetry
|
||||
|
||||
If `prepare-plan.json.instrumentation.appInsightsEnabled` is `true`, add `APPLICATIONINSIGHTS_CONNECTION_STRING` as a plain app setting wired from the App Insights module output. The connection string is not a secret — no KV storage needed. For Container Apps: use a plain env var (not `secretRef`).
|
||||
@@ -0,0 +1,139 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Scaffold Phase — MCP Tools
|
||||
|
||||
Phase-exclusive tool parameters for the scaffold phase. For shared tools (`get_azure_bestpractices`, `subscription_list`, `group_list`, `extension_cli_install`), see [mcp-tool-reference.md](../../references/mcp-tool-reference.md).
|
||||
|
||||
> **Troubleshooting:** If a tool call fails with unknown parameter or missing command errors, consult the official docs: <https://learn.microsoft.com/en-us/azure/developer/azure-mcp-server/tools/>
|
||||
|
||||
---
|
||||
|
||||
## Azure MCP Tools — `mcp_azure_mcp_*`
|
||||
|
||||
### `mcp_azure_mcp_deploy` (hierarchical)
|
||||
|
||||
| Sub-command | Required Params | Optional Params | Read-Only |
|
||||
|-------------|----------------|-----------------|-----------|
|
||||
| `deploy_plan_get` | `workspace-folder`, `project-name`, `target-app-service` (ContainerApp\|WebApp\|FunctionApp\|AKS), `provisioning-tool` (AzCli\|AZD), `source-type` (from-project\|from-azure\|from-context), `deploy-option` (provision-and-deploy\|deploy-only\|provision-only) | `iac-options` (bicep\|terraform), `resource-group` | ✅ |
|
||||
| `deploy_iac_rules_get` | `deployment-tool` (AzCli\|AZD) | `iac-type` (bicep\|terraform), `resource-types` (comma-sep: appservice, containerapp, function, aks, azuredatabaseforpostgresql, azuredatabaseformysql, azuresqldatabase, azurecosmosdb, azurestorageaccount, azurekeyvault) | ✅ |
|
||||
| `deploy_pipeline_guidance_get` | `is-azd-project` (bool), `pipeline-platform` (github-actions\|azure-devops), `deploy-option` (deploy-only\|provision-and-deploy) | subscription, tenant | ✅ |
|
||||
|
||||
### `mcp_azure_mcp_bicepschema` (hierarchical)
|
||||
|
||||
| Sub-command | Required Params | Optional Params | Read-Only |
|
||||
|-------------|----------------|-----------------|-----------|
|
||||
| `bicepschema_get` | `resource_type` (e.g. Microsoft.Web/sites) | `api_version` | ✅ |
|
||||
|
||||
**Usage:** ARM resource type schemas. Primary tool for scaffold — returns resource properties, required fields, and constraints. Prefer over `mcp_bicep_get_az_resource_type_schema` when you need a quick schema lookup without specifying an exact API version.
|
||||
|
||||
### `mcp_azure_mcp_azureterraformbestpractices` (flat)
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| *(none)* | `resource_type` (e.g. "azurerm_linux_web_app") | ✅ |
|
||||
|
||||
**Usage:** Returns Terraform best practices for Azure resources. Omit `resource_type` for general guidance. Provide `resource_type` to get resource-specific `azurerm` configuration patterns, recommended properties, and security settings. Used during scaffold Step 5 for per-resource HCL generation (Terraform path only).
|
||||
|
||||
---
|
||||
|
||||
## Bicep MCP Tools — `mcp_bicep_*`
|
||||
|
||||
### `mcp_bicep_get_bicep_best_practices`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| *(none)* | — | ✅ |
|
||||
|
||||
Returns: comprehensive best practices for Bicep authoring (naming, organization, parameters, security, testing).
|
||||
|
||||
### `mcp_bicep_get_az_resource_type_schema`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| `azResourceType` (e.g. Microsoft.Web/sites), `apiVersion` (e.g. 2024-11-01) | — | ✅ |
|
||||
|
||||
Returns: complete JSON schema for the resource type including all properties, nested types, constraints.
|
||||
|
||||
### `mcp_bicep_list_avm_metadata`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| *(none)* | — | ✅ |
|
||||
|
||||
Returns: metadata for all Azure Verified Modules (AVM) including versions and docs.
|
||||
|
||||
### `mcp_bicep_list_az_resource_types_for_provider`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| `providerNamespace` (e.g. Microsoft.Compute) | — | ✅ |
|
||||
|
||||
Returns: all resource types and API versions for the provider.
|
||||
|
||||
### `mcp_bicep_build_bicep`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| `filePath` (absolute) | — | ✅ |
|
||||
|
||||
Returns: compiled ARM template JSON + compilation errors, warnings, info for a `.bicep` file.
|
||||
|
||||
### `mcp_bicep_format_bicep_file`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| `filePath` (absolute) | — | ❌ |
|
||||
|
||||
Formats the Bicep file per official standards. Respects `bicepconfig.json`.
|
||||
|
||||
### `mcp_bicep_get_deployment_snapshot`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| `filePath` (absolute, `.bicepparam`) | — | ✅ |
|
||||
|
||||
Returns: deployment snapshot from a Bicep parameters file — resolves all parameter values and template references into a single deployable payload. Useful for pre-deploy validation.
|
||||
|
||||
### `mcp_bicep_get_file_references`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| `filePath` (absolute, `.bicep` or `.bicepparam`) | — | ✅ |
|
||||
|
||||
Returns: all file references (modules, parameters, imports) from a Bicep file. Useful for dependency analysis and verifying scaffold output completeness.
|
||||
|
||||
### `mcp_bicep_decompile_arm_template_file`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| `filePath` (absolute, .json/.jsonc/.arm) | — | ❌ |
|
||||
|
||||
Converts ARM JSON → Bicep. Best-effort; may need manual review.
|
||||
|
||||
### `mcp_bicep_decompile_arm_parameters_file`
|
||||
|
||||
| Required | Optional | Read-Only |
|
||||
|----------|----------|-----------|
|
||||
| `filePath` (absolute, .json/.jsonc/.arm) | — | ❌ |
|
||||
|
||||
Converts ARM parameters JSON → `.bicepparam`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 Tool Map
|
||||
|
||||
| Tool | Sub-command | AppOnboard Step | Purpose |
|
||||
|------|-----------|----------|---------|
|
||||
| `mcp_bicep_get_bicep_best_practices` | *(flat)* | Step 5 | **Primary** — Bicep conventions, naming, module patterns for IaC generation |
|
||||
| `mcp_bicep_get_az_resource_type_schema` | *(flat)* | Step 5 | **Primary** — ARM resource type JSON schema per service for property validation |
|
||||
| `mcp_bicep_list_avm_metadata` | *(flat)* | Step 5 | **Primary** — AVM module catalog; prefer verified modules over raw resource definitions |
|
||||
| `mcp_azure_mcp_bicepschema` | `bicepschema_get` | Step 5 | **Primary** — ARM resource type schemas without requiring exact API version |
|
||||
| `mcp_azure_mcp_deploy` | `deploy_iac_rules_get` | Step 5 | IaC best practices and rules. `--iac-type bicep` (default) or `--iac-type terraform` |
|
||||
| `mcp_azure_mcp_get_azure_bestpractices` | `get_azure_bestpractices_get` | Step 5 | SDK patterns during IaC gen. `resource: "general"`, `action: "code-generation"` |
|
||||
| `mcp_bicep_build_bicep` | *(flat)* | Step 9 | **Primary** — validate generated `.bicep` files in self-review L3 (syntax, properties, API versions) |
|
||||
| `mcp_bicep_format_bicep_file` | *(flat)* | Step 9 | Format generated `.bicep` files per official standards after self-review fixes |
|
||||
| `mcp_bicep_get_file_references` | *(flat)* | Step 11 | Verify scaffold output completeness — all module references resolve |
|
||||
| `mcp_azure_mcp_deploy` | `deploy_pipeline_guidance_get` | Step 10 | CI/CD pipeline config. `is-azd-project: false`, `pipeline-platform: "github-actions"`, `deploy-option: "provision-and-deploy"` |
|
||||
| `mcp_azure_mcp_deploy` | `deploy_plan_get` | Step 12 | Structured deployment plan as validation input. Set `source-type: "from-project"`, `target-app-service` from plan |
|
||||
| `mcp_azure_mcp_azureterraformbestpractices` | *(flat)* | Step 5 | Terraform path only — patterns and conventions for HCL generation |
|
||||
@@ -0,0 +1,58 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# RBAC — Common Roles Reference
|
||||
|
||||
Shared reference for role assignment GUIDs used in both Bicep and Terraform. Loaded by both pattern files.
|
||||
|
||||
## Deterministic Role Assignments
|
||||
|
||||
Use `guid()` (Bicep) or deterministic naming (Terraform) for reproducible assignment names. Always set `principalType` to `'ServicePrincipal'` — prevents AAD graph lookup delays.
|
||||
|
||||
If the required role is NOT in the table below, call `azure__documentation` with the target resource type to look up the correct built-in role definition ID before generating the role assignment.
|
||||
|
||||
| Role | ID | Use |
|
||||
|------|-----|-----|
|
||||
| AcrPull | `7f951dda-4ed3-4680-a7ca-43fe172d538d` | Container Apps → ACR |
|
||||
| Storage Blob Data Contributor | `ba92f5b4-2d11-453d-a403-e96b0029c9fe` | App → Storage blobs (read/write/delete) |
|
||||
| Storage Blob Data Reader | `2a2b9908-6ea1-4ae2-8e65-a410df84e7d1` | App → Storage blobs (read-only) |
|
||||
| Storage Queue Data Contributor | `974c5e8b-45b9-4653-ba55-5f855dd0fb88` | App → Storage queues |
|
||||
| Storage Table Data Contributor | `0a9a7e1f-b9d0-4cc4-a60d-0319b160aaa3` | App → Storage tables |
|
||||
| Azure Service Bus Data Sender | `69a216fc-b8fb-44d8-bc22-1f3c2cd27a39` | App → Service Bus (send) |
|
||||
| Azure Service Bus Data Receiver | `4f6d3b9b-027b-4f4c-9142-0e5a2a2247e0` | App → Service Bus (receive) |
|
||||
| Azure Event Hubs Data Sender | `2b629674-e913-4c01-ae53-ef4638d8f975` | App → Event Hubs (send) |
|
||||
| Azure Event Hubs Data Receiver | `a638d3c7-ab3a-418d-83e6-5f17a39d4fde` | App → Event Hubs (receive) |
|
||||
| App Configuration Data Reader | `516239f1-63e1-4d78-a4de-a74fb236a071` | App → App Configuration (read) |
|
||||
| SignalR Service Owner | `7e4f1700-ea5a-4f59-8f37-079cfe29dce3` | App → SignalR |
|
||||
| Cognitive Services OpenAI User | `5e0bd9bd-7b93-4f28-af87-19fc36ad61bd` | App → Azure OpenAI (inference) |
|
||||
| Search Index Data Reader | `1407120a-92aa-4202-b7e9-c0e197c71c8f` | App → AI Search (query) |
|
||||
| Search Index Data Contributor | `8ebe5a00-799e-43f5-93ac-243d3dce84a7` | App → AI Search (read/write index) |
|
||||
| Monitoring Metrics Publisher | `3913510d-42f4-4e42-8a64-420c390055eb` | App → Azure Monitor (custom metrics) |
|
||||
|
||||
## Cosmos DB — Data Plane RBAC (NOT ARM RBAC)
|
||||
|
||||
⛔ **Cosmos DB uses its OWN role system** — do NOT use `Microsoft.Authorization/roleAssignments` for Cosmos DB data access. ARM RBAC roles (Contributor, Reader) grant control-plane access only. For data access (read/write documents), use `Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments`.
|
||||
|
||||
| System | Resource type | Use for |
|
||||
|--------|--------------|--------|
|
||||
| ARM RBAC | `Microsoft.Authorization/roleAssignments` | Subscription/RG-level permissions (Contributor, Reader, Storage/DB data roles) |
|
||||
| Cosmos DB data plane | `Microsoft.DocumentDB/databaseAccounts/sqlRoleAssignments` | Document read/write access (Data Reader, Data Contributor) |
|
||||
|
||||
Built-in Cosmos DB data roles:
|
||||
- Data Reader: `00000000-0000-0000-0000-000000000001`
|
||||
- Data Contributor: `00000000-0000-0000-0000-000000000002`
|
||||
|
||||
## Delegation
|
||||
|
||||
For complex RBAC scenarios (custom roles, cross-subscription, conditional access), AppOnboard does not generate custom role definitions inline. Instead, append an entry to `prepare-plan.json → postDeployRecommendations[]` (schema: `PostDeployRecommendation` in [`prepare-schemas.ts`](../../prepare/references/prepare-schemas.ts)):
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Configure custom RBAC role for <service>",
|
||||
"reason": "<why the built-in roles above are insufficient>",
|
||||
"effort": "medium",
|
||||
"services": ["<affected-service>"]
|
||||
}
|
||||
```
|
||||
|
||||
At handoff, the agent will detect this recommendation and offer to call `mcp_azure_mcp_role` to list existing role assignments and create custom roles for the deployed resource group. See `handoff-protocol.md` Agent-Based Next Steps — the RBAC condition row triggers this automatically.
|
||||
@@ -0,0 +1,49 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Scaffold Healing Rules
|
||||
|
||||
Self-healing loop for scaffold validation failures. Contains error classification (FIXABLE vs BLOCKING) and scaffold-specific escalation and plan-change rules.
|
||||
|
||||
## Error Classification
|
||||
|
||||
| Error Type | Class | Auto-Fix Strategy |
|
||||
|------------|-------|-------------------|
|
||||
| Invalid property name | FIXABLE | Fix from schema |
|
||||
| Syntax error (HCL/Bicep) | FIXABLE | Regenerate from references |
|
||||
| Missing required property | FIXABLE | Call `mcp_bicep_build_bicep` for structured error, fix from diagnostics output |
|
||||
| Wrong API version | FIXABLE | Call `mcp_bicep_list_az_resource_types_for_provider` with `{ providerNamespace: "..." }` for latest GA. Fallback: reference file examples |
|
||||
| Provider version conflict | FIXABLE | Update `required_providers` |
|
||||
| Undeclared variable | FIXABLE | Add to `variables.tf` |
|
||||
| Policy-blocked SKU | FIXABLE | Next-best from `rejectedAlternatives[]` |
|
||||
| Circular dependency | FIXABLE | Break cycle — refactor module refs |
|
||||
| Permission/RBAC insufficient | BLOCKING | Surface role + `az role assignment create` |
|
||||
| State backend inaccessible | BLOCKING | Surface `az storage account create` |
|
||||
| Region unsupported | BLOCKING | Suggest alternate regions (user decision) |
|
||||
| Quota exhaustion (all tiers+regions) | PLAN_LEVEL_CHANGE | ⛔ See § PLAN_LEVEL_CHANGE |
|
||||
| Quota exhaustion (single region) | PLAN_LEVEL_CHANGE | ⛔ Region pivot — skip `quotaValidation.checkedRegions` failures |
|
||||
| Policy blocks service entirely | PLAN_LEVEL_CHANGE | ⛔ Map to `rejectedAlternatives[]` |
|
||||
|
||||
## Healing Escalation Cadence
|
||||
|
||||
Classify Step 11 failures using the table above. **FIXABLE:** edit IaC → re-validate (max 3 before asking). Each attempt must be a *different* fix. **BLOCKING:** surface to user, halt.
|
||||
|
||||
> ⛔ **After each FIXABLE auto-fix:** call `mcp_bicep_build_bicep` with `{ filePath: "infra/main.bicep" }` to quick-check before re-dispatching the validate sub-agent. If errors remain, fix again — saves a sub-agent dispatch. Fallback: `az bicep build`.
|
||||
|
||||
> ⛔ **After 3 attempts:** summarize error pattern, propose specific next fix (never "I'll try again" without naming the change), present options: **"Yes, try that"** (5 more) | **"I have a suggestion"** (apply user fix) | **"Stop"** (write `validationResult.status: "Failed"`). Ask again every 5 thereafter.
|
||||
|
||||
> ⛔ **Same error 3×:** read [iac-resources.md](../../references/iac-resources.md) for docs. Present to user; stop auto-healing that pattern.
|
||||
|
||||
## PLAN_LEVEL_CHANGE
|
||||
|
||||
> ⛔ Service type or region changes require user re-approval — never silently rewrite IaC.
|
||||
|
||||
| Step | Action |
|
||||
|------|--------|
|
||||
| 1. STOP | Never silently change service/region |
|
||||
| 2. Update plan | `prepare-plan.json`: `services[]`, `naming.resources[]`, `costEstimate`, `deployStrategy` per [`prepare-schemas.ts`](../../prepare/references/prepare-schemas.ts) |
|
||||
| 3. Re-approve | "⚠️ Plan change: {old} → {new} (~${new}/mo). Approve? (Yes / Edit / Cancel)" |
|
||||
| 4. Regenerate | New module files with correct names, delete stale, re-run self-review + validation |
|
||||
| 5. Log | [`ScaffoldHealingAttempt`](scaffold-schemas.ts) with `planLevelChange: true`. Counts toward healing cap |
|
||||
|
||||
> ⛔ **Artifact consistency:** After any service/region/SKU change, update ALL upstream artifacts (`prepare-plan.json`, `scaffold-manifest.json`, `context.json`, IaC files) — the plan MUST match deployed reality.
|
||||
@@ -0,0 +1,115 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/**
|
||||
* Scaffold artifact schema — scaffold-manifest.json.
|
||||
* Read by scaffold refs (validation-and-manifest.md, scaffold-healing-rules.md, iac-generation-rules.md).
|
||||
*/
|
||||
|
||||
// ─── Scaffold healing types ──────────────────────────────────────────────────
|
||||
|
||||
export type ScaffoldErrorClassification = "FIXABLE" | "BLOCKING";
|
||||
export type ScaffoldHealingResult = "fixed" | "still-failing" | "blocked";
|
||||
|
||||
export interface ScaffoldHealingError {
|
||||
check: string;
|
||||
detail: string;
|
||||
classification: ScaffoldErrorClassification;
|
||||
}
|
||||
|
||||
export interface ScaffoldHealingFix {
|
||||
file: string;
|
||||
change: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface ScaffoldHealingAttempt {
|
||||
attempt: number;
|
||||
errors: ScaffoldHealingError[];
|
||||
fixes: ScaffoldHealingFix[];
|
||||
result: ScaffoldHealingResult;
|
||||
/** True when this healing attempt changed the service type or region — requires re-approval */
|
||||
planLevelChange?: boolean;
|
||||
/** Original service before the pivot (e.g., "App Service B1") */
|
||||
originalService?: string;
|
||||
/** New service after the pivot (e.g., "Container Apps Consumption") */
|
||||
newService?: string;
|
||||
}
|
||||
|
||||
// ─── scaffold-manifest.json ──────────────────────────────────────────────────
|
||||
|
||||
export type SelfReviewRating = "VERIFIED" | "PLAUSIBLE" | "FLAGGED";
|
||||
|
||||
export interface ScaffoldFile {
|
||||
path: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface SelfReviewFinding {
|
||||
layer: string; // "L1" | "L2" | "L3" | "L4"
|
||||
claim: string;
|
||||
rating: SelfReviewRating;
|
||||
detail: string;
|
||||
}
|
||||
|
||||
export type IacFormat = "bicep" | "terraform";
|
||||
|
||||
export interface ValidationCheck {
|
||||
name: string;
|
||||
passed: boolean;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of Step 11 IaC validation. This is what `ScaffoldManifest.validationResult` holds.
|
||||
* Distinct from `ConformanceResult` below — do not substitute one for the other.
|
||||
*/
|
||||
export interface ValidationResult {
|
||||
status: "Validated" | "Partial" | "Failed";
|
||||
checks: ValidationCheck[];
|
||||
proof?: string;
|
||||
}
|
||||
|
||||
/** One BLOCK failure emitted by scaffold-conformance.{ps1,sh} (Step 3c plan-conformance gate). */
|
||||
export interface ConformanceFailure {
|
||||
id: string; // e.g. "TAGS-NO-CAMEL", "NO-PLAINTEXT-SECRET", "DB-TLS-ON"
|
||||
detail: string;
|
||||
file: string;
|
||||
}
|
||||
|
||||
/** Result of the deterministic plan-conformance gate (subagent-validate.md Step 3c). */
|
||||
export interface ConformanceResult {
|
||||
passed: boolean;
|
||||
failures: ConformanceFailure[];
|
||||
/** "script" when scaffold-conformance.{ps1,sh} ran; "manual" when the fallback assertion table was used. */
|
||||
source: "script" | "manual";
|
||||
}
|
||||
|
||||
export interface ScaffoldManifest {
|
||||
/** Session id this manifest belongs to. */
|
||||
sessionId: string;
|
||||
/** UTC timestamp when scaffold completed. */
|
||||
scaffoldCompletedUtc: string;
|
||||
iacFormat: IacFormat;
|
||||
/** Deployment scope for the generated IaC (deploy preflight branches on this). */
|
||||
targetScope: "subscription" | "resourceGroup";
|
||||
files: ScaffoldFile[];
|
||||
/** Entry-point IaC file, e.g. "infra/main.bicep". */
|
||||
entryPoint?: string;
|
||||
/** Parameters file, e.g. "infra/main.parameters.json". */
|
||||
parametersFile?: string;
|
||||
/** Exact command the deploy phase runs to provision (read by deploy preflight). */
|
||||
deployCommand: string;
|
||||
/** Container Apps two-phase (infra, then image) wiring, when applicable. */
|
||||
twoPhaseWiring?: boolean;
|
||||
/** Ordered phase-2 steps (e.g. image build/push) for two-phase deploys. */
|
||||
phase2Steps?: readonly string[];
|
||||
selfReview: {
|
||||
findings: SelfReviewFinding[];
|
||||
healingAttempts?: readonly ScaffoldHealingAttempt[];
|
||||
};
|
||||
validationResult?: ValidationResult;
|
||||
conformance?: ConformanceResult;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Self-Healing Loop — Error Classification & Auto-Fix
|
||||
|
||||
Step 11 runs validation against the generated IaC via CLI commands (`az bicep build` + `az deployment sub what-if`). On failure, classify each error and apply the strategy below. Max 3 attempts before pausing to present a diagnosis (explain pattern → propose fix → ask user). After user approves, 5 more attempts before asking again — then every 5 thereafter. See scaffold instructions.md § Self-Healing Loop for the full escalation protocol.
|
||||
|
||||
| Error Type | Class | Auto-Fix Strategy |
|
||||
|------------|-------|-------------------|
|
||||
| Invalid property name | FIXABLE | Replace with correct property from schema summary |
|
||||
| Syntax error (HCL/Bicep) | FIXABLE | Re-generate affected module from reference patterns |
|
||||
| Missing required property | FIXABLE | Add with default value from MCP best practices |
|
||||
| Wrong API version | FIXABLE | Update to version from schema result |
|
||||
| Provider version conflict | FIXABLE | Update `required_providers` block |
|
||||
| Undeclared variable | FIXABLE | Add declaration to `variables.tf` |
|
||||
| Policy-blocked SKU | FIXABLE | Substitute with next-best from `rejectedAlternatives[]` |
|
||||
| Circular dependency | FIXABLE | Refactor module references — break cycle |
|
||||
| Permission/RBAC insufficient | BLOCKING | Surface required role + `az role assignment create` command |
|
||||
| State backend inaccessible | BLOCKING | Surface `az storage account create` instructions |
|
||||
| Region unsupported for resource | BLOCKING | Suggest alternate regions — requires user decision |
|
||||
| Quota exhaustion (ALL tiers in ALL regions) | PLAN_LEVEL_CHANGE | ⛔ Service type pivot required — see scaffold instructions.md § Self-Healing Loop. Update `prepare-plan.json` → present re-approval gate → regenerate IaC. Counts as 1 healing attempt |
|
||||
| Quota exhaustion (single region) | PLAN_LEVEL_CHANGE | ⛔ Region pivot required — read `prepare-plan.json.quotaValidation.checkedRegions` and `failedResources` to skip already-failed regions. After checking new regions, append results back to these fields. See scaffold instructions.md § Self-Healing Loop. Update plan region → present re-approval → regenerate IaC |
|
||||
| Policy blocks planned service entirely | PLAN_LEVEL_CHANGE | ⛔ Alternative service required — see scaffold instructions.md § Self-Healing Loop. Map to next-best from `rejectedAlternatives[]` → present re-approval → regenerate IaC |
|
||||
@@ -0,0 +1,132 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Self-Review Checklist
|
||||
|
||||
4-layer adversarial review of generated IaC. Run after scaffold generates all files, before `scaffold-manifest.json`.
|
||||
|
||||
## Rating System
|
||||
|
||||
- **VERIFIED** — confirmed correct by inspecting the generated code
|
||||
- **PLAUSIBLE** — likely correct but cannot fully verify (e.g., API version exists but not checked against registry)
|
||||
- **FLAGGED** — incorrect, missing, or contradicts the plan/patterns
|
||||
|
||||
**Consume results:** If any finding is FLAGGED → fix the IaC, then re-run validation. If all VERIFIED/PLAUSIBLE → proceed. Write findings to `scaffold-manifest.json.selfReview`.
|
||||
|
||||
> ⛔ **Halt on critical failures** — if any finding is FLAGGED at L1 (Security) or L3 (Hallucination), do NOT proceed to deploy. Present findings and ask: **"Fix / Continue with risks / Cancel"**.
|
||||
|
||||
> ⛔ **Step 12 validation remains mandatory** regardless of self-review results — IaC may change during FLAGGED fixes.
|
||||
|
||||
## Layer 1 — Security Claims Extraction
|
||||
|
||||
Extract every security claim from the generated IaC and check for internal contradictions.
|
||||
|
||||
| Check | Example |
|
||||
|-------|---------|
|
||||
| Managed identity declared but secret hardcoded | `identity: { type: 'SystemAssigned' }` but `password: 'hardcoded'` in same file |
|
||||
| HTTPS enforced but HTTP endpoint exposed | `httpsOnly: true` but ingress allows HTTP |
|
||||
| Resource accessed via managed identity but matching role not granted | ACR image pull without AcrPull, Storage access without a Storage Data role, or any MI→resource dependency missing its `roleAssignment` — access fails at runtime → `FLAGGED` |
|
||||
| ⛔ Role assignment scope targets wrong resource | `scope: resourceGroup()` on resource-specific roles → `FLAGGED`. Must scope to specific resource. |
|
||||
| `principalType` missing on role assignments | Causes intermittent 30s+ delays |
|
||||
| ⛔ Identity block missing on compute resource | ⛔ **MANDATORY FAIL** — ALL compute MUST have `identity: { type: 'SystemAssigned' }`. There is NO exception: managed identity is required on every SKU (the compute floor is B1, which supports MI; F1/D1/Free are never generated). Missing identity → `FLAGGED`. |
|
||||
| SQL firewall `0.0.0.0/0` without private endpoint | Prefer MI + private endpoint. AllowAzureServices genuinely needed → `PLAUSIBLE`. |
|
||||
| ⛔ SCM/FTP auth policy missing on App Service | ALL App Service (and Premium/EP1 Functions) MUST have `basicPublishingCredentialsPolicies`: `scm.allow: true`, `ftp.allow: false`. Missing → `FLAGGED`. ⛔ **Functions on Flex Consumption must NOT have these policies** — flag their presence instead. |
|
||||
| ⛔ Any `Microsoft.KeyVault/vaults`, `@Microsoft.KeyVault(...)`, or `keyVaultUrl` present | ⛔ **MANDATORY FAIL** — no Key Vault is created; app-internal secrets live on the compute resource. → ⛔ **FLAGGED** |
|
||||
|
||||
**Rating:** Claims that contradict each other → `FLAGGED`. Consistent claims → `VERIFIED`.
|
||||
|
||||
## Layer 2 — Pattern Validation
|
||||
|
||||
Validate against pattern files loaded at Steps 3–5 and [rbac-roles.md](rbac-roles.md).
|
||||
|
||||
### Bicep
|
||||
|
||||
| Check | Source |
|
||||
|-------|--------|
|
||||
| File structure: `main.bicep` → `modules/*.bicep` | `bicep-patterns.md` |
|
||||
| `main.parameters.json` uses ARM JSON (not `.bicepparam`) | `bicep-patterns.md` |
|
||||
| Naming: `{prefix}{name}{token}` ≤32 chars | `bicep-patterns.md` |
|
||||
| System-assigned managed identity on all services | `bicep-patterns-security.md` |
|
||||
| No `administratorLogin`/`administratorLoginPassword`/access key on ANY data service (SQL, PG, MySQL, Redis, Storage, Cosmos) — Entra/MI-only | `bicep-patterns-security.md` § Data Services |
|
||||
| PG/MySQL set an Entra admin child resource; PG `authConfig.passwordAuth: 'Disabled'` | `bicep-patterns-data.md` |
|
||||
| No F1/D1 App Service SKU, no Free Static Web Apps tier | `sku-matrix.md` |
|
||||
| ⛔ No `Microsoft.KeyVault/vaults` resource | No Key Vault is created — app-internal secrets are stored on the compute resource → any KV resource is ⛔ **FLAGGED** | `bicep-patterns-security.md` § No Key Vault |
|
||||
| App-internal secrets passed as `@secure()` params → App Service `appSettings` / CA native `secrets` | `bicep-patterns-security.md` § Secrets |
|
||||
| Prereq `warnings[]` each have a corresponding IaC fix | Read [`env-var-secrets.md`](env-var-secrets.md) for SSL/TLS fixes |
|
||||
| Detected Azure Functions topology preserved | Every Azure Functions component in `context.json.components[]` has a planned Azure Functions service and a `Microsoft.Web/sites` Function App resource. If an SWA frontend is also present, no `buildProperties.apiLocation`, SWA-managed API source copy, or `azureStaticWebApps` backend auth/runtime setting appears. Missing or merged Function App → `FLAGGED`. |
|
||||
| Container Apps: two-phase ACR wiring, `registries` populated when ACR in plan, port alignment | `bicep-container-apps.md` |
|
||||
| ⛔ BuildKit Dockerfile without `Dockerfile.azure` | `hasBuildKitSyntax == true` but no `Dockerfile.azure` in `files[]` → ⛔ **MANDATORY FAIL**. ACR does not support BuildKit. |
|
||||
| ⛔ Role assignment `scope` targets specific resource, not `resourceGroup()` | `rbac-roles.md` |
|
||||
| ⛔ No `azure.yaml` in `scaffold-manifest.json.files[]` | `pipeline-rules.md` |
|
||||
| Non-Azure TF in separate dir | `terraform-patterns.md` |
|
||||
|
||||
### Cross-Module Reference Validation
|
||||
|
||||
Trace references BETWEEN modules — per-file checks miss broken cross-module wiring.
|
||||
|
||||
| Check | Rating |
|
||||
|-------|--------|
|
||||
| **Param wiring** — every `module` call in `main.bicep`: verify every param without `= default` is passed | Missing param → `FLAGGED` |
|
||||
| **Secret ref completeness** — every CA `secretRef` has a matching entry in the CA's native `secrets[]` (name + `value`); ⛔ NO `keyVaultUrl` / Key Vault | Missing native secret or any `keyVaultUrl` → `FLAGGED` |
|
||||
| **Output ref validity** — every `moduleRef.outputs.X` is declared in the referenced module | Missing output → `FLAGGED` |
|
||||
|
||||
### Terraform
|
||||
|
||||
| Check | Source |
|
||||
|-------|--------|
|
||||
| File structure: `main.tf`, `variables.tf`, `outputs.tf`, `backend.tf`, `modules/` | `mcp_azure_mcp_azureterraformbestpractices` |
|
||||
| Provider: `azurerm ~> 4.0` | Terraform registry |
|
||||
| System-assigned managed identity, no `administrator_login`, no Key Vault (`shared_access_key_enabled=false` etc.) | `terraform-patterns.md` |
|
||||
| Container Apps: two-phase ACR wiring | Same pattern as Bicep |
|
||||
|
||||
**Rating:** Matches → `VERIFIED`. Reasonable deviation → `PLAUSIBLE`. Violates → `FLAGGED`.
|
||||
|
||||
## Layer 3 — Hallucination Detection
|
||||
|
||||
Catch fabricated resource types, API versions, SKU names, or properties.
|
||||
|
||||
### Bicep (default path)
|
||||
|
||||
| Check | How |
|
||||
|-------|-----|
|
||||
| API versions, resource types, property names valid | `bicep build` — errors = `FLAGGED` |
|
||||
| ⛔ Deploy-time value validity (`bicep build` blind spot) | The compiler accepts any schema-valid string, but ARM rejects wrong enum-like values (engine versions, region-restricted SKUs), wrong resource `scope`, and empty resource-ID properties. Any such value the generator chose from memory — not traceable to `prepare-plan.json` or a pattern file — MUST be confirmed against the provider capabilities API; unconfirmed → `FLAGGED`. |
|
||||
| SKU names match `prepare-plan.json` | Cross-reference `services[].sku` |
|
||||
| OpenAI deployment uses `scaleSettings` instead of `sku` | `scaleSettings` deprecated → `FLAGGED` |
|
||||
| Any resource uses `-preview` API version | Use latest GA from MCP tool → `FLAGGED` |
|
||||
| VNet subnets as separate child resources | Must be inline in `properties.subnets[]` → `FLAGGED` |
|
||||
|
||||
Run `bicep build main.bicep --stdout > /dev/null` as syntax + schema validation. Parse errors = `FLAGGED`.
|
||||
|
||||
> ⛔ **`az bicep build` is MANDATORY for L3.** If unavailable, write `FLAGGED` with "bicep build unavailable."
|
||||
|
||||
> ⛔ **Verify `main.bicep` has `targetScope = 'subscription'`.** Missing → FLAGGED (FIXABLE — add targetScope, RG resource with tags, `scope: rg` on modules).
|
||||
|
||||
> `mcp_bicep_build_bicep` + `az deployment sub what-if` also appropriate at L3. ⛔ Do NOT use `az deployment sub validate` (known bug).
|
||||
|
||||
**Rating:** Passes `bicep build` → `VERIFIED`. Build warning → `PLAUSIBLE`. Build error → `FLAGGED`.
|
||||
|
||||
### Terraform (alternative path)
|
||||
|
||||
Run `terraform init -backend=false && terraform validate` + `terraform plan -detailed-exitcode`. Same rating criteria as Bicep.
|
||||
|
||||
**Rating:** Passes validate + plan → `VERIFIED`. Plan warning → `PLAUSIBLE`. Validate/plan error → `FLAGGED`.
|
||||
|
||||
## Layer 4 — WAF Alignment
|
||||
|
||||
Per-pillar spot check against [Azure Well-Architected Framework](https://learn.microsoft.com/en-us/azure/well-architected/).
|
||||
|
||||
| Pillar | Check |
|
||||
|--------|-------|
|
||||
| Reliability | Health probes configured. ⛔ Verify probe path matches `prereq-output.json.healthEndpoint` — non-existent or mismatched path = `FLAGGED`. ⛔ ACA probes do NOT follow HTTP redirects — if app has trailing-slash normalization (Express `redirect`), use path WITHOUT trailing `/` (e.g., `/app` not `/app/`). |
|
||||
| Security | No public blob access, TLS 1.2+, managed identity |
|
||||
| Cost | SKU matches budget tier from `prepare-plan.json` |
|
||||
| Ops | App Insights present + connected for APM, 5 AppOnboard tags on resources, all values parameterized. Present = `VERIFIED`. App Insights absent = `PLAUSIBLE`. ⛔ `diagnostic-settings` is not part of the plan — if the generator added one it MUST be gated (`if (enableDiagnostics)`, default `false`) or absent; wired UNCONDITIONALLY in `main.bicep` = `FLAGGED` (blocks first deploy). |
|
||||
| Performance | Autoscale rules present for production SKUs |
|
||||
| Reliability | ⛔ Env var values compatible with app config validation (Pydantic `Settings`, Django `settings.py`). Typed fields reject wrong formats → `FLAGGED` |
|
||||
|
||||
**Rating:** Pillar addressed → `VERIFIED`. Not applicable for SKU → `PLAUSIBLE`. Missing for production SKU → `FLAGGED`.
|
||||
|
||||
## Output
|
||||
|
||||
Write findings to `scaffold-manifest.json.selfReview.findings[]`: `{ "layer": "L1", "claim": "...", "rating": "FLAGGED", "detail": "..." }` (layer is one of `"L1"`|`"L2"`|`"L3"`|`"L4"`). All `FLAGGED` must be resolved or surfaced at deploy gate. `PLAUSIBLE` = informational.
|
||||
@@ -0,0 +1,28 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Self-Review Procedure — Step 9
|
||||
|
||||
Adversarial self-review using a sub-agent to perform L1–L4 review of generated IaC.
|
||||
|
||||
## Sub-Agent Setup
|
||||
|
||||
Use a sub-agent to perform the review. Provide:
|
||||
- All generated IaC file contents (every .bicep or .tf file from Step 5)
|
||||
- The `prepare-plan.json` services/naming/deploymentVariables sections
|
||||
- The `scaffold-manifest.json.files[]` list
|
||||
- The full content of [self-review-checklist.md](self-review-checklist.md) AND [waf-checklist.md](waf-checklist.md) verbatim
|
||||
|
||||
## Sub-Agent Prompt
|
||||
|
||||
> "Follow the self-review-checklist.md procedures for EACH of L1–L4. Rate each finding as VERIFIED | PLAUSIBLE | FLAGGED. Check: L1 Security (RBAC scope, network rules, managed identity, Key Vault — check contradictions between IaC and plan), L2 Pattern (anti-patterns, missing supporting resources — verify every file in scaffold-manifest.json.files[] exists on disk and is non-empty, FLAGGED if any missing or empty), L3 Hallucination (resource names match prepare-plan.json.naming exactly, API versions are real, SKU names match plan, no invented resource types), L4 WAF (use waf-checklist.md — Reliability, Security, Cost, Ops, Performance per-service checks). Do not fabricate results — check each claim against the actual IaC content provided. Return: { findings: [{ layer: 'L1'|'L2'|'L3'|'L4', claim: '...', rating: 'VERIFIED'|'PLAUSIBLE'|'FLAGGED', detail: '...' }], summary: 'N/N VERIFIED, N PLAUSIBLE, N FLAGGED' }. ≤1000 tokens."
|
||||
|
||||
## Consume Results
|
||||
|
||||
- If any finding is FLAGGED → fix the IaC, then re-run validation (`az bicep build`, `az deployment sub what-if`)
|
||||
- If all VERIFIED/PLAUSIBLE → proceed to Step 10
|
||||
- Write findings to `scaffold-manifest.json.selfReview`
|
||||
|
||||
> ⛔ **Self-review is COMPLETE after L1–L4.** L3 may use `mcp_bicep_get_bicep_file_diagnostics`, `az bicep build`, or `az deployment sub what-if` — all are appropriate for catching errors early. **Step 12 remains mandatory regardless of what self-review found** — IaC may change between Steps 9–12 (FLAGGED fixes), and Step 12 writes the contractual `validationResult` to the manifest.
|
||||
|
||||
> ⛔ **Halt on critical self-review failures** — if any selfReview finding is FLAGGED at L1 (Security) or L3 (Hallucination), do NOT proceed to deploy. Present findings and ask: **"Fix / Continue with risks / Cancel"**.
|
||||
@@ -0,0 +1,103 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Subagent Template — IaC Generation (Steps 5–8)
|
||||
|
||||
Generate deployment-ready IaC from `prepare-plan.json`. Follow the workflow below — each step specifies which reference to read and what to do with it.
|
||||
|
||||
## Critical Rules
|
||||
|
||||
- ⛔ **Do NOT invoke any other agents or hand off** — no external agent calls of any kind. Use the procedures in THIS file only.
|
||||
- ⛔ **Do NOT generate `azure.yaml`**
|
||||
- ⛔ **Do NOT modify app source code** — only write files under `infra/` (and `Dockerfile.azure` if needed)
|
||||
- ⛔ **Do NOT run app build/test/lint commands** (`npm test`, `npm run build`, `pnpm build`, `python -m pytest`, `dotnet build`, etc.). Only validate generated IaC via `az bicep build`.
|
||||
|
||||
## Input (provided by caller)
|
||||
|
||||
| Field | Source | Required |
|
||||
|-------|--------|----------|
|
||||
| `prepare-plan.json` content | Session folder — services, naming, quotas, cost, deploymentVariables | YES (verbatim) |
|
||||
| `context.json.overrides` | `iacFormat`, `detectedInfraProvider` | YES |
|
||||
| `buildRequirements` | From `prereq-output.json` — runtime, deps, Dockerfiles | YES |
|
||||
| `warnings[]` | From `prereq-output.json` — prereq warnings requiring IaC fixes (env var overrides, config changes). Applied during Steps 3–4. | YES |
|
||||
| Compute targets | App Service/Functions, Container Apps, or both + whether PostgreSQL/Redis present | YES |
|
||||
| `apiVersions` | Map of `resourceType → latestGAVersion` from main-thread MCP lookup. Use these versions in generated Bicep — do NOT use versions from training data. If `"MCP unavailable"` — see Step 1 for fallback. | YES |
|
||||
|
||||
## Output
|
||||
|
||||
| Artifact | Location |
|
||||
|----------|----------|
|
||||
| `infra/main.bicep` (or `main.tf`) | Workspace `infra/` |
|
||||
| `infra/main.parameters.json` (or `variables.tf`) | Workspace `infra/` |
|
||||
| `infra/modules/{service}.bicep` per service | Workspace `infra/modules/` |
|
||||
| File list | Return to caller for `scaffold-manifest.json.files[]` |
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1 — Read skeleton + tag patterns
|
||||
|
||||
Read [bicep-patterns.md](bicep-patterns.md) (Bicep) OR [terraform-patterns.md](terraform-patterns.md) (Terraform) — NOT both.
|
||||
|
||||
**Do:** Extract the `main.bicep` skeleton structure (targetScope, parameters, variables, resource group, module calls). Extract the 5-tag block definition. Use `prepare-plan.json.naming` for all resource names — never derive names with `take()`, `substring()`, `uniqueString()`, or string manipulation. The 4-char session suffix in the plan names already provides uniqueness. ⛔ For each `resource 'Type@Version'` declaration, use the version from `apiVersions` input. If type missing from map, use version from reference file examples.
|
||||
|
||||
> ⛔ **If `apiVersions` is `"MCP unavailable"` or missing a resource type:** run `az provider show --namespace {ns} --query "resourceTypes[?resourceType=='{type}'].apiVersions[?!contains(@, 'preview')] | [0][0]" -o tsv` for each missing provider — this filters to GA-only and picks the latest. NEVER fall back to training data — hallucinated API versions cause multiple deploy healing cycles.
|
||||
|
||||
### Step 2 — Read compute-target patterns
|
||||
|
||||
Read ONLY the compute-target reference(s) matching the plan, if the plan has multiple compute targets, read each matching file.:
|
||||
- If plan has App Service/Functions → read [bicep-app-service.md](bicep-app-service.md).
|
||||
- If plan has Container Apps → read [bicep-container-apps.md](bicep-container-apps.md).
|
||||
- If plan has Static Web Apps → read [bicep-swa.md](bicep-swa.md).
|
||||
- If plan has BOTH → read both.
|
||||
|
||||
**Do:** Generate compute module(s) using the patterns from each reference file. F1/D1 App Service: do NOT generate Dockerfile, do NOT add managed identity (OOM). App Service health probe: if `prereq-output.json.healthEndpoint` is non-null, set `siteConfig.healthCheckPath` to that value; otherwise omit (do NOT default to `/`).
|
||||
|
||||
### Step 3 — Read security patterns
|
||||
|
||||
⛔ **You MUST read [bicep-patterns-security.md](bicep-patterns-security.md).** It contains Key Vault config, managed identity, HTTPS/TLS, and credential hygiene rules. Apply to every generated module.
|
||||
|
||||
### Step 4 — Read generation rules
|
||||
|
||||
Read [iac-generation-rules.md](iac-generation-rules.md).
|
||||
|
||||
**Do:** Apply ALL rules from the reference file to every generated module. The file contains mandatory tag definitions, naming constraints, security patterns, env var completeness checks, and Dockerfile generation rules. Do NOT skip any section — every rule applies.
|
||||
|
||||
### Step 5 — Read env var + secrets wiring
|
||||
|
||||
Read [env-var-secrets.md](env-var-secrets.md).
|
||||
|
||||
**Do:** For each component in the plan, read `.env.example` (or `.env.sample`, config files like Pydantic `Settings`, Django `settings.py`) from the workspace. Map every env var to either: (1) a Bicep parameter, (2) an **`@secure()` param stored on the compute resource** (App Service `appSettings` / Container Apps native `secrets`) for app-internal secrets that are NOT Azure resource credentials (e.g. `SECRET_KEY`, JWT key), or (3) a value derived from other resources. ⛔ **No Key Vault.** ⛔ **Database/cache/storage access is managed-identity + token — never a connection-string password or access key.** Wire DB/cache **connection parameters** (host, db name, MI username, `sslmode=require`) as plain `appSettings`/`env` values. See [bicep-patterns-security.md](bicep-patterns-security.md) § Data Services and § No Key Vault.
|
||||
|
||||
### Step 6 — Generate data modules (if needed)
|
||||
|
||||
ONLY if PostgreSQL, MySQL, or Redis is in the plan. Skip if none are present.
|
||||
|
||||
**PostgreSQL Flexible Server module** — **Entra-only** (`authConfig: { activeDirectoryAuth: 'Enabled', passwordAuth: 'Disabled', tenantId }`). NO `administratorLogin`/`administratorLoginPassword`. Add an `administrators` child resource setting the **deploying principal** as Entra admin (pass `entraAdminObjectId`/`entraAdminName`/`entraAdminType`). Include the `AllowAllAzureServicesAndResourcesWithinAzureIps` (`0.0.0.0`) firewall rule, extension allow-list (`azure.extensions` config: `uuid-ossp,pgcrypto,pg_trgm`), storage config (default 32 GB). Set the server `version` from `prepare-plan.json.services[].version` (capabilities-verified) — do NOT hardcode or guess. The app MI is granted a DB role post-deploy (see [database-post-deploy.md](../../deploy/references/database-post-deploy.md)).
|
||||
|
||||
**MySQL Flexible Server module** — **Entra-only**: a **user-assigned managed identity (REQUIRED for MySQL AAD auth)** + `identity: { type: 'UserAssigned', … }` on the server + an `administrators` child resource (deploying principal) with `identityResourceId` set to that UAMI (⛔ `null` fails to deploy) + `aad_auth_only: ON` config. NO password params. Emit a `postDeployRecommendation` to grant the UAMI **Directory Readers** in Entra. Include the MySQL-only deltas: `require_secure_transport: ON` config, the server `version` from `prepare-plan.json.services[].version` (ARM rejects major-only strings like `'8.0'` — needs an exact patch such as `'8.0.21'`), and a `flexibleServers/databases` child resource for the compose-declared DB name (e.g. `MYSQLDB_DATABASE`) so the app's schema DB exists in IaC before the container starts. See [bicep-patterns-data.md § MySQL Flexible Server Module](bicep-patterns-data.md).
|
||||
|
||||
**Redis Cache module** — Basic SKU, `enableNonSslPort: false`, `minimumTlsVersion: '1.2'`, **`disableAccessKeyAuthentication: true`** + `redisConfiguration: { 'aad-enabled': 'true' }` + an `accessPolicyAssignments` child granting the app MI Data Owner. ⛔ **No access key in Key Vault.** ⛔ Known Bicep type issue: `sku` property may cause BCP035/BCP187 warnings — these are false positives. If deploy fails with `InvalidRequestBody` for `properties.sku.name`, create via `az redis create --sku Basic --vm-size c0` then reference with `existing` keyword in Bicep.
|
||||
|
||||
Wire DB/cache **connection parameters** (host, db name, MI username, SSL required) as plain `appSettings`/`env` values — NOT secrets. The driver fetches an Entra token at runtime. App-internal (non-Azure) secrets are stored on the compute resource via an `@secure()` param (App Service `appSettings` / Container Apps native `secrets`) — ⛔ **no Key Vault**.
|
||||
|
||||
### Step 7 — Generate all files
|
||||
|
||||
> ⛔ **Before writing ANY file, verify:** (1) ⛔ **NO `Microsoft.KeyVault/vaults` resource anywhere** — app-internal secrets live on the compute resource. (2) No secrets in module outputs — `@secure()` params flow in at deploy, never out. (3) API versions from `apiVersions` input, not memory. (4) Container Apps: no `revisionSuffix`, placeholder image as default, `isPlaceholder` conditionals on registries only (native secrets need no gating).
|
||||
|
||||
**Do:** Create the `infra/` directory and write all files:
|
||||
1. `infra/bicepconfig.json` — write `{ "formatting": { "newlineKind": "LF" } }` if it doesn't already exist (user's repo may have one). LF is critical because Bicep triple-quoted strings pass content literally to ARM, and `\r` bytes crash `/bin/sh` in containers.
|
||||
2. `infra/main.bicep` — subscription scope, RG creation with tags, module calls for all services + `role-assignments` module (AcrPull + app-to-resource data-plane RBAC — NO Key Vault roles), all unconditional.
|
||||
3. `infra/main.parameters.json` — ARM JSON format (NOT `.bicepparam`). Include `environmentName`, `location`, `sessionId`, `deployedBy`, `createdAt`, and `entraAdminObjectId`/`entraAdminName` (Entra DB admin = deployer). ⛔ **`createdAt` value:** run `Get-Date -Format "o"` in terminal to get the current ISO 8601 timestamp — NEVER use a hardcoded or placeholder date. Do NOT include `@secure()` app-internal secret params (passed at deploy time). Deploy phase passes the deployer object id via `az ad signed-in-user show --query id -o tsv`.
|
||||
4. `infra/modules/{service}.bicep` — one module per service from the plan, PLUS `role-assignments.bicep` (AcrPull for Container Apps → ACR, and app-MI → resource data-plane roles per [rbac-roles.md](rbac-roles.md)). ⛔ **No Key Vault module and no Key Vault role assignments.**
|
||||
5. If `buildRequirements.hasBuildKitSyntax == true`: ⛔ create `{component}/Dockerfile.azure` per [dockerfile-generation.md § ACR Build Compatibility](dockerfile-generation.md).
|
||||
6. If Container Apps and component has NO Dockerfile: read [dockerfile-generation.md](dockerfile-generation.md) and generate one. Follow the layer ordering, port alignment, and security defaults from that reference — do NOT generate from memory.
|
||||
|
||||
> ⛔ **Health probes for Container Apps:** Probe path priority: (1) `prereq-output.json.healthEndpoint` if non-null, (2) first detected GET route from the app, (3) `/` only if the app has a root handler. Do NOT default to `/` for APIs that only serve sub-paths — returns 404, blocks activation. For DB apps: use `/healthz` not `/readyz` (DB not wired in Phase 1).
|
||||
|
||||
### Step 8 — Validate syntax
|
||||
|
||||
**Do:** Run `az bicep build --file infra/main.bicep --stdout > $null`. If errors, fix and re-run (max 2 attempts). Do NOT use the `azure-validate` agent.
|
||||
|
||||
### Step 9 — Return results
|
||||
|
||||
**Do:** Return the list of generated files and any validation notes to the caller. Keep status report ≤1500 tokens.
|
||||
@@ -0,0 +1,59 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Subagent Template — Security + Adversarial Review (Steps 6–9)
|
||||
|
||||
Review generated IaC for security compliance and correctness. Follow the workflow below — each step specifies which reference to read and what to check.
|
||||
|
||||
## Critical Rules
|
||||
|
||||
- ⛔ **Do NOT invoke any other agents or hand off** — no external agent calls of any kind. Use the procedures in THIS file only.
|
||||
- ⛔ **Do NOT run `az deployment` commands** — review is read-only analysis of generated files.
|
||||
- ⛔ **Do NOT modify IaC files** — report findings only. The caller fixes issues.
|
||||
|
||||
## Input (provided by caller)
|
||||
|
||||
| Field | Required |
|
||||
|-------|----------|
|
||||
| All generated IaC file contents (every `.bicep` or `.tf` file) | YES |
|
||||
| `prepare-plan.json` — services (service types, SKUs), naming, deploymentVariables sections | YES |
|
||||
| `scaffold-manifest.json.files[]` list | YES |
|
||||
| `prereq-output.json.warnings[]` — all prereq warnings that require IaC fixes | YES |
|
||||
|
||||
## Output
|
||||
|
||||
Return JSON (≤1000 tokens):
|
||||
```json
|
||||
{
|
||||
"findings": [
|
||||
{ "layer": "L1|L2|L3|L4", "file": "modules/app.bicep", "claim": "...", "rating": "VERIFIED|PLAUSIBLE|FLAGGED", "detail": "..." }
|
||||
],
|
||||
"summary": "N/N VERIFIED, N PLAUSIBLE, N FLAGGED"
|
||||
}
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1 — Read security patterns + run L1 security baseline
|
||||
|
||||
Read [bicep-patterns-security.md](bicep-patterns-security.md) and [rbac-roles.md](rbac-roles.md).
|
||||
|
||||
**Do:** Check every generated IaC file against ALL security checks defined in the reference file. The file contains the complete check table with FLAGGED conditions, edge cases, and Bicep code patterns. Do NOT guess checks from memory — use the reference file as the checklist.
|
||||
|
||||
### Step 2 — Read checklist + run L2–L4 adversarial review
|
||||
|
||||
Read [self-review-checklist.md](self-review-checklist.md).
|
||||
|
||||
**Do:** First run the **cross-module reference trace** from the checklist's § Cross-Module Reference Validation: parse every `module` call in `main.bicep`, read each target module's `param`/`output` declarations and `secrets[]` entries, then verify every reference resolves (params passed match params declared, outputs referenced exist, every CA `secretRef` has a matching native `secrets[]` entry — ⛔ NO `keyVaultUrl` / Key Vault). Then run L2–L4:
|
||||
- **L2 (Pattern Validation):** File structure matches `main.bicep` → `modules/*.bicep`, naming follows plan, Container Apps uses two-phase wiring, every `files[]` entry exists on disk, no `azure.yaml`, cross-module references all resolve
|
||||
- **L3 (Hallucination Detection):** Resource names match `naming.resources[]` exactly, API versions are real (verify via `az bicep build`), SKU names match plan, no invented resource types
|
||||
- **L4 (WAF Alignment):** Check per-pillar:
|
||||
- Reliability: zone redundancy (prod SKUs), health probes, GRS storage, min replicas ≥1
|
||||
- Security: managed identity, on-compute app-internal secrets (no Key Vault), HTTPS+TLS 1.2, no public blob, no `administratorLogin`
|
||||
- Cost: SKU matches budget, scale-to-zero for dev/test CA, free grants applied
|
||||
- Ops: App Insights, 5 AppOnboard tags, all values parameterized
|
||||
- Performance: autoscale (prod), CDN for SPA, connection pooling, cache tier
|
||||
|
||||
### Step 3 — Compile findings + return
|
||||
|
||||
**Do:** Merge L1–L4 results into the findings JSON. Apply rating per [self-review-checklist.md](self-review-checklist.md) § Rating System: VERIFIED (evidence confirms claim), PLAUSIBLE (no counter-evidence but unverified), FLAGGED (evidence contradicts or missing critical pattern). ⛔ FLAGGED at L1 (Security) or L3 (Hallucination) → caller must fix before deploy. Return to caller.
|
||||
@@ -0,0 +1,104 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Subagent Template — Validation & Manifest (Steps 10–12)
|
||||
|
||||
Validate IaC syntax, write `scaffold-manifest.json`, and generate deploy checklist. Follow the workflow below.
|
||||
|
||||
## Critical Rules
|
||||
|
||||
- ⛔ **Do NOT invoke any other agents or hand off** — no external agent calls of any kind. Use the procedures in THIS file only.
|
||||
- ⛔ **Do NOT create or modify Azure resources** — validation is syntax-only (`bicep build` / `terraform validate`), never `az deployment sub create`.
|
||||
- ⛔ **Do NOT run `what-if` or `terraform plan`** — deploy runs the mandatory what-if with real secret params. Scaffold validates syntax only.
|
||||
|
||||
## Input (provided by caller)
|
||||
|
||||
| Field | Required |
|
||||
|-------|----------|
|
||||
| IaC file paths (all generated `.bicep` or `.tf` files) | YES |
|
||||
| Self-review findings from Steps 6–9 | YES |
|
||||
| `prepare-plan.json` — services, naming, region, subscriptionId | YES |
|
||||
| `prereq-output.json.warnings[]` — prereq warnings with `fixPhase` | YES |
|
||||
| `prereq-output.json.healthEndpoint` — detected health path (or `null`) | YES |
|
||||
| Conformance result JSON (from main-thread Step 10a-conf) | YES |
|
||||
|
||||
## Output
|
||||
|
||||
| Artifact | Location |
|
||||
|----------|----------|
|
||||
| `scaffold-manifest.json` | Session folder |
|
||||
| `deploy-result.json` skeleton | Session folder |
|
||||
| Validation result status | Return to caller: `Validated` or `Failed` |
|
||||
| Deploy checklist | `.copilot-azure/sessions/{id}/deploy-checklist.md` |
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1 — Read validation + manifest rules
|
||||
|
||||
Read [validation-and-manifest.md](validation-and-manifest.md) and [scaffold-schemas.ts](scaffold-schemas.ts).
|
||||
|
||||
**Do:** Understand the `ScaffoldManifest` interface (field names, types, required fields) and the validation sequence.
|
||||
|
||||
### Step 2 — Format and validate IaC
|
||||
|
||||
**Do:**
|
||||
1. Run `az bicep build --file infra/main.bicep --stdout > $null` (Bicep) or `terraform validate` (TF). Process output for BCP errors and warnings. Record pass/fail.
|
||||
|
||||
### Step 3 — Check RBAC completeness
|
||||
|
||||
**Do:** Verify every managed identity ↔ resource pair in the IaC has a corresponding `Microsoft.Authorization/roleAssignments` resource with the correct role GUID. Cross-reference with the review findings from Steps 6–9 input.
|
||||
|
||||
### Step 3b — Azure runtime constraint check (Container Apps)
|
||||
|
||||
> Skip if no Container Apps in the plan.
|
||||
|
||||
For each Container App resource in the generated Bicep:
|
||||
|
||||
1. **cpu/memory combo** — verify `cpu` (must be type `string`) + `memory` is one of: `0.25/0.5Gi`, `0.5/1Gi`, `0.75/1.5Gi`, `1/2Gi`, `1.25/2.5Gi`, `1.5/3Gi`, `1.75/3.5Gi`, `2/4Gi`. FIXABLE: adjust to nearest valid combo (use smallest valid combo for sidecars/companions).
|
||||
2. **secretRef coverage** — every `secretRef` in container env vars must have a matching entry in `configuration.secrets[]` (native `name` + `value`). ⛔ **No `keyVaultUrl` / Key Vault** — a `keyVaultUrl` or any `Microsoft.KeyVault/vaults` resource is a BLOCK. Missing native secret entry → FIXABLE: add `{ name, value: <secureParam> }` to the CA's `secrets[]`.
|
||||
3. **probe path** — if `prereq-output.json.healthEndpoint` is non-null, verify `probePath` matches it. If null AND any `plainEnvVars` entry is named `BASE` or `PATH_PREFIX`, verify `probePath` starts with that value (not bare `/`). If null AND no BASE var: verify the app has a route handler for the probe path (check source entry point for `app.get('/')` or framework root handler) — bare `/` on a REST API with only sub-path routes (e.g., `/users`, `/messages`) returns 404 and blocks revision activation. FIXABLE: update `probePath` to a known GET endpoint from the app.
|
||||
|
||||
FIXABLE errors: fix the Bicep → re-run `az bicep build` → proceed to Step 3c.
|
||||
|
||||
### Step 3b2 — App Service security constraint check
|
||||
|
||||
> Skip if no App Service or Functions in the plan.
|
||||
|
||||
For each App Service resource, and each **Premium (EP1)** Functions resource, in the generated Bicep:
|
||||
|
||||
1. **basicPublishingCredentialsPolicies** — verify both child resources exist: `basicPublishingCredentialsPolicies/scm` (with `allow: true`) and `basicPublishingCredentialsPolicies/ftp` (with `allow: false`). Missing → FIXABLE: add the child resources per [bicep-patterns-security.md](bicep-patterns-security.md) § Publishing Credential Lockdown. ⛔ **Skip Flex Consumption function apps** (`kind: 'functionapp,linux'` with `functionAppConfig`) — they must NOT have these policies; if present, FIXABLE: remove them (see [bicep-functions-flex.md](bicep-functions-flex.md)).
|
||||
2. **uniqueString in naming** — verify the App Service name uses `uniqueString()` or a unique suffix (not a hardcoded literal). Hardcoded names cause global collisions. Missing → FIXABLE: wrap name with `uniqueString(resourceGroup().id)`.
|
||||
|
||||
FIXABLE errors: fix the Bicep → re-run `az bicep build` → proceed to Step 3c.
|
||||
|
||||
### Step 3c — Record plan conformance result
|
||||
|
||||
The main thread (instructions.md Step 10a-conf) already ran the conformance script and passed you its JSON. Record it in `scaffold-manifest.json.conformance` = `{ passed, failures, source: "script" }`. ⛔ Do NOT set `validationResult.status: "Validated"` while any BLOCK failure is unresolved.
|
||||
|
||||
**Fallback** (only if the caller passed NO result AND `infra/main.bicep` exists — the gate is Bicep-only, skip for Terraform): run `{scaffoldDir}/scripts/scaffold-conformance.ps1 -SessionPath "{sessionPath}" -InfraPath infra` (or `.sh` on bash) yourself, then record with `source: "script"`. Never hand-judge when a shell is available.
|
||||
|
||||
### Step 4 — Write scaffold-manifest.json
|
||||
|
||||
Read [scaffold-schemas.ts](scaffold-schemas.ts) for exact field names.
|
||||
|
||||
**Do:** Write `scaffold-manifest.json` to the session folder with: `sessionId`, `scaffoldCompletedUtc`, `iacFormat`, `targetScope`, `entryPoint`, `parametersFile`, `files[]`, `deployCommand`, `twoPhaseWiring` (if Container Apps), `phase2Steps` (if applicable), `selfReview` (from caller input), `validationResult` (from Steps 2–3). Use the exact field names from [scaffold-schemas.ts](scaffold-schemas.ts) § `ScaffoldManifest`.
|
||||
|
||||
### Step 5 — Handle failures (if any)
|
||||
|
||||
Read [scaffold-healing-rules.md](scaffold-healing-rules.md) ONLY if validation failed.
|
||||
|
||||
**Do:** Classify errors as FIXABLE or BLOCKING. FIXABLE: auto-fix IaC → re-validate (max 3 attempts before asking user). BLOCKING: surface to user and halt. PLAN_LEVEL_CHANGE: requires re-approval — do NOT auto-fix.
|
||||
|
||||
### Step 6 — Verify deploy checklist exists
|
||||
|
||||
**Do:** Check that `.copilot-azure/sessions/{id}/deploy-checklist.md` exists (written by the parallel checklist subagent at scaffold Step 5b). If missing, read [`deploy-checklist-template.md`](../../deploy/references/deploy-checklist-template.md) and write it now as a fallback — fill `{placeholders}` from `prepare-plan.json`, delete non-applicable sections. This file survives conversation compaction
|
||||
|
||||
### Step 7 — Create deploy-result.json skeleton (MANDATORY)
|
||||
|
||||
Read [`deploy-schemas.ts`](../../deploy/references/deploy-schemas.ts) — specifically the `DeployResult` interface.
|
||||
|
||||
**Do:** Create `.copilot-azure/sessions/{id}/deploy-result.json` conforming to the `DeployResult` interface. Populate fields from all session artifacts already written (`prepare-plan.json`, `context.json`, `scaffold-manifest.json`, `prereq-output.json`). Use sensible defaults for fields the deploy phase will fill later. The deploy main agent updates this file in-place at Step 8 with real values.
|
||||
|
||||
### Step 8 — Return results
|
||||
|
||||
**Do:** Return validation status (`Validated` or `Failed`) to the caller. Confirm deploy-checklist.md exists. Keep status report ≤500 tokens.
|
||||
@@ -0,0 +1,186 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Terraform Patterns
|
||||
|
||||
Alternative-path patterns for AppOnboard scaffold. Used when `.tf` files detected or user overrides `iacFormat`. Per-resource config comes from `mcp_azure_mcp_azureterraformbestpractices` at runtime — this file covers layout, provider, naming, state, tagging, and wiring.
|
||||
|
||||
## File Structure
|
||||
|
||||
### Default (greenfield or user override)
|
||||
|
||||
```
|
||||
infra/
|
||||
├── main.tf # Root module — provider, resource group, module calls
|
||||
├── variables.tf # Input variables (all configurable values)
|
||||
├── outputs.tf # Exported values (endpoints, resource IDs)
|
||||
├── backend.tf # State backend config (local default, Azure Storage for prod)
|
||||
├── terraform.tfvars # Default variable values (from prepare-plan.json)
|
||||
└── modules/
|
||||
├── app-service/
|
||||
│ ├── main.tf
|
||||
│ ├── variables.tf
|
||||
│ └── outputs.tf
|
||||
├── container-app/
|
||||
├── sql-database/
|
||||
├── log-analytics/
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Non-Azure IaC coexistence (GCP/AWS TF already in repo)
|
||||
|
||||
When `detectedInfraProvider.terraform` is `"gcp"`, `"aws"`, or `"multi"` (without `azurerm`), write Azure TF to a **separate directory** from existing non-Azure TF. Never overwrite or modify existing IaC files.
|
||||
|
||||
**Output directory rule:** If existing TF is NOT at `infra/`, write to `infra/`. If existing TF IS at `infra/` (or any path containing `infra`), write to `infra-azure/`. Same module structure as default layout.
|
||||
|
||||
Each Azure service gets its own module. `main.tf` orchestrates resource group + module calls.
|
||||
|
||||
## Provider Configuration
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_version = ">= 1.5"
|
||||
|
||||
required_providers {
|
||||
azurerm = {
|
||||
source = "hashicorp/azurerm"
|
||||
version = "~> 4.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "azurerm" {
|
||||
features {}
|
||||
subscription_id = var.subscription_id
|
||||
resource_provider_registrations = "none"
|
||||
}
|
||||
```
|
||||
|
||||
> ⛔ Never pin to exact patch versions (e.g., `= 4.1.0`). Use `~> 4.0` to allow minor/patch updates. `azurerm` manages API versions internally — if a resource isn't available in `azurerm`, use `azapi_resource` with the latest stable ARM API version.
|
||||
|
||||
> **Conditional access (AADSTS530084):** `azurerm` provider re-requests tokens that violate device-binding policies. Fix: (1) switch to Bicep, or (2) use service principal auth (`ARM_CLIENT_ID` + `ARM_CLIENT_SECRET` + `ARM_TENANT_ID`).
|
||||
|
||||
## variables.tf
|
||||
|
||||
Required variables: `environment_name` (string, default "dev"), `location` (string, default "eastus"), `subscription_id` (string), `session_id` (string), `deployed_by` (string). All configurable values MUST be variables — no hardcoded regions, names, or SKUs.
|
||||
|
||||
## terraform.tfvars
|
||||
|
||||
Populate from `prepare-plan.json`: `environment_name`, `location`, `subscription_id`, `session_id`. See naming-patterns.md for naming convention.
|
||||
|
||||
## Backend
|
||||
|
||||
Local backend by default: `backend "local" { path = "terraform.tfstate" }`. Recommend Azure Storage backend in `postDeployRecommendations[]` for production.
|
||||
|
||||
## Resource Group
|
||||
|
||||
Use `rg-${var.project_name}-${var.environment_name}-${random_string.suffix.result}` with `tags = local.tags`. Suffix prevents collisions across AppOnboard sessions.
|
||||
|
||||
## Naming Convention
|
||||
|
||||
```hcl
|
||||
resource "random_string" "suffix" {
|
||||
length = 4
|
||||
special = false
|
||||
upper = false
|
||||
}
|
||||
|
||||
locals {
|
||||
# Pattern: {type}-{appname}-{env}-{suffix}
|
||||
app_name = "app-${var.environment_name}-${random_string.suffix.result}"
|
||||
sql_name = "sql-${var.environment_name}-${random_string.suffix.result}"
|
||||
# Storage/ACR: alphanumeric only, no hyphens
|
||||
storage_name = "st${replace(var.environment_name, "-", "")}${random_string.suffix.result}"
|
||||
acr_name = "cr${replace(var.environment_name, "-", "")}${random_string.suffix.result}"
|
||||
}
|
||||
```
|
||||
|
||||
Cross-reference naming with [prepare/references/naming-patterns.md](../../prepare/references/naming-patterns.md) — Terraform names must match `prepare-plan.json.naming.resources[]`.
|
||||
|
||||
## Resource Tags — Mandatory
|
||||
|
||||
Apply all 5 AppOnboard tags via `local.tags` — see [iac-generation-rules.md § Session Tags](iac-generation-rules.md) for tag names and values.
|
||||
|
||||
```hcl
|
||||
locals {
|
||||
tags = {
|
||||
"app-onboard-skill" = "true"
|
||||
"app-onboard-session-id" = var.session_id
|
||||
"created-at" = timestamp()
|
||||
"environment" = var.environment_name
|
||||
"deployed-by" = var.deployed_by
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ `timestamp()` changes on every plan. Add `lifecycle { ignore_changes = [tags["created-at"]] }` on every resource.
|
||||
|
||||
## Secrets — App-Internal Only, Stored On-Compute (No Key Vault)
|
||||
|
||||
Use `random_password` ONLY for app-internal secrets that are NOT an Azure resource credential (e.g. a Django `SECRET_KEY`, JWT signing key), and store the value **directly on the compute resource** as an app setting / container secret. ⛔ **No Key Vault.** Azure resource auth (database, cache, storage, Cosmos) is **managed-identity + token — never a generated password**.
|
||||
|
||||
```hcl
|
||||
resource "random_password" "app_secret_key" {
|
||||
length = 50
|
||||
special = true
|
||||
lifecycle { ignore_changes = [result] }
|
||||
}
|
||||
|
||||
# App Service — store directly as an app setting (no Key Vault):
|
||||
resource "azurerm_linux_web_app" "app" {
|
||||
# ...
|
||||
app_settings = {
|
||||
SECRET_KEY = random_password.app_secret_key.result
|
||||
}
|
||||
}
|
||||
# Container Apps — use a native `secret { name, value }` block + `env { secret_name = ... }`.
|
||||
```
|
||||
|
||||
> ⛔ NEVER use `random_string` for secrets — it is not marked `sensitive` in state. Always use `random_password`.
|
||||
> ⛔ **NEVER generate a database/cache/storage password or access key** (no `administrator_login_password`, no `random_password` for a DB). Those services use `azuread_authentication_only`/`password_auth_enabled = false`/`shared_access_key_enabled = false` + managed identity. See [bicep-patterns-security.md](bicep-patterns-security.md) § Data Services.
|
||||
|
||||
## Container Apps — Two-Phase Wiring
|
||||
|
||||
Same circular dependency as Bicep — see [bicep-container-apps.md](bicep-container-apps.md). Phase 1: placeholder image, no ACR refs. Phase 2: build + push, assign AcrPull, update via `az containerapp update --image` outside Terraform.
|
||||
|
||||
```hcl
|
||||
# Phase 1: Placeholder image
|
||||
resource "azurerm_container_app" "app" {
|
||||
template {
|
||||
container {
|
||||
name = "app"
|
||||
image = "mcr.microsoft.com/azuredocs/containerapps-helloworld:latest"
|
||||
cpu = 0.25
|
||||
memory = "0.5Gi"
|
||||
}
|
||||
}
|
||||
identity {
|
||||
type = "SystemAssigned"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## outputs.tf
|
||||
|
||||
Export: `resource_group_name`, `app_url` (https://${hostname}), `resource_ids` (list of all deployed resource IDs for deploy-result.json).
|
||||
|
||||
## Security Defaults
|
||||
|
||||
Apply same security rules as Bicep — see [bicep-patterns-security.md](bicep-patterns-security.md). Terraform-specific syntax:
|
||||
|
||||
| Rule | Terraform HCL |
|
||||
|------|---------------|
|
||||
| Managed identity (MANDATORY — all compute) | `identity { type = "SystemAssigned" }` |
|
||||
| ⛔ No DB admin password (SQL/PG/MySQL) | SQL: `azuread_authentication_only = true`; PostgreSQL: `authentication { password_auth_enabled = false, active_directory_auth_enabled = true, tenant_id = ... }` + `azurerm_postgresql_flexible_server_active_directory_administrator`; MySQL: `azurerm_mysql_flexible_server_active_directory_administrator` + `aad_auth_only`. NEVER `administrator_login_password`. |
|
||||
| ⛔ No storage/cache keys | Storage: `shared_access_key_enabled = false`; Redis: `azurerm_redis_cache` with `access_keys_authentication_enabled = false` + `azurerm_redis_cache_access_policy_assignment` for the app MI |
|
||||
| ⛔ No Key Vault | Do NOT create `azurerm_key_vault` / `azurerm_key_vault_secret`. App-internal secrets go directly into `app_settings` (App Service) or a native container `secret` block |
|
||||
| App-internal secret (on-compute) | `app_settings = { SECRET_KEY = random_password.app_secret_key.result }` — no `@Microsoft.KeyVault(...)` |
|
||||
| HTTPS only | `https_only = true`, `minimum_tls_version = "1.2"` |
|
||||
| Storage | `https_traffic_only_enabled = true`, `allow_nested_items_to_be_public = false`, `min_tls_version = "TLS1_2"` |
|
||||
| ⛔ Cosmos DB RBAC | `azurerm_cosmosdb_sql_role_assignment`, NOT `azurerm_role_assignment` + `local_authentication_disabled = true` — see [rbac-roles.md](rbac-roles.md) |
|
||||
| RBAC assignments | `principal_type = "ServicePrincipal"` REQUIRED — see [rbac-roles.md](rbac-roles.md) |
|
||||
| SCM/FTP auth | `scm.allow: true` (scaffold), `ftp.allow: false` (always) — use `azapi_resource` |
|
||||
@@ -0,0 +1,89 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# Validation, Manifest & Approval — Steps 10–12.5
|
||||
|
||||
## Step 10 — CI/CD
|
||||
|
||||
Do NOT auto-generate workflow files or create branches/PRs. Scaffold only writes IaC files. This step activates only when `context.json.repo.remote` is non-null AND user explicitly requests branch/PR creation. When `repo.remote` is absent or user declines, write IaC directly to working tree. If the user asks for CI/CD, or after deploy completes, suggest it as a follow-up: call `mcp_azure_mcp_deploy` → `deploy_pipeline_guidance_get` with `is-azd-project: false`, `pipeline-platform: "github-actions"`, `deploy-option: "provision-and-deploy"` and present the guidance for the user to apply.
|
||||
|
||||
## Step 11 — Validate Generated IaC
|
||||
|
||||
> ⛔ **Validation MUST happen BEFORE the manifest is written.** The manifest requires `validationResult` — you cannot write it without completing validation first. Do NOT write `scaffold-manifest.json` until validation has run.
|
||||
|
||||
> ⛔ Do NOT call other agents during scaffold/deploy — see [pipeline-rules.md](../../references/pipeline-rules.md).
|
||||
|
||||
Run these checks directly. All must pass.
|
||||
|
||||
**11a. Bicep compilation:**
|
||||
```powershell
|
||||
az bicep build --file infra/main.bicep --stdout > $null
|
||||
```
|
||||
(Bash: redirect to `/dev/null` instead of `$null`.) This discards the compiled ARM JSON; diagnostics still arrive on **stderr**, so read stderr — do not just check the exit code.
|
||||
|
||||
> ⛔ **Exit 0 does NOT mean the template is valid.** `az bicep build` exits 0 on `Warning BCP*` diagnostics, and those warnings describe a template that will not do what it says. Measured on bicep 0.46.1: a resource missing its required `location`, `sku` and `kind` compiles with **exit 0** and only `Warning BCP035`. An agent that trusts the exit code ships a resource with no SKU and reports it as validated.
|
||||
|
||||
**Pass criteria — all three must hold:**
|
||||
|
||||
| Signal | Verdict |
|
||||
|---|---|
|
||||
| Non-zero exit, or any `Error BCP*` | **Fail** — fix and retry |
|
||||
| `Warning BCP*` (e.g. `BCP035` missing required property, `BCP036` wrong property type) | **Fail** — fix and retry |
|
||||
| `BCP081` "does not have types available" | **Advisory only** — see below |
|
||||
| Linter warnings (`no-unused-params`, `prefer-interpolation`, …) | Pass — do not block |
|
||||
|
||||
`BCP081` is carved out because bicep ships a bundled type index that lags ARM, so it also fires on API versions that are real and current. It cannot distinguish an invented resource type from a merely newer one. Do not treat it as a failure, but do read it — if the resource type is genuinely misspelled, that is a real bug.
|
||||
|
||||
Fail: fix errors and retry.
|
||||
|
||||
**11b. Static RBAC review** — review generated Bicep for correct role assignments per [rbac-roles.md](rbac-roles.md). Every managed identity ↔ resource pair must have a `Microsoft.Authorization/roleAssignments` resource with the correct role GUID.
|
||||
|
||||
**11c. Write `validationResult`** — after all checks pass, write:
|
||||
```json
|
||||
{
|
||||
"validationResult": {
|
||||
"status": "Validated",
|
||||
"checks": [
|
||||
{ "name": "bicep build", "passed": true, "detail": "<the command you ran and what it reported>" },
|
||||
{ "name": "RBAC review", "passed": true, "detail": "<which identity/resource pairs you checked>" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> ⛔ **Field names are fixed — copy them exactly.** `validationResult` MUST match `ValidationResult` in [`scaffold-schemas.ts`](scaffold-schemas.ts): `status` + `checks[]`, where each check is `{ name, passed: boolean, detail?: string }`.
|
||||
> - `passed` is a **boolean** — not `"result": "PASS"`, not `"status"`.
|
||||
> - The array is `checks` — not `notes`, `warnings`, or `errors`.
|
||||
> - Do NOT use `ConformanceResult` (`{ passed, failures[], source }`) here. That is a **different type** for a different gate (the Step 3c plan-conformance script). `validationResult` is never shaped like it.
|
||||
>
|
||||
> ⛔ **`detail` must describe what you actually observed** — the command you ran and its real output. Do not copy the placeholder text above, and never write a `detail` claiming a command succeeded unless you ran it and it did.
|
||||
|
||||
**Terraform path:** Replace 11a with `terraform init -backend=false && terraform validate`.
|
||||
|
||||
- If validation finds FIXABLE errors: edit IaC → re-run self-review (L1–L4) → re-validate (max 3 attempts). ⛔ **You MUST read [scaffold-healing-rules.md](scaffold-healing-rules.md) before entering the healing loop** — it defines error classification (FIXABLE vs BLOCKING) and auto-fix strategies.
|
||||
- If BLOCKING errors remain after 3 attempts: surface to user and halt.
|
||||
|
||||
## Step 12 — Write `scaffold-manifest.json`
|
||||
|
||||
⛔ **You MUST read [`scaffold-schemas.ts`](scaffold-schemas.ts)** to get the exact `ScaffoldManifest` interface. Write to the session folder with ALL fields populated: `files[]`, `selfReview.findings[]`, AND `validationResult` (from Step 11). This is a single write — validation is already complete.
|
||||
|
||||
> ⛔ **Phase exit gate: `scaffold-manifest.json.validationResult` MUST NOT be null.** If validation ran: `{ status: 'Validated'/'Partial'/'Failed', checks: [{ name, passed, detail? }] }` (per `ValidationResult` in [`scaffold-schemas.ts`](scaffold-schemas.ts)). Null = incomplete scaffold.
|
||||
|
||||
**You MUST also update `context.json`** per `AppOnboardContext` in [`session-schemas.ts`](../../references/session-schemas.ts): append `"scaffold"` to `completedPhases`, set `currentPhase` to `"deploy"`, update `lastModifiedUtc`.
|
||||
|
||||
## Step 12.5 — Deploy Approval Gate
|
||||
|
||||
Present the user with: files generated, selfReview findings, **validation results** (pass/fail per check from Step 11), services + SKUs, secure-defaults applied. End with: **"Ready to deploy? (Yes / Run manually / Edit plan / Cancel)"** — do not continue until the user approves.
|
||||
|
||||
> ⛔ **Self-check before presenting the deploy gate.** Does `scaffold-manifest.json` contain a `validationResult` field with `status` set? If NO → you skipped Step 11. Go back and run validation. Do NOT present the deploy gate with `validationResult: null`.
|
||||
|
||||
> ⛔ **Quota gate — MANDATORY.** Read `prepare-plan.json.quotaValidation`. If `verified == false`, `method == "unverifiable"`, or `method` is not `"cli"` for quota-constrained services: read [`sku-quota-validation.md`](../../prepare/references/sku-quota-validation.md) § Deploy Gate Re-Validation and follow the procedure.
|
||||
> ⛔ Do NOT use `az vm list-usage`, `az appservice list-locations`, or `mcp_azure_mcp_quota` for quota checks — see Anti-Patterns in [`sku-quota-validation.md`](../../prepare/references/sku-quota-validation.md).
|
||||
|
||||
|
||||
> ⛔ **Azure service compatibility warnings.** Read `prereq-output.json.warnings[]` for any warnings with `fixPhase: "deploy-gate"`. Surface EACH at the deploy gate: "⚠️ Azure compatibility: {warning.summary}. Fix: {warning.fix}. Approve? (Yes / Skip / Cancel)". If the user skips, add to `postDeployRecommendations[]`.
|
||||
|
||||
> ⛔ **Phase exit — NOT complete until ALL done:**
|
||||
> 1. `scaffold-manifest.json` written with `files[]`, `selfReview.findings[]`, AND `validationResult`
|
||||
> 2. `context.json`: `"scaffold"` appended to `completedPhases`, `currentPhase` → `"deploy"`, `lastModifiedUtc` updated
|
||||
> 3. `deploy-checklist.md` exists in session folder — written by the parallel checklist subagent at scaffold Step 5b (before IaC gen completes). Verify it exists. If missing (subagent failed), write it now from [`deploy-checklist-template.md`](../../deploy/references/deploy-checklist-template.md) — fill in real values from `prepare-plan.json`, delete unrelated compute sections.
|
||||
@@ -0,0 +1,47 @@
|
||||
<!-- azure-cor-disclaimer -->
|
||||
> **Important:** This skill provides guidance and recommended instructions to assist the AI system. Outputs are not guaranteed to be complete, correct, secure, or applicable to every scenario. Results should be reviewed and validated by a human before being applied. The AI model may choose not to follow all instructions exactly, and additional verification may be required.
|
||||
|
||||
# WAF Checklist
|
||||
|
||||
Per-pillar Well-Architected Framework alignment for AppOnboard-generated infrastructure. Use during scaffold self-review (Layer 4) and prepare validation (WAF Alignment dimension).
|
||||
|
||||
> **Reference:** [Azure Well-Architected Framework](https://learn.microsoft.com/en-us/azure/well-architected/) — link, don't duplicate. See [WAF Service Guides](https://learn.microsoft.com/en-us/azure/well-architected/service-guides/) for per-service checklists.
|
||||
|
||||
## Reliability
|
||||
|
||||
- Zone redundancy enabled for production SKUs (App Service P1v3+, SQL Premium, Redis Premium)
|
||||
- Health probes configured (Container Apps liveness/readiness, App Service `/health`)
|
||||
- GRS storage for production data (Standard_GRS or RA-GRS)
|
||||
- Retry policies in application code for transient failures
|
||||
- Min replicas ≥ 1 for production Container Apps (no cold-start)
|
||||
|
||||
## Security
|
||||
|
||||
- System-assigned managed identity on all services
|
||||
- App-internal secrets stored on-compute (App Service app settings / CA native secrets) — ⛔ NO Key Vault; no inline/committed values
|
||||
- HTTPS-only + TLS 1.2+ on all endpoints
|
||||
- No public blob access on storage accounts
|
||||
- Private endpoints where budget allows (balanced/performance tiers)
|
||||
- No `administratorLogin`/password or access key on ANY data service (SQL, PostgreSQL, MySQL, Redis, Storage, Cosmos) — Entra / managed-identity-only auth
|
||||
- Compute floor B1 / SWA Standard — no F1/D1/Free (managed identity is mandatory)
|
||||
|
||||
## Cost Optimization
|
||||
|
||||
- SKU matches `prepare-plan.json` budget tier — no over-provisioning
|
||||
- Scale-to-zero enabled for dev/test Container Apps
|
||||
- Free tier grants applied in cost estimate (see [pricing-guide.md](../../prepare/references/pricing-guide.md) § Free Grants Summary)
|
||||
- Reserved instances noted as option for production (don't auto-apply)
|
||||
|
||||
## Operational Excellence
|
||||
|
||||
- `diagnostic-settings` is not scaffolded; if the generator added one it MUST be gated behind `enableDiagnostics` (default `false`) or absent — never wired unconditionally (that blocks the first deploy).
|
||||
- Application Insights connected for APM
|
||||
- Resource tagging: `app-onboard-skill`, `app-onboard-session-id`, `created-at` (see `bicep-patterns.md` § Service Tagging or `terraform-patterns.md` § Resource Tags)
|
||||
- All configurable values parameterized (no hardcoded regions, names, SKUs)
|
||||
|
||||
## Performance Efficiency
|
||||
|
||||
- Autoscale rules for production SKUs (Container Apps max replicas, App Service auto-scale)
|
||||
- CDN for static assets when SPA frontend detected
|
||||
- Connection pooling for database access
|
||||
- Appropriate cache tier (Redis) when session/cache pattern detected
|
||||
Reference in New Issue
Block a user