Audit Your Azure Resources with Resource Graph
I recently delivered a session at Microsoft Ignite The Tour in London around governance in Azure. One of the critical points in this session is that before you try and implement any controls around resources, cost or security you need to have a good understanding of what your Azure estate currently looks like and what resources you are making use of, so you know where to focus your effort.
There is no point spending lots of time implementing policies to restrict which size web apps you can deploy if no one is using them. Similarly, if you go ahead and lock down the allowed sizes of VMs to D2-16 V3, but 70% of your deployments are still actually using the V2 SKU you are going to have some angry users. Before you implement any of these changes, it is essential that you have a good grasp on how your company is using Azure today. A great tool to help do this is Resource Graph.
Resource Graph is a command line tool that allows you to quickly and easily query your whole Azure estate using the familiar Kusto query language that is used in Log Analytics and App Insights. If you have never used resource graph before I have a primer on getting set up and running your first queries here. For the rest of this article, we will look at how to use Resource Graph as part of our audit and data collection process and some useful queries to help you get a handle on your current Azure usage.
Resources
First things first, let’s get a simple overview of what resource we are using the most of in Azure. First off, let’s get a list of resource types, ordered by count:
CLI
az graph query -q "summarize count() by type| project resource=type , total=count_ | order by total desc" --output table
PowerShell
Search-AzureRmGraph -Query "summarize count() by type| project resource=type , total=count_ | order by total desc" | Format-Table
Which results in a summarised table like this:
Resource Total
------------------------------------------------ -------
microsoft.storage/storageaccounts 38
microsoft.network/networkwatchers 29
microsoft.automation/automationaccounts/runbooks 26
microsoft.compute/disks 15
microsoft.web/sites 13
microsoft.compute/virtualmachines/extensions 13
microsoft.operationsmanagement/solutions 10
microsoft.network/networkinterfaces 10
microsoft.web/serverfarms 9
microsoft.insights/components 9
microsoft.keyvault/vaults 8
microsoft.network/publicipaddresses 7
microsoft.compute/virtualmachines 7
microsoft.compute/images 7
microsoft.sql/servers/databases 6
microsoft.network/virtualnetworks 6
microsoft.network/networksecuritygroups 6
microsoft.insights/alertrules 6
microsoft.insights/actiongroups 4
microsoft.web/connections 4
microsoft.automation/automationaccounts 4
microsoft.network/loadbalancers 2
microsoft.managedidentity/userassignedidentities 2
microsoft.logic/workflows 2
microsoft.eventhub/namespaces 2
microsoft.sql/servers 2
microsoft.documentdb/databaseaccounts 2
microsoft.streamanalytics/streamingjobs 2
microsoft.operationalinsights/workspaces 2
microsoft.recoveryservices/vaults 2
microsoft.network/dnszones 2
microsoft.containerservice/managedclusters 1
microsoft.containerregistry/registries 1
microsoft.devtestlab/schedules 1
microsoft.portal/dashboards 1
microsoft.sql/servers/jobagents 1
microsoft.network/routetables 1
microsoft.compute/availabilitysets 1
sendgrid.email/accounts 1
microsoft.aad/domainservices 1
microsoft.containerinstance/containergroups 1
microsoft.compute/restorepointcollections 1
Regions
Now we have a better idea of what resources are in use in our organisation. Next question, where are these resource being used? Do we have some specific regions where perhaps we ought to focus or deployments and look at things like reserved instances.
CLU
az graph query -q "summarize count() by location | project total=count_, location | order by total desc " --output table
PowerShell
Search-AzureRmGraph -Query "summarize count() by location | project total=count_, location | order by total desc " | Format-Table
This query breaks our resource count down per region. In the example below, we can see that our focus is clearly in West Europe.
Location Total
------------------ -------
westeurope 219
global 6
uksouth 6
eastus 5
northeurope 5
westus2 2
eastasia 2
westindia 1
koreasouth 1
westus 1
ukwest 1
southeastasia 1
koreacentral 1
eastus2 1
japanwest 1
centraluseuap 1
australiasoutheast 1
francecentral 1
centralus 1
eastus2euap 1
centralindia 1
canadacentral 1
canadaeast 1
australiaeast 1
southindia 1
northcentralus 1
brazilsouth 1
westcentralus 1
southcentralus 1
japaneast 1
Tags
Whilst we can view the types and locations of resources in use easily, one thing that is much harder is grouping by owner, or purpose for these resources. One way to do this is to implement tags in your environment that denote these sort of things, which then makes it much easier to query this information. For example, if you implement an “owner” tag for all resource then you can query by that to see who owns the most resources:
az graph query -q "summarize count() by tostring(tags.owner) " --output table
Search-AzureRmGraph -Query "summarize count() by tostring(tags.owner) " | Format-Table
As you can see, I’m not very good at applying this tag!
Count_ Tags_owner
-------- ------------
252
16 sam
Virtual Machines
We’ve looked at overall resources, let’s drop down and look at some resource specific queries now, starting with VMs. We already mentioned that we want to get a better understanding of which VM types are in use, so we can do a quick query to get that information.
CLI
az graph query -q "where type =~ 'Microsoft.Compute/virtualMachines' | project SKU = tostring(properties.hardwareProfile.vmSize)| summarize count() by SKU" --output table
PowerShell
Search-AzureRmGraph -Query "where type =~ 'Microsoft.Compute/virtualMachines' | project SKU = tostring(properties.hardwareProfile.vmSize)| summarize count() by SKU" | Format-Table
Which gives us a table grouped by VM type:
SKU Count_
--------------- --------
Standard_DS1_v2 4
standard_D1_v2 2
Standard_DS2_v2 1
OS Type
We can also query the VMs by OS to see how many Windows and Linux machines we have running:
CLI
az graph query -q "where type =~ 'Microsoft.Compute/virtualMachines' | summarize count() by tostring(properties.storageProfile.osDisk.osType)" --output table
PowerShell
Search-AzureRmGraph -Query "where type =~ 'Microsoft.Compute/virtualMachines' | summarize count() by tostring(properties.storageProfile.osDisk.osType)" | Format-Table
OS Count_
------- --------
Linux 3
Windows 4
If we are only interested in Windows VMs we can adjust our query above to only include them.
az graph query -q "where type =~ 'Microsoft.Compute/virtualMachines' | where tostring(properties.storageProfile.osDisk.osType) =~ 'linux' | project SKU = tostring(properties.hardwareProfile.vmSize)| summarize count() by SKU" --output table
Search-AzureRmGraph -Query "where type =~ 'Microsoft.Compute/virtualMachines' | where tostring(properties.storageProfile.osDisk.osType) =~ 'linux' | project SKU = tostring(properties.hardwareProfile.vmSize)| summarize count() by SKU" | Format-Table
Managed Disk
Another useful query is to get a list of machines that are not using managed disks; this allows us to see what impact enforcing a policy of only allowing managed disks will have, and perhaps warn people who use them that it is coming.
CLI
az graph query -q "where type =~ 'Microsoft.Compute/virtualMachines' | where isnull(properties.storageProfile.osDisk.managedDisk) "
PowerShell
Search-AzureRmGraph -Query "where type =~ 'Microsoft.Compute/virtualMachines' | where isnull(properties.storageProfile.osDisk.managedDisk) "
A similar query can be used to query other properties of VMs, such as whether they are using Managed Identity or have boot diagnostics enabled.
Storage
Account Type
Another common resource we can look at is storage accounts. First off, let’s so how many of each storage account type we have (V1, Blob Only, V2). This allows us to asses the impact of forcing V2 storage.
CLI
az graph query -q "where type =~ 'Microsoft.Storage/storageAccounts' | summarize count() by kind" --output table
PowerShell
Search-AzureRmGraph -Query "where type =~ 'Microsoft.Storage/storageAccounts' | summarize count() by kind" | Format-Table
As you can see I have a mixture of all 3:
Count_ Kind
-------- -----------
18 StorageV2
19 Storage
1 BlobStorage
Security
We can also have a look at some of the security features of our resources and see if they are enabled, or whether we need a policy to enforce them. For example, do our storage accounts have encryption enabled? This query lists all storage accounts that do not have Storage Service Encryption enabled.
CLI
az graph query -q "where type =~ 'microsoft.storage/storageaccounts' | where aliases['Microsoft.Storage/storageAccounts/enableBlobEncryption'] =='false'| project name"
Search-AzureRmGraph -Query "where type =~ 'microsoft.storage/storageaccounts' | where aliases['Microsoft.Storage/storageAccounts/enableBlobEncryption'] =='false'| project name"
Similarly, we can look to see of HTTPS traffic only is enabled:
CLI
az graph query -q "where type =~ 'microsoft.storage/storageaccounts' | where aliases['Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly'] =='false'| project name"
PowerShell
Search-AzureRmGraph -Query "where type =~ 'microsoft.storage/storageaccounts' | where aliases['Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly'] =='false'| project name"
Summary
I could go on for much longer with more examples of queries that help you audit your subscriptions, but hopefully, this gives you an idea of the power of using Resource Graph for auditing your subscriptions and getting a good picture of the current state before you try and improve it through the governance tools available in Azure. Undertaking this sort of audit allows you to understand the impact your changes are going to have, but also to set a baseline to asses your improvements against. If you don’t know what you started with, how will you know if it is getting better?
If you have any useful queries you’re using in Resource Graph, please do post them in the comments, I would love to start building a collection of these.
Image Credits
Audit Key flickr photo by Got Credit shared under a [Creative Commons (BY) license](https://creativecommons.org/licenses/by/