Using ARM Policies to implement Runbook Best Practices

Have you ever heard of Azure Resource Manager Policies? With policies, you can prevent users in your organization from breaking conventions that are needed to manage your organizations resources.

One of the scenarios where you can use policies is making sure that tags are being added to your resources (ie Runbooks) or that a certain naming convention is being used and applied. Policies can be used for many more scenarios but in this blog article I’m going to focus on the following scenario.

 

Scenario

All Azure Automation Runbooks need to have tags containing a tag environment with a value of “Test” or “Prod”. And the following naming convention needs to used for the Runbook names “contoso<description>rb”

 

Tags

Tags are Name-value pairs assigned to resources or resource groups and have a Subscription-wide taxonomy. Each resource can have up to 15 tags.

 

image

 

Tags Best Practices/Uses

  • Tag by environment, e.g. dev/test/prod
  • Tag by role, e.g. web/cache/db
  • Tag by department, e.g. finance/retail/legal
  • Tag by responsible party, e.g. Bob

 

Solution

Policies will be evaluated when a resource is being created and to implement the above mentioned scenario we can use the one of the following Conditions:

Conditions

Condition Name Syntax
Equals “equal”:”<value>”
Like “like”:”<value>”
Contains “contains”:”<value>”
In "in" : [ "<value1>","<value2>" ]
ContainsKey "containsKey" : "<keyName>"

 

Fields

The following fields are supported: name, kind, type, tags,tags.* and property alias.

 For our scenario we are going to use the Conditions like and in and the fields Name and tags.*

 

Policy in JSON format

To define your policy you need to store your Policy in a JSON formatted object.

 

To deny all Runbooks which don’t have a tag with a key environment containing a value of “test” or “prod” you need to create the following JSON formatted Policy:

 
[sourcecode language='csharp'  padlinenumbers='true']
{
  "if": {
    "not" : {
      "field" : "tags.Environment",
      "in" : ["test" , "prod"]
    }
  },
  "then" : {
    "effect" : "deny"
  }
}

 

The next step is to create a Policy for the naming convention.

 
[sourcecode language='csharp' ]
{
  "if" : {
    "not" : {
      "field" : "name",
      "like" : "contoso*rb"
    }
  },
  "then" : {
    "effect" : "deny"
  }
}

 

Putting it all together

We can test the above Policies using the following PowerShell script:

 
[sourcecode language='powershell' ]
# ---------------------------------------------------
# Script: C:\Users\Stefan\OneDrive for Business\Scripts\PS\Azure\ARM\ARMPolicies.ps1
# Tags: Azure, Policy, ARM, Automation, Runbook
# Version: 0.1
# Author: Stefan Stranger
# Date: 06/29/2016 15:59:53
# Description: Example on how to use an ARM Policy on Azure Resources
#              https://azure.microsoft.com/en-us/documentation/articles/resource-manager-policy/
# Comments:
# Changes:  
# Disclaimer: 
# This example is provided “AS IS” with no warranty expressed or implied. Run at your own risk. 
# **Always test in your lab first**  Do this at your own risk!! 
# The author will not be held responsible for any damage you incur when making these changes!
# ---------------------------------------------------

#Info: https://azure.microsoft.com/en-us/documentation/articles/resource-manager-policy/

#region Authentication
#Login to Azure
Add-AzureRmAccount
 
#Select Azure Subscription
$subscription = 
(Get-AzureRmSubscription |
    Out-GridView `
    -Title 'Select an Azure Subscription ...' `
-PassThru)
 
Set-AzureRmContext -SubscriptionId $subscription.subscriptionId -TenantId $subscription.TenantID
#endregion

#region pre-requisites
#Variables
$ResourceGroupName = 'ContosoRG'
$AutomationAccountName = 'contosoauto' #existing automation account
$NewRunbookName = 'contososhutdownrb'
$NewRunbookDescription = 'ShutdownAzureVM with Azure Automation'
$Location = 'West Europe'

#Create a ResourceGroup
New-AzureRmResourceGroup -Name $ResourceGroupName -Location $Location -Tags @{Name="Demo"}, @{Name="Customer";Value="Contoso"} -OutVariable ResourceGroup

#Create new Automation Account
New-AzureRmAutomationAccount -Name $AutomationAccountName -Location $Location -ResourceGroupName $ResourceGroup.ResourceGroupName -Tags @{Name="Customer";Value="Contoso"} -OutVariable AutomationAccount
#endregion

#region create ARM Policy
#Define ARM Policy for Runbook

$policy = New-AzureRmPolicyDefinition -Name RunbookPolicyAssignment -Description "Policy to deny runbooks without Environment tags with a value of test or prod" -Policy '{
  "if": {
    "not" : {
        "allOf": [
          {
             "field" : "tags.environment",
             "in" : ["test" , "prod"]
          },
          {
            "field" : "name",
            "like" : "contoso*rb"
          }
        ]
    }
  },
  "then" : {
    "effect" : "deny"
  }
}'


#Assign Policy
$Scope = "$($ResourceGroup.ResourceId)"+"/providers/Microsoft.Automation/automationAccounts/"+$($AutomationAccount.AutomationAccountName)
New-AzureRmPolicyAssignment -Name RunbookPolicyAssignment -PolicyDefinition $policy -Scope $Scope

#Show ARM Policies
Get-AzureRmPolicyAssignment
Get-AzureRmPolicyDefinition -Name 'RunbookPolicyAssignment'
#endregion

#region Create Runbooks
#Create a a Runbook and check if it fails because not adhering to naming convention policy.
#This one should fail.
New-AzureRmAutomationRunbook -AutomationAccountName $AutomationAccountName -Name "FailedRunbook" -type PowerShell -ResourceGroupName $ResourceGroup.ResourceGroupName -outvariable NewRunbook

#This one should not fail
$tags  = @{}
$tags.environment = 'test'
$tags.name = 'demo'

New-AzureRmAutomationRunbook -AutomationAccountName $AutomationAccountName -Name $NewRunbookName -Tags $tags -type PowerShell -ResourceGroupName $ResourceGroup.ResourceGroupName -Description $NewRunbookDescription -outvariable NewRunbook

#Check if runbook is created
Get-AzureRmAutomationRunbook -AutomationAccountName $AutomationAccountName -Name $NewRunbookName -ResourceGroupName $ResourceGroup.ResourceGroupName
#endregion

#region auditlogs
#Check Policy Audit logs
Get-AzureRmLog | Where-Object {$_.OperationName -eq "Microsoft.Authorization/policies/deny/action"} | ogv
#endregion

#region Clean up

#Remove Policy
Remove-AzureRmPolicyAssignment -Name RunbookPolicyAssignment -Scope $Scope

#Remove Runbook
Remove-AzureRmAutomationRunbook -Name $NewRunbookName -ResourceGroupName $ResourceGroup.ResourceGroupName -AutomationAccountName $AutomationAccountName

#Remove Automation Account
Remove-AzureRmAutomationAccount -Name $AutomationAccountName -ResourceGroupName $ResourceGroup.ResourceGroupName

#Remove Resource Group
Remove-AzureRmResourceGroup -Name $ResourceGroupName -Force

#endregion

 

In action:

armpolicy

 

Disclaimer:
This example is provided “AS IS” with no warranty expressed or implied. Run at your own risk.
**Always test in your lab first** Do this at your own risk!!
The author will not be held responsible for any damage you incur when making these changes!

 

Hope this helps to implement some best practices.

References:

Use Policy to manage resources and control access