Blog Post

Healthcare and Life Sciences Blog
3 MIN READ

Tracking Azure History with Azure Resource Graph

j_folberth's avatar
j_folberth
Icon for Microsoft rankMicrosoft
Sep 08, 2022

Introduction

When administrating an Azure environment, or any environment really, one will most likely find a way to track changes that were introduced. There are a number of ways to do this. Within Azure can query the Subscription or Resource Group Deployment, the downside though is this approach is limited to just the scope you are querying on. What if this is a larger organization with multiple subscriptions? You could also rely on a well-established CI/CD pipeline, a third-party governance tool, or in this case query Azure directly via the Resource Graph Explorer.

 

For this blog will focus on using the Azure Portal offering of the tool; however, want to note that since this is API driven there are numerous offerings such as Azure PowerShell, Azure CLI, .NET, even Ruby.

 

PreReqs

Limitations

  • Documentation clearly supports will store only last 14 days worth of changes.
  • Latest documentation shows the Resource Change History API has been in preview since 4/23/19

 

How

We will be querying two tables for this exercise. It is fairly straight forward; however, something anyone can further customize by joining additional tables and/or filters two. This will deal with only two tables: resources and resource changes. To access the Resource Graph Explorer type 'Resource Graph Explorer' in the Azure search bar.

 

resources

This table is defined as: "The default table if none defined in the query. Most Resource Manager resource types and properties are here."

What this essentially translates to is that this table will hold the basic properties of all the resources.  As of this writing the here are some of the columns are available, and I am providing a brief description of what the values mean.

Column Name Description
id Unique id of the Azure Resource. This will include subscription ID down to individual resource.
name Name of the Azure Resource
type The Microsoft defined type of resource. Complete list available here.
tenantId Azure AD Tenant associated with the resource
kind Not always present: however, some values could be "StorageV2" or "functionapp" for microsoft.storage/storage account or microsoft.web/sites respectively
location What location within azure the resource is deployed to, 'global' for those that are global.
resourceGroup The Azure Resource Group the resource resides in
subscriptionId The associated Azure Subscription
sku Not always present; however, what pricing tier or sku the resource has been set to
properties This will include the provisioning status as well as the JSON Azure Resource Manager definition of the resource
tags Any tags that have been applied

 

resourcechanges

This is the table that is preview and whose dataset is limited to 14 days as mentioned above. This table will follow the same schema; however, the all-important information on what changed is contained in the properties as a JSON object.

See:

Screenshot of resourcechanges properties json

Query

So now we have the two tables, the question becomes how to join them together. If new I highly advise brushing up on KQL.  Essentially our query will need to accomplish:

  • Expanding the resourcechanges properties to retrieve change details and targetResourceId of the change
  • Join resources to resourcechanges on the resourceId and targetResourceId field
  • pull any additional resource information for reporting purposes.

For our purposes I'll pull the Resource Name, Resource Type, Subscription, and Resource Group Name as these could be beneficial for anyone doing reporting.

 

One thing that did trip me up when working with joins in KQL. All columns which will be used from the join table need to be exposed in the project command, this INCLUDES the field you are joining on.

 

The Query

 
resourcechanges

| extend changeTime = todatetime(properties.changeAttributes.timestamp), targetResourceId = tostring(properties.targetResourceId),

changeType = tostring(properties.changeType), correlationId = properties.changeAttributes.correlationId, 

changedProperties = properties.changes, changeCount = properties.changeAttributes.changesCount

| where changeTime > ago(180d)

| join kind=inner (resources | project resources_Name = name, resources_Type = type, resources_Subscription= subscriptionId, resources_ResourceGroup= resourceGroup, id) on $left.targetResourceId == $right.id 

| project resources_Name, resources_Type, resources_Subscription, resources_ResourceGroup, changeTime, targetResourceId, changeType, correlationId, changeCount, changedProperties

 

 

The Results

 

Conclusion

There you go. This may seem like a complex approach to something as easy as what's changed across my Azure subscriptions; however, this approach does accurately achieve that goal. Furthermore, with the APIs being exposed across multiple programming languages leaves upon limitless possibilities as to what one can do.

 

 

Updated Sep 08, 2022
Version 1.0
No CommentsBe the first to comment