Welcome back! This time, I’m writing a contribution to the Azure Spring Clean running in March 2025. It’s a community effort, so very happy to contribute. Please visit Azure Spring Clean for more content from the community!
I’ve chosen to cover the topic of Azure Tenant Security and how we can use Maester to achieve that. I’ve written on the topic of testing your own infrastructure before in my post Test Yourself: The Prelude. I also expanded on the topic later in Test Yourself Part 1: Identity (where the URL references -Pt2, which is just me being very good at naming conventions). In that part I covered a Maester and how to run tests, so I recommend also checking that out.
Now, covering tenant security and Maester has it’s limits. Maester has a bunch of tests, and going through them one by one isn’t helping anyone - least of all my sanity. I did cover some tests in some previous posts, so I will summarize and link to those when we need to.
So what’s the point of this post? Well, given that we are diving into something as important as Azure Tenant Security, we need to make sure we’re doing it right. We need to make sure we’re prioritizing the right things, and that we’re not missing anything. This is where AI can come in handy. I say can, I mean “might” and “could” … most likely “will not”. But let’s see how it goes.
So, let’s get started!
First of all, an Azure tenant consists of two logical pieces in my mind:
Both of these two components are connected in that they share the same identity, but they are separate in that they provide access differently. To put it very generically, Azure uses Entra ID and Azure role based access control (RBAC) to provide access to resources, while Microsoft 365 uses Entra ID and the Entra ID roles to provide access.
There are some exceptions of course, like Defender (unified) RBAC, or Global Admins being able to essentially become owners of all Azure resources, but for the most part this is how it works.
In the context of this blog post, I have chosen to define the tenant security settings as everything covered by Maester. For a full list, check out Maester’s documentation. Currently, the tests are split into four broad categories:
These settings are core because they are important even if we don’t have any resources deployed. Anything that uses single sign-on (SSO) will most likely be affected by these settings.
To put the context of this blog post into perspective it’s important to note that I’m generally going to advocate strict security settings, but we also need to consider that we need to balance security with usability. So please keep that in mind. At scale some of these settings might cause issues with less tech-savvy users, or just be too much to manage.
So what is Maester.dev? Well, it’s a test framework built on top of Pester, a Powershell test framework. Maester allows you to test your infrastructure by allowing you to run tests against it. I’ve covered ScubaGear from CISA before, and the Secure Cloud Business Applications (SCuBA) tests are all part of Maester, along with some Maester-specific tests, the Entra ID Security Config Analyzer (EIDSCA) tests from the Entra ID Attack and Defense Playbook and finally the option to write your own custom tests!
This is all made possible by the Maester PowerShell module, which runs the tests and generates interactive test reports for you to review outcomes. It can also be set up to run in a CI/CD pipeline, so you can run tests either on a schedule or automatically, triggered by new deployments.
The introduction guide for setting up Maester is solid, so I won’t go into much details here apart from sharing the commands I run to generate my report. You can find the guide here.
Start up a PowerShell session and run the following commands:
Install-Module Pester -SkipPublisherCheck -Force -Scope CurrentUser
Install-Module Maester -Scope CurrentUser
At this point, we got a warning because I’ve previously installed the Maester module to do testing, so we had an old version (0.0.130) installed.
WARNING: Version '0.0.130' of module 'Maester' is already installed at 'C:\Users\infx\Documents\PowerShell\Modules\Maester\0.0.130'. To install version '0.3.0', run Install-Module and add the -Force parameter, this command will install version '0.3.0' side-by-side with version '0.0.130'.
To fix this, we can update the module:
Update-Module Maester -Force
Import-Module Maester
Now, let’s create a directory for our tests and navigate into it:
md maester-tests
cd maester-tests
Install-MaesterTests
Output will look like this:
If you’re like me and have already installed Maester and the tests before, you can run the following command to update the tests:
cd maester-tests
Update-MaesterTests
There are a few other things we might want to include as well, because the SCuBA tests from CISA requires the Az and ExchangeOnlineManagement modules. We can install these by running the following commands:
Install-Module Az -Scope CurrentUser
Install-Module ExchangeOnlineManagement -Scope CurrentUser
If the modules are already installed, you can update them by running the following commands:
Update-Module Az -Force
Update-Module ExchangeOnlineManagement -Force
This is all we need to do for setup right now.
To run Maester, we need to run the following command:
Connect-Maester
This should bring you to a consent window, which looks like this (pardon the Norwegian there):
Note, if we want to run all the tests, including SCuBA tests, we need to connect to all services:
Connect-Maester -Service All
Now we can run Maester:
Invoke-Maester
The Invoke-Maester
command has a few parameters that can be useful, it’s documented on the maester.dev website. For our purposes, to get a bit more output we’ll use the -Verbosity
parameter and set it to normal, as well as the different output formats and output folder:
Invoke-Maester -Verbosity Normal -OutputFolder "C:\temp\AzureSpringClean\output"
This will generate a report in the folder C:\temp\AzureSpringClean\output
that we can open in a browser.
Now, I’ve already gone through some tests in my previous post (which you can view over here). Please check out that article, if not now, then at least later. It was a lot of work to write, since I also went through the process of remediating multiple tests.
For sake of clarity, last time we walked through and made changes to the following tests:
From last time, our results looked like this:
Tests completed in 36.14s
Tests Passed: 57, Failed: 65, Skipped: 11, Inconclusive: 0, NotRun: 5
🔥 Maester test report generated at C:\temp\AzureSpringClean\output\TestResults-2024-10-26-124008.html
We moved from a 32% pass rate to a 47% pass rate. This is a good improvement, but we still had a long way to go.
So, with some summary from last time, now it’s time to get to .. spring cleaning? I guess? Let’s see what we can do to improve our security settings. But let’s put a little spin on it. I’m normally not a fan of “AI-powered”-everything, but for this use case it might be a good tool to use. I usually advocate AI as a good tool to summarize text and aggregata data. Imagine we have a limited supply of people to do the work, and manually reviewing and prioritizing all the tests might be a bit much. So, let’s use some AI to help us out.
To put it in the immortal words of Ludwig: “Boys, the plan is simple”.
We’ll run Maester and then make the results available to either Security Copilot or Azure OpenAI to help us prioritize what to fix.
We will use a storage account as a data source - so we’ll start creating that.
For the data to appear in the storage account, we’ll need to either upload the data manually, or, if we’re running Maester in a pipeline we can modify it to upload the data to the storage account. I’m using Azure DevOps, set up according to the Maester guidance, but you can use Github or Gitlab if you feel like it.
A small note here, if you’re running Azure DevOps just to test like me, you need to apply for a free tier pipeline. You can do that here.
Now, the pipeline itself already publishes the report as an artifact using the following task:
- publish: $(System.DefaultWorkingDirectory)/test-results
displayName: Publish Maester Html Report
artifact: TestResults
All the test-results are saved to that same folder, so in order to upload the report to the storage account, we can add a task to the pipeline like this:
- task: AzureFileCopy@4
inputs:
SourcePath: '$(System.DefaultWorkingDirectory)/test-results/*'
azureSubscription: 'maester'
Destination: 'AzureBlob'
storage: 'infexaistorageacc'
ContainerName: 'maester'
BlobPrefix: ''
The SourcePath
variable supports wildcards, so we can upload all the files in the folder. Also note that I made a container in the storage account named maester
, so we’re uploading the files there.
This will upload the report to the storage account:
Next, we’ll spin up an Azure AI Search instance using the basic tier.
Small note here - you might need to configure an Azure OpenAI instance and set up a supported model (the vector field must use text-embedding-ada-002
.
) to make it all work. I followed this guide to set everything up and be able to upload vectorized data.
Now, let’s jump into Azure AI search and continue there. Inside Azure AI search we can simply select import and vectorize data and add our storage account as a data source:
Follow the wizard. For the sake of brevity, keep everything as default. If all is well in the world of automagic, you should see your data source indexed, vectorized and searchable. If you’re like me and these terms confuse you and make you feel a bit scared, don’t worry. We’re in this together.
I’m afraid I can’t let you leave just yet, Dave. We need to set up the AI to help us prioritize the tests 🤖🔧.
Let’s spin up a Security Copilot instance following Microsoft’s guide. Now, to make our files available to Security Copilot, we’re going to use Azure AI Search to index the files and make them searchable. We’ve already created a storage account, so we’ll upload the files there.
Heading over to https://securitycopilot.microsoft.com/, we can click on sources to add a new source:
This opens up a window, where we can select among a few different sources. The important one is Azure AI Search, which we’ll select:
Clicking “Set up” takes us to fill out some data:
For more detailed guidance on this part, I recommend checking out the Microsoft guide.
Note: Requirements for Azure AI Search Plugin
To use the Azure AI Search plugin in Copilot, ensure that the Azure AI Search index is configured as follows:
text-embedding-ada-002
.Given all this, the following values are present in our index vector-1740331313661
:
So the values we would use is as follows:
Field Name | Value |
---|---|
Azure AI Search Service | infex-ai-search |
Index Name | vector-1740331313661 |
Name of vector field | text_vector |
Name of title field | title |
Name of text field | chunk |
Value | This will be your API key from going into Azure AI search, selecting keys and copying an admin key. |
According to Microsoft guidance, we can now prompt for a knowledge base connected using Azure AI Search. Please refer to guide for more information.
We can also just, you know, upload the file manually in Security Copilot:
Simple as that.
If you’re not interested in shelling out to use Security Copilot, but you still don’t want to upload what is basically your security configuration to ChatGPT directly, we can spin up an Azure OpenAI instance.
From here, we can head to https://ai.azure.com and head over to the playground. Here we can head into the playground and add our data.
We can either chose to upload the data manually, or we can use a data source like the storage account or Azure AI search. In this case, we’ll use the Azure AI search since we have already set up uploading from our pipeline.
We can now select the data source we set up earlier:
Now, I’m not an AI-expert, nor am I really convinced this is the best course of action, but let’s see what the AI says.
First out, let’s check with Security Copilot! Our prompt will be this:
We have run Security tests using a tool called Maester on our Azure tenant and have uploaded the results to Azure AI Search. We would like to prioritize the tests based on the results. Can you help us with that?
Prioritized Test Results
High Priority (Failed Tests)
Medium Priority (Passed Tests)
Summary
Total Tests: 138 Passed: 57 Failed: 65 Not Run: 11
Now we give Azure OpenAI the same prompt using the Azure AI foundry and the chat integration:
To prioritize the security tests based on the results from your Azure tenant using Maester, we can categorize the tests into “Pass” and “Fail” based on their outcomes. Here’s a summary of the test results:
Tests with Failures (High Priority)
Tests with Passes (Lower Priority)
Prioritization Strategy
By addressing the high-priority tests first, you can significantly enhance the security of your Azure tenant. If you need further assistance or specific action steps for each test, please let me know!
Well, reviewing some of the recommendations from the AI, it seems like we have some work to do. For instance, if we investigate the second priority from Security Copilot, EIDSCA.AG03
which refers to the setting “Report suspicious activity - Included users/groups”. It supposedly failed, but looking into the file manually we see that this is not actually the case:
In this case, Azure OpenAI was able to get all four recommendations correct. This is a good example of why it’s important to verify the results from AI, as they might not always be correct. It’s also probably a good idea to have better prompt engineering skills than me, but that’s a different story. Things like asking the AI to prioritize based on the number of users affected, or the potential impact of the setting might be a good idea.
Through this post, we have looked at the following things:
Some reflections here - Maester is a godlike tool. We haven’t discussed it, but as long as something has an API we can also write customs tests which is well documented on the Maester page. There’s also potential for some good reporting here, by uploading multiple reports and comparing them over time. This can be a great tool for improving security settings over time, especially if you’re giving the AI a bit more to work with in terms of context.
All in all, this was a fun little experiment. I look forward to experimenting with it more, and maybe setting it up to automatically upload the reports to Azure AI Search every week, then having a weekly report generated by the AI using a logic app or something like that.
With that said, have a nice spring cleaning, everyone!
Please visit Azure Spring Clean for more content from the community.