Skip to main content

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

ScopeRolePurpose
TenantOwnerCreate management groups
TenantUser Access AdministratorAssign RBAC
Root MGPolicy ContributorCreate policy definitions
SubscriptionsOwnerDeploy 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

PhaseScopeModule
1TenantmanagementGroups.bicep
2MGcustomPolicyDefinitions.bicep
3Subscriptionlogging.bicep
4SubscriptionhubNetworking.bicep
5MGpolicyAssignments.bicep

Next Steps

Continue to Terraform Module for the CAF Terraform implementation.