Azure Bicep Modules for Azure Policy Resources
Although I’m a big fan of Microsoft CARML Bicep module repo, and have used many of their modules in my projects, Sometimes I still prefer using the modules I have created myself. I created 3 Bicep modules a while back for Azure Policy Definitions, Initiatives and Assignments. They are all created for management-group scoped deployments because I have not had requirements for subscription scoped deployments to date. I have used these modules in several projects now. I thought I’d share with the community, gives you an alternative to the CARML modules.
You can find the modules coupled with sample templates in my Azure Policy Github repository
Policy Definition Module
Azure policy definitions are written in JSON format. With the Policy Definition module takes a JSON payload as an input and parses it to get all attributes that Bicep requires. When deploying multiple policy definitions in bulk, you can also use the @batchsize
decorator to control the concurrent deployments. I was able to use this module to deploy 100+ policy definitions in a single deployment in a timely manner.
For example:
targetScope = 'managementGroup'
//specify th relative path to the policy definition JSON files in the loadTextContent function
var PolicyDefinitions = [
loadTextContent('pol-control-preview-api.json')
loadTextContent('pol-required-minimum-api-version.json')
loadTextContent('pol-restrict-to-specific-api-version.json')
loadTextContent('pol-inherit-tags-from-sub.json')
]
//use the @batchSize decorator to control the concurrent deployments
@batchSize(15)
module policyDefs '../../modules/policyDefinitions/main.bicep' = [for (definition, i) in PolicyDefinitions: {
name: 'policy_definitions_${i}'
params: {
policyDefinition: definition
}
}]
//outputs
output policyDefNames array = [for (definition, i) in PolicyDefinitions: json(definition).name]
output policies array = [for (definition, i) in PolicyDefinitions: {
resourceId: policyDefs[i].outputs.resourceId
name: policyDefs[i].outputs.name
}]
NOTE: This sample template above is located here
Policy Initiative (Policy Set) Module
Similar to the Policy Definition module, the Policy Initiative module can be used together with the loadTextContent()
bicep function so you can easily deploy existing Policy initiative definition JSON files.
For example:
targetScope = 'managementGroup'
@description('Policy Definition Source Management Group Id')
param managementGroupId string = managementGroup().id
// ------Read policy initiative definitions from json files------
var tagPolicySetDefinitionFromFile = json(loadTextContent('polset-tags.json'))
// ------replace resource Ids in policy set definitions------
var tagPolicyDefinitions = [for policy in tagPolicySetDefinitionFromFile.properties.policyDefinitions: {
policyDefinitionId: replace(policy.policyDefinitionId, '{policyLocationResourceId}', managementGroupId)
policyDefinitionReferenceId: policy.policyDefinitionReferenceId
parameters: policy.parameters
groupNames: policy.groupNames
}]
// ------Construct payload for the Policy Initiative bicep module------
var tagPolicySetDefinition = {
name: tagPolicySetDefinitionFromFile.name
properties: {
displayName: tagPolicySetDefinitionFromFile.properties.displayName
description: tagPolicySetDefinitionFromFile.properties.description
metadata: tagPolicySetDefinitionFromFile.properties.metadata
parameters: tagPolicySetDefinitionFromFile.properties.parameters
policyDefinitionGroups: tagPolicySetDefinitionFromFile.properties.policyDefinitionGroups
policyDefinitions: tagPolicyDefinitions
}
}
//------Deploy Policy Initiatives------
module tagPolicyInitiative '../../modules/policySetDefinitions/main.bicep' = {
name: tagPolicySetDefinitionFromFile.name
params: {
policySetDefinition: tagPolicySetDefinition
}
}
//------ Outputs ------
output tagPolicySetDefinition object = tagPolicySetDefinition
In this example, I’m deploying a policy initiative defined in the polset-tags.json
file. The loadTextContent()
function is used to read the JSON file.
In the Policy Initiative definition JSON file, I have defined the member policy location as below:
"policyDefinitionId": "{policyLocationResourceId}/providers/Microsoft.Authorization/policyDefinitions/pol-inherit-tags-from-sub",
The Bicep template uses the replace()
function to replace the {policyLocationResourceId}
placeholder with the management group Id (in this example, assuming the member policies are located at the same management group as the policy initiative).
NOTE: The sample template above is located here
Policy Assignment Module
The policy assignment module is used to assign policy definitions or policy initiatives to a management group scope. It can also create role assignments for Policy Assignments Managed Identity, which is required for policies with DeployIfNotExists
and Modify
effects.
For example:
targetScope = 'managementGroup'
@sys.description('Optional. Location for all resources.')
param location string = deployment().location
@sys.description('Managed identity type. Managed identity is required for policies with DeployIfNotExists or Modify effects. Possible values are "UserAssigned", "systemAssigned" and "None". Default value is "None".')
@allowed([
'SystemAssigned'
'UserAssigned'
'None'
])
param assignmentIdentityType string = 'SystemAssigned'
@description('Optional. Policy Definition Source Management Group Id. Default to the target management group')
param definitionSourceManagementGroupId string = managementGroup().id
@sys.description('Optional. Policy Assignment Scope. Default to the target management group')
param assignmentScope string = managementGroup().id
//--------- Tagging Parameters ---------
@sys.description('Required. Tagging Policy Assignment Name')
@maxLength(24)
param tagAssignmentName string
@sys.description('Tagging Initiative definition Id')
param tagPolicyDefinitionId string
@sys.description('Tagging Policy Assignment Parameters')
param tagAssignmentParameters object
@sys.description('Required. The display name of the Tagging policy assignment. Maximum length is 128 characters.')
@maxLength(128)
param tagAssignmentDisplayName string
@sys.description('Optional. The IDs Of the Azure Role Definition list that is used to assign permissions to the Tagging policy assignment identity. You need to provide either the fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.. See https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles for the list IDs for built-in Roles. They must match on what is on the policy definition')
param tagAssignmentRoleDefinitionIds array = []
@sys.description('The Tagging policy assignment enforcement mode. Possible values are Default and DoNotEnforce.')
@allowed([
'Default'
'DoNotEnforce'
])
param tagAssignmentEnforcementMode string = 'Default'
@sys.description('Tagging Policy assignment metadata')
param tagAssignmentMetadata object = {}
@sys.description('Optional. The Tagging policy assignment excluded scopes')
param tagAssignmentNotScopes array = []
//--------- variables ---------
var assignedByMetadata = {
assignedBy: 'TYANG Policy Assignment Pipeline'
}
//--------- policy assignments ---------
module tagAssignment '../../modules/policyAssignments/main.bicep' = {
name: tagAssignmentName
params: {
name: tagAssignmentName
description: 'Implement resource tagging requirements'
displayName: tagAssignmentDisplayName
policyDefinitionId: replace(tagPolicyDefinitionId, '{policyLocationResourceId}', definitionSourceManagementGroupId)
parameters: tagAssignmentParameters
scope: assignmentScope
location: location
identityType: assignmentIdentityType
roleDefinitionIds: tagAssignmentRoleDefinitionIds
enforcementMode: tagAssignmentEnforcementMode
metadata: union(tagAssignmentMetadata, assignedByMetadata)
notScopes: tagAssignmentNotScopes
nonComplianceMessage: 'The resource configuration is not aligned with resource tagging requirements.'
}
}
//--------- outputs ---------
@sys.description('Tagging Policy Assignment resource ID')
output tagAssignmentResourceId string = tagAssignment.outputs.resourceId
This sample bicep template requires parameter inputs, which can be defined in a parameter file. It adds the assignedBy
metadata to existing assignment metadata, and similar to the Policy Initiative sample, it replaces the {policyLocationResourceId}
token with the definitionSourceManagementGroupId
parameter.
NOTE: The sample template above is located here
Conclusion
I was able to use these modules in Azure DevOps pipelines to deploy policy resources in scale. Since Policy definitions will need to be in place before creating Policy Initiatives and assignments, the order of deployments would be:
- Deploy Policy Definitions
- Deploy Policy Initiatives
- Deploy Policy Assignments
- Optionally create policy remediation tasks to remediate non-compliant existing resources
Leave a comment