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. For data module templates (PostgreSQL, Redis), see subagent-iac-gen.md Step 6.
Key Vault Deployer RBAC
The deploying user/principal needs RBAC to write secrets (scaffold seeds initial values) and read them (verify wiring):
- Key Vault Secrets Officer (
b86a8fe4-44ce-4948-aee5-eccb2c155cd7) โ write secrets - Key Vault Secrets User (
4633458b-17de-408a-b874-0445c86b69e6) โ read secrets (also needed by app MI)
If the app seeds data using a generated secret (admin password, API key), either display it to the user at deploy time OR ensure the deployer has read RBAC on the Key Vault.
โ Include a role assignment for the deploying user (
context.json.azure.userObjectId) with Key Vault Secrets Officer scoped to the Key Vault resource. Without this,az keyvault secret setfails with 403 during deploy secret seeding.
Security Defaults
Source: Adapted from Azure security best practices. See Azure security baseline for updates.
Identity โ Managed Identity Everywhere
โ Managed identity decision โ evaluate top to bottom, first match wins.
Condition Include MI? F1 or D1 SKU on Linux NO (MI sidecar causes OOM โ use @secure()param + KV deployer RBAC instead)Any Key Vault, database, storage, queue, or ACR access YES None of the above YES (default secure)
- System-assigned managed identity for all services (default). User-assigned only when shared identity is explicitly needed.
- โ Never generate
administratorLoginoradministratorLoginPasswordfor SQL โ including inside conditional branches. Use Entra-only auth (see SQL Server pattern below). - App-to-service auth: managed identity + RBAC role assignments. Zero secrets in code or config.
identity: {
type: 'SystemAssigned'
}SQL Server โ Entra-Only Authentication
For full SQL auth reference (connection strings, managed identity SQL grants, CI/CD principal types), see
azure-prepare/references/services/sql-database/auth.md.
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
principalTypeto'Application'. The default'User'only works for interactive deployments.
Secrets โ Key Vault References
Store secrets in Key Vault. Reference via app settings โ never inline.
โ No plaintext secrets in Bicep
appSettings. Values likeSECRET_KEY,JWT_SECRET,API_KEY, session secrets, and database passwords MUST NOT be hardcoded โ not even as placeholders. Never useuniqueString()for secrets (deterministic/predictable). These appear in ARM deployment history and persist in source control.Container Apps exception: Phase 1 of two-phase deployment uses
secrets: []โ NO secrets at all (not plaintext, not KV). KVsecretRefentries are activated in Phase 2 after RBAC propagates. See bicep-container-apps.md ยง Two-Phase Wiring.โ Container Apps KV URL โ do NOT use
environment().suffixes.keyvaultDns. That function returns.vault.azure.net(WITH leading dot) โ double-dot URL โContainerAppSecretKeyVaultUrlInvalid. Use'https://${kvName}.vault.azure.net/secrets/...'with#disable-next-line no-hardcoded-env-urlsto suppress the linter.Correct patterns:
- Key Vault reference (preferred):
'@Microsoft.KeyVault(VaultName=${kvName};SecretName=secret-key)'- Deploy-time seeding (free-tier): Omit from Bicep; run
az webapp config appsettings set --settings SECRET_KEY=$(openssl rand -base64 32)post-deploy- Bicep
@secure()parameter: Pass via CLI--parameters secretKey=$(openssl rand -base64 32)โ never committed to parameters.jsonโ NEVER:
{ name: 'SECRET_KEY', value: 'hard-to-guess-string' }orvalue: 'change-me'in Bicep
// App Service / Functions โ Key Vault reference pattern
appSettings: [
{
name: 'DB_CONNECTION_STRING'
value: '@Microsoft.KeyVault(VaultName=${kvName};SecretName=db-connection-string)'
}
]Key Vault module โ emit this resource EXACTLY; add no other properties. enablePurgeProtection is deliberately absent (ARM rejects false; true blocks cleanup).
resource kv 'Microsoft.KeyVault/vaults@{apiVersion}' = {
name: kvName
location: location
tags: tags
properties: {
sku: { family: 'A', name: 'standard' }
tenantId: subscription().tenantId
enableRbacAuthorization: true // RBAC, not access policies
enableSoftDelete: true
softDeleteRetentionInDays: 7
networkAcls: { defaultAction: 'Allow', bypass: 'AzureServices' }
}
}Transport โ HTTPS Only
All web-facing resources:
// App Service
httpsOnly: true
siteConfig: {
minTlsVersion: '1.2'
}
// Storage
supportsHttpsTrafficOnly: true
allowBlobPublicAccess: false
minimumTlsVersion: 'TLS1_2'App Service / Functions โ Publishing Credential Lockdown
โ Every App Service and Functions app MUST include both
basicPublishingCredentialsPolicieschild resources. Missing these means deploy cannot toggle SCM auth post-deployment โ the REST API call targets a resource that doesn't exist in ARM.
// 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: truesoaz webapp deployworks. After code upload + health check, deploy phase runsaz rest --method put .../basicPublishingCredentialsPolicies/scmwithallow: falseto 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 ยง 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.
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
}
}