Lazy loaded image
Collections
Automated Alerts on Azure (Entra ID) Application Secret Expirations - The Lazy Administrator
00 min
Dec 19, 2023
Apr 13, 2024
type
status
date
summary
tags
category
URL
password
slug
icon
Monitoring Azure AD (Entra ID now) application secret expirations in an enterprise is a critical aspect of maintaining robust security and ensuring uninterrupted service. When application secrets expire without timely renewal, it can disrupt business operations by causing application failures. Proactive management of application secret expirations helps enterprises avoid last-minute issues, enabling a more secure and efficient operational environment.
During my brief research in finding an automated approach to monitoring application secret expirations, I found multiple write-ups and articles but many only showed the code on how to get the expiration property without walking through setting up the automation itself. Another issue was not converting the default UTC time to local time to get more accurate expiration datetimes, and also dealing with applications with multiple secrets that expire at different times.
This article will walk one through the code’s logic, including converting time and dealing with multiple values, and creating multiple different automated alerting systems.

Create a New Azure (Entra ID) Application

First, we need to connect to Azure using Connect-AzAccount
notion image
Once connected, we need to create our own Azure or Entra Application that we will use to connect to the Microsoft Graph REST API in order to report on the other applications.
In my example below, I am creating a new application called EntraAppMonitor. I am also getting the default tenant domain in order to create a generic Identifier URI.
notion image
Going back to the Azure Portal, I can see my newly created Application.
notion image

Assign Permissions

Next, we need to give the application the permission, Application.Read.All which is the following ID: 9a5d68dd-52b0-4cc2-bd40-abcf44ac3a30. Luckily, during the process of creating the application we stored the application information in the variable, $AzureADApp so we can call the objectID property of the application by using “$AzureADApp.ID“.
notion image
Jumping back into the Azure Portal, we now need to grant admin consent, allowing the application to be granted to permission we assigned. I can see in the status pane that admin consent is required but has not been granted.
To grant admin consent, first click “Grant admin consent…” and then click “Yes
notion image

Create an App Secret

Next, we need to create an application secret to we can connect to the Microsoft Graph API. The secret is similar to a password so it must be protected and secured. In the example below I will create a secret that will expire in one (1) year. Using the variable, “$AzureADApp” that we created earlier, I can call the “AppID” property to fill in our Application ID value.
By calling “AppSecret” we can retrieve different values such as the Secret itself. Take note of the SecretText value for later.
notion image

Build Our Monitor Logic

Connect to the Microsoft Graph API

The first thing we will want to do is build a PowerShell function to connect to the Microsoft Graph REST API using the application and secret that we created above.
Put in the Application ID of the application we made above, the Tenant ID of your Azure/Entra ID Tenant, and the Application Secret we created in the previous step.
Note: If you need to retrieve the application ID again, go to portal.azure.com > Microsoft Entra ID > App Registrations > [click you application]
notion image
TIP: If you are following along step by step, when running Connect-MSGraphAPI, store the results in a variable for our other API calls.

Get All Applications

Next, we need to recursively retrieve all applications. We can do this because we granted our application the Application.Read.All permission so we just need to perform a GET at the /applications/ endpoint.
For this, I created a single Function that performs a GET request on an endpoint, this makes it so I can re-use this function for different endpoints.
notion image
Lets run ‘Get-MSGraphRequest -AccessToken $tokenResponse.access_token -Uri "https://graph.microsoft.com/v1.0/applications/"‘ and store the results in the variable $Applications so we can view the properties easier.
Now I can run $Applications.value and see my different applications and their associated properties.
notion image

Pagination

API pagination is a method implemented in API design and development for handling the retrieval of extensive data sets in an organized and efficient way. This technique is particularly useful when an API endpoint needs to deliver a substantial volume of data. By using pagination, the data is segmented into smaller, easier-to-manage portions, often referred to as pages. Each of these pages holds a specific, limited quantity of records or entries.
Some larger enterprises may have a large quantity of applications, so we need to ensure that when we get all applications, there are no other pages to parse. The way we do that, is if the results contain ‘@odata.nextLink‘, grab the next pages URI and perform another GET method against that URI until there are no more pages.
In order to account for pagination, we will change our function to the code below:

Get Application Secret Expiration

To view the application secret expiration, we can view the passwordcredentials property.
If we wanted to view the application name along with the passwordexpiration details, we can run the following:
The passwordCredentials results aren’t very human-friendly. This is because the property contains name-value pairs. passwordCredentials is of the type NoteProperty.
if we want to view the passwordCredentials and the application displayName we can run the following PowerShell code:

Finding Expiring or Expired Secrets

By using New-Timespan we can determine if a secret is expiring or has expired already. For the purposes of this article, a secret will be close to expiring if it’s going to expire in 30 days or under.
There are two issues with the code below that I will dive into below:
  1. endDateTime is always UTC time
  1. Get-Date may not be your time zone. If you end up running this in a serverless runbook that is hosted in the Eastern Time Zone but you are located in the Central Time Zone, Get-Date will display the eastern time.
Now, since we are dealing with days and not hours, this most likely wont be a problem. But depending on what you are monitoring or alerting on, this could be an issue, so for our article lets convert all DateTime objects to our time zone.

Converting Time to Local Time Zone

As mentioned above, there are two DateTime objects that we need to convert to our time zone. First, we need to run Get-Date and ensure that it is getting the date for our time zone (central standard time for my case). Second, we need to convert endDateTime from UTC to CST.

Get the Current DateTime from a Specific Time Zone

To get the current date and time for a specific time zone (in my case Central Standard Time) we can run the following:

Convert UTC to our Time Zone

Next, we need to convert endDateTime from UTC to our Time Zone. To do this we can feed it the endDateTime value.

Dealing with Multiple Secrets per Application

When looking up similar articles, many just parse each application, get the secret expiration, and then see how many days until it expires. But some applications may contain multiple secrets, it’s not as common (think of a user/service account having multiple passwords) but I have seen it with different companies.
So, we must see if passwordCredentials.endDateTime contains more than 1 value. Below is a snippet (will not work on its own) of that logic including the timezone conversions.

Bringing All The Application Expiration Logic Together

Remember earlier when we got all the application, we stored those results in a variable called Applications. Now we can iterate through that array and output our application ID, Name and how many days until each secret is set to expire, while also converting everything to our local time zone.
notion image

Sending the Alert to Email

One of the available options of sending the alert is sending it via e-mail, which is the more traditional way. Since we are already interfacing with the Microsoft Graph REST API, we will be sending the e-mail through the API and then using Azure Serverless Automation (runbooks) to run on a set schedule.

Adding Send.Mail Permissions

First, we must grant our application the Send.Mail permission. For this I will be going through the Azure Portal instead of through PowerShell (only because we have already seen how to do it via PS).
One thing to note: to send email using the Microsoft Graph, you must send as a licensed user. In my case I will send it as myself ([email protected])
Go to portal.azure.com and then Microsoft Entra ID > App Registrations > and then click on your application that you created earlier and finally click “API Permissions” on the left pane.
notion image
Click + Add a Permission
notion image
Click Microsoft Graph > Application Permission and add Mail.Send
Once you have added the permissions, click Grant admin consent for... to apply the permissions.
notion image

Adding Send Email Logic

Next, we need to create a new PowerShell function that will send our email alert out. The below function will send an HTML email that contains a table of our application. When calling this function you will need to give it the URI (endpoint), AccessToken, To (who the email, or where the email goes to), and the Body (which the script will auto feed and format)
On the Outlook side here is what the email looks like:

Sending the Alert to Microsoft Teams

Next, I want to automate this to run daily and alert me in Microsoft Teams if any secrets are set to expire in thirty (30) days or less.
Luckily, I already have a Microsoft Team and channel that I send all of my enterprise alerting to (seen below)

Create a Channel Webhook

In order to send my alert to a Microsoft Teams Channel, I need to get the channels webhook information.
Go to the channel that you chose and navigate to Manage Channel > Connectors and then click Configure.
Give you Webhook a name and icon (optional) and then click Create
Notate the URI information and save it for later, we will need it in a later step.
Back in the channel chat you should see a message regarding your newly created webhook.

Send Alert to Teams

A new If statement declares that if the daysUntil value is less than, or equal to ’30’ to add the daysUntil value, id, and displayName of the application in an array called array.
Next, we need to convert the array object into HTML by using ConvertTo-HTML. Lastly, I pasted in my webhook url to the $uri variable.
Jumping back to Teams I can see my newly created alert in my Teams Channel.

Automatic Serverless Automation

Create Automation Account and Runbook

Next step is to add this to a PowerShell runbook, this way I am not hardcoding secrets, and its running on a schedule.
Go to the Azure Portal > Automation Accounts and create a new one or use an existing one.
Next, go to your automation account and click Variables. I will be adding three variables:
  1. tenantID
  1. appSecret
  1. appID
Change the PowerShell Script to get the newly created automation variables.
Next, I will create a new runbook
Next, I will paste the code from below to my newly created runbook. Make sure you have your webhook url to the variable teamswebhookURI. (this is only applicable if you are doing the Microsoft Teams alerting)
When finished, click Publish.

Create Schedule

We now need to create a schedule so this runbook can run on a regular cadence. In the runbook click Schedules and then create a schedule that best fits your needs. In my example I have it running daily at 8:00 AM.

Obtain the Source Code

The scripts are hosted on my GitHub . Feel free to send in issues and contribute to the project as well.
上一篇
Create and host a tunnel - Microsoft dev tunnels | Microsoft Learn
下一篇
微软免费开发隧道(内网穿透),支持Linux/Windows/macOS - 如有乐享