Deploy and Assign Azure Policy via Azure DevOps Pipelines

10 Min. Read

Today, a common theme in cloud environments is enforcing organizational standards and adopting cloud governance since day one. And this is very important since it will give you the ability to define policies, processes, and procedures. These policies then dictate what can be done and verify that what does exist is correct. A service from Microsoft called Azure Policy is a great way to make that happen and take corrective action.

This article will demonstrate how to deploy and assign Azure Policy definitions via Azure DevOps Pipelines.

Introduction

Azure Policy is a service in Azure that you use to create, assign, and manage policies. These policies enforce different rules and effects over your resources, so those resources stay compliant with your corporate standards and service level agreements. Azure Policy meets this need by continuously evaluating your resources for non-compliance with assigned policies.

If you develop many Azure Policy definitions with ARM templates as I do, you find that using the Azure Portal is not efficient to deploy and author those policies. The good news is, we could leverage Azure DevOps to perform such deployment via pipeline.

Azure DevOps — formerly known as Visual Studio Team Services (VSTS)— is a Software as a service (SaaS) platform from Microsoft that provides an end-to-end DevOps toolchain for developing and deploying software. It also integrates with most leading tools on the market and is a great option for orchestrating a DevOps toolchain.

In this guide, I will walk you step-by-step on how to perform the Azure Policy deployment and assignment process via the Azure DevOps pipelines.

Prerequisites

To follow this article, you need to have the following:

  1. Azure subscription – If you don’t have an Azure subscription, you can create a free one here.
  2. Azure DevOps – If you don’t have one, you can create a free one here.
  3. Azure DevOps Organization/Project – If you don’t have one, follow these steps to create an organization.
  4. Azure DevOps Git repository – If you don’t have one, follow these steps to create a new Git repository in your project.
  5. Azure DevOps service connection for Azure Pipelines – The service connection (principal) can be created at the Subscription or Management Group level. I highly recommend using Management Groups where possible. To create a service connection, please follow the steps described here.
  6. Last but not least, you need to give the appropriate permissions for the Azure DevOps service principal to create and manage Azure Policy definitions (more on this in the next section).

Assign Resource Policy Contributor

By default, Azure DevOps grants ‘Contributor’ permissions for the service principals to authenticate pipelines to Azure, which are just fine for the majority of regular deployments. However, the ‘Contributor’ built-in role doesn’t have permission to create and manage Azure Policy definitions. In this case, you need to assign the ‘Resource Policy Contributor’ to the Azure DevOps service principal which is attached to your Subscription or Management Group.

To assign the additional ‘Resource Policy Contributor’ role to your DevOps service principal, you can do it in two ways:

The first one is, through the Azure Portal.

Navigate to your desired subscription or management group and select the Access control (IAM) blade.

Select the Role assignments tab, and then click +Add > Add role assignment.

In the Add role assignment blade, you need to choose the ‘Resource Policy Contributor’ role.

Next, leave the ‘Assign access to‘ as default because we want to choose a service principal, then search for your DevOps service principal that you’d like to permit to, and then click ‘Save‘ as shown in the figure below.

Azure DevOps Service Principal - Add role assignment
Azure DevOps Service Principal – Add role assignment

The second option is, through Azure PowerShell.

First, we need to get the service principal application (client) ID, you can navigate to the service connection page in Azure DevOps (Select Project settings > Service connections), and then click on ‘Manage Service Principal‘ as shown in the figure below.

Azure DevOps - Manage Service Principal
Azure DevOps – Manage Service Principal

The enterprise application page will open where you can copy with Application (client) ID.

Next, you want to identify the needed scope for this role assignment. You can assign the ‘Resource Policy Contributor’ role to a subscription or a management group level. I will include both options below.

Open the Cloud Shell and then run the following command.

Please make sure to replace the service principal client ID and subscription ID.

# Subscription scope
New-AzRoleAssignment -ObjectId (Get-AzADServicePrincipal -ApplicationId <your_service_principal_client_ID>).Id -RoleDefinitionName 'Resource Policy Contributor' -Scope /subscriptions/<subscriptionId>

Please make sure to replace the service principal client ID and management group name.

# Management group scope
New-AzRoleAssignment -ObjectId (Get-AzADServicePrincipal -ApplicationId <your_service_principal_client_ID>).Id -RoleDefinitionName 'Resource Policy Contributor' -Scope /providers/Microsoft.Management/managementGroups/<groupName>

Assuming you have all the prerequisites in place, take now the following steps:

Create a new release pipeline

Sign in to the Azure DevOps portal and take the following steps:

On the left-hand side of the blade, select Releases under Pipelines as shown in the figure below and then click on +New. Select +New release pipeline.

Azure DevOps - Create new release pipeline
Azure DevOps – Create a new release pipeline

A new window will open to select a template. Search for ‘Azure Policy Deployment‘ as shown in the figure below and then click on Apply.

Azure DevOps - Azure Policy Deployment
Azure DevOps – Azure Policy Deployment

You’ll get a small pop-up window to show us the stage details (Stage 1) of this release pipeline. Enter a descriptive name for the stage as shown in the figure below, and then close this window.

Azure DevOps - New release pipeline
Azure DevOps – New release pipeline

Before configuring the different Tasks for this stage, give a descriptive name for the New release pipeline as shown in the figure below. In this example, I renamed my release pipeline to ‘Deploy & Assign Azure Policy‘.

Rename Azure DevOps release pipeline
Rename Azure DevOps release pipeline

Next, click on +Add an artifact and then select ‘Azure Repos Git‘ as shown in the figure below. In this example, we will deploy the Azure Policy definitions from the Git repository in Azure DevOps where we store our JSON templates.

New release pipeline - Add an artifact
New release pipeline – Add an artifact

Select your Project name, the Source (repository), the Default branch, the Default version, and then click Add to continue to the next step.

Create Azure Policy task

In this step, we will define and create our first task (Create Azure Policy).

Click on the first stage in your release pipeline (1 job, 2 tasks) as shown in the figure below.

Azure DevOps - Release Pipeline Stages
Azure DevOps – Release Pipeline Stages

The Tasks details page will open where you can see the stage details and the Tasks in the hierarchy as shown in the figure below (Create Azure Policy and Assign Azure Policy). Next, select the available Azure service connection(s) that you created as part of the prerequisites for your Azure Subscription or management group as an input parameter.

Release pipeline - Deployment process parameters
Release pipeline – Deployment process parameters

Select the first task (Create Azure Policy). Please note that since here we are using Azure PowerShell as the backend script, please select the appropriate Task version as shown in the figure below. The Task version represents the PowerShell version, and the versions may vary depending upon the cmdlets we will be using. It is always recommended to use the latest version. At the time of this writing, I am using task version 5.

Create Azure Policy - Azure PowerShell Task version
Create Azure Policy – Azure PowerShell Task version

Next, choose Script Type as Inline Script as we are providing the PowerShell script as inline content. We can use Script File Path as well in case we are using any repository like GitHub as the source for files.

Copy the PowerShell code below into the Inline Script block and update it with this one.

# Input variables: set these values in the variables section of the release pipeline

#   policyName          - [Required] Policy definition name
#   policyDisplayName   - [optional] Policy definition display name
#   policyDescription   - [optional] Policy definition description
#   subscriptionId      - [optional] Id of subscription the definition will be available in
#   managementGroupName - [optional] Name of management group the definition will be available in
#   policyRule          - [Required] Policy definition rule in JSON string format or path to a file containing JSON policy definition rule
#   policyParameters    - [optional] Policy parameter values in JSON string format

# Notes:
#   File path value for $(PolicyRule) may be a fully qualified path or a path relative to $(System.DefaultWorkingDirectory)

$policyName = "$(policyName)"
$policyDisplayName = "$(policyDisplayName)"
$policyDescription = "$(policyDescription)"
$subscriptionId = "$(subscriptionId)"
$managementGroupName = "$(managementGroupName)"
$policyRule = "$(policyRule)"
$policyParameters = "$(policyParameters)"

if (!$policyName)
{
    throw "Unable to create policy definition: required input variable value `$(PolicyName) was not provided"
}

if (!$policyRule)
{
    throw "Unable to create policy definition: required input variable value `$(PolicyRule) was not provided"
}

if ($subscriptionId -and $managementGroupName)
{
    throw "Unable to create policy definition: `$(SubscriptionId) '$subscriptionId' and `$(ManagementGroupName) '$managementGroupName' were both provided. Either may be provided, but not both."
}

$cmdletParameters = @{Name=$policyName; Policy=$policyRule; Mode='Indexed'}
if ($policyDisplayName)
{
    $cmdletParameters += @{DisplayName=$policyDisplayName}
}

if ($policyDescription)
{
    $cmdletParameters += @{Description=$policyDescription}
}

if ($subscriptionId)
{
    $cmdletParameters += @{SubscriptionId=$subscriptionId}
}

if ($managementGroupName)
{
    $cmdletParameters += @{ManagementGroupName=$managementGroupName}
}

if ($policyParameters)
{
    $cmdletParameters += @{Parameter=$policyParameters}
}

&New-AzPolicyDefinition @cmdletParameters

Assign Azure Policy task

In this step, we will define and create our second task (Assign Azure Policy).

Select the second task (Assign Azure Policy). Similarly to the first task, please select the appropriate Task version as shown in the figure below. At the time of this writing, I am using task version 5.

Assign Azure Policy - Azure PowerShell Task version
Assign Azure Policy – Azure PowerShell Task version

Next, choose Script Type as Inline Script as we are providing the PowerShell script as inline content. We can use Script File Path as well in case we are using any repository like GitHub as the source for files.

Copy the PowerShell code below into the Inline Script block and update it with this one.

# Input variables: set these values in the variables section of the release pipeline

#   AssignmentName        - [Required] Policy assignment name
#   AssignmentDisplayName - [optional] Policy assignment display name
#   AssignmentDescription - [optional] Policy assignment description
#   PolicyName            - [optional] Name of policy definition to assign
#   PolicySetName         - [optional] Name of policy set definition to assign
#   ResourceGroupName     - [optional] Name of resource group the policy [set] definition will be applied to
#   SubscriptionId        - [optional] Id of subscription the policy [set] definition will be applied to
#   ManagementGroupName   - [optional] Name of management group the policy [set] definition will be applied to
#   PolicyParameters      - [optional] Policy parameter values in JSON string format
#   ManagedIdentity       - [optional] Enter [Y] to generate and assign an Azure AD Identity for this policy assignment. The identity will be used when executing deployments for 'deployIfNotExists' and 'modify' policies.

$assignmentName = "$(AssignmentName)"
$assignmentDisplayName = "$(AssignmentDisplayName)"
$assignmentDescription = "$(AssignmentDescription)"
$policyName = "$(PolicyName)"
$policySetName = "$(PolicySetName)"
$resourceGroupName = "$(ResourceGroupName)"
$subscriptionId = "$(SubscriptionId)"
$managementGroupName = "$(managementGroupName)"
$policyParameters = "$(PolicyParameters)"
$managedIdentity = "$(managedIdentity)"

if (!$assignmentName)
{
    throw "Unable to create policy assignment: required input variable value `$(AssignmentName) was not provided"
}

if (!$policyName -and !$policySetName)
{
    throw "Unable to create policy assignment: neither `$(PolicyName) nor `$(PolicySetName) was provided. One or the other must be provided."
}

if ($policyName -and $policySetName)
{
    throw "Unable to create policy assignment: `$(PolicyName) '$policyName' and `$(PolicySetName) '$policySetName' were both provided. Either may be provided, but not both."
}

if ($subscriptionId -and $managementGroupName)
{
    throw "Unable to create policy assignment: `$(SubscriptionId) '$subscriptionId' and `$(ManagementGroupName) '$managementGroupName' were both provided. Either may be provided, but not both."
}

if ($managementGroupName -and $resourceGroupName)
{
    throw "Unable to create policy assignment: `$(ManagementGroupName) '$managementGroupName' and `$(ResourceGroupName) '$resourceGroupName' were both provided. Either may be provided, but not both."
}

if ($managementGroupName)
{
    $scope = "/providers/Microsoft.Management/managementGroups/$managementGroupName"
    $searchParameters = @{ManagementGroupName=$managementGroupName}
}
else
{
    if (!$subscriptionId)
    {
        $subscription = Get-AzContext | Select-Object -Property Subscription
        $subscriptionId = $subscription.Id
    }

    $scope = "/subscriptions/$subscriptionId"
    $searchParameters = @{SubscriptionId=$subscriptionId}
    
    if ($resourceGroupName)
    {
        $scope += "/resourceGroups/$resourceGroupName"
    }
}

$cmdletParameters = @{Name=$assignmentName; Scope=$scope}
if ($assignmentDisplayName)
{
    $cmdletParameters += @{DisplayName=$assignmentDisplayName}
}
else
{
   $cmdletParameters += @{DisplayName=$assignmentName}
}

if ($assignmentDescription)
{
    $cmdletParameters += @{Description=$assignmentDescription}
}

if ($policyName)
{
    $policyDefinition = Get-AzPolicyDefinition @searchParameters | Where-Object { $_.Name -eq $policyName }
    if (!$policyDefinition)
    {
        throw "Unable to create policy assignment: policy definition $policyName does not exist"
    }

    $cmdletParameters += @{PolicyDefinition=$policyDefinition}
}

if ($policySetName)
{
    $policySetDefinition = Get-AzPolicySetDefinition @searchParameters | Where-Object { $_.Name -eq $policySetName }
    if (!$policySetDefinition)
    {
        throw "Unable to create policy assignment: policy set definition $policySetName does not exist"
    }

    $cmdletParameters += @{PolicySetDefinition=$policySetDefinition}
}

if ($policyParameters)
{
    $cmdletParameters += @{PolicyParameter=$policyParameters}
}

if ($managedIdentity)
{
    &New-AzPolicyAssignment @cmdletParameters -Location 'eastus' -AssignIdentity
}
else 
{
   &New-AzPolicyAssignment @cmdletParameters
}

Next, click on the ‘Variables‘ tab of the release pipeline as shown in the figure below to add and set the values for the required variables.

Release pipeline - Variables
Release pipeline – Variables

In the variable page, add a new variable by clicking on (+Add) and then set the variable name to ‘managedIdentity‘ as shown in the figure below. The Managed Identity is needed only when executing deployments for ‘deployIfNotExists‘ and ‘modify‘ policies. Make sure you set the ‘Scope‘ for this new variable to the current release pipeline name, and then select the checkbox next to ‘Settable at release time‘. This will give you the option to enter the needed values (parameters) for all variables before the deployment.

You can also enter other values for any variable that will be constantly used as part of this release pipeline.

Release pipeline - Add variable
Release pipeline – Add variable

After updating both Tasks (Create Azure Policy and Assign Azure Policy) as described above, click on the ‘Save‘ button as shown in the figure below. A new pop-up window will open where you can enter your comments (optional) and then click OK. Please note that every time you save a release pipeline, Azure Pipelines keeps a copy of the changes. This copy allows you to compare the changes at a later point, especially when you’re debugging a deployment failure.

Azure DevOps - Save release pipeline
Azure DevOps – Save release pipeline

Deploy and Assign Azure Policy

Now that we have updated both tasks for our release pipeline, we are ready to deploy and assign our first Azure Policy definition via Azure DevOps.

From the release pipeline, click on ‘Create release‘ as shown in the figure below.

Azure DevOps - Create release
Azure DevOps – Create release

Another window will pop up where you need to enter the required parameters as follows. You can also set the optional parameters if you want.

  • Policy Name: Policy definition name.
  • Assignment Name: Policy assignment name.
  • Policy Rule: Path to a file containing the policy definition rule in JSON string format. The file path value could be a fully qualified path or a path relative to $(System.DefaultWorkingDirectory) of your repository. In this example, my policy rule path is ‘_infra-code-cn/AZ-POLICY/azpolicy-demo.json, where the repository is called (infra-code-cn), followed by a folder called (AZ-POLICY), and the name of the JSON template for my policy.
  • Management Group Name: Enter the name of the management group Id and not the management group name where you want to deploy this policy. You can also specify a subscription Id instead.
  • Managed Identity: If you want to deploy and assign Azure Policies with the ‘deployIfNotExists’ and ‘modify’ effect, then you want to enter any value for the Managed Identity variable (e.g. Yes). In this way, a new managed identity will be created as part of the release pipeline deployment. The policy with ‘deployIfNotExists’ and ‘modify’ can remediate and modify your resources. To understand how the Azure Policy effect works, please check the official documentation from Microsoft here.

Finally, click on the “Create” button to proceed as shown in the figure below.

Create release - Enter a value for the variables
Create release – Enter a value for the variables

Once you click on the ‘Create‘ button, you will get a message that “Release Release-## has been created”, where ## is the release number.

Click on the message link – “Release has been created” and it will show the progress of all the Tasks in the stage as shown in the figure below.

Azure DevOps - Deploy release pipeline
Azure DevOps – Deploy release pipeline

In less than 3 minutes, the release will be deployed successfully as shown in the figure below.

Azure DevOps - Release pipeline deploy succeeded
Azure DevOps – Release pipeline deploy succeeded

If you click on the ‘Succeeded‘ link in the Stage, you will get to see the status of each step that was performed by the agent as shown in the figure below, and further, you can click on the succeeded status of each step to see the operation logs in more details.

Azure DevOps - Deployment process jobs
Azure DevOps – Deployment process jobs

Finally, if you switch the Azure Portal | Policy Definitions blade, you’ll see that a new policy was created successfully as shown in the figure below.

Azure Portal - Policy Definitions blade
Azure Portal – Policy Definitions blade

And under the Policy Assignments blade, you’ll see the policy was assigned successfully as shown in the figure below.

Azure Portal - Policy Assignments blade
Azure Portal – Policy Assignments blade

That’s it there you have it!

Summary

In this guide, I showed you how to deploy and assign Azure Policy definitions via Azure DevOps Pipelines.

With the help of simple tasks in our release pipeline, we can automate and simplify the deployment of Azure Policies via Azure DevOps Pipelines at scale. You also get the power of version control using Git repositories where you can store and author all your policies in Visual Studio code.

__
Thank you for reading my blog.

If you have any questions or feedback, please leave a comment.

-Charbel Nemnom-

Related Posts

Previous

(Solution) Azure File Sync – Server Registration – Error 0x80092004

Getting Started with NFS v4.1 for Azure Files

Next

Let me know what you think, or ask a question...

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to Stay in Touch

Never miss out on your favorite posts and our latest announcements!

The content of this website is copyrighted from being plagiarized!

You can copy from the 'Code Blocks' in 'Black' by selecting the Code.

Please send your feedback to the author using this form for any 'Code' you like.

Thank you for visiting!