Azure Enterprise Policy as Code – Azure Landing Zones Integration
Published Oct 03 2022 12:26 AM 6,034 Views

Welcome to Part 2 in a series about using the Enterprise Policy as Code project to deploy and manage Azure Policy in your environment. This article covers integration with Azure Landing Zones and how to integrate the policies applied in that solution with this code.


Later parts in this series discuss different applications and features of the Enterprise Policy as Code project and these are detailed below.

  • Getting Started
  • Integrating with the Cloud Adoption Framework (CAF) Azure Landing Zones (this article)
  • Dealing with brownfield environments and decentralized policy management
  • How do you manage multiple tenants
  • Tips for writing custom policies and initiatives

When I was introduced to this project it was already very mature – but with no ability to automate the inclusion of the policies and assignments that Microsoft recommends as part of Azure Landing Zones. The policies included in this reference architecture include controls such as:

  • Ensuring Microsoft Defender for Cloud is enabled
  • Ensuring the Azure Security Benchmark is deployed
  • Controlling resource diagnostics
  • Several other security controls such as the use of TLS and HTTPS

While not a complete list they give an excellent base to any Landing Zone deployment – and as such are included in the Azure Landing Zone accelerators which are available for anyone to deploy in their environment either via the Azure Portal, Terraform, or Bicep.


As well as the included policies and initiatives there are also default assignments available at the different scopes in the Azure Landing Zone management group hierarchy.


The script and assignment template provided in the project to synchronize the policies works under the assumption that you have followed the diagram above to create your management group structure. If your structure is different from above, you will need to review the included policy assignments and adjust them for your environment.


Let's look at how this works.


Downloading the Policies

Before running ensure your repository is up to date with the main by updating your fork and running the Sync-Repo script. If there are changes to the policy assignments which are made upstream, they will be updated in your local environment.


There is a single script in the project located at “.\Scripts\Deploy\CloudAdoptionFramework\Sync-CAFPolicies.ps1”


Running this script will download all the policies and initiatives from the main Azure Landing Zones repository and split up the mega policy file into its individual policy and initiative files. It will also copy the template assignments into your project. Each new policy and assignment is put into a CAF folder so it doesn’t conflict with any policies you already have.


As of writing this article it will download 115 policies, 7 initiative and 6 assignments – the best part is that running the script again later will resynchronize any policy changes from the source and if your repository is under source control they will show as file changes. This allows you to easily view new policies and initiatives added to the Azure Landing Zone defaults.


NOTE: The Azure Landing Zone repository has recently changed to splitting the main policies.json file into individual files to make it easier to update. This project currently reads the compiled file but updates to the sync script in the future will take advantage of this change.


Adding a Deployment Scope and Parameters

Each assignment file contains a placeholder for a deployment scope – you will need to update this to match your management group structure – and also the reflect the environments you have specified in your global-settings.jsonc file (Check the link for a reminder of how this file works and the previous post for an example).


For instance, the CAF-RootMG-Default.json assignment file contains the section below by default.alz1.png

I need to update the “tenant1” key to match my global settings – and I also must fix the management group Id to match my environment (Contoso). So, I would modify it to look like the below.


For the other assignment files, you need to update their scope as well – the file names are representative of which management group they are scoped to e.g. CAF-Connectivity-Default.json is aligned to the Connectivity management group. Continue to do this until all the files have the correct scope.


There are also some parameters exposed in the assignment files – you will need to provide values for those to be applied during the assignment process – for instance, the CAF-ManagementMG-Default.json file needs a Log Analytics workspace resource Id added to a parameter as below.


The main parameters are presented at the top of each assignment file however there are some others lower down within the environment structure so it is a good idea to review them as well.


When these parameters are filled in you can move on to the deployment.


Policy Deployment

Now we can just follow the process as detailed in the previous post. (Refer to the section on Use Cases for how to run the scripts)


  • Run the Build-AzPoliciesInitiativesAssignmentsPlan.ps1 file to generate the policy plan.

In a deployment where there are no other policies the plan output should look like below:


You can also review the output from the JSON file in the Outputs directory to show exactly what is going to be deployed.


  • Run the Deploy-AzPoliciesInitiativesAssignmentsFromPlan.ps1 script and the policies will be deployed into the environment.

When this is done the output should contain something like the image below, while the code has deployed all the policies, initiatives and assignment – the roles haven’t been added to the managed identities yet.


You can review the output in the JSON file to see which roles are going to be deployed. Once complete run the code below to deploy the role assignments.


  • Run the Set-AzPolicyRolesFromPlan.ps1 file to deploy the roles.

Once this is complete, I can check the permissions in the portal and view that each of the policy assignments has the managed identity assigned the correct roles.


This approach will work well to manage Azure Policy when you are using either the ARM template deployments for Azure Landing Zones or the Bicep version. If you are using the Terraform module to deploy ALZ you will need to make changes to the module to decouple the policy deployments from the built-in archetypes.


To do this I have created a set of archetype exclusions which will remove the default policies and assignments from the Terraform deployed Azure Landing Zone. The archetypes are available for downloading here and instructions for incorporating them into the Terraform project are available in the ALZ Terraform Wiki. Be aware if you have already made changes to the base archetypes to test thoroughly when incorporating these files.


So, there you have it – the Enterprise Policy as Code project extended to cover Azure Landing Zones. Using this project allows you to better test the effects of incoming policies across multiple tenants and environments from a single repository.



The sample scripts are not supported under any Microsoft standard support program or service. The sample scripts are provided AS IS without warranty of any kind. Microsoft further disclaims all implied warranties including, without limitation, any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the sample scripts and documentation remains with you. In no event shall Microsoft, its authors, or anyone else involved in the creation, production, or delivery of the scripts be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, or other pecuniary loss) arising out of the use of or inability to use the sample scripts or documentation, even if Microsoft has been advised of the possibility of such damages.


Version history
Last update:
‎Oct 03 2022 12:27 AM
Updated by: