Skip to content

Commit

Permalink
Support for Tag inheritance from Subscription to Resource Group (Azur…
Browse files Browse the repository at this point in the history
…e#161)

* Add policy and policy set to inherit tags from subscription to resource group

* Add branch config for testing

* Remove policy type as it's not built in

* Updated resource type for resource group

* Update policy assignment

* Ensure assignment name is <= 24 chars

* Revert resource group type

* Setting mode to all

* Update documentation

* Add branch config

* Add explicit dependsOn for subscription scaffolding to complete

* Update test deployment parameters

* Remove explicit dependsOn for subscription scaffolding to complete

* Update doc to describe approaches for adding tags to RGs

* Reduce the options for tagging resources given subscripton to RG tagging is available

* Add example scenarios for tag inheritence

* Fix typo

* Remove branch configs

* Resolve linter error: no-loc-expr-outside-params
  • Loading branch information
SenthuranSivananthan authored Feb 18, 2022
1 parent e71ed26 commit edabd87
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 19 deletions.
43 changes: 36 additions & 7 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,8 @@ For example, when you grant access to your team responsible for infrastructure s
| IT Teams responsible for one or more line of business with permissions to one or more subscriptions, resource groups or resources with at least Reader role. | Access the logs through the resource's Logs menu for the Azure resource (i.e., VM or Storage Account or Database). | Only to Azure resources based on RBAC. User can query logs for specific resources, resource groups, or subscription they have access to from any workspace but can't query logs for other resources. |
| Application Team with permissions to one or more subscriptions, resource groups or resources with at least Reader role. | Access the logs through the resource's Logs menu for the Azure resource (i.e., VM or Storage Account or Database). | Only to Azure resources based on RBAC. User can query logs for specific resources, resource groups, or subscription they have access to from any workspace but can't query logs for other resources. |


---


## 7. Tagging

Organize cloud assets to support governance, operational management, and accounting requirements. Well-defined metadata tagging conventions help to quickly locate and manage resources. These conventions also help associate cloud usage costs with business teams via chargeback and show back accounting mechanisms.
Expand All @@ -349,11 +347,17 @@ A tagging strategy include business and operational details:
* The business side of this strategy ensures that tags include the organizational information needed to identify the teams. Use a resource along with the business owners who are responsible for resource costs.
* The operational side ensures that tags include information that IT teams use to identify the workload, application, environment, criticality, and other information useful for managing resources.

Tags can be assigned to resources using 3 approaches:
Tags can be assigned to resource groups using 2 approaches:

| Approach | Mechanism |
| --- | --- |
| Automatically assigned from the Subscription tags | Azure Policy: Inherit a tag from the subscription to resource group if missing |
| Explicitly set on a Resource Group | Azure Portal, ARM templates, CLI, PowerShell, etc. All tags can be inherited by default from subscription and can be changed as needed per resource group. |

Tags can be assigned to resources using 2 approaches:

| Approach | Mechanism |
| --- | --- |
| Automatically assigned from the Subscription tags | Azure Policy: Inherit a tag from the subscription if missing |
| Automatically assigned from the Resource Group tags | Azure Policy: Inherit a tag from the resource group if missing |
| Explicitly set on a Resource | Azure Portal, ARM templates, CLI, PowerShell, etc.<br /><br />**Note:** It's recommended to inherit tags that are required by the organization through Subscription & Resource Group. Per resource tags are typically added by Application Teams for their own purposes. |

Expand All @@ -363,18 +367,43 @@ Azure Landing Zones for Canadian Public Sector recommends the following tagging
![Tags](media/architecture/tags.jpg)

To achieve this design, built-in and custom Azure Policies are used to automatically propagate tags from Resource Group, validate mandatory tags at Resource Groups and to provide remediation to back-fill resources with missing tags. Azure Policies used to achieve this design are:
To achieve this design, custom Azure Policies are used to automatically propagate tags from subscription & resource group, validate mandatory tags at resource groups and to provide remediation to back-fill resource groups and resources with missing tags. Azure Policies used to achieve this design are:

* [Built-in] Inherit a tag from the resource group if missing
* [Custom] Inherit a tag from the subscription to resource group if missing (1 policy per tag)
* [Custom] Inherit a tag from the resource group if missing (1 policy per tag)
* [Custom] Require a tag on resource groups (1 policy per tag)
* [Custom] Audit missing tag on resource (1 policy per tag)

This approach ensures that:

* All resource groups contain the expected tags; and
* All resource groups can inherit common tags from subscription when missing; and
* All resources in that resource groups will automatically inherit those tags.

This helps remove deployment friction by eliminating the explicit tagging requirement per resource. The tags can be override per resource if required.
This helps remove deployment friction by eliminating the explicit tagging requirement per resource. The tags can be overridden per resource group & resource if required.

**Example scenarios for inheriting from subscription to resource group**

These example scenarios outline the behaviour when using Azure Policy for inheriting tag values.

To simplify, let's assume a single `CostCenter` tag is required for every resource group.

| Subscription Tags | Resource Group Tags | Outcome |
| --- | --- | --- |
| `CostCenter=123` | `CostCenter` tag not defined when creating a resource group. | `CostCenter=123` is inherited from subscription. Resource group is created. |
| `CostCenter=123` | `CostCenter=ABC` defined when creating the resource group. | `CostCenter=ABC` takes precedence since it's explicitly defined on the resource group. Resource group is created. |
| `CostCenter` tag is not defined. | `CostCenter` tag not defined when creating a resource group. | Policy violation since tag can't be inherited from subscription nor it hasn't been defined on resource group. Resource group is not created. |

**Example scenarios for inheriting from resource group to resources**

These example scenarios outline the behaviour when using Azure Policy for inheriting tag values.

To simplify, let's assume a single `CostCenter` tag is required for every resource.

| Resource Group Tags | Resource Tags | Outcome |
| --- | --- | --- |
| `CostCenter=123` | `CostCenter` tag not defined when creating a resource. | `CostCenter=123` is inherited from resource group. Resource is created. |
| `CostCenter=123` | `CostCenter=ABC` defined when creating the resource. | `CostCenter=ABC` takes precedence since it's explicitly defined on the resource. Resource is created. |

*We chose custom policies so that they can be grouped in a policy set (initiative) and have unique names to describe their purpose.*

Expand Down
Binary file modified docs/media/architecture/tags.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/visio/03 - Tags Design.vsdx
Binary file not shown.
16 changes: 8 additions & 8 deletions landingzones/lz-generic-subscription/main.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ resource rgAutomation 'Microsoft.Resources/resourceGroups@2020-06-01' = {
tags: resourceTags
}

// Create Azure backup RecoveryVault Resource Group
resource rgBackupVault 'Microsoft.Resources/resourceGroups@2020-06-01' =if (backupRecoveryVault.enabled) {
name: resourceGroups.backupRecoveryVault
location: location
tags: resourceTags
}

// Create automation account
module automationAccount '../../azresources/automation/automation-account.bicep' = {
name: 'deploy-automation-account'
Expand All @@ -160,17 +167,10 @@ module automationAccount '../../azresources/automation/automation-account.bicep'
}
}

// Create Azure backup RecoveryVault Resource Group
resource backupRgVault 'Microsoft.Resources/resourceGroups@2020-06-01' =if(backupRecoveryVault.enabled) {
name: resourceGroups.backupRecoveryVault
location: location
tags: resourceTags
}

//create recovery vault for backup of vms
module backupVault '../../azresources/management/backup-recovery-vault.bicep'= if(backupRecoveryVault.enabled){
name:'deploy-backup-recoveryvault'
scope: backupRgVault
scope: rgBackupVault
params:{
vaultName: backupRecoveryVault.name
tags: resourceTags
Expand Down
38 changes: 34 additions & 4 deletions policy/custom/assignments/Tags.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,47 @@ param enforcementMode string = 'Default'

var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId)

// Tags Inherited from Resource Groups
var rgInheritedPolicyId = 'custom-tags-inherited-from-resource-group'
var rgInheritedAssignmentName = 'Custom - Tags inherited from resource group if missing'

// Telemetry - Azure customer usage attribution
// Reference: https://docs.microsoft.com/azure/marketplace/azure-partner-customer-usage-attribution
var telemetry = json(loadTextContent('../../../config/telemetry.json'))
module telemetryCustomerUsageAttribution '../../../azresources/telemetry/customer-usage-attribution-management-group.bicep' = if (telemetry.customerUsageAttribution.enabled) {
name: 'pid-${telemetry.customerUsageAttribution.modules.policy}'
}

// Tags Inherited from Subscription to Resource Groups
var rgInheritedPolicyFromSubscriptionToResourceGroupId = 'custom-tags-inherited-from-subscription-to-resource-group'
var rgInheritedAssignmentFromSubscriptionToResourceGroupName = 'Custom - Tags inherited from subscription to resource group if missing'

resource rgInheritedPolicySetFromSubscriptionToResourceGroupAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = {
name: 'tags-torg-${uniqueString('tags-torg-', policyAssignmentManagementGroupId)}'
properties: {
displayName: rgInheritedAssignmentFromSubscriptionToResourceGroupName
policyDefinitionId: '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${rgInheritedPolicyFromSubscriptionToResourceGroupId}'
scope: scope
notScopes: []
parameters: {}
enforcementMode: enforcementMode
}
identity: {
type: 'SystemAssigned'
}
location: location
}

resource rgPolicySetRoleAssignmentFromSubscriptionToResourceGroupContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = {
name: guid(rgInheritedPolicyFromSubscriptionToResourceGroupId, 'RgRemediation', 'Contributor')
scope: managementGroup()
properties: {
roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c'
principalId: rgInheritedPolicySetFromSubscriptionToResourceGroupAssignment.identity.principalId
principalType: 'ServicePrincipal'
}
}

// Tags Inherited from Resource Groups
var rgInheritedPolicyId = 'custom-tags-inherited-from-resource-group'
var rgInheritedAssignmentName = 'Custom - Tags inherited from resource group if missing'

resource rgInheritedPolicySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = {
name: 'tags-rg-${uniqueString('tags-from-rg-', policyAssignmentManagementGroupId)}'
properties: {
Expand Down
31 changes: 31 additions & 0 deletions policy/custom/definitions/policyset/Tags.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,41 @@ param policyEnforcement string = 'Deny'
var customPolicyDefinitionMgScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId)

// Retrieve the templated azure policies as json object
var tagsInheritedFromSubscriptionToResourceGroupPolicyTemplate = json(loadTextContent('templates/Tags-Inherit-Tag-From-Subscription-To-ResourceGroup/azurepolicy.json'))
var tagsInheritedFromResourceGroupPolicyTemplate = json(loadTextContent('templates/Tags-Inherit-Tag-From-ResourceGroup/azurepolicy.json'))
var tagsRequiredOnResourceGroupPolicyTemplate = json(loadTextContent('templates/Tags-Require-Tag-ResourceGroup/azurepolicy.json'))
var tagsAuditOnResourcePolicyTemplate = json(loadTextContent('templates/Tags-Audit-Missing-Tag-Resource/azurepolicy.json'))

// Inherit tags from subscription to resource group
resource tagsInheritedFromSubscriptionToResourceGroupPolicy 'Microsoft.Authorization/policyDefinitions@2020-09-01' = [for tag in requiredResourceTags: {
name: toLower(replace('tags-inherited-from-sub-to-rg-${tag}', ' ', '-'))
properties: {
metadata: {
'tag': tag
}
displayName: '${tagsInheritedFromSubscriptionToResourceGroupPolicyTemplate.properties.displayName}: ${tag}'
mode: tagsInheritedFromSubscriptionToResourceGroupPolicyTemplate.properties.mode
policyRule: tagsInheritedFromSubscriptionToResourceGroupPolicyTemplate.properties.policyRule
parameters: tagsInheritedFromSubscriptionToResourceGroupPolicyTemplate.properties.parameters
}
}]

resource tagsInheritedFromSubscriptionToResourceGroupPolicySet 'Microsoft.Authorization/policySetDefinitions@2020-09-01' = {
name: 'custom-tags-inherited-from-subscription-to-resource-group'
properties: {
displayName: 'Custom - Inherited tags from subscription to resource group if missing'
policyDefinitions: [for (tag, i) in requiredResourceTags: {
policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', tagsInheritedFromSubscriptionToResourceGroupPolicy[i].name)
policyDefinitionReferenceId: toLower(replace('Inherit ${tag} tag from the subscription to resource group if missing', ' ', '-'))
parameters: {
tagName: {
value: tag
}
}
}]
}
}

// Inherit tags from resource group to resources
resource tagsInheritedFromResourceGroupPolicy 'Microsoft.Authorization/policyDefinitions@2020-09-01' = [for tag in requiredResourceTags: {
name: toLower(replace('tags-inherited-from-rg-${tag}', ' ', '-'))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"type": "Microsoft.Authorization/policyDefinitions",
"name": "Tags-Inherit-Tag-From-Subscription-To-ResourceGroup",
"properties": {
"displayName": "Inherit a tag from the subscription to resource group if missing",
"mode": "All",
"description": "Adds the specified tag with its value from the containing subscription when any resource group is missing this tag is created or updated. If the tag exists with a different value it will not be changed.",
"parameters": {
"tagName": {
"type": "String",
"metadata": {
"displayName": "Tag Name",
"description": "Name of the tag, such as 'environment'"
}
}
},
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Resources/subscriptions/resourceGroups"
},
{
"field": "[concat('tags[', parameters('tagName'), ']')]",
"exists": "false"
},
{
"value": "[subscription().tags[parameters('tagName')]]",
"notEquals": ""
}
]
},
"then": {
"effect": "modify",
"details": {
"roleDefinitionIds": [
"/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"
],
"operations": [
{
"operation": "add",
"field": "[concat('tags[', parameters('tagName'), ']')]",
"value": "[subscription().tags[parameters('tagName')]]"
}
]
}
}
}
}
}

0 comments on commit edabd87

Please sign in to comment.