Bicep and Azure Policy: Manage Policy and Initiative Assignment
Tags: azure, powershell, bicep, governance
Published: 2022-10-31 16:03:29

This is the third post about Azure Policy. This time, the post will focus on policy assignments with Azure Bicep and PowerShell.
Policy assignment enforces a policy and a policy set at a given scope, management group, or subscription. This is where policies are applied to target resources. 

A policy Assignment object has several properties: 

- A name (limited to 24 characters at the management group scope, 64 characters for other scopes)
- A location, the Azure to store the operation metadata
- A display name, limited to 128 characters
- An identity object
- A description 
- The enforcement mode, either default (enforced) or DonotEnforce
- A non-compliance object. The message will be displayed when resources are not compliant with the policy.
- A not scope array, to not apply the assignment at some management group or subscriptions
- A parameters object, to apply parameters for the policy for the assignment
- The Policy definition ID, resource ID of the policy definition, or the policy set 

In Bicep language 

```
resource symbolicname 'Microsoft.Authorization/policyAssignments@2021-06-01' = {
  name: 'assignment001'
  location: 'westeurope'
  properties: {
    description: 'test assignment'
    displayName: 'test assignment'
    enforcementMode: ‘default’
    nonComplianceMessages: [
      {
        message: 'resource non compliante'
      }
    ]
    parameters: {}
    policyDefinitionId: '/providers/Microsoft.Management/managementGroups/c986548e-494d-4f3a-b716-42287a39531b/providers/Microsoft.Authorization/policyDefinitions/6fb207dd-dfc6-48ce-a00d-5cdd49b64cc4'
  }
}
```

The deployment of this bicep file could be done by the New-AzManagementGroupDeployment cmdlet. 
But like custom policy definitions and policy sets, you will certainly be asked to not assign only one policy.  How can you manage several policy assignments in one place?
This is the same problem we had with deploying policies. But even if a policy assignment can be seen as a JSON document, the amount of information needed to assign a policy is limited. Instead of using one JSON file per assignment, we can create a single JSON document with all assignments, but we need to take care of the scope. 

the bicep file:

```
@maxLength(24)
@description('Assignment name')
param assignmentName string 

@maxLength(128)
@description('Assignement display Name')
param assignmentDisplayName string

@description('Assignement description')
param assignmentDescription string

@description('Assignement enforcement mode')
param assignmentEnforcementMode string

@description('remediation message JSON string')
param assignmentMessage string

@description('parameter json string')
param assignmentParameters string

@description('policy definition or policy set ID')
param assignmentPolicyID string

resource policyAssignment 'Microsoft.Authorization/policyAssignments@2021-06-01' = {
  name: assignmentName
  properties: {
    description: assignmentDescription
    displayName: assignmentDisplayName
    enforcementMode: assignmentEnforcementMode
    nonComplianceMessages: json(assignmentMessage)
    parameters: json(assignmentParameters)
    policyDefinitionId: assignmentPolicyID
  }
}
```

This Bicep file will deploy a policy assignment. As the deployment will be made via PowerShell, we need to convert the value of the parameters and the nonComplianceMessage properties from string to JSON with the JSON function in Bicep.

All the parameters needed for the deployment are stored in a JSON document.

```json
[
    {
      "assignmentDisplayName": "Audit remote debugging",
      "assignmentName": "tunctionprod",
      "assignmentDescription": "Audit if Function apps  have remote debugging turned off ",
      "assignmentEnforcementMode": "default",
      "assignmentMessage":  {
        "message": "Remote debuging must be turned off"
      },
      "assignmentNoScope": [],
      "assignmentParameters": {
        "effect": {
            "value": "AuditIfNotExists"
          }
      },
      "assignmentPolicyID": "/providers/Microsoft.Authorization/policyDefinitions/0e60b895-3786-45da-8377-9c6b4b6ac5f9",
      "scope": "prod-mgt-group"
    },
    {
        "assignmentDisplayName": "Audit remote debugging",
        "assignmentName": "tunctiondev",
        "assignmentDescription": "Audit if Function apps  have remote debugging turned off ",
        "assignmentEnforcementMode": "DonotEnforce",
        "assignmentMessage":  {},
        "assignmentNoScope": [],
        "assignmentParameters": {
          "effect": {
              "value": "AuditIfNotExists"
            }
        },
        "assignmentPolicyID": "/providers/Microsoft.Authorization/policyDefinitions/0e60b895-3786-45da-8377-9c6b4b6ac5f9",
        "scope": "dev-mgt-group"
      }
  ]
```

There is one policy to assign but two assignments in the JSON document. It’s to illustrate the power of parameters in the assignment process. You can assign the same policy, multiple times, even at the same scope, as long as the name changes and the parameters are different. 

Each object in the JSON document will serve to deploy the assignment via a PowerShell script.

```powershell
[CmdletBinding()]
param (
    [string]
    $location = "westeurope"
)
$jsonData = Get-Content -Path "./assignments.json"| ConvertFrom-Json  

foreach ($assignment in $jsonData) {
    $assignmentDisplayName = $assignment.assignmentDisplayName
    $assignmentName = $assignment.assignmentName
    $assignmentDescription = $assignment.assignmentDescription
    $assignmentEnforcementMode = $assignment.assignmentEnforcementMode
    $assignmentPolicyID = $assignment.assignmentPolicyID
    $scope = $assignment.scope
    $assignmentParameters = $assignment.assignmentParameters | convertTo-Json 
    if ($assignment.assignmentMessage.count -EQ 0) {
        $assignmentMessage = "[]"
    }
    elseif ($assignment.assignmentMessage.count-EQ 1) {
        $assignmentTmp = $assignment.assignmentMessage | convertTo-Json  
        $assignmentMessage = "[$($assignmentTmp)]"

    }
    else {
        $assignmentMessage = $assignment.assignmentMessage | convertTo-Json 
    }
    $randomNumber = Get-Random
    $deployName = "$($assignmentName)-$($randomNumber)"
    New-AzManagementGroupDeployment -Name $deployName  -ManagementGroupId $scope -Location $location -TemplateFile ./deployAssignement.bicep -assignmentName $assignmentName -assignmentDisplayName $assignmentDisplayName -assignmentDescription $assignmentDescription -assignmentEnforcementMode $assignmentEnforcementMode -assignmentMessage $assignmentMessage -assignmentParameters $assignmentParameters -assignmentPolicyID $assignmentPolicyID

}
```

The script read the content of the JSON document and for each object, it extracts the variable needed to deploy the bicep file. 
But there is a difficulty, the bicep nonComplianceMessages require a JSON array, but most of the time there will be only one message or no message at all (multiple messages are only used for policy set). And if there is only one message (or none) you will not end up with a JSON array, but a simple JSON object, so a modification is needed.

To deploy, simply run the deployAssignment.ps1 from its folder. You can add the "location" parameter to adjust the azure region for your needs.

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