Bring your investigation queries with you across multiple workspaces

Koos Goossens
5 min readJul 13, 2022

--

… by saving them inside a 'query pack'

Introduction

As a security analyst, responsible for performing triage as part of incident response, you're performing similar tasks quite regularly. Like checking a user by determining from where they logged on, how, and with what device as an example.

When you're using Microsoft Sentinel (and/or their Defender suite of products) you're probably getting quite familair with KQL to easily retrieve this information. And you've probably already collected a small arsenal of such investigation queries and saved them into the Log Analytics workspace for later use.

But what happens when you have to switch workspaces? All of your carefully saved queries are suddenly no longer available.

Larger enterprises tend to have multiple Sentinel workspaces to limit the amount of cross region traffic and the additional costs that come with it.

Please don't start duplicating your queries by saving them into every workspace you touch. To help with such tasks, Microsoft has you covered with query packs (preview). An Azure resource which helps you to centrally manage all of your queries and use them across all of your workspaces!

Query packs are not exclusive to Microsoft Sentinel-enabled workspaces. You can use them for all of your other Azure Monitor / Log Analytics workspaces as well.

Pack the queries!

Query packs are a type of Azure resource in which you can store, well … KQL queries. Queries inside those packs can be shared across multiple workspaces and even across multiple subscriptions. And with Azure Lighthouse, this will even work across multiple tenants as well!

Think of a query pack like a carefully built deck which you can with you and prepares you for every situation

If you save your first query from the portal, it will nowadays default to create a new blank query pack called DefaultQueryPack in a resource group called loganalyticsdefaultresources inside your subscription.

But you probably want to go ahead a deploy a query pack somewhere on a cetralized location and maintain it from there.

Queries can still be stored inside a Log Analytics Workspace (‘savedQueries’) but this is considered “legacy” since Microsoft introduced Query Packs @ Build 2021.

Microsoft loves to quickly jump ahead and categorize something as 'legacy' once a new kid is on the block

Using an ARM template

Since you want to centrally manage your queries, you might as well deploy your query pack by leveraging infrastructure-as-code and maintain it inside a Git repository. This way you can easily deploy it to multiple locations if needed (i.e. tenants outside your Lighthouse reach) and you'll benefit from version control on your queries.

I've prepared an ARM template for a single query pack with a couple of queries you can try out to get familiar with the functionality. Click the button below to get started:

Both the ARM template, as well as an example parameters file, are also available on my Github page.

Parameters

In the automated deployment above, you won't be using a separate parameters file but I'd suggest you do so if you're planning to deploy query packs. This way you can put all of your KQL queries together in an array and let the ARM template loop through them during the deployment.

Example parameters file with an array of two KQL queries

During deployment you'll notice that the query pack itself and its underlying queries are deployed separately:

Your deployment overview shows all individual queries as separate resources

We see this with a lot of different Azure resources as well. But this can make it a little bit tricky to make use of the copy() function inside the template if you're not careful. This is because the individual query "resources" obviously require a unique name so you cannot just use a static string for it.

You also want this dynamic string to be same for that particular query in your array every time you deploy the template. If you would just use a guid() function as part of the name, the deployment will be successful on the first try but, on the second it will fail. This is because it tries to deploy the same query as a new resource and it will fail because its displayName will already exist.

For this reason I'd like to use the guid() function, but provide an input which will reliably be the same every time you deploy the template. In this case the query's displayName. Check it out below on line 12:

Resource definition where both copy() and guid() functions are used to successfully deploy an array of queries

Using your new shiny query pack

Once a query pack exists in your environment you can select if from within your workspace.

Open up logsQueries and click on the dotted button that appeared right of the search field:

Once selected you can find/sort your queries based on label, categories, resourceTypes and solutions depending on what you've provided within the definition of the parameters file:

For more details about all supported parameter values please check out the ARM reference on Microsoft docs.

Final words

So query packs can be a very usefull and handy way of storing your KQL queries on a centralized location. And with an automated deployment via ARM, you have full control on versioning and history.

I think that especially for larger enterprises or MSPs (Microsoft Service Providers) attending multiple workspaces across their customers. its a no-brainer to stop storing queries in individual workspaces and embrace the use of query packs. You can of course also distribute multiple query packs to be available for different teams.

--

--

Koos Goossens
Koos Goossens

Written by Koos Goossens

Microsoft Security MVP | Photographer | Watch nerd | Pinball enthusiast | BBQ Grillmaster

No responses yet