Private/23-GetAtlasApplications.ps1
|
function Get-AtlasApplication { [CmdletBinding()] param( [Parameter(Mandatory)] [string] $TenantId, [Parameter(Mandatory)] [AllowEmptyCollection()] [AtlasNode[]] $KnownNode ) $result = [AtlasCollectionResult]::new() $keyById = @{} $nodeById = @{} $nodeByAppId = @{} foreach ($node in $KnownNode) { if (-not $keyById.ContainsKey($node.Id) -or $node.Status -eq 'complete') { $keyById[$node.Id] = $node.Key $nodeById[$node.Id] = $node } if ($node.Properties.ContainsKey('appId') -and $node.Properties.appId) { $nodeByAppId[$node.Properties.appId] = $node } } $servicePrincipalsEndpoint = '/v1.0/servicePrincipals?$select=id,appId,displayName,servicePrincipalType,accountEnabled,appOwnerOrganizationId,appRoles' $applicationsEndpoint = '/v1.0/applications?$select=id,appId,displayName,signInAudience,createdDateTime,passwordCredentials,keyCredentials,requiredResourceAccess' try { $servicePrincipalResponse = Invoke-AtlasGraphRequest -Uri $servicePrincipalsEndpoint } catch { $result.Status = 'partial' $result.Warnings.Add("Applications could not be collected: $(Get-AtlasSafeErrorDetail -ErrorRecord $_)") $result.Metrics = @{ servicePrincipalCount = 0 applicationCount = 0 appRoleAssignmentCount = 0 ownerCount = 0 credentialCount = 0 apiPermissionCount = 0 requestCount = 0 retryCount = 0 } return $result } try { $applicationResponse = Invoke-AtlasGraphRequest -Uri $applicationsEndpoint } catch { $result.Status = 'partial' $result.Warnings.Add("Application registrations could not be collected: $(Get-AtlasSafeErrorDetail -ErrorRecord $_)") $applicationResponse = [pscustomobject] @{ Items = @() Metrics = @{ requestCount = 0; retryCount = 0 } } } $servicePrincipalKeyById = @{} $servicePrincipalKeyByAppId = @{} $applicationKeyById = @{} $applicationKeyByAppId = @{} $appRoleNameByPrincipalId = @{} $ownerEdgeCount = 0 $credentialCount = 0 $apiPermissionCount = 0 foreach ($servicePrincipal in $servicePrincipalResponse.Items) { $displayName = if ($servicePrincipal.displayName) { $servicePrincipal.displayName } else { $servicePrincipal.id } $appRoles = @($servicePrincipal.appRoles) $appRoleNames = @{} foreach ($appRole in $appRoles) { if ($appRole.id) { $appRoleNames[$appRole.id] = if ($appRole.displayName) { $appRole.displayName } else { $appRole.value } } } $node = New-AtlasNode -TenantId $TenantId -Id $servicePrincipal.id -Kind 'servicePrincipal' -DisplayName $displayName -Properties @{ appId = $servicePrincipal.appId servicePrincipalType = $servicePrincipal.servicePrincipalType accountEnabled = $servicePrincipal.accountEnabled appOwnerOrganizationId = $servicePrincipal.appOwnerOrganizationId appRoleCount = $appRoles.Count } -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' odataType = '#microsoft.graph.servicePrincipal' resourcePath = "/servicePrincipals/$($servicePrincipal.id)" collector = 'applications' } $result.Nodes.Add($node) $servicePrincipalKeyById[$servicePrincipal.id] = $node.Key if ($servicePrincipal.appId) { $servicePrincipalKeyByAppId[$servicePrincipal.appId] = $node.Key } $appRoleNameByPrincipalId[$servicePrincipal.id] = $appRoleNames } foreach ($application in $applicationResponse.Items) { $passwordCredentials = @( @(Get-AtlasResponseProperty -InputObject $application -Name 'passwordCredentials') | Where-Object { $null -ne $_ } ) $keyCredentials = @( @(Get-AtlasResponseProperty -InputObject $application -Name 'keyCredentials') | Where-Object { $null -ne $_ } ) $requiredResourceAccess = @( @(Get-AtlasResponseProperty -InputObject $application -Name 'requiredResourceAccess') | Where-Object { $null -ne $_ } ) $displayName = if ($application.displayName) { $application.displayName } else { $application.id } $applicationNode = New-AtlasNode -TenantId $TenantId -Id $application.id -Kind 'application' -DisplayName $displayName -Properties @{ appId = $application.appId signInAudience = Get-AtlasResponseProperty -InputObject $application -Name 'signInAudience' createdDateTime = Get-AtlasResponseProperty -InputObject $application -Name 'createdDateTime' passwordCredentialCount = $passwordCredentials.Count keyCredentialCount = $keyCredentials.Count requiredResourceAccessCount = $requiredResourceAccess.Count } -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' odataType = '#microsoft.graph.application' resourcePath = "/applications/$($application.id)" collector = 'applications' } $result.Nodes.Add($applicationNode) $applicationKeyById[$application.id] = $applicationNode.Key if ($application.appId) { $applicationKeyByAppId[$application.appId] = $applicationNode.Key } if ($application.appId -and $servicePrincipalKeyByAppId.ContainsKey($application.appId)) { $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'applications' -Endpoint $applicationsEndpoint -SourceObjectId "$($application.id)|backingServicePrincipal|$($application.appId)" -Fields @{ applicationId = $application.id appId = $application.appId relationship = 'hasServicePrincipal' } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $applicationNode.Key -To $servicePrincipalKeyByAppId[$application.appId] -Relationship 'hasServicePrincipal' -State @{ appId = $application.appId } -EvidenceIds @($evidence.Key) -Source @{ collector = 'applications' }) ) } foreach ($credential in $passwordCredentials + $keyCredentials) { $credentialKind = if ($passwordCredentials -contains $credential) { 'passwordCredential' } else { 'keyCredential' } $credentialId = "$($application.id):$credentialKind`:$($credential.keyId)" $credentialName = if ($credential.displayName) { $credential.displayName } else { $credentialKind } $credentialNode = New-AtlasNode -TenantId $TenantId -Id $credentialId -Kind 'applicationCredential' -DisplayName $credentialName -Properties @{ credentialType = $credentialKind keyId = $credential.keyId startDateTime = $credential.startDateTime endDateTime = $credential.endDateTime } -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' odataType = "#microsoft.graph.$credentialKind" resourcePath = "/applications/$($application.id)" collector = 'applications' } -Tags @('credential') $result.Nodes.Add($credentialNode) $credentialCount++ $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'applications' -Endpoint $applicationsEndpoint -SourceObjectId "$($application.id)|credential|$($credential.keyId)" -Fields @{ applicationId = $application.id keyId = $credential.keyId credentialType = $credentialKind endDateTime = $credential.endDateTime } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $applicationNode.Key -To $credentialNode.Key -Relationship 'hasCredential' -State @{ credentialType = $credentialKind endDateTime = $credential.endDateTime } -EvidenceIds @($evidence.Key) -Source @{ collector = 'applications' }) ) } foreach ($resourceAccess in $requiredResourceAccess) { foreach ($permission in @($resourceAccess.resourceAccess)) { if (-not $permission.id) { continue } $permissionId = "$($resourceAccess.resourceAppId):$($permission.id):$($permission.type)" $permissionNode = New-AtlasNode -TenantId $TenantId -Id "apiPermission:$permissionId" -Kind 'apiPermission' -DisplayName "API permission $($permission.type) $($permission.id)" -Properties @{ resourceAppId = $resourceAccess.resourceAppId resourceAccessId = $permission.id permissionType = $permission.type } -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' odataType = '#microsoft.graph.resourceAccess' resourcePath = "/applications/$($application.id)" collector = 'applications' } -Tags @('apiPermission') $result.Nodes.Add($permissionNode) $apiPermissionCount++ $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'applications' -Endpoint $applicationsEndpoint -SourceObjectId "$($application.id)|requiredResourceAccess|$permissionId" -Fields @{ applicationId = $application.id resourceAppId = $resourceAccess.resourceAppId resourceAccessId = $permission.id permissionType = $permission.type } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $applicationNode.Key -To $permissionNode.Key -Relationship 'requiresApiPermission' -State @{ resourceAppId = $resourceAccess.resourceAppId permissionType = $permission.type } -EvidenceIds @($evidence.Key) -Source @{ collector = 'applications' }) ) } } } foreach ($servicePrincipal in $servicePrincipalResponse.Items) { $assignmentsEndpoint = "/v1.0/servicePrincipals/$($servicePrincipal.id)/appRoleAssignedTo?`$select=id,principalId,principalDisplayName,principalType,resourceId,resourceDisplayName,appRoleId,createdDateTime" try { $assignmentResponse = Invoke-AtlasGraphRequest -Uri $assignmentsEndpoint $servicePrincipalResponse.Metrics.requestCount += $assignmentResponse.Metrics.requestCount $servicePrincipalResponse.Metrics.retryCount += $assignmentResponse.Metrics.retryCount } catch { $result.Status = 'partial' $result.Warnings.Add("App role assignments could not be collected for application '$($servicePrincipal.displayName)': $(Get-AtlasSafeErrorDetail -ErrorRecord $_)") continue } foreach ($assignment in $assignmentResponse.Items) { if (-not $servicePrincipalKeyById.ContainsKey($assignment.resourceId)) { $result.Status = 'partial' $result.Warnings.Add("Application '$($assignment.resourceId)' was referenced by an app role assignment but was not returned.") continue } if ($keyById.ContainsKey($assignment.principalId)) { $principalKey = $keyById[$assignment.principalId] } else { $principalKind = switch ($assignment.principalType) { 'User' { 'user' } 'Group' { 'group' } 'ServicePrincipal' { 'servicePrincipal' } default { 'directoryObject' } } $principalDisplayName = if ($assignment.principalDisplayName) { $assignment.principalDisplayName } else { $assignment.principalId } $principalNode = New-AtlasNode -TenantId $TenantId -Id $assignment.principalId -Kind $principalKind -DisplayName $principalDisplayName -Status 'unresolved' -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' resourcePath = "/directoryObjects/$($assignment.principalId)" collector = 'applications' } $result.Nodes.Add($principalNode) $principalKey = $principalNode.Key } $appRoleName = $null if ( $appRoleNameByPrincipalId.ContainsKey($assignment.resourceId) -and $appRoleNameByPrincipalId[$assignment.resourceId].ContainsKey($assignment.appRoleId) ) { $appRoleName = $appRoleNameByPrincipalId[$assignment.resourceId][$assignment.appRoleId] } $assignmentKind = if ($nodeById.ContainsKey($assignment.principalId) -and $nodeById[$assignment.principalId].Kind -eq 'group') { 'group' } else { 'direct' } $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'applications' -Endpoint $assignmentsEndpoint -SourceObjectId $assignment.id -Fields @{ principalId = $assignment.principalId principalType = $assignment.principalType resourceId = $assignment.resourceId appRoleId = $assignment.appRoleId appRoleDisplayName = $appRoleName createdDateTime = $assignment.createdDateTime } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $principalKey -To $servicePrincipalKeyById[$assignment.resourceId] -Relationship 'assignedAppRole' -State @{ assignment = $assignmentKind principalType = $assignment.principalType appRoleId = $assignment.appRoleId appRoleDisplayName = $appRoleName createdDateTime = $assignment.createdDateTime } -EvidenceIds @($evidence.Key) -Source @{ collector = 'applications' }) ) } } $ownerTargets = @() foreach ($applicationId in $applicationKeyById.Keys) { $ownerTargets += @{ Id = $applicationId Key = $applicationKeyById[$applicationId] Endpoint = "/v1.0/applications/$applicationId/owners?`$select=id,displayName" Name = 'application' } } foreach ($servicePrincipalId in $servicePrincipalKeyById.Keys) { $ownerTargets += @{ Id = $servicePrincipalId Key = $servicePrincipalKeyById[$servicePrincipalId] Endpoint = "/v1.0/servicePrincipals/$servicePrincipalId/owners?`$select=id,displayName" Name = 'service principal' } } foreach ($target in $ownerTargets) { try { $ownerResponse = Invoke-AtlasGraphRequest -Uri $target.Endpoint $servicePrincipalResponse.Metrics.requestCount += $ownerResponse.Metrics.requestCount $servicePrincipalResponse.Metrics.retryCount += $ownerResponse.Metrics.retryCount } catch { $result.Status = 'partial' $result.Warnings.Add("Owners could not be collected for $($target.Name) '$($target.Id)': $(Get-AtlasSafeErrorDetail -ErrorRecord $_)") continue } foreach ($owner in $ownerResponse.Items) { $ownerId = Get-AtlasResponseProperty -InputObject $owner -Name 'id' if (-not $ownerId) { continue } if ($keyById.ContainsKey($ownerId)) { $ownerKey = $keyById[$ownerId] } elseif ($servicePrincipalKeyById.ContainsKey($ownerId)) { $ownerKey = $servicePrincipalKeyById[$ownerId] } elseif ($applicationKeyById.ContainsKey($ownerId)) { $ownerKey = $applicationKeyById[$ownerId] } else { $odataType = Get-AtlasResponseProperty -InputObject $owner -Name '@odata.type' $ownerKind = switch ($odataType) { '#microsoft.graph.user' { 'user' } '#microsoft.graph.group' { 'group' } '#microsoft.graph.servicePrincipal' { 'servicePrincipal' } default { 'directoryObject' } } $ownerName = Get-AtlasResponseProperty -InputObject $owner -Name 'displayName' if (-not $ownerName) { $ownerName = $ownerId } $ownerNode = New-AtlasNode -TenantId $TenantId -Id $ownerId -Kind $ownerKind -DisplayName $ownerName -Status 'unresolved' -Source @{ provider = 'microsoftGraph' apiVersion = 'v1.0' odataType = $odataType resourcePath = "/directoryObjects/$ownerId" collector = 'applications' } $result.Nodes.Add($ownerNode) $ownerKey = $ownerNode.Key } $evidence = New-AtlasEvidence -TenantId $TenantId -Collector 'applications' -Endpoint $target.Endpoint -SourceObjectId "$($target.Id)|owner|$ownerId" -Fields @{ ownedObjectId = $target.Id ownerId = $ownerId } $result.Evidence.Add($evidence) $result.Edges.Add( (New-AtlasEdge -TenantId $TenantId -From $target.Key -To $ownerKey -Relationship 'ownedBy' -State @{ ownerId = $ownerId } -EvidenceIds @($evidence.Key) -Source @{ collector = 'applications' }) ) $ownerEdgeCount++ } } $result.Metrics = @{ servicePrincipalCount = $servicePrincipalResponse.Items.Count applicationCount = $applicationResponse.Items.Count appRoleAssignmentCount = $result.Edges.Count ownerCount = $ownerEdgeCount credentialCount = $credentialCount apiPermissionCount = $apiPermissionCount requestCount = $servicePrincipalResponse.Metrics.requestCount + $applicationResponse.Metrics.requestCount retryCount = $servicePrincipalResponse.Metrics.retryCount + $applicationResponse.Metrics.retryCount } return $result } |