Bicep and Azure Policy: Create an Azure Policy Set (or Policy Initiative)
Tags: azure, governance, powershell, bicep
Published: 2022-09-29 15:35:02

This part article is part of a series on Azure Policy, Azure Bicep, and Azure PowerShell 

There are many things you can automate by using Bicep and PowerShell 

- Policy definition, creation, and update
- Policy definition (or Policy Set)
- Policy assignment 
- Policy remediation

I will start by showing how to create and deploy a Policy Set. But first, let's define what is an Azure policy set (or initiative). A policy Set (or Initiative) is a collection of Azure policies. It simplifies the life cycle of these policies (adding or removing policies) and assignments where you apply the Initiative to a scope (subscriptions or management group).

A Policy Set is a JSON definition that contains several properties.

- A display name (limited to 128 characters)
- A Description 
- A name (limited to 64 characters)
- Metadata (like a category for classification, versions, …)
- The policy initiative parameters
- The list of policy definitions to be included in the set
- A policy type, and as we cannot create a Built-in Azure Initiative, it will always be custom

Last things, policy definition and policy set are deployed at a scope, either management group or subscription scope. 

In Bicep it looks like this:

```bicep
resource symbolicname 'Microsoft.Authorization/policySetDefinitions@2021-06-01' = {
  name: 'string'
  scope: resourceSymbolicName
  properties: {
    description: 'string'
    displayName: 'string'
    metadata: {}
    parameters: {}
    policyDefinitions: [
      {
        groupNames: [
          'string'
        ]
        parameters: {}
        policyDefinitionId: 'string'
        policyDefinitionReferenceId: 'string'
      }
    ]
    policyType: 'custom'
  }
}
```

Imagine now that you are asked to create and manage a policy Initiative with 3 built-in Policies.

 

- Public network access on Azure SQL Database should be disabled (ID: /providers/Microsoft.Authorization/policyDefinitions/1b8ca024-1d5c-4dec-8995-b1a932b41780)
- Public network access should be disabled for MySQL servers (ID/ /providers/Microsoft.Authorization/policyDefinitions/d9844e8a-1437-4aeb-a32c-0c992f056095)
- Public network access should be disabled for PostgreSQL servers (ID: /providers/Microsoft.Authorization/policyDefinitions/b52376f7-9612-48a1-81cd-1ffe4b61032c)

You know that other policies can be added and those existing can be removed, how to design a solution. 
We know that the PolicyDefinitions property is an array of objects and those policy definitions used here use default parameters.
Bicep can deal with loop and array, so it easy to imagine a simple way to define a textual version of the policy initiative we want to deploy, a simple JSON file. 
This file will need to store, the policy set display name, and description, the category and the version, and finally an array of policy IDs.

The JSON definition will look like 

```json
{
  "initiativeName": "demoPolicySet",
  "displayName": "Demo Policy Initiative for Dev.to",
  "Category": "compliance",
  "version": "1.0.0",
  "policyToInclude": [
    "/providers/Microsoft.Authorization/policyDefinitions/b52376f7-9612-48a1-81cd-1ffe4b61032c",
    "/providers/Microsoft.Authorization/policyDefinitions/d9844e8a-1437-4aeb-a32c-0c992f056095",
    "/providers/Microsoft.Authorization/policyDefinitions/1b8ca024-1d5c-4dec-8995-b1a932b41780"
  ],
  "description": "Demo Policy Initiative for Dev.to"
}
```
It is simple and human-readable. 

Now let’s look at the bicep file to deploy the solution 

```biceo
targetScope = 'managementGroup' 

@maxLength(64)
@description('PolicySet name')
param initiativeName string 

@maxLength(128)
@description('PolicySet display Name')
param initiativeDisplayName string

@description('PolicySet description')
param initiativeDescription string

@minLength(1)
@description('array of policy ID')
param initiativePoliciesID array

param category string = 'compliance' 
param verion string = '1.0.0'

resource policySetDef 'Microsoft.Authorization/policySetDefinitions@2021-06-01' = {

  name: initiativeName

    properties: {
      description: initiativeDescription
      displayName: initiativeDisplayName 
      metadata: {
        category: Category
        version: version
      }

      parameters: {}

      policyDefinitions: [for ID in initiativePoliciesID: {
          parameters: {}
          policyDefinitionId: ID
          policyDefinitionReferenceId: ''
        } ]
      policyType: 'Custom'

    }
}
```
We have a param block with some constraint on the initiativeName and the initiativeDisplayName to avoid an error in case someone uses a wrong value.

To deal with the array of policies, a for loop is needed. 

Now to deploy the policy initiative we need to connect the bicep file with the JSON file. 
A simple PowerShell script is needed!

This script will read the JSON file and create variables needed to deploy the bicep file.

```powershell
[CmdletBinding()]
param (
    [Parameter(Mandatory = $true)]
    [string]
    $managementGroupID,

    [string]
    $location = "westeurope"
)

if (!(Test-Path -Path "./policysetdef.json")) {
    throw "Definition policy configuration json file is not present"
    exit 1
}

if ($null -eq ( Get-AzContext -ErrorAction SilentlyContinue)){
    throw "You need to have an open session in Azure, please use connect-azaccount"
    exit 1 
}

try {
    $jsonDefinition = Get-Content -Path "./policysetdef.json" | ConvertFrom-Json
}
catch {
    write-error "unable to get configuration data"
    exit 1
}

$randomNumber = Get-Random
$deployName = "$($jsonDefinition.initiativeName)-$($randomNumber)"

New-AzManagementGroupDeployment -Name $deployName  -ManagementGroupId $managementGroupID -Location $location -TemplateFile ./deploySet.bicep -initiativeName $jsonDefinition.initiativeName  -initiativeDisplayName $jsonDefinition.displayName -initiativeDescription $jsonDefinition.description -initiativePoliciesID $jsonDefinition.policyToInclude
```

The script takes two parameters, managementGroupID, required, the deployment scope of the policy initiative, and the location, location of the deployment, by default, westeurope. 

Then, the script tests the presence of the bicep file and if an Azure session is open. After that, it imports the JSON file and with the data deploy the policy initiative with New-AzManagementGroupDeployment Cmdlet.

_You can find the related PwSh/Bicep code [here](https://github.com/omiossec/bicep-azure-policy)_