This article is contributed. See the original author and article here.

It’s recommended that you plan and implement a governance strategy before you start deploying cloud resources, but in reality it doesn’t always work out like that. How can we find which existing resources don’t meet our new standards?


 


There’s a couple of ways to do it, including using a Kusto query or PowerShell, but today we’ll look at using an Azure Policy definition in audit mode.


 


The scenario:
Tailwind Traders have decided that all of the resources in one subscription should have two resource tags applied to them: Dept (department) and Env (environment). This has almost become an accepted standard, as some teams have already tagged their resources, but not consistently. Tailwind Traders doesn’t want to enforce that these tags are required, as they have some automated provisioning processes that will need to be updated first. They do want to see how many of the existing resources do not have these tags, and be able to check this easily on an ongoing basis.


 


The challenge:
The current built-in Azure Policy definitions for tags either require (on new resources), add, append or inherit tags. The DeployIfNotExists method can be use to retro-actively apply a require tags policy to existing resources, but Tailwind Traders only wants to report which resources are non-compliant, not modify them. Azure Policy can run in audit mode, and Tailwind Traders needs to create a policy definition that looks for two different tag names on each resource. If a resource doesn’t have both tags, we want to know.



The process:
Start with what you know – I mentioned there’s already an in-built Azure Policy definition for requiring a tag on resources. That’s a good place to start to check for the schema of how our JSON should be written. If, like me, you’re not the sort of person who writes JSON off the top of your head, find things that do incorporate some of what you need to apply and take it from there.


 


Here’s the policy definition for “Require a tag on resources”:


 


 


 


 

{
  "properties": {
    "displayName": "Require a tag on resources",
    "policyType": "BuiltIn",
    "mode": "Indexed",
    "description": "Enforces existence of a tag. Does not apply to resource groups.",
    "metadata": {
      "version": "1.0.1",
      "category": "Tags"
    },
    "parameters": {
      "tagName": {
        "type": "String",
        "metadata": {
          "displayName": "Tag Name",
          "description": "Name of the tag, such as 'environment'"
        }
      }
    },
    "policyRule": {
      "if": {
        "field": "[concat('tags[', parameters('tagName'), ']')]",
        "exists": "false"
      },
      "then": {
        "effect": "deny"
      }
    }
  },
  "id": "/providers/Microsoft.Authorization/policyDefinitions/871b6d14-10aa-478d-b590-94f262ecfa99",
  "type": "Microsoft.Authorization/policyDefinitions",
  "name": "871b6d14-10aa-478d-b590-94f262ecfa99"
}

 


 


 


 


The first edit is the easiest one – I need to change the line “effect”: “deny” to read “effect”: “audit”
This will show me if any existing resources are non-compliant and allows new resources to be created without enforcing these tags.


 


Now to figure out how to add an additional tag!


 


I know that there’s an in-built policy called Allowed locations. This policy lets you select more than one location where a resource can be deployed. If we take a look at that definition:


 


 


 


 

{
  "properties": {
    "displayName": "Allowed locations",
    "policyType": "BuiltIn",
    "mode": "Indexed",
    "description": "This policy enables you to restrict the locations your organization can specify when deploying resources. Use to enforce your geo-compliance requirements. Excludes resource groups, Microsoft.AzureActiveDirectory/b2cDirectories, and resources that use the 'global' region.",
    "metadata": {
      "version": "1.0.0",
      "category": "General"
    },
    "parameters": {
      "listOfAllowedLocations": {
        "type": "Array",
        "metadata": {
          "description": "The list of locations that can be specified when deploying resources.",
          "strongType": "location",
          "displayName": "Allowed locations"
        }
      }
    },
    "policyRule": {
      "if": {
        "allOf": [
          {
            "field": "location",
            "notIn": "[parameters('listOfAllowedLocations')]"
          },
          {
            "field": "location",
            "notEquals": "global"
          },
          {
            "field": "type",
            "notEquals": "Microsoft.AzureActiveDirectory/b2cDirectories"
          }
        ]
      },
      "then": {
        "effect": "deny"
      }
    }
  },
  "id": "/providers/Microsoft.Authorization/policyDefinitions/e56962a6-4747-49cd-b67b-bf8b01975c4c",
  "type": "Microsoft.Authorization/policyDefinitions",
  "name": "e56962a6-4747-49cd-b67b-bf8b01975c4c"
}

 


 


 


 


The key thing is the use of the “allOf” expression after the “if”. This means that all of the conditions that follow have to be satisfied for the “then” effect to kick in. They are encapsulated by the square brackets [ ].


 


It’s also worth noting that the “listOfAllowedLocations” parameter is only mentioned once, as it’s using an array. That gives you a list of the Azure regions that you can multi-select from. In our case, we want to specify the two tag names when we apply the policy, so we will need to duplicate the parameters entries.


 


The result:
Merging the above together, we can create the custom policy we want – “Audit if two tags exist”:


 


 


 

{
  "mode": "Indexed",
  "policyRule": {
    "if": {
      "allOf": [
        {
          "field": "[concat('tags[', parameters('tagName1'),  ']')]",
          "exists": "false"
        },
        {
          "field": "[concat('tags[', parameters('tagName2'),  ']')]",
          "exists": "false"
        }
      ]
    },
    "then": {
      "effect": "audit"
    }
  },
  "parameters": {
    "tagName1": {
      "type": "String",
      "metadata": {
        "displayName": "Tag Name 1",
        "description": "Name of first tag, such as 'environment'"
      }
    },
    "tagName2": {
      "type": "String",
      "metadata": {
        "displayName": "Tag Name 2",
        "description": "Name of second tag, such as 'owner'"
      }
    }
  }
}

 


 


 


We’ve added a second tag as a parameter, so both tag names can be specified when the policy is assigned to a scope. This means you could use the same definition to search for different tags when it is assigned to different scopes (e.g. Resource groups or subscriptions).


 


We have our “allOf” statement to look for both tag name 1 and tag name 2.


 


And we have our effect in Audit mode.


Tag_Policy_compliance.png


 


Troubleshooting:
The trickiest part of merging concepts and duplicating sections is keeping all of your brackets and commas in the right places! In this image, I’ve removed a bracket on purpose.


Tag_Policy_error.png


The Azure Policy definition editor has thrown some grey, yellow and red boxes down the right margin to warn me that something isn’t right. And if I mouse-over a bracket, it will automatically highlight the corresponding bracket pair, so I can also see I’m missing one that should “close off” the parameters section. That final bracket should correspond to the opening bracket at the very top.


 


Some JSON errors will prevent you from saving your edits, or will generate an error in the notifications.


Tag_Policy_notification_error.png


 


Some errors won’t be obvious until the policy has had time to run against your resources. For testing, it’s worth having a couple of known resources that you know should pass and some you know should fail against your policy. If these don’t show up with the correct compliance state, double-check both your JSON and your policy assignment – for example, is there a typo in the tag name?


 


And if all else fails – ask someone else to help you!


 


Now, it’s over to you!
Don’t be afraid to learn the basics concepts, take what you know, and explore what you can create, even when it’s not explicitly documented. Share with us if you’ve built some advanced Azure Policy definitions – you can even share your work with the community by adding to the Azure Community Policy GitHub repository.


 


Learn more:


Docs – What is Azure Policy?


Docs – Tutorial: Manage tag governance with Azure Policy


Docs – Azure Policy pattern: tags


Docs – Understand Azure Policy effects


MS Learn – Build a cloud governance strategy on Azure


 


-SCuffy


 


 

Brought to you by Dr. Ware, Microsoft Office 365 Silver Partner, Charleston SC.