Last active
August 15, 2024 05:04
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
Author : Nitish Kumar ([email protected]) | |
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 "[email protected]" | |
$body = Get-Content $ReportPath1 -Raw | |
New-Email -RecipientAddressTo "[email protected]" -SenderAddress "[email protected]" -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