We recently had a requirement from a client that all of their Azure resources must be tagged with a specific set of tags, which were ultimately to be used for cost accounting when the bill came rolling in.
For simplicity of this blog post, let's assume the client just required that all resources had to have a single
costCode tag, with the tag value containing a business-defined cost code that was applicable for that resource (e.g.
We found that the best way to ensure that this rule is enforced is to use Azure Policy.
Azure Policy allows you to use either built-in or custom-defined policy definitions and assign them to either a specific resource group or across a whole Azure subscription. The policies are executed whenever new resources are created within the assigned policy scope, and can be used to either deny or audit the deployment if the policy requirements have not been met. This blog post assumes some basic knowledge of how Azure Policy works.
There are actually a few built-in policy definitions related to resource tagging, but we'll take a quick look at them and see why they didn't quite fit the bill in this case.
Firstly, there is the 'Apply tag and its default value' policy definition.
Looking at this definition, we can see that it will apply the named tag to the resource, and if no value has been explicitly specified, then the default value will be used. Whilst this would ensure that the named tag (e.g.
costCode) would be applied to all resources, it means that there is nothing to stop someone forgetting to explicitly set a value, and thus the default value will be incorrectly applied to the resource.
The other built-in tag-based policy definition is 'Enforce tag and its value'.
This definition is almost what we want, as it will enforce that the named tag is present. However, it will also enforce that the tag value must be the value specified in the policy, which does not work for us as the value will change.
Since the built-in policy definitions do not quite do what we want, we can create a custom policy definition instead.
The policy we want is to enforce the tag, but to allow any value to be used.
mode is set to
Indexed - this ensures the policy is only applied to resources that can be tagged)
With this policy applied at the subscription level for the
costCode tag, it successfully meets the client's requirements for having the tag enforced on all resources that are created within the subscription.
However, we found this added a level of unnecessary complexity to deployments. Most new resources were created via ARM template deployments, and now, in order to comply with this policy, every single resource that was defined in the templates had to have the
costCode tag defined on it, as shown in the example ARM template below.
This felt like it was unnecessarily mixing business logic into the templates, especially when some templates were designed for re-use.
It would be better if the tags could be set at the resource group level, then automatically applied to the resources within that resource group, thus negating the need to have the tags defined in the templates.
The solution to this was to use Azure Policy again. We could create a custom policy definition that checked to see if the resource's parent resource group contained a specific tag and applied that tag to the resource if so, with the exception being if the resource had the tag explicitly defined on itself.
This means that for each deployment, we just need to apply the tags to the resource group, and they would automatically flow down to the child resources.
This works when used in combination with the previous 'Enforce tag' policy definition, because Azure Policy will evaluate all
append effects of the applicable policies before the
deny effects, thus the parent resource group tag value will attempt to be appended before the policy will attempt to deny the deployment.
Since writing this post, Microsoft have introduced a built-in policy that provides the same behaviour as my 'ApplyTagValueFromResourceGroup' custom policy above. The policy definition is called 'Inherit a tag from the resource group if missing' and can be used to the same effect (without having to create a custom policy definition).