Bicep and Azure Policy: Create and manage custom Azure Policies
Tags: azure, bicep, powershell, governance
Published: 2022-10-11 15:03:50

This is the second post about Azure Policy. It will focus on policy definition and how to manage your custom policies with Azure Bicep and PowerShell.

Azure provides several policy definitions that cover a wide range of situations and governance strategies. 
But sometimes you need policies that cover particular needs and requirements. You will need to write your own custom Azure Policy. 

An Azure Policy is a JSON file with several properties.
- A name (limited to 64 characters)
- A display Name (limited to 128 characters)
- A description
- The mode of the policy, all (for all resources) or indexed (only resources that support tags and location)
- Metadata (version, category, …)
- A Parameters bloc
- Policy Rule bloc including the evaluation to filter target resources and the effect
- A type, as we are creating a custom policy, it will be custom

In Bicep we can retrieve these policy elements plus the name of the policy (that will form the policy ID with de deployment scope)

```bicep
resource symbolicname 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
  name: 'string'
  properties: {
    description: 'string'
    displayName: 'string'
    metadata: any()
    mode: 'string'
    parameters: {}
    policyRule: any()
    policyType: 'string'
  }
}
```

Like Policy Initiatives, Policy definitions are deployed at a given scope, management group, or subscription. 

Now imagine that your task is to deploy several policy definitions and update some existing ones. How can you automate this task using PowerShell and Bicep?
You may not have to manage only one policy but several.

A Policy definition is a JSON file, it can easily be read by a PowerShell script. If you put all the policy definition JSON files into a single folder, PowerShell can parse them and extract all the information needed to deploy the policy definition. 

A bicep file is needed to deploy policy definitions. 

```bicep 
targetScope = 'managementGroup' 

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

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

@description('PolicySet description')
param definitionDescription string

@allowed([
  'All'
  'Indexed'
])
@description('Policy Definition Mode')
param policyMode string

@description('JSON string')
param policyParameters string

@description('JSON string')
param policyMetadata string

@description('JSON string')
param policyRule string

resource policyInitiative 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
  name: defintionName
  properties: {
    displayName: definitionDisplayName
    description: definitionDescription
    policyType: 'Custom'
    mode: policyMode
    metadata: json(policyMetadata)
  
    parameters: json(policyParameters) 

    policyRule: json(policyRule)
  }
}
```
As with Policy initiative the target scope is a management group but it could be change to subscription. 
The definitionName and defintionDisplayName parameters have limited length, so control is needed to avoid deployment errors. 
Policy mode can only have two values indexed (for resources supporting tags) or all (all resource types). The policyMode parameter restricts the choice of these two values. 

For metadata, parameters, and policyRule, bicep only accepts JSON objects while PowerShell can only provide a string representation of a JSON object. The bicep function json() is here to translate a string to a corresponding JSON object. 

The PowerShell script needs to parse any JSON file in a given folder and extract displayName, Description, and mode as string and parameters, metadata, and policyRule as JSON objects. Then it can run the bicep deployment. 
The name of the policy definition is extracted from the name of the JSON file

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

    [string]
    $location = "westeurope"
)

$jsonFilesList = Get-ChildItem -Path ./definition/* -include *.json 

foreach ($jsonFile in $jsonFilesList) {
    $jsonData = Get-Content -Path $jsonFile.FullName | ConvertFrom-Json  
    $displayName = $jsonData.properties.DisplayName 
    $name = $jsonFile.name.replace(".json","")
    $parameters = $jsonData.properties.parameters | convertTo-Json  -Depth 5
    $policyRules = $jsonData.properties.policyRule | convertTo-Json  -Depth 5
    $policyMetadata = $jsonData.properties.metadata | convertTo-Json  -Depth 5
    $mode = $jsonData.properties.mode 
    $description = $jsonData.properties.description 

    $randomNumber = Get-Random
    $deployName = "$($name)-$($randomNumber)"
    New-AzManagementGroupDeployment -Name $deployName  -ManagementGroupId $managementGroupID -Location $location -TemplateFile ./deployDefinition.bicep -defintionName $name -definitionDisplayName $displayName -definitionDescription $description -policyMode $mode -policyParameters $parameters -policyMetadata $policyMetadata -policyRule $policyRules
}
```

Next post we will talk about assignment.

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