Azure Policy as Code with GitHub Actions

Phil Peters
Contino Engineering
11 min readSep 9, 2022

--

One of the biggest drivers of migrating to a Cloud Platform is the autonomy given to developers to build, scale and configure their own infrastructure resources; thus removing the friction that can be caused in releasing code to customers.

However, in the gallant mission that is reducing the time it takes to deliver infrastructure, features and software to users must not be unregulated and run in a ‘Wild West’ style without rules and operational oversight.

So how can you empower developers to utilise the speed and flexibility of Azure but ensure they work in a controlled environment which meets the requirements of your organisation?

The answer to this question is to utilise Azure Policy!

Azure Policy

Azure Policy is a feature included when using Microsoft Azure to audit and enforce settings on resources to notify or prevent when configuration contravenes your Cloud Security Policy. With any subscription in Azure you’ll have a selection of common policies provided for you for use out-of-the-box. You can also group Policies together in Initiatives to make alignment to contexts such as policy, or accreditations easier; for example there is an IS27001 Initiative available in Azure Policy to make it easier to assure resources meet the relevant requirements for this accreditation.

You can inspect what ‘Built-In’ (default provided by Azure) Policies and Initiatives are available in the Policy blade in the Azure Portal. Once you’ve defined and created some Policies you’ll also be able to see these here.

From here you can select “Definitions” to see what is available, you can also assign Policies and Initiatives to your desired scope in this blade. From the “Overview” blade you can see an overview of all your applied Policies & Initiatives and their level of compliance across the scoped resources. This blade enables you to drill down into an individual resource to see its compliance and noncompliance to any active Policies or Initiatives assigned to it.

However, as your cloud environment grows to span across multiple Resource Groups, Subscriptions or even Management Groups you may come to find that it is difficult to manage your definitions and their assignments across these different scopes. Maybe you want different Policies or Initiatives in your Production and Development environments. You’ll want a process of testing Policies and Initiatives before deploying them onto the Production environment to ensure that your policies don’t interfere with the running and operation of your applications.

To solve this problem you can lean on the DevOps & Infrastructure as Code principles you’ll no-doubt have been developing as you adopt the cloud and can take advantage of Azure Policy as Code!

Azure Policy as Code

Azure Policy as Code follows standard Infrastructure as Code conventions — where infrastructure is defined entirely as machine readable code and deployed using CI/CD Pipelines.

The advantage of an Azure Policy as Code solution is that it makes it really easy to create, develop, manage, and assign Policies and Initiatives across your cloud environment as it grows. It also allows for consistency and repeatability when working with Azure Policy.

If you’re using GitHub to store your code and GitHub Actions to deploy it then this solution will hopefully feel familiar to you. There is a GitHub action that exists to make deploying and assigning Policies and Initiatives to Azure very simple.

First let’s look at the layout of the repository. For the purpose of the GitHub action the directory structure doesn’t really matter, the action will search all files in paths that it isn’t configured to ignore. However following this directory structure will make managing and understanding your Policies much easier.

When creating your directory structure you’ll want to create 2 folders at the root, one labelled policies and one labelled initiatives. Within these folders you’ll create a folder with the display name of each of the Policies or Initiative; by encapsulating a Policy or Initiative to a single directory it makes the management of the definition and assignment files really easy in the future.

An example of the Directory Structure in your repository.

Within a Policy Directory you’ll need to create a definition file. A definition file is a JSON representation of your policy or which we’ll explore a little later on. For an Initiative Directory you’ll have a definition to a Policy Set, which lists all the Policies to include in the initiative. To accompany these definition files you’ll create assignment files. These files define the scope (Management Group, Subscription, Resource Group or Resource) where the Policy or Initiative should be applied and also sets the values for any parameters defined in the Definitions. You can have any number of these files to cover different scopes with different configurations you need.

When the GitHub Action runs over the repo it will search in all the directories found, however you can configure it to ignore certain paths if this is required.

Policy Definition Files

As previously mentioned a Policy Definition File is a JSON representation of your Azure Policy. You can define Parameters and the rules within a Policy Definition file. Azure Policy works by auditing properties of Azure Resources that have already been deployed and as they are deploying — for new Resources.

To find out which properties are available for auditing you can run the following PowerShell

Get-AzPolicyAlias -ListAvailable

If you know the Resource type you want to audit you can use this PowerShell to scale down your search.

(Get-AzPolicyAlias -NamespaceMatch 'storage').Aliases

Once you’ve got the field you wish to Audit you can begin constructing your Policy File. The Gist below is an example of a policy.json file that can be used to define a mandatory tag on a Resource Group.

When creating a definition from scratch you’ll need to define a few things that are slightly different to a Built-In Policy. The root type property denotes what type of definition this is, this is required and is the same for all Policy Definitions.

The root name property can be any value which is unique at the scope which it is defined — you’ll want to scope Policies to either Management Groups or Subscriptions depending how you’ve setup your environment. I tend to just use GUIDs for these to give a good level of randomness so not to box myself in for expansion in the future.

If you’re scoping the policy at the Management Group level then your name property needs to be 24 characters or less. This handy bit of PowerShell can generate that for you:

(New-Guid).Guid.SubString(0,23)

The id property also at the root is slightly more complex. The id needs to start with the resourceId for the scope you want to store the policy definition at, for example:

/subscriptions/00000000-0000-0000-0000-000000000000/ /providers/Microsoft.Management/managementGroups/00000000-0000-0000-0000-000000000000

Where the Zero-GUID is the id for your Subscription or Management Group respectively. The last segment of the id property is the same value used in the name property.

Not having these properties will cause your Policy Creation to fail.

The properties object contains the rules and parameters for your Policy. You can use this section to develop your Policy and genericise it for multiple uses.

For example, this Resource Groups Should Have Tag Policy can be reused for all Mandatory Tags — so you could use it to deny any Resource Groups that don’t have an environment , costCentre and technicalOwner tag. You could decide that this is a Tagging Policy and want to create a view where you can see all non-compliant Resources. This is where Initiatives become useful, we’ll discuss them in detail next.

Initiative Definition Files

Initiatives are collections of Policies, you can add up to 1000 Policies to an Initiative. Initiatives can be used to allow several Policies to be audited together in a single view, this is useful for encapsulating Policies for meeting accreditations or team/projects. For example, an ISO27001 Initiative exists to make it easier to audit and track compliance of Resources against this accreditation.

Similar to a Policy, an Initiative Definition File is a JSON representation of your Initiative that makes it machine readable.

To start building your Initiative you’ll need the Definition ID of all the policies you wish to add. It is important to remember the Azure Hierarchy here

A Policy that has been assigned to a Child Scope cannot be accessed by the Parent, a Policy assigned to the Parent Scope can be accessed by the Child. Meaning, an Initiative assigned to the Subscription scope can read a Policy defined at the Management Group level — however a Policy defined at the Subscription scope cannot be read by an Initiative defined at the Management Group scope.

Once you’ve got your Policy IDs you can begin to build your policyset.json file.

Similarly to a Policy Definition file you’ll need to define a name , id and type in your Initiative Definition file. The type for an Initiative is the same for all initiatives and can be seen above. The name of an Initiative needs to be unique across all Initiatives defined at the scope, as before with Policies if an Initiative is defined at the Management Group scope the name needs to be 24 characters or less. The last segment of the id property is the same value used for the name property.

The id needs to start with the resourceId for the scope you want to store the policy definition at, for example:

/subscriptions/00000000-0000-0000-0000-000000000000//providers/Microsoft.Management/managementGroups/00000000-0000-0000-0000-000000000000

Where the Zero-GUID is the id for your Subscription or Management Group respectively.

Once you’ve written your Polices and Initiatives and they’ve been pushed to the remote repository you’ll need a way to deploy them to Azure.

Manage Azure Policy GitHub Action

To support you in managing your Policy as Code through GitOps and Pipelines the Manage Azure Policy GitHub Action exists.

Using this GitHub Action makes it much simpler to manage your Policies and Initiatives. Simply add the YAML into your workflow file, specify the paths to search for the Policies and Initiatives and run the pipeline. When successful you should see an output in the workflow confirming the policy has been created. At this point you can return to the Azure Policy blade in the Azure Portal where your custom Policy or Initiative can now be seen.

Output of a successful pipeline overlaid on the Policy in Azure Portal

Now you’ve created your Definitions you’ll want to be able to assign them somewhere.

Assign Azure Policy as Code

For assigning an Azure Policy or Initiative you’ll need to create an Assignment Definition file. Once more this file is a JSON representation of your assignment which makes it machine readable.

In an assignment file you’ll specify the scope to assign the Policy or Initiative and the values for any parameters required. To reference a Policy or Initiative you’ll need its Definition Id.

You can have multiple assignment files for each Policy or Initiative each with different scopes and values for the parameters. Each of these assignment files are placed in the directory with the Policy or Initiative they are assigning. There is a good practice naming convention used to denote assignment files within a directory, it also is used to add context on where it is applied.

assign.<context>.json

Within the naming structure the <context> element is free-text where you can specify the scope of the file, this could be a Subscription Name, a Management Group name or an abstract environment such as Production, or Pre-Production.

The assignment file above gives an example of how you can assign a Policy to a Subscription, where the Policy is also scoped to the Subscription. As with Policies and Initiatives you’ll need to create a name property that is unique and this needs to be the last segment of the id property.

A property worth discussing in the assignment file is the enforcementMode property. This is how you can set the assignment to be passive or active. When you’re testing your Assignment to see which resources would be affected and how you can apply it passively meaning it will only audit your resources, you can see the results of the audit in the Policy Blade the Azure Portal. To do this you’d provide the value DoNotEnforce to the property.

Once you’ve tested your Assignment and are happy with the proposed effect to new and existing resources you can change this value to Default this will apply the assignment in an active mode which will have the power to block the creation of non-compliant resources, or in some cases change values of existing ones.

With your Assignment Files defined and in place next to their respective Policy or Initiative Definition you can push your changes to your remote repository. The previously discussed GitHub action will find an apply any Assignment file it can find.

It may be the case that you don’t want to apply all your policies everywhere to begin with, perhaps you want to deploy to a test or pre-production environment before applying it to production. You can use the GitHub Action to filter the assignment files at each stage of the pipeline — allowing you to have one pipeline, or job, per environment you wish to deploy.

This sample code would deploy any Definitions which are children of the Policies and Initiatives directory. When it comes to applying the assignments the Action will filter for any assignment files with the name test. So assign.testenvironment.json would be applied but assign.productionenvironment.json would not.

Removal

At the time of writing the GitHub action does not handle the deletion of Definitions and Assignments which are removed from the repository. If you do want to remove a Policy, Initiative or Assignment you’ll need to remove it using the Azure Portal or you could choose to do this in PowerShell, AZ CLI or Terraform. Once the Policy, Initiative or Assignment has been removed from Azure you’ll need to remember to remove its corresponding file from your repository also.

Conclusion

The purpose of this post is to give you an insight into how you could design your Policy as Code to enhance your Cloud Platform. When I was doing this myself I found a few ‘gotcha’ and missing bits in the documentation that I wanted to bring together in one place.

Now you should start to consider how this would work in your organisation. Perhaps you’ve had developers build Virtual Machines that are too big and have blown your budget, or you’ve had resources built in regions which breach your Corporate Policies, maybe you feel that your Cloud Environment has become a ‘Wild-West’ with users working with unfettered access. Regain control of your Cloud Environment by using this example.

For more posts about Azure, DevOps and good Cloud Practice follow me and the Contino Engineering Publication for a number of blogs on how to adopt, migrate and use the most common Cloud Service Providers!

If you read this and thought to yourself I already knew that or I know a different way then why not consider Joining Contino and work with leading businesses looking to transform their Cloud and DevOps strategies!

--

--