Bicep Accelerator
Deep dive into ALZ Bicep for deploying Azure Landing Zones.
ALZ Bicep Overview
ALZ Bicep is Microsoft's official modular Bicep implementation of Azure Landing Zones.
Repository: Azure/ALZ-Bicep
Prerequisites
Required Permissions
| Scope | Role | Purpose |
|---|---|---|
| Tenant | Owner | Create management groups |
| Tenant | User Access Administrator | Assign RBAC |
| Root MG | Policy Contributor | Create policy definitions |
| Subscriptions | Owner | Deploy resources |
Tools Setup
# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Install Bicep
az bicep install
az bicep upgrade
# Verify
az bicep version
# Login
az login --tenant <tenant-id>
az account set --subscription <management-subscription-id>
Deployment Phases
Phase Overview
Phase 1: Management Groups
Module: managementGroups.bicep
targetScope = 'tenant'
@description('Prefix for the management group hierarchy')
@minLength(2)
@maxLength(10)
param parTopLevelManagementGroupPrefix string = 'alz'
@description('Display name for top-level management group')
param parTopLevelManagementGroupDisplayName string = 'Azure Landing Zones'
@description('Optional suffix for management group names')
param parTopLevelManagementGroupSuffix string = ''
@description('Deploys Corp & Online Management Groups under Landing Zones')
param parLandingZoneMgAlzDefaultsEnable bool = true
@description('Deploys Confidential Corp & Confidential Online Management Groups')
param parLandingZoneMgConfidentialEnable bool = false
// Variables
var varTopLevelManagementGroupId = '${parTopLevelManagementGroupPrefix}${parTopLevelManagementGroupSuffix}'
// Resources
resource resTopLevelMg 'Microsoft.Management/managementGroups@2021-04-01' = {
name: varTopLevelManagementGroupId
properties: {
displayName: parTopLevelManagementGroupDisplayName
}
}
resource resPlatformMg 'Microsoft.Management/managementGroups@2021-04-01' = {
name: '${varTopLevelManagementGroupId}-platform'
properties: {
displayName: 'Platform'
details: {
parent: {
id: resTopLevelMg.id
}
}
}
}
resource resIdentityMg 'Microsoft.Management/managementGroups@2021-04-01' = {
name: '${varTopLevelManagementGroupId}-platform-identity'
properties: {
displayName: 'Identity'
details: {
parent: {
id: resPlatformMg.id
}
}
}
}
resource resManagementMg 'Microsoft.Management/managementGroups@2021-04-01' = {
name: '${varTopLevelManagementGroupId}-platform-management'
properties: {
displayName: 'Management'
details: {
parent: {
id: resPlatformMg.id
}
}
}
}
resource resConnectivityMg 'Microsoft.Management/managementGroups@2021-04-01' = {
name: '${varTopLevelManagementGroupId}-platform-connectivity'
properties: {
displayName: 'Connectivity'
details: {
parent: {
id: resPlatformMg.id
}
}
}
}
resource resLandingZonesMg 'Microsoft.Management/managementGroups@2021-04-01' = {
name: '${varTopLevelManagementGroupId}-landingzones'
properties: {
displayName: 'Landing Zones'
details: {
parent: {
id: resTopLevelMg.id
}
}
}
}
resource resCorpMg 'Microsoft.Management/managementGroups@2021-04-01' = if (parLandingZoneMgAlzDefaultsEnable) {
name: '${varTopLevelManagementGroupId}-landingzones-corp'
properties: {
displayName: 'Corp'
details: {
parent: {
id: resLandingZonesMg.id
}
}
}
}
resource resOnlineMg 'Microsoft.Management/managementGroups@2021-04-01' = if (parLandingZoneMgAlzDefaultsEnable) {
name: '${varTopLevelManagementGroupId}-landingzones-online'
properties: {
displayName: 'Online'
details: {
parent: {
id: resLandingZonesMg.id
}
}
}
}
resource resSandboxesMg 'Microsoft.Management/managementGroups@2021-04-01' = {
name: '${varTopLevelManagementGroupId}-sandboxes'
properties: {
displayName: 'Sandboxes'
details: {
parent: {
id: resTopLevelMg.id
}
}
}
}
resource resDecommissionedMg 'Microsoft.Management/managementGroups@2021-04-01' = {
name: '${varTopLevelManagementGroupId}-decommissioned'
properties: {
displayName: 'Decommissioned'
details: {
parent: {
id: resTopLevelMg.id
}
}
}
}
// Outputs
output outTopLevelMgId string = resTopLevelMg.id
output outPlatformMgId string = resPlatformMg.id
output outIdentityMgId string = resIdentityMg.id
output outManagementMgId string = resManagementMg.id
output outConnectivityMgId string = resConnectivityMg.id
output outLandingZonesMgId string = resLandingZonesMg.id
output outCorpMgId string = parLandingZoneMgAlzDefaultsEnable ? resCorpMg.id : ''
output outOnlineMgId string = parLandingZoneMgAlzDefaultsEnable ? resOnlineMg.id : ''
Deploy Management Groups
az deployment tenant create \
--name "alz-management-groups" \
--location eastus \
--template-file ./modules/managementGroups/managementGroups.bicep \
--parameters parTopLevelManagementGroupPrefix=alz
Phase 2: Policy Definitions
Custom Policy Example
targetScope = 'managementGroup'
@description('Management Group ID')
param parTargetManagementGroupId string
// Deploy-Diagnostics-LogAnalytics Policy
resource policyDiagnostics 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
name: 'Deploy-Diagnostics-LogAnalytics'
properties: {
displayName: 'Deploy Diagnostic Settings for all resources'
description: 'Deploys diagnostic settings for all supported resources to Log Analytics'
policyType: 'Custom'
mode: 'All'
metadata: {
version: '1.0.0'
category: 'Monitoring'
source: 'ALZ Bicep'
}
parameters: {
logAnalytics: {
type: 'String'
metadata: {
displayName: 'Log Analytics workspace'
description: 'Resource ID of the Log Analytics workspace'
strongType: 'omsWorkspace'
}
}
}
policyRule: {
if: {
field: 'type'
equals: 'Microsoft.Resources/subscriptions'
}
then: {
effect: 'DeployIfNotExists'
details: {
type: 'Microsoft.Insights/diagnosticSettings'
name: 'setByPolicy'
deploymentScope: 'subscription'
existenceScope: 'subscription'
existenceCondition: {
allOf: [
{
field: 'Microsoft.Insights/diagnosticSettings/workspaceId'
equals: '[parameters(\'logAnalytics\')]'
}
]
}
deployment: {
location: 'eastus'
properties: {
mode: 'incremental'
template: {
'$schema': 'https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#'
contentVersion: '1.0.0.0'
parameters: {
logAnalytics: {
type: 'string'
}
}
resources: [
{
type: 'Microsoft.Insights/diagnosticSettings'
apiVersion: '2021-05-01-preview'
name: 'setByPolicy'
properties: {
workspaceId: '[parameters(\'logAnalytics\')]'
logs: [
{
category: 'Administrative'
enabled: true
}
{
category: 'Security'
enabled: true
}
{
category: 'ServiceHealth'
enabled: true
}
{
category: 'Alert'
enabled: true
}
{
category: 'Recommendation'
enabled: true
}
{
category: 'Policy'
enabled: true
}
{
category: 'Autoscale'
enabled: true
}
{
category: 'ResourceHealth'
enabled: true
}
]
}
}
]
}
parameters: {
logAnalytics: {
value: '[parameters(\'logAnalytics\')]'
}
}
}
}
roleDefinitionIds: [
'/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa'
'/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293'
]
}
}
}
}
}
Phase 3: Logging
Module: logging.bicep
targetScope = 'resourceGroup'
@description('Location for resources')
param parLocation string = resourceGroup().location
@description('Log Analytics Workspace name')
param parLogAnalyticsWorkspaceName string = 'log-platform-${parLocation}'
@description('Log Analytics retention in days')
@minValue(30)
@maxValue(730)
param parLogAnalyticsWorkspaceRetention int = 365
@description('Automation Account name')
param parAutomationAccountName string = 'aa-platform-${parLocation}'
@description('Tags')
param parTags object = {}
// Log Analytics Workspace
resource resLogAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
name: parLogAnalyticsWorkspaceName
location: parLocation
tags: parTags
properties: {
sku: {
name: 'PerGB2018'
}
retentionInDays: parLogAnalyticsWorkspaceRetention
features: {
enableLogAccessUsingOnlyResourcePermissions: true
}
}
}
// Automation Account
resource resAutomationAccount 'Microsoft.Automation/automationAccounts@2022-08-08' = {
name: parAutomationAccountName
location: parLocation
tags: parTags
identity: {
type: 'SystemAssigned'
}
properties: {
sku: {
name: 'Basic'
}
encryption: {
keySource: 'Microsoft.Automation'
}
}
}
// Link Automation to Log Analytics
resource resAutomationAccountDiagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
scope: resAutomationAccount
name: 'toLogAnalytics'
properties: {
workspaceId: resLogAnalyticsWorkspace.id
logs: [
{
categoryGroup: 'allLogs'
enabled: true
}
]
metrics: [
{
category: 'AllMetrics'
enabled: true
}
]
}
}
// Solutions
resource resVmInsights 'Microsoft.OperationsManagement/solutions@2015-11-01-preview' = {
name: 'VMInsights(${resLogAnalyticsWorkspace.name})'
location: parLocation
tags: parTags
properties: {
workspaceResourceId: resLogAnalyticsWorkspace.id
}
plan: {
name: 'VMInsights(${resLogAnalyticsWorkspace.name})'
publisher: 'Microsoft'
product: 'OMSGallery/VMInsights'
promotionCode: ''
}
}
resource resSecurity 'Microsoft.OperationsManagement/solutions@2015-11-01-preview' = {
name: 'Security(${resLogAnalyticsWorkspace.name})'
location: parLocation
tags: parTags
properties: {
workspaceResourceId: resLogAnalyticsWorkspace.id
}
plan: {
name: 'Security(${resLogAnalyticsWorkspace.name})'
publisher: 'Microsoft'
product: 'OMSGallery/Security'
promotionCode: ''
}
}
// Outputs
output outLogAnalyticsWorkspaceId string = resLogAnalyticsWorkspace.id
output outLogAnalyticsWorkspaceName string = resLogAnalyticsWorkspace.name
output outAutomationAccountId string = resAutomationAccount.id
Phase 4: Hub Networking
Module: hubNetworking.bicep
targetScope = 'resourceGroup'
@description('Location')
param parLocation string = resourceGroup().location
@description('Hub VNet name')
param parHubNetworkName string = 'vnet-hub-${parLocation}'
@description('Hub VNet address prefix')
param parHubNetworkAddressPrefix string = '10.0.0.0/16'
@description('Enable Azure Firewall')
param parAzFirewallEnabled bool = true
@description('Azure Firewall Tier')
@allowed(['Basic', 'Standard', 'Premium'])
param parAzFirewallTier string = 'Premium'
@description('Enable Bastion')
param parBastionEnabled bool = true
@description('Tags')
param parTags object = {}
// Variables
var varSubnets = [
{
name: 'GatewaySubnet'
addressPrefix: cidrSubnet(parHubNetworkAddressPrefix, 24, 0) // 10.0.0.0/24
}
{
name: 'AzureFirewallSubnet'
addressPrefix: cidrSubnet(parHubNetworkAddressPrefix, 24, 1) // 10.0.1.0/24
}
{
name: 'AzureFirewallManagementSubnet'
addressPrefix: cidrSubnet(parHubNetworkAddressPrefix, 24, 2) // 10.0.2.0/24
}
{
name: 'AzureBastionSubnet'
addressPrefix: cidrSubnet(parHubNetworkAddressPrefix, 24, 3) // 10.0.3.0/24
}
]
// Hub VNet
resource resHubVnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
name: parHubNetworkName
location: parLocation
tags: parTags
properties: {
addressSpace: {
addressPrefixes: [parHubNetworkAddressPrefix]
}
subnets: [for subnet in varSubnets: {
name: subnet.name
properties: {
addressPrefix: subnet.addressPrefix
}
}]
}
}
// Azure Firewall
resource resFirewallPip 'Microsoft.Network/publicIPAddresses@2023-05-01' = if (parAzFirewallEnabled) {
name: 'pip-afw-${parLocation}'
location: parLocation
tags: parTags
sku: {
name: 'Standard'
tier: 'Regional'
}
zones: ['1', '2', '3']
properties: {
publicIPAllocationMethod: 'Static'
publicIPAddressVersion: 'IPv4'
}
}
resource resFirewallPolicy 'Microsoft.Network/firewallPolicies@2023-05-01' = if (parAzFirewallEnabled) {
name: 'afwp-${parLocation}'
location: parLocation
tags: parTags
properties: {
sku: {
tier: parAzFirewallTier
}
threatIntelMode: 'Deny'
intrusionDetection: parAzFirewallTier == 'Premium' ? {
mode: 'Deny'
} : null
dnsSettings: {
enableProxy: true
}
}
}
resource resFirewall 'Microsoft.Network/azureFirewalls@2023-05-01' = if (parAzFirewallEnabled) {
name: 'afw-${parLocation}'
location: parLocation
tags: parTags
zones: ['1', '2', '3']
properties: {
sku: {
name: 'AZFW_VNet'
tier: parAzFirewallTier
}
firewallPolicy: {
id: resFirewallPolicy.id
}
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
subnet: {
id: '${resHubVnet.id}/subnets/AzureFirewallSubnet'
}
publicIPAddress: {
id: resFirewallPip.id
}
}
}
]
}
}
// Bastion
resource resBastionPip 'Microsoft.Network/publicIPAddresses@2023-05-01' = if (parBastionEnabled) {
name: 'pip-bastion-${parLocation}'
location: parLocation
tags: parTags
sku: {
name: 'Standard'
tier: 'Regional'
}
zones: ['1', '2', '3']
properties: {
publicIPAllocationMethod: 'Static'
publicIPAddressVersion: 'IPv4'
}
}
resource resBastion 'Microsoft.Network/bastionHosts@2023-05-01' = if (parBastionEnabled) {
name: 'bastion-${parLocation}'
location: parLocation
tags: parTags
sku: {
name: 'Standard'
}
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
subnet: {
id: '${resHubVnet.id}/subnets/AzureBastionSubnet'
}
publicIPAddress: {
id: resBastionPip.id
}
}
}
]
enableFileCopy: true
enableIpConnect: true
enableShareableLink: false
enableTunneling: true
}
}
// Outputs
output outHubVnetId string = resHubVnet.id
output outFirewallPrivateIp string = parAzFirewallEnabled ? resFirewall.properties.ipConfigurations[0].properties.privateIPAddress : ''
Phase 5: Policy Assignments
Orchestration Module
targetScope = 'managementGroup'
@description('Management Group ID')
param parTopLevelManagementGroupId string
@description('Log Analytics Workspace ID')
param parLogAnalyticsWorkspaceId string
@description('Location for deployment')
param parLocation string = 'eastus'
// Security Baseline at Root
resource resSecurityBaseline 'Microsoft.Authorization/policyAssignments@2022-06-01' = {
name: 'Deploy-ASB'
location: parLocation
identity: {
type: 'SystemAssigned'
}
properties: {
policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/1f3afdf9-d0c9-4c3d-847f-89da613e70a8'
displayName: 'Azure Security Benchmark'
enforcementMode: 'Default'
}
}
// Diagnostics at Root
resource resDiagnostics 'Microsoft.Authorization/policyAssignments@2022-06-01' = {
name: 'Deploy-Diagnostics'
location: parLocation
identity: {
type: 'SystemAssigned'
}
properties: {
policyDefinitionId: tenantResourceId('Microsoft.Authorization/policyDefinitions', 'Deploy-Diagnostics-LogAnalytics')
displayName: 'Deploy Diagnostic Settings'
enforcementMode: 'Default'
parameters: {
logAnalytics: {
value: parLogAnalyticsWorkspaceId
}
}
}
}
// Role assignment for remediation
resource resRemediationRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(managementGroup().id, 'Monitoring Contributor', resSecurityBaseline.name)
properties: {
roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa'
principalId: resSecurityBaseline.identity.principalId
principalType: 'ServicePrincipal'
}
}
Full Deployment Script
#!/bin/bash
# Variables
LOCATION="eastus"
PREFIX="alz"
TENANT_ID="<your-tenant-id>"
MANAGEMENT_SUB_ID="<management-subscription-id>"
CONNECTIVITY_SUB_ID="<connectivity-subscription-id>"
# Phase 1: Management Groups
echo "Deploying Management Groups..."
az deployment tenant create \
--name "alz-mg-$(date +%Y%m%d%H%M%S)" \
--location $LOCATION \
--template-file ./modules/managementGroups/managementGroups.bicep \
--parameters parTopLevelManagementGroupPrefix=$PREFIX
# Phase 2: Custom Policies
echo "Deploying Custom Policies..."
az deployment mg create \
--name "alz-policies-$(date +%Y%m%d%H%M%S)" \
--location $LOCATION \
--management-group-id $PREFIX \
--template-file ./modules/policy/definitions/customPolicyDefinitions.bicep
# Phase 3: Logging (in Management subscription)
echo "Deploying Logging..."
az account set --subscription $MANAGEMENT_SUB_ID
az group create --name "rg-management-$LOCATION" --location $LOCATION
az deployment group create \
--name "alz-logging-$(date +%Y%m%d%H%M%S)" \
--resource-group "rg-management-$LOCATION" \
--template-file ./modules/logging/logging.bicep \
--parameters parLocation=$LOCATION
# Get Log Analytics Workspace ID
LAW_ID=$(az monitor log-analytics workspace show \
--resource-group "rg-management-$LOCATION" \
--workspace-name "log-platform-$LOCATION" \
--query id -o tsv)
# Phase 4: Hub Networking (in Connectivity subscription)
echo "Deploying Hub Network..."
az account set --subscription $CONNECTIVITY_SUB_ID
az group create --name "rg-connectivity-$LOCATION" --location $LOCATION
az deployment group create \
--name "alz-hub-$(date +%Y%m%d%H%M%S)" \
--resource-group "rg-connectivity-$LOCATION" \
--template-file ./modules/hubNetworking/hubNetworking.bicep \
--parameters parLocation=$LOCATION
# Phase 5: Policy Assignments
echo "Deploying Policy Assignments..."
az deployment mg create \
--name "alz-assignments-$(date +%Y%m%d%H%M%S)" \
--location $LOCATION \
--management-group-id $PREFIX \
--template-file ./orchestration/policyAssignments.bicep \
--parameters parLogAnalyticsWorkspaceId=$LAW_ID
echo "Deployment complete!"
Quick Reference Card
| Phase | Scope | Module |
|---|---|---|
| 1 | Tenant | managementGroups.bicep |
| 2 | MG | customPolicyDefinitions.bicep |
| 3 | Subscription | logging.bicep |
| 4 | Subscription | hubNetworking.bicep |
| 5 | MG | policyAssignments.bicep |
Next Steps
Continue to Terraform Module for the CAF Terraform implementation.