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
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.Going back to the Azure Portal, I can see my newly created Application.
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
“.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“
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.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]
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.
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.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:
endDateTime
is always UTC time
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.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.
Click + Add a Permission
Click Microsoft Graph > Application Permission and add Mail.Send
Once you have added the permissions, click Grant admin consent for... to apply the permissions.
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:
- tenantID
- appSecret
- 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 - 如有乐享
- Author:NetSec
- URL:https://blog.51sec.org/article/28c58677-e382-491c-a92f-8043ede15477
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!