Notes on Azure Policy Exemption

There are ways to exclude your resources from being evaluated by Azure Policy. You can add a condition in a policy rule set. You can also use exclusion from notScopes.

In this article, let’s explore another feature in Azure Policy exemption. We will then see how to deploy it as code.

Feature Overview

Azure Policy Exemption is a feature that allows you to exempt a resource from an Azure Policy evaluation. That resource is still counted toward overall compliance but isn’t evaluated. Azure Policy Exemption has several differences when compared with exclusion (notScopes)

  • It supports expiration. When creating an exemption you can specify when a resource exemption expires. After that, the exemption is no longer honored.
  • It supports Resource Manager mode.
  • It doesn’t depend on Policy Assignment. It means when adding an exemption you don’t have to go to Policy Assignment to update. It can be created independently. 

Azure Policy Exemption is helpful in several cases. One of the most common cases is when you want to exempt resources from Microsoft Defender for Cloud recommendations (those that are run under Azure Policy) to reduce negative secure score impact. For example, your virtual machines have 3rd party vulnerability assessment solutions that aren’t credited by Microsoft Defender for Cloud. In this case, you can automatically create an exemption for each virtual machine. 

For details of Azure Policy Exemption, read this article to understand more about the scope.

Creating exemption with CLI

You can use either PowerShell or Azure CLI to create a policy exemption. In this article, I use az policy exemption.

#!/bin/bash
# Use this script to create a policy exemption for testing purpose.
# Reference: https://azsec.azurewebsites.net/2021/12/22/notes-on-azure-policy-exemption/
# The following script is used to create an exemption for the storage account named jpstoragedata.

date=$(date +%F-%H%M%SD)
policy_exemption_name="storage-exemption-${date}"
policy_exemption_desc="This storage account is approved to allow publicAccess temporarily"
policy_exemption_displayname="Storage account exemption - ${date}"
policy_exemption_category="waiver"
expire_on="2021-12-23T00:00:00"
policy_exemption_metadata=("RequestedBy=ts" "ApprovedBy=azsec" "ApprovedOn=12/21/2021" "TicketRef=123456789")
policy_assignment="/subscriptions/67d6179d-a99d-4ccd-8c56-4d3ff2e13349/providers/Microsoft.Authorization/policyAssignments/2ca44c27185a4776ab0a5016"

# policy definition reference id is only needed for policy initiative
policy_definition_reference_id=""
policy_exemption_scope="/subscriptions/67d6179d-a99d-4ccd-8c56-4d3ff2e13349/resourceGroups/azsec-corporate-rg/providers/Microsoft.Storage/storageAccounts/jpstoragedata"

az policy exemption create --name "${policy_exemption_name}" \
                           --description "${policy_exemption_desc}" \
                           --display-name "${policy_exemption_displayname}" \
                           --exemption-category "${policy_exemption_category}" \
                           --expires-on "${expire_on}" \
                           --metadata "${policy_exemption_metadata[@]}" \
                           --policy-assignment "${policy_assignment}" \
                           --scope "${policy_exemption_scope}"

You can use New-AzPolicyExemption cmdlet in PowerShell.

ARM Template

You can use ARM template to create a new policy exemption. Below is the sample template:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "policyExemptionName": {
      "type": "string",
      "metadata": {
        "description": "Name of the policy exemption"
      }
    },
    "displayName": {
      "type": "string",
      "metadata": {
        "description": "Display name of the policy exemption"
      }
    },
    "description": {
      "type": "string",
      "metadata": {
        "description": "Description of the policy exemption"
      }
    },
    "exemptionCategory": {
      "type": "string",
      "allowedValues": ["mitigated", "waiver"],
      "metadata": {
        "description": "The policy exemption category. Supported values are Waiver and Mitigated."
      }
    },
    
    "exemptionScope": {
      "type": "string",
      "metadata": {
        "description": "Scope of the policy exemption such as resource Id of the storage account"
      }
    },
    "policyAssignmentId": {
      "type": "string",
      "metadata": {
        "description": "Policy assignment resource Id that the policy exemption is effected"
      }
    },
    "expireOn": {
      "type": "string",
      "metadata": {
        "description": "The expiration date and time (in UTC ISO 8601 format yyyy-MM-ddTHH:mm:ssZ) of the policy exemption."
      }
    },
    "requestedBy": {
      "type": "string",
      "metadata": {
        "description": "Specify who requested the policy exemption"
      }
    },
    "approvedBy": {
      "type": "string",
      "metadata": {
        "description": "Specify who approved for the policy exemption"
      }
    },
    "ticketReference": {
      "type": "string",
      "metadata": {
        "description": "Specify ticket reference number if you have ITSM e.g. ServiceNow"
      }
    }
  },
  "variables": {
    "metadata": {
      "requestedBy": "[parameters('requestedBy')]",
      "approvedBy": "[parameters('approvedBy')]",
      "ticketReference": "[parameters('ticketReference')]"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Authorization/policyExemptions",
      "apiVersion": "2020-07-01-preview",
      "scope": "[parameters('exemptionScope')]",
      "name": "[parameters('policyExemptionName')]",
      "properties": {
        "displayName": "[parameters('displayName')]",
        "description": "[parameters('description')]",
        "exemptionCategory": "[parameters('exemptionCategory')]",
        "expiresOn": "[parameters('expireOn')]",
        "policyAssignmentId": "[parameters('policyAssignmentId')]",
        "metadata": "[variables('metadata')]"
      }
    }
  ]
}

You can use a sample ARM deployment and parameter template file here.

Exempt VM in Microsoft Defender for Cloud

There are different use cases for policy exemption. In this article, I would like to demonstrate a case that deployment of a virtual machine includes a policy exemption to add it to an existing Microsoft Defender for Cloud recommendation named Machines should have a vulnerability assessment solution. 

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "vmName": {
      "type": "string",
      "metadata": {
        "description": "Name of the virtual machine"
      }
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]",
      "metadata": {
        "description": "Location of the virtual machine"
      }
    },
    "adminUsername": {
      "type": "string",
      "metadata": {
        "description": "Administrator username of the virtual machine"
      }
    },
    "adminPassword": {
      "type": "securestring",
      "metadata": {
        "description": "Administrator password of the virtual machine"
      }
    },
    "vmSize": {
      "type": "string",
      "defaultValue": "Standard_A4_v2",
      "metadata": {
        "description": "Size of the virtual machine."
      }
    },
    "subnetId": {
      "type": "string",
      "metadata": {
        "description": "Subnet where the virtual machine belongs to"
      }
    },
    "policyAssignmentId": {
      "type": "string",
      "metadata": {
        "description": "Resource id of the policy assignment the VM will be exempted"
      }
    },
    "expireOn": {
      "type": "string",
      "metadata": {
        "description": "The expiration date and time (in UTC ISO 8601 format yyyy-MM-ddTHH:mm:ssZ) of the policy exemption."
      }
    }
  },
  "variables": {
    "nicName": "[concat(parameters('vmName'), '-nic')]"
  },
  "resources": [
    {
      "type": "Microsoft.Network/networkInterfaces",
      "apiVersion": "2018-11-01",
      "name": "[variables('nicName')]",
      "location": "[parameters('location')]",
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "subnet": {
                "id": "[parameters('subnetId')]"
              }
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Compute/virtualMachines",
      "apiVersion": "2018-10-01",
      "name": "[parameters('vmName')]",
      "location": "[parameters('location')]",
      "identity": {
        "type": "SystemAssigned"
      },
      "dependsOn": [
        "[resourceId('Microsoft.Network/networkInterfaces/', variables('nicName'))]"
      ],
      "properties": {
        "hardwareProfile": {
          "vmSize": "[parameters('vmSize')]"
        },
        "osProfile": {
          "computerName": "[parameters('vmName')]",
          "adminUsername": "[parameters('adminUsername')]",
          "adminPassword": "[parameters('adminPassword')]"
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "RedHat",
            "offer": "RHEL",
            "sku": "7.8",
            "version": "latest"
          },
          "osDisk": {
            "createOption": "FromImage"
          },
          "dataDisks": [
            {
              "diskSizeGB": 1023,
              "lun": 0,
              "createOption": "Empty"
            }
          ]
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]"
            }
          ]
        }
      }
    },
    {
      "type": "Microsoft.Authorization/policyExemptions",
      "apiVersion": "2020-07-01-preview",
      "scope": "[resourceId('Microsoft.Compute/virtualMachines',parameters('vmName'))]",
      "name": "vmexemption",
      "dependsOn": [
        "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]"
      ],
      "properties": {
        "displayName": "[concat(parameters('vmName'),'exemption')]",
        "description": "[concat('Exemption for ', parameters('vmName'))]",
        "exemptionCategory": "waiver",
        "expiresOn": "[parameters('expireOn')]",
        "policyAssignmentId": "[parameters('policyAssignmentId')]",
        "policyDefinitionReferenceIds": [
          "serverVulnerabilityAssessment"
        ],
        "metadata": {}
      }
    }
  ],
  "outputs": {}
}

Look at the policyDefinitionReferenceIds property. This one is needed if the exemption is added to a policy initiatives. The policy definition reference id is used to indicate the policy definition. In this case, serverVulnerabilityAssessment is the reference id of the policy named A vulnerability assessment solution should be enabled on your virtual machines . (Note that this is the policy definition name that runs the evaluation and sends the report to Machines should have a vulnerability assessment solution recommendation in the Azure Portal).

If you want to exempt the virtual machine from Endpoint protection should be installed on your machines recommendation, the reference Id is installEndpointProtection. In this case, your policyDefinitionReferenceIds  should look like the one below:

...
"policyDefinitionReferenceIds": [
  "serverVulnerabilityAssessment",
  "installEndpointProtection"
]
...

Go to Azure Policy Exemption and verify.

The sample deployment and parameter file templates can be found here.

Key Takeways

  • Azure Policy Exemption allows you to exempt resources from Azure Policy evaluation.
  • It supports expiration date so you can set to allow the exemption temporarily.
  • This feature is helpful when you have waiver or mitigation on your cloud resources but those resources aren’t supported or credited by Microsoft Defender for Cloud (Azure Security Benchmark).
This entry was posted in Governance & Compliance and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published.