How to Automate the Renewal of Expired Azure App Registration Secrets

Krizzia 🤖
8 min readAug 14, 2023

This article is a follow-up to my previous story, where I shared how to receive notifications about expiring certificates and secrets. If you haven’t read the first story yet, I recommend doing so in order to understand the steps involved in the renewal process.

Assuming you have already implemented the steps in the previous story, you should have received the email notification. Now, continue reading this story to proceed with setting up the renewal process.

Getting Started

  1. Create a Runbook

Create a new runbook under the same automation account you created in the previous story and copy and paste the script below.

<#
=========================================================================================================================
Required - Powershell Version 5.1
=========================================================================================================================
AUTHOR: KJR
DATE: 01/07/2021
Version: 1.0
=========================================================================================================================
.SYNOPSIS

.DESCRIPTION

#>
param (
[Parameter(Mandatory=$true)][string] $SecretName,
[Parameter(Mandatory=$true)][string] $ObjectID
)

try {
Connect-AzAccount -Identity | Out-Null
} catch {
Write-Error -Message $_.Exception
throw $_.Exception
}

$appregistration = Get-AzADApplication -ObjectId $ObjectID
$clientSecret = $appregistration.PasswordCredentials | Where-Object { $_.DisplayName -eq $SecretName }
if ($clientSecret) {
$TotalDays = ($clientSecret.EndDateTime.Date - $clientSecret.StartDateTime.Date).TotalDays
if ($TotalDays -le 0) { $TotalDays = 30 }
$removed = Remove-AzADAppCredential -ObjectId $appregistration.Id -KeyId $clientSecret.KeyId
$newClientSecret = New-Object Microsoft.Azure.PowerShell.Cmdlets.Resources.MSGraph.Models.ApiV10.MicrosoftGraphPasswordCredential
$newClientSecret.DisplayName = $clientSecret.DisplayName
$newClientSecret.EndDateTime = (Get-Date).AddDays($TotalDays)
$response = New-AzADAppCredential -ObjectId $appregistration.Id -PasswordCredentials $newClientSecret
$response = $response | Select-Object -Property DisplayName, StartDateTime, EndDateTime
$tbody = '<table style="width: 100%; color: rgb(255, 255, 255); border-collapse: collapse;">
<tbody>
<tr>
<td style="border: 1px solid black; width: 25%; background-color: rgb(0, 0, 0);">
<div style="text-align: center;">Name</div>
</td>
<td style="border: 1px solid black; width: 25%; background-color: rgb(0, 0, 0);">
<div style="text-align: center;">Date Time</div>
</td>
<td style="border: 1px solid black; width: 25%; background-color: rgb(0, 0, 0);">
<div style="text-align: center;">Previous Expiration Date Time</div>
</td>
<td style="border: 1px solid black; width: 25%; background-color: rgb(0, 0, 0);">
<div style="text-align: center;">New Expiration Date Time</div>
</td>
</tr>
<tr>
<td style="border: 1px solid black; width: 25.0000%; color:black;"><div style="text-align: center;">' + $response.DisplayName + '</div></td>
<td style="border: 1px solid black; width: 25.0000%; color:black;"><div style="text-align: center;">' + $response.StartDateTime + '</div>
<td style="border: 1px solid black; width: 25.0000%; color:black;"><div style="text-align: center;">' + $clientSecret.EndDateTime + '</div>
<td style="border: 1px solid black; width: 25.0000%; color:black;"><div style="text-align: center;">' + $response.EndDateTime + '</div>
</tr>
</tbody>
</table>'
$tbody
} else {
"Secret $($SecretName) does not exist. No changes were made."
}

2. Update “get-appregistration-expired-certificatesandsecrets” runbook

Copy and paste the script below and replace the existing runbook named “get-appregistration-expired-certificatesandsecrets” that you created in the previous story.

<#
=========================================================================================================================
Required - Powershell Version 5.1
=========================================================================================================================
AUTHOR: KJR
DATE: 01/07/2021
Version: 1.0
=========================================================================================================================
.SYNOPSIS

.DESCRIPTION

#>

$renewbaseurl = "<INSERT lg-renew-appsecrets http url>"
try {
Connect-AzAccount -Identity | Out-Null
} catch {
Write-Error -Message $_.Exception
throw $_.Exception
}

$objects = @()
$appRegistrations = Get-AzADApplication | Select-Object -Property Id, AppId, DisplayName, PasswordCredentials, keyCredentials

foreach ($app in $appRegistrations) {
$secrets = @()
foreach ($secret in $app.PasswordCredentials) {
$secrets += @{ "Name" = $secret.DisplayName; "StartDateTime" = $secret.StartDateTime.ToString("MM/dd/yyyy"); "EndDateTime" = $secret.EndDateTime.ToString("MM/dd/yyyy"); "RemainingDays" = ($secret.EndDateTime - (Get-Date)).Days }
}
$certificates = @()
foreach ($certficate in $app.KeyCredentials) {
$certificates += @{ "Name" = $certficate.DisplayName; "StartDateTime" = $certficate.StartDateTime.ToString("MM/dd/yyyy"); "EndDateTime" = $certficate.EndDateTime.ToString("MM/dd/yyyy"); "RemainingDays" = ($certficate.EndDateTime - (Get-Date)).Days }
}
$objects += @{ "ObjectID" = $app.Id; "AppId" = $app.AppId; "DisplayName" = $app.DisplayName; Secrets = $secrets; Certificates = $certificates}
}
$tbody = @()
foreach ($obj in $objects) {
$approw = $null
$secretsrow = $null
$certsrow = $null

$approw += '
<tr>
<td style="border: 1px solid black; width: 12.4818%;"><strong>Application ID</strong></td>
<td style="border: 1px solid black; width: 75%;" colspan="3">' + $obj.AppId + '</td>
</tr>
<tr>
<td style="border: 1px solid black; width: 12.4818%;"><strong>Name</strong></td>
<td style="border: 1px solid black; width: 75%;" colspan="3">' + $obj.DisplayName + '</td>
</tr>'


if ($obj.Secrets.Count -le 0) {
$secretsrow += '
<tr style="text-align: center;">
<td style="border: 1px solid black; width: 100%;" colspan="4">No records found.</td>
</tr>'
} else {
foreach ($secret in $obj.Secrets) {
$encodedUrl = "$($renewbaseurl)&SecretName=$($secret.Name)&ObjectID=$($obj.ObjectID)"
#$encodedUrl = [System.Web.HttpUtility]::UrlEncode($renewbaseurl)
$secretsrow += '
<tr style="text-align: center;">
<td style="border: 1px solid black; width: 12.4818%;">' + $secret.Name + '</td>
<td style="border: 1px solid black; width: 12.4818%;">' + $secret.StartDateTime + '</td>
<td style="border: 1px solid black; width: 12.4818%;">' + $secret.EndDateTime + '</td>
<td style="border: 1px solid black; width: 12.4818%;">' + $(if([int]$secret.RemainingDays -le 30) { '<span style="color: rgb(184, 49, 47);">' + $secret.RemainingDays + "</span>&nbsp;<span><a href='$encodedUrl' target='_blank'>renew</a></span>" } else { $secret.RemainingDays }) + '</td>
</tr>'
}
}

if ($obj.Certificates.Count -le 0) {
$certsrow += '
<tr style="text-align: center;">
<td style="border: 1px solid black; width: 100%;" colspan="4">No records found.</td>
</tr>'
} else {
foreach ($cert in $obj.Certificates) {
$certsrow += '
<tr style="text-align: center;">
<td style="border: 1px solid black; width: 12.4818%;">' + $cert.Name + '</td>
<td style="border: 1px solid black; width: 12.4818%;">' + $cert.StartDateTime + '</td>
<td style="border: 1px solid black; width: 12.4818%;">' + $cert.EndDateTime + '</td>
<td style="border: 1px solid black; width: 12.4818%;">' + $(if([int]$cert.RemainingDays -le 0) { '<span style="color: rgb(184, 49, 47);">' + $cert.RemainingDays + '</span>' } else { $cert.RemainingDays }) + '</td>
</tr>'
}
}

$tbody += '<table style="width: 90%;border-collapse: collapse"><tbody> ' + $approw + '
<tr style="text-align: center; border: 1px solid black; width: 62.5547%; background-color: rgb(0, 0, 0);">
<td colspan="4">
<div style="text-align: center;"><strong><span style="color: rgb(255, 255, 255);">Secrets List</span></strong></div>
</td>
</tr>
<tr style="text-align: center;">
<td style="border: 1px solid black; width: 12.4818%;">
<div><strong>Name</strong></div>
</td>
<td style="border: 1px solid black; width: 12.4818%;">
<div>&nbsp;<strong>Start Date Time</strong></div>
</td>
<td style="border: 1px solid black; width: 12.4818%;">
<div><strong>End Date Time</strong></div>
</td>
<td style="border: 1px solid black; width: 12.4818%;">
<div><strong>Remaining Day(s)</strong></div>
</td>
</tr> ' + $secretsrow + '
<tr style="text-align: center; border: 1px solid black; width: 62.5547%; background-color: rgb(0, 0, 0);">
<td colspan="4">
<div style="text-align: center;"><strong><span style="color: rgb(255, 255, 255);">Certificates List</span></strong></div>
</td>
</tr>
<tr style="text-align: center;">
<td style="border: 1px solid black; width: 12.4818%;">
<div><strong>Name</strong></div>
</td>
<td style="border: 1px solid black; width: 12.4818%;">
<div>&nbsp;<strong>Start Date Time</strong></div>
</td>
<td style="border: 1px solid black; width: 12.4818%;">
<div><strong>End Date Time</strong></div>
</td>
<td style="border: 1px solid black; width: 12.4818%;">
<div><strong>Remaining Day(s)</strong></div>
</td>
</tr> ' + $certsrow + '
</tbody></table><br>'
}
$tbody

3. Create New Logic App

We can either create a new logic app or clone the previous one. For this tutorial, let’s choose to clone the existing logic app instead of creating a new one.

Please follow the steps below to clone the existing logic app.

Step 1. Navigate to the logic app named “lg-appregrotation”.

Step 2. Click on “Clone”.

Step 3. Configure the details of the logic app.

Step 4. Click on “Create”.

After creating the Logic App, make sure to enable the System Managed Identity for this resource and assign the Contributor role to the resource group where your Automation Account was created. (Refer to the previous article for instructions on setting this up)

4. Modify the logic app flow.

Step 1. After setting up the system identity of the Logic App, navigate to the newly cloned Logic App named “lg-renew-appsecrets” and click on “Edit”.

Step 2. Click on “Recurrence”, then click on the ellipsis in the top right corner, and finally, select “Delete”.

Step 3. Once the “Recurrence” is deleted, you will be asked to select a new event trigger. Type “Request”, then select and choose “When an HTTP request is received”.

Step 4. After selecting, use “GET” as the method of the HTTP trigger.

Step 5. Add new action called “Response” after the event trigger.

Select the (+) icon → Add an action → Request → Response.

Step 6. After configuring the HTTP trigger response, click on the existing “Create job” action and make changes to the runbook name. Select “renew-appregistration-expired-certificatesandsecrets” and supply the values of the runbook parameters.

Follow the expressions I used to obtain the parameter values for SecretName and ObjectID: triggerOutputs()['queries']['ObjectID'] and triggerOutputs()['queries']['SecretName']

Step 7. Click on the “Send an email (v2)” action and customize the email notification according to your preferences..

Step 8. Click “Save”.

5. Modify runbook “get-appregistration-expired-certificatesandsecrets”.

Once you have completed the configuration of the “lg-renew-appsecrets” logic app, retrieve the HTTP request URL.

Step 1. Navigate to lg-renew-appsecrets → click Edit → select When a HTTP request is received → copy the HTTP GET URL.

Step 2. Navigate to the runbook “get-appregistration-expired-certificatesandsecrets” then select Edit and paste the HTTP GET URL value into the $renewbaseurl parameter.

Step 3. Click save and publish.

6. Validation

After updating the logic app, you can manually trigger the “lg-appregrotation” logic app to verify if everything is functioning correctly. Once you execute it, you should receive an email similar to this.

The renew link will now be visible in the email notification if the remaining days until expiration are fewer than 30 days. The intended recipient will have the ability to renew it by clicking the provided link.

Once the user clicked the renew link, the secret will be automatically renewed, and an email notification will be sent out to inform the user or team that the secret have been successfully renewed.

Congratulations! You have successfully renewed your secrets.

Best Practice

The best practice is to store sensitive credentials in Azure Key Vault. If your app secrets are stored in Azure Key Vault and you would like to learn how to automatically update the key vault secret once it is renewed, please leave a comment below. I will be glad to enhance this article based on your feedback.

Thank you for taking the time to read my article. If you found it enjoyable, please consider showing your appreciation by clapping and following. Your support will motivate me to create more guides in the future that are based on real scenarios in the IT world.☺️☺️☺️

Is my blog putting a smile on your face? If so, why not treat me to a coffee to keep the good times brewing? Your java generosity not only perks up my day but also fuels my blogging mojo. And if this brings a laugh to your day, apologies in advance for the ‘tita’ jokes!

--

--