Runbook to check Entra Apps with expiring ClientSecrets and Certificates

Runbook to check Entra Apps with expiring ClientSecrets and Certificates

Hi All,

In the previous Article I’ve explained how to check for Entra Apps with expiring ClientSecrets and Certificates.

In this Blog Article i explain how we can extend this into a Azure Automation Runbook.

Automation Account

The Azure Automation Account needs to have a Managed Identity

Use the AZ PowerShell to get Infos about the Azure Automation Account

###############################################################################
# Get Managed Identity of Azure Automation Account
###############################################################################
Connect-AzAccount -Tenant icewolfch.onmicrosoft.com
Get-AzAutomationAccount -Name icewolfautomation -ResourceGroupName RG_DEV

#Get ManagedIdentity
$AutomationAccount = Get-AzAutomationAccount -Name icewolfautomation -ResourceGroupName RG_DEV
$AutomationAccount.Identity

Service Principal

We can now use the ObjectID of the ManagedIdentity to get all the Details with Microsoft.Graph

###############################################################################
# Get AzureAD Application with Microsoft.Graph PowerShell
###############################################################################
Connect-MgGraph -Scopes Application.Read.All -NoWelcome
$ServicePrincipalDetails = Get-MgServicePrincipal -ServicePrincipalId "547c53c8-9e84-4dc7-8a65-92c59fb1c34f"
$AppID = $ServicePrincipalDetails.AppId
$ObjectId = $ServicePrincipalDetails.Id
$AppDisplayName = $ServicePrincipalDetails.Displayname
$ServicePrincipalDetails | Format-List Id, AppId, DisplayName, ServicePrincipalType

Exchange Online RBAC for Application

I’ve blogged about this earlyer

Here is the short and updated Version

###############################################################################
# Create Exchange Service Principal
###############################################################################
Connect-ExchangeOnline -ShowBanner:$false
New-ServicePrincipal -AppId $AppID -ObjectId $ObjectId -DisplayName "EXO Serviceprincipal $AppDisplayName"
Get-ServicePrincipal | where-object {$_.AppId -eq $AppID}

###############################################################################
#New-ManagementScope
###############################################################################
# New-ManagementScope 
# https://learn.microsoft.com/en-us/powershell/module/exchange/new-managementscope?view=exchange-ps
# Filterable properties for the RecipientFilter parameter on Exchange cmdlets
# https://learn.microsoft.com/en-us/powershell/exchange/recipientfilter-properties?view=exchange-ps
Get-ManagementScope
New-ManagementScope -Name "a.bohren" -RecipientRestrictionFilter "PrimarySmtpAddress -eq 'a.bohren@icewolf.ch'"

###############################################################################
#New-ManagementRoleAssignment
###############################################################################
New-ManagementRoleAssignment -App $ObjectId -Role "Application Mail.Send" -CustomResourceScope "a.bohren"
Get-ManagementRoleAssignment | where-object {$_.App -eq $ObjectId}

Show the Management Role details

###############################################################################
#Get-ManagementRoleAssignment
###############################################################################
Get-ManagementRoleAssignment | where-object {$_.App -eq $ObjectId}
Get-ManagementRoleAssignment | where-object {$_.App -eq $ObjectId} | fl

Azure Runbook

Here is the Runbook that can be used in Azure Automation

###############################################################################
# Azure Automation Runbook
# CheckEntraAppAuthenticationExpiration
###############################################################################
# Azure Automation Account
# - Managed Identinty
# Required Modules:
# - Microsoft.Graph.Authentication
# - Microsoft.Graph.Applications
# Required Permissions
# - User.Read.All
# - Application.Read.All
# - Mail.Send (Mail.Send.Shared does not exist in Exchange Online RBAC for Applications)
###############################################################################
# Important
###############################################################################
# Limiting application permissions to specific Exchange Online mailboxes
# https://docs.microsoft.com/en-us/graph/auth-limit-mailbox-access
#
# Limit Microsoft Graph Access to specific Exchange Mailboxes
# https://blog.icewolf.ch/archive/2021/02/06/limit-microsoft-graph-access-to-specific-exchange-mailboxes.aspx
#
# or Limit Graph Access with Exchange Online Role Based Access Control (RBAC) for Applications
# https://blog.icewolf.ch/archive/2023/01/05/exchange-online-role-based-access-control-rbac-for-applications/
###############################################################################


###############################################################################
#Send Mail Function Graph
###############################################################################
Function Send-GraphEmailNotification{

    Param(
    [parameter(Mandatory=$true)][String]$From,
    [parameter(Mandatory=$true)][String]$To,
    [parameter(Mandatory=$true)][String]$Subject,
    [parameter(Mandatory=$true)][String]$MessageBody
    )
$URI = "https://graph.microsoft.com/v1.0/users/$From/sendMail"
$ContentType = "application/json"
$Headers = @{"Authorization" = "Bearer "+ $AccessToken}
$Body = @"
{
    "message": {
        "subject": "$Subject",
        "body": {
            "contentType": "Text",
            "content": "$MessageBody"
        },
        "toRecipients": [
            {
                "emailAddress": {
                    "address": "$To"
                }
            }
        ]
    }
}
"@
Invoke-GraphRequest -Method "POST" -Uri $uri -Body $Body -ContentType $ContentType
}


###############################################################################
#Main Script
###############################################################################
Write-Output "Connect-MgGraph"
Connect-MgGraph -Identity

#Expiration Comparsion Date
$ExpirationDate = (Get-Date).Adddays(+60)

#FromAddress
$FromAddress = "a.bohren@icewolf.ch"
$NotificationSubject = "Entra App Authentication Expiration"

#Get Entra Apps
Write-Output "Getting Entra Apps"
$EntraApps = Get-MgApplication

#Loop through the Apps
Foreach ($EntraApp in $EntraApps)
{
    $AppDisplayName = $EntraApp.DisplayName
    $AppId = $EntraApp.AppId
    $ID = $EntraApp.ID
    Write-Output "$AppID > $ID > $AppDisplayName"

    #Get Owner
    $OwnerMailArray = @()
    $OwnerObject = Get-MgApplicationOwner -ApplicationId $ID
    $Owners = $OwnerObject.AdditionalProperties
    If ($Null -eq $Owners)
    {
        Write-Output "No Owner found"
    } else {
        Foreach ($Owner in $Owners)
        {
            $UPN = $Owner.userPrincipalName
            $Mail = $Owner.mail
            $OwnerMailArray += $Mail
            Write-Output "OwnerUPN: $UPN > OwnerMail: $Mail"
        }
    }

    #Certificate
    If ($Null -ne $EntraApp.KeyCredentials)
    {
        $Certificates = $EntraApp.KeyCredentials
        Foreach ($Certificate in $Certificates)
        {
            $CertDisplayName = $Certificate.DisplayName
            $CertStartDate = $Certificate.StartDateTime
            $CertEndDate = $Certificate.EndDateTime
            Write-Output "Certificate: $CertDisplayName > StartDate: $CertStartDate > EndDate: $CertEndDate"
             If ($ExpirationDate  -gt $CertEndDate)
            {
                Write-Output "$CertDisplayName Certificate will soon expire"
                If ($OwnerMailArray.Count -gt 0)
                {
                    #Send Mail to each Owner
                    Foreach ($OwnerMail in $OwnerMailArray)
                    {
                        Write-Output "Send Mail to $OwnerMail > The App $AppDisplayName has an expiring Certificate $CertDisplayName expiring at $CertEndDate"
                        Send-GraphEmailNotification -From $FromAddress -To $OwnerMail -Subject $NotificationSubject -MessageBody "The App <$AppDisplayName> has an expiring Certificate <$CertDisplayName> expiring at $CertEndDate"
                    }
                }
            }
        }
    }

    #ClientSecret
    If ($Null -ne $EntraApp.PasswordCredentials)
    {
        $ClientSecrets = $EntraApp.PasswordCredentials
        Foreach ($ClientSecret in $ClientSecrets)
        {
            $DisplayName = $ClientSecret.DisplayName
            $StartDate = $ClientSecret.StartDateTime
            $EndDate = $ClientSecret.EndDateTime
            Write-Output "ClientSecret: $DisplayName > StartDate: $StartDate > EndDate: $EndDate"
            If ($ExpirationDate  -gt $EndDate)
            {
                Write-Output "ClientSecret $DisplayName will soon expire"
                If ($OwnerMailArray.Count -gt 0)
                {
                    #Send Mail to each Owner
                    Foreach ($OwnerMail in $OwnerMailArray)
                    {
                        Write-Output "Send Mail to $OwnerMail > The App $AppDisplayName has an expiring Certificate $CertDisplayName expiring at $CertEndDate"
                        Send-GraphEmailNotification -From $FromAddress -To $OwnerMail -Subject $NotificationSubject -MessageBody "The App <$AppDisplayName> has an expiring ClientSecret <$DisplayName> expiring at $EndDate"
                    }
                }
            }
        }
    }
}

Notification Email

This is how the Notification Email looks like

Regards
Andres Bohren

EntraID Logo

PowerShell Logo