Skip to content

Instantly share code, notes, and snippets.

@laymanstake
Last active August 15, 2024 05:04
Show Gist options
  • Save laymanstake/55cb8e0918bd6e4c62661fb351926090 to your computer and use it in GitHub Desktop.
Save laymanstake/55cb8e0918bd6e4c62661fb351926090 to your computer and use it in GitHub Desktop.
Fetches all service principals, their creation date, permissions, secret end dates, certificate end dates
<#
Author : Nitish Kumar (nitish@nitishkumar.net)
Performs Entra ID Assessment
version 1.0 | 17/07/2024 Initial version
version 1.2 | 28/07/2024 Application details performance improvements
Disclaimer: This script is designed to only read data from the entra id and should not cause any problems or change configurations but author do not claim to be responsible for any issues. Do due dilligence before running in the production environment
#>
Import-module Microsoft.Graph.Authentication
# Output formating options
$logopath = "https://raw.githubusercontent.com/laymanstake/laymanstake/master/images/logo.png"
$ReportPath = "c:\temp\EntraIDReport_$(get-date -Uformat "%Y%m%d-%H%M%S").html"
$CopyRightInfo = " @Copyright Nitish Kumar <a href='https://github.com/laymanstake'>Visit nitishkumar.net</a>"
# CSS codes to format the report
$header = @"
<style>
body { background-color: #D3D3D3; }
h1 { font-family: Arial, Helvetica, sans-serif; color: #e68a00; font-size: 28px; }
h2 { font-family: Arial, Helvetica, sans-serif; color: #000099; font-size: 16px; }
table { font-size: 12px; border: 1px; font-family: Arial, Helvetica, sans-serif; }
td { padding: 4px; margin: 0px; border: 1; }
th { background: #395870; background: linear-gradient(#49708f, #293f50); color: #fff; font-size: 11px; text-transform: uppercase; padding: 10px 15px; vertical-align: middle; }
tbody tr:nth-child(even) { background: #f0f0f2; }
CreationDate { font-family: Arial, Helvetica, sans-serif; color: #ff3300; font-size: 12px; }
</style>
"@
If ($logopath) {
$header = $header + "<img src=$logopath alt='Company logo' width='150' height='150' align='right'>"
}
# Function to parse datetime string with different cultures
function Convert-ToDateTime {
param (
[string[]]$dateStrings
)
# List of cultures to test
$cultures = @('en-US', 'en-GB', 'fr-FR', 'de-DE', 'es-ES', 'en-IN')
$results = @()
if(-Not $dateStrings){
return $null
}
foreach ($dateString in $dateStrings) {
if ([string]::IsNullOrEmpty($dateString)) {
$results += $null
continue
}
$parsed = $null
foreach ($culture in $cultures) {
try {
$cultureInfo = [System.Globalization.CultureInfo]::GetCultureInfo($culture)
$parsed = [datetime]::Parse($dateString, $cultureInfo)
break
} catch {
# Continue to the next culture if parsing fails
continue
}
}
if (-NOT $parsed) {
throw "Unable to parse date string: $dateString"
}
$results += $parsed.ToString("dd-MM-yyyy HH:mm:ss")
}
return $results
}
function Get-SensitiveApps {
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline = $true, mandatory = $false)][array]$Sensitivepermissions = ("User.Read.All", "User.ReadWrite.All", "Mail.ReadWrite", "Files.ReadWrite.All", "Calendars.ReadWrite", "Mail.Send", "User.Export.All", "Directory.Read.All", "Exchange.ManageAsApp", "Directory.ReadWrite.All", "Sites.ReadWrite.All", "Application.ReadWrite.All", "Group.ReadWrite.All", "ServicePrincipalEndPoint.ReadWrite.All", "GroupMember.ReadWrite.All", "RoleManagement.ReadWrite.Directory", "AppRoleAssignment.ReadWrite.All")
)
# Populate a set of hash tables with permissions used for different Office 365 management functions
$GraphApp = (invoke-MgGraphRequest -uri "https://graph.microsoft.com/v1.0/serviceprincipals?`$filter=appid eq '00000003-0000-0000-c000-000000000000'").value
$GraphRoles = @{}
ForEach ($Role in $GraphApp.AppRoles) { $GraphRoles.Add([string]$Role.Id, [string]$Role.Value) }
$ExoPermissions = @{}
$ExoApp = (invoke-MgGraphRequest -uri "https://graph.microsoft.com/v1.0/serviceprincipals?`$filter=appid eq '00000002-0000-0ff1-ce00-000000000000'").value
ForEach ($Role in $ExoApp.AppRoles) { $ExoPermissions.Add([string]$Role.Id, [string]$Role.Value) }
$O365Permissions = @{}
$O365API = (invoke-MgGraphRequest -uri "https://graph.microsoft.com/v1.0/serviceprincipals?`$filter=DisplayName eq 'Office 365 Management APIs'").value
ForEach ($Role in $O365API.AppRoles) { $O365Permissions.Add([string]$Role.Id, [string]$Role.Value) }
$AzureADPermissions = @{}
$AzureAD = (invoke-MgGraphRequest -uri "https://graph.microsoft.com/v1.0/serviceprincipals?`$filter=DisplayName eq 'Windows Azure Active Directory'").value
ForEach ($Role in $AzureAD.AppRoles) { $AzureADPermissions.Add([string]$Role.Id, [string]$Role.Value) }
$TeamsPermissions = @{}
$TeamsApp = (invoke-MgGraphRequest -uri "https://graph.microsoft.com/v1.0/serviceprincipals?`$filter=DisplayName eq 'Skype and Teams Tenant Admin API'").value
ForEach ($Role in $TeamsApp.AppRoles) { $TeamsPermissions.Add([string]$Role.Id, [string]$Role.Value) }
$RightsManagementPermissions = @{}
$RightsManagementApp = (invoke-MgGraphRequest -uri "https://graph.microsoft.com/v1.0/serviceprincipals?`$filter=DisplayName eq 'Microsoft Rights Management Services'").value
ForEach ($Role in $RightsManagementApp.AppRoles) { $RightsManagementPermissions.Add([string]$Role.Id, [string]$Role.Value) }
$Appdetails = @()
$sps = @()
$managedidentities = @()
$appcreds = @()
$approles = @()
$Sensitivepermissions = ("User.Read.All", "User.ReadWrite.All", "Mail.ReadWrite", "Files.ReadWrite.All", "Calendars.ReadWrite", "Mail.Send", "User.Export.All", "Directory.Read.All", "Exchange.ManageAsApp", "Directory.ReadWrite.All", "Sites.ReadWrite.All", "Application.ReadWrite.All", "Group.ReadWrite.All", "ServicePrincipalEndPoint.ReadWrite.All", "GroupMember.ReadWrite.All", "RoleManagement.ReadWrite.Directory", "AppRoleAssignment.ReadWrite.All")
$uri = "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=tags/any(t:t+eq+'WindowsAzureActiveDirectoryIntegratedApp')&`$top=999&`$select=id,appid,displayname,createdDateTime,accountEnabled,servicePrincipalType,signInAudience,appRoleAssignmentRequired,appOwnerOrganizationId"
do {
$response = Invoke-MgGraphRequest -Uri $uri
$apps = $response.value
$SPs += $apps
$uri = $response.'@odata.nextLink'
} while ($uri)
$Uri = "https://graph.microsoft.com/v1.0/servicePrincipals?`$filter=ServicePrincipalType eq 'ManagedIdentity'&`$top=999&`$select=id,appid,displayname,createdDateTime,accountEnabled,servicePrincipalType,signInAudience,appRoleAssignmentRequired,appOwnerOrganizationId"
do {
$response = Invoke-MgGraphRequest -Uri $uri
$apps = $response.value
$managedidentities += $apps
$uri = $response.'@odata.nextLink'
} while ($uri)
$AllApps = $SPs + $managedidentities
$Uri = "https://graph.microsoft.com/v1.0/applications?`$select=appid,passwordCredentials,keycredentials&`$top=999"
do {
$response = Invoke-MgGraphRequest -Uri $uri
$apps = $response.value
$appcreds += $apps
$uri = $response.'@odata.nextLink'
} while ($uri)
$Uri = "https://graph.microsoft.com/v1.0/serviceprincipals?`$top=999&`$expand=appRoleAssignments&`$select=appId,appRoleAssignments"
do {
$response = Invoke-MgGraphRequest -Uri $uri
$apps = $response.value
$approles += $apps
$uri = $response.'@odata.nextLink'
} while ($uri)
$i = 0
$count = $AllApps.count
ForEach ($app in $AllApps) {
$i++
Write-Progress -Activity "Processing $($app.displayName)" -Status "$i of $count completed" -PercentComplete ($i * 100 / $count)
$Roles = $null
$Roles = $approles | Where-Object { $_.appid -eq $app.appid }
[array]$Permission = $Null
$spermissions = $null
if (($Roles.count) -gt 0) {
ForEach ($Approle in $Roles.appRoleAssignments) {
Switch ($AppRole.ResourceDisplayName) {
"Microsoft Graph" {
$Permission += $GraphRoles[$AppRole.AppRoleId]
}
"Office 365 Exchange Online" {
$Permission += $ExoPermissions[$AppRole.AppRoleId]
}
"Office 365 Management APIs" {
$Permission += $O365Permissions[$AppRole.AppRoleId]
}
"Windows Azure Active Directory" {
$Permission += $AzureADPermissions[$AppRole.AppRoleId]
}
"Skype and Teams Tenant Admin API" {
$Permission += $TeamsPermissions[$AppRole.AppRoleId]
}
"Microsoft Rights Management Services" {
$Permission += $RightsManagementPermissions[$AppRole.AppRoleId]
}
}
}
if ($Permission) {
$spermissions = (compare-object -ReferenceObject ($Permission | Where-Object { $_ }) -DifferenceObject $Sensitivepermissions -IncludeEqual | Where-Object { $_.SideIndicator -eq "==" }).inputobject
}
}
$secrets = @()
$secrets = $appcreds | Where-Object { $_.appid -eq $app.appid }
$passwords = $secrets.passwordcredentials | ForEach-Object { [pscustomobject]@{displayname = $_.displayname; startdatetime = $_.startdatetime; enddatetime = $_.enddatetime } }
$certs = $secrets.keycredentials | ForEach-Object { [pscustomobject]@{displayname = $_.displayname; startdatetime = $_.startdatetime; enddatetime = $_.enddatetime; usage = $_.usage; type = $_.type; customKeyIdentifier = $_.customKeyIdentifier } }
$temp = [pscustomobject]@{
id = $app.id
displayName = $app.displayName
createdDateTime = $app.createdDateTime
enabled = $app.accountEnabled
servicePrincipalType = $app.servicePrincipalType
permissions = $permission -join "`n"
sensitivepermissions = $spermissions -join "`n"
secretdisplayname = $passwords.displayname -join "`n"
secretstartdate = (Convert-ToDateTime -dateStrings $passwords.startdatetime) -join "`n"
secretenddate = (Convert-ToDateTime -dateStrings $passwords.enddatetime) -join "`n"
certdisplayname = $certs.displayname -join "`n"
certthumbprint = $certs.customKeyIdentifier -join "`n"
certstartdate = (Convert-ToDateTime -dateStrings $certs.startdatetime) -join "`n"
certenddate = (Convert-ToDateTime -dateStrings $certs.enddatetime) -join "`n"
certusage = $certs.usage -join "`n"
certtype = $certs.type -join "`n"
signInAudience = $app.signInAudience
appRoleAssignmentRequired = $app.appRoleAssignmentRequired
appOwnerOrganizationId = $app.appOwnerOrganizationId
}
$Appdetails += $temp
}
return $Appdetails
}
$threshold = 30 # number of days after which cert/secret would be expired
$apps = @()
$expiringsecrets = @()
$expiringcerts = @()
$sensitiveapps = @()
disconnect-mggraph
Connect-MgGraph -NoWelcome -scopes Directory.read.all
$ConnectionDetail = Get-mgContext
clear-host
if ($ConnectionDetail.scopes -contains "Directory.Read.All") {
$apps = Get-SensitiveApps
$expiringsecrets = ($apps | Where-Object { $_.secretenddate }) | Where-Object { (($_.secretenddate -split "`n" | ForEach-Object { [datetime]::ParseExact($_, "dd-MM-yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture) }) | Measure-Object -Maximum).maximum -lt (get-date).Adddays($threshold) }
$expiringcerts = ($apps | Where-Object { $_.certenddate }) | Where-Object { (($_.certenddate -split "`n" | ForEach-Object { [datetime]::ParseExact($_, "dd-MM-yyyy HH:mm:ss", [System.Globalization.CultureInfo]::InvariantCulture)}) | Measure-Object -Maximum).maximum -lt (get-date).Adddays($threshold) }
$sensitiveapps = $apps | Where-Object { $_.sensitivepermissions }
}
$sensitiveapps | export-csv -nti c:\temp\appreport.csv
$expiringcerts | export-csv -nti c:\temp\appreport-expiringcerts.csv
$expiringsecrets | export-csv -nti c:\temp\appreport-expiringsecerts.csv
if ($sensitiveapps) {
$sensitiveappssummary = ($sensitiveapps | ConvertTo-Html -As Table -Fragment -PreContent "<h2>Sensitive apps Summary</h2>") -replace "`n", "<br>"
}
if ($expiringcerts) {
$expiringcertssummary = ($expiringcerts | ConvertTo-Html -As Table -Fragment -PreContent "<h2>Expiriring certificates Summary</h2>") -replace "`n", "<br>"
}
if ($expiringsecrets) {
$expiringsecretssummary = ($expiringsecrets | ConvertTo-Html -As Table -Fragment -PreContent "<h2>Expiring secrets Summary</h2>") -replace "`n", "<br>"
}
$ReportRaw = ConvertTo-HTML -Body "$sensitiveappssummary $expiringcertssummary $expiringsecretssummary" -Head $header -Title "Report on Entra ID: $($TenantBasicDetail.Displayname)" -PostContent "<p id='CreationDate'>Creation Date: $(Get-Date) $CopyRightInfo </p>"
# To preseve HTMLformatting in description
$ReportRaw = [System.Web.HttpUtility]::HtmlDecode($ReportRaw)
$ReportRaw | Out-File $ReportPath
Invoke-item $ReportPath
# $expiringsecrets , $expiringcerts and $sensitiveapps variables can be used to get particular details.
<# $MailCredential = Get-Credential -Message "Enter the password for the email account: " -UserName "contactfor_nitish@hotmail.com"
$body = Get-Content $ReportPath1 -Raw
New-Email -RecipientAddressTo "nitish@nitishkumar.net" -SenderAddress "contactfor_nitish@hotmail.com" -SMTPServer "smtp.office365.com" -SMTPServerPort 587 -Subject "AD Assessment Report $(get-date -Uformat "%Y%m%d-%H%M%S")" -Body $body -credential $MailCredential #>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment