My other posts
Governance As Code with Azure Bicep
Table of contents
Introduction
Azure Governance refers to the practice of defining and enforcing business policies to your Azure workloads. Governance is an important part of Microsoft's Cloud Adoption Framework.
Governance consists of many disciplines, as we can see in the Microsoft documentation:
- Cost management
- Security
- Resource structure/naming
- Policies
- Etc.
Governance as Code (GaC) as presented in this post, refers to the idea of going a step further than just Infrastructure as Code (IaC), and having as much of the governance as possible written in code.
I must note that this post will not attempt to define a governance strategy, since that varies greatly from company to company, and the official Microsoft documentation has plenty written about the subject.
Instead, we'll skip the initial step of defining the governance, and take a look at how we can achieve GaC by using Azure Resource Manager (ARM) through Bicep templates. Please note that I will not try to cover all the aspects of governance in one go, but rather focus on establishing a baseline.
Example governance structure
The structure we will be building is more or less simple and by no means attempts to represent a full structure.
The structure above follows Microsoft recommendations for using Management Groups and, while simple, is extensible and scalable. We have:
- A root Management Group (which is required).
- An IT Management group (for platform/network/identity subscriptions) with policies attached.
- A Networking subscription.
- A Development Management Group (for teams to run their development and test subscriptions) with policies attached.
- Two subscriptions, each with its set of policies.
- A Production Management Group (for production deployments) with policies attached.
- An Internal Management Group (for internal applications).
- Two subscriptions (each for different products).
- An External Management Group (for applications managed by clients).
- One subscription (for a given client).
- An Internal Management Group (for internal applications).
- An IT Management group (for platform/network/identity subscriptions) with policies attached.
Bicep modules
To achieve an automated deployment like the above, there are a few prerequisites, which I've listed on my GitHub sample repository.
Bicep makes things a bit harder than they should be with scopes for these types of deployments, such as having to change the scope from tenant to management group to subscription, but I'm hopeful this will be made simpler in future releases.
Management Group
Creating a Management Group (MG) can be done with a module such as this one, and notice the scope being the tenant.
targetScope = 'tenant'
@description('The ID of the parent MG')
param parentManagementGroupId string
@description('The name of the MG')
param name string
resource managementGroup 'Microsoft.Management/managementGroups@2021-04-01' = {
name: name
scope: tenant()
properties: {
details: {
parent: {
id: parentManagementGroupId
}
}
displayName: name
}
}
output id string = managementGroup.id
The template forces the MG to be under another MG, even if it's just the root MG.
Subscription
A Subscription can be deployed at the Tenant or at the Management Group level, but in our case, we want all Subscriptions to be parented by one of our Management Groups, so the template looks like this:
targetScope = 'managementGroup'
@description('The billing scope for this subscription')
param billingScope string
@description('The ID of the management group that is the parent of the subscription')
param parentManagementGroupId string
@description('Name of the subscription')
param subscriptionName string
@description('Workload type for the subscription')
@allowed([
'Production'
'DevTest'
])
param subscriptionWorkload string
resource subscription 'Microsoft.Subscription/aliases@2021-10-01' = {
name: subscriptionName
scope: tenant()
properties: {
workload: subscriptionWorkload
displayName: subscriptionName
billingScope: billingScope
additionalProperties: {
managementGroupId: parentManagementGroupId
}
}
}
output subscriptionId string = subscription.id
Do note the billingScope
parameter for this template. That specifies the full path to the scope, and you can see in the prerequisites link how to obtain that.
Assigning policies and policy sets
Assigning existing policies and policy sets to MGs and Subscriptions is easy, as long as you use the correct scope for the module and obtain the policy's ID from Azure. For example, to link a MG to the ISO 27001 policy set, we first need to get full resource ID:
targetScope = 'managementGroup'
resource policySetDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' existing = {
name: '89c6cddc-1c73-4ac1-b19c-54d1a15a42f2'
}
output policyDefinitionId string = policySetDefinition.id
And then assign the policy:
resource policyAssignment 'Microsoft.Authorization/policyAssignments@2022-06-01' = {
name: policyName
properties: {
policyDefinitionId: policyDefinitionId
description: policyDescription
}
}
Defining new policies
Policies can also be created through Bicep templates and subsequently assigned to the appropriate resources. For example, if you want to create a policy to restrict resource deployments to particular Azure regions, you can define the policy like this:
targetScope = 'managementGroup'
@description('The region to which resources can be deployed to')
@allowed([
'UKSouth'
'EuropeNorth'
])
param allowedRegion string
var policyDefinitionName = 'Region restriction'
resource policyDefinition 'Microsoft.Authorization/policyDefinitions@2021-06-01' = {
name: policyDefinitionName
properties: {
policyType: 'Custom'
mode: 'All'
parameters: {}
policyRule: {
if: {
not: {
field: 'location'
in: [ allowedRegion ]
}
}
then: {
effect: 'deny'
}
}
}
}
output policyDefinitionId string = policyDefinition.id
Notice that the policy definition is deployed at the Management Group level, so it's reusable for the Subscriptions below it. You would then apply the policy using the same Bicep template as we saw in the previous section.
Another note about scopes
As I said previously, Bicep makes things a bit complicated when it comes to the scopes for deploying things like policy assignments. If you want to have generic modules for deploying policy assignments to MGs and to Subscriptions, the best I could find is that you need to duplicate the modules.
For example, if the MG assignment module is like this:
targetScope = 'managementGroup'
@description('The ID of the policy definition to assign')
param policyDefinitionId string
@description('The name of the policy assignment')
param policyName string
@description('The description of the policy assignment')
param policyDescription string
resource policyAssignment 'Microsoft.Authorization/policyAssignments@2022-06-01' = {
name: policyName
properties: {
policyDefinitionId: policyDefinitionId
description: policyDescription
}
}
You'd have to duplicate the module, changing the targetScope
from managementGroup
to subscription
.
Putting it all together
You can find the entire Bicep project available in my GitHub samples repository here. The sample is MIT licensed, so you are free to do as you please with the source code.
As usual, let me know in the comments if you find this useful or if you have any questions.