Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release of EntraOps Version 0.3.3 #30

Merged
merged 15 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@
# Change Log
All essential changes on EntraOps will be documented in this changelog.

## [0.3.3] - 2024-11-27

### Added
- Status of Restricted Management in Privileged EAM Workbook [#28](https://github.com/Cloud-Architekt/EntraOps/issues/28)
- Added support for EligibilityBy and enhanced PIM for Groups support

### Changed
- Added tenant root group as default for high privileged scopes
- Support for multiple scopes for high privileged
- Improvement in visualization of Privileged EAM Workbook
- Support to identify Privileged Auth Admin as Control Plane

### Fixed
- Order of ResourceApps by tiered levels
- Improvements to Ingest API processing (fix by [weskroesbergen](https://github.com/weskroesbergen))
- Process files in batches of 50 to avoid errors hitting the 1Mb file limit for DCRs

## [0.3.2] - 2024-10-26

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion EntraOps/EntraOps.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'EntraOps.psm1'

# Version number of this module.
ModuleVersion = '0.3.2'
ModuleVersion = '0.3.3'

# Supported PSEditions
CompatiblePSEditions = 'Core', 'Desktop'
Expand Down
14 changes: 5 additions & 9 deletions EntraOps/Public/Configuration/New-EntraOpsConfigFile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ function New-EntraOpsConfigFile {
# Get TenantId
try {
$TenantId = (Invoke-RestMethod -Uri ("https://login.windows.net/$($TenantName)/.well-known/openid-configuration")).token_endpoint.split('/')[3]
}
catch {
} catch {
Write-Error "Can't find tenant with name $TenantName. Error: $_"
}

Expand All @@ -109,15 +108,13 @@ function New-EntraOpsConfigFile {
if ($AzContext.Tenant.Id -ne $TenantId) {
Write-Verbose "Call Connect-AzAccount to $($TenantId)..."
Connect-AzAccount -TenantId $TenantId
}
else {
} else {
Write-Verbose "Already connected to $($AzContext.Tenant.Id)"
}

try {
$TenantDetails = Get-AzTenant -TenantId $TenantId
}
catch {
} catch {
Write-Error "Failed to get Tenant details for TenantId $TenantId. Error: $_"
}
#endregion
Expand All @@ -140,7 +137,7 @@ function New-EntraOpsConfigFile {
PrivilegedObjectClassificationSource = ("EntraOps", "PrivilegedRolesFromAzGraph", "PrivilegedEdgesFromExposureManagement")
EntraOpsScopes = ("EntraID", "IdentityGovernance", "ResourceApps", "DeviceManagement")
AzureHighPrivilegedRoles = ("Owner", "Role Based Access Control Administrator", "User Access Administrator")
AzureHighPrivilegedScopes = ("/")
AzureHighPrivilegedScopes = ("/", "/providers/microsoft.management/managementgroups/$($TenantId)")
ExposureCriticalityLevel = "<1"
}
AutomatedClassificationUpdate = [ordered]@{
Expand Down Expand Up @@ -196,8 +193,7 @@ function New-EntraOpsConfigFile {
try {
Write-Output "Writing configuration file to $($ConfigFilePath)..."
$EnvConfigSchema | ConvertTo-Json | Out-File -Path $($ConfigFilePath)
}
catch {
} catch {
Write-Error "Failed to write configuration file to $($ConfigFilePath). Error: $_"
}
#endregion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function Get-EntraOpsClassificationControlPlaneObjects {
[object]$AzureHighPrivilegedRoles = ("Owner", "Role Based Access Control Administrator", "User Access Administrator")
,
[Parameter(Mandatory = $false)]
[string]$AzureHighPrivilegedScopes = "*"
[object]$AzureHighPrivilegedScopes = "*"
,
[Parameter(Mandatory = $false)]
[string]$ExposureCriticalityLevel = "<1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,34 +51,34 @@ function Get-EntraOpsPrivilegedEamEntraId {
"RoleId" = "9f06204d-73c1-4d4c-880a-6edb90606fd8" # Azure AD Joined Device Local Administrator
"Service" = 'Global Endpoint Management'
}
$ControlPlaneRolesWithoutRoleActions += New-Object PSObject -Property @{
"RoleId" = "7be44c8a-adaf-4e2a-84d6-ab2649e08a13" # Privileged Authentication Administrator
"Service" = 'Privileged User Management'
}

#endregion

#region Check if classification file custom and/or template file exists, choose custom template for tenant if available
$ClassificationFileName = "Classification_AadResources.json"
if (Test-Path -Path "$($DefaultFolderClassification)/$($TenantNameContext)/$($ClassificationFileName)") {
$AadClassificationFilePath = "$($DefaultFolderClassification)/$($TenantNameContext)/$($ClassificationFileName)"
}
elseif (Test-Path -Path "$($DefaultFolderClassification)/Templates/$($ClassificationFileName)") {
} elseif (Test-Path -Path "$($DefaultFolderClassification)/Templates/$($ClassificationFileName)") {
$AadClassificationFilePath = "$($DefaultFolderClassification)/Templates/$($ClassificationFileName)"
}
else {
} else {
Write-Error "Classification file $($ClassificationFileName) not found in $($DefaultFolderClassification). Please run Update-EntraOpsClassificationFiles to download the latest classification files from AzurePrivilegedIAM repository."
}
#endregion

#region Get all role assignments and global exclusions
if ($SampleMode -eq $True) {
$AadRbacAssignments = get-content -Path "$EntraOpsBaseFolder/Samples/AadRoleManagementAssignments.json" | ConvertFrom-Json -Depth 10
}
else {
} else {
$AadRbacAssignments = Get-EntraOpsPrivilegedEntraIdRoles -TenantId $TenantId
}

if ($GlobalExclusion -eq $true) {
$GlobalExclusionList = (Get-Content -Path "$DefaultFolderClassification/Global.json" | ConvertFrom-Json -Depth 10).ExcludedPrincipalId
}
else {
} else {
$GlobalExclusionList = $null
}
#endregion
Expand All @@ -88,16 +88,6 @@ function Get-EntraOpsPrivilegedEamEntraId {
$AadRbacClassifications = foreach ($AadRbacAssignment in $AadRbacAssignments) {
$Classification = $AadRbacEamScope | Where-Object { $_.ResourceId -eq $CurrentRoleAssignmentScope } | select-object AdminTierLevel, AdminTierLevelName, Service, TaggedBy | Sort-Object AdminTierLevel, AdminTierLevelName, Service

if ($ControlPlaneRolesWithoutRoleActions.RoleId -contains $AadRbacAssignment.RoleId) {
$Classification = $ControlPlaneRolesWithoutRoleActions | Where-Object { $_.RoleId -contains $AadRbacAssignment.RoleId }
$Classification = [PSCustomObject]@{
'AdminTierLevel' = "0"
'AdminTierLevelName' = "ControlPlane"
'Service' = $Classification.Service
'TaggedBy' = "ControlPlaneRolesWithoutRoleActions"
}
}

[PSCustomObject]@{
'RoleAssignmentId' = $AadRbacAssignment.RoleAssignmentId
'RoleAssignmentScopeId' = $AadRbacAssignment.RoleAssignmentScopeId
Expand All @@ -124,8 +114,7 @@ function Get-EntraOpsPrivilegedEamEntraId {
# Get all role actions for Entra ID roles, role actions are defined tenant wide
if ($SampleMode -eq $True) {
$AllAadRoleActions = get-content -Path "$EntraOpsBaseFolder/Samples/AadRoleManagementRoleDefinitions.json" | ConvertFrom-Json -Depth 10
}
else {
} else {
$AllAadRoleActions = (Invoke-EntraOpsMsGraphQuery -Method Get -Uri "https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions" -OutputType PSObject)
}
#endregion
Expand All @@ -151,13 +140,26 @@ function Get-EntraOpsPrivilegedEamEntraId {
$MatchedClassificationByScope | Where-Object { $_.RoleDefinitionActions -Contains $Action -and $Classification.ExcludedRoleDefinitionActions -notcontains $_.RoleDefinitionActions }
}

$CurrentAadRbacClassification.Classification = New-Object System.Collections.ArrayList

if ($ControlPlaneRolesWithoutRoleActions.RoleId -contains $CurrentAadRbacClassification.RoleDefinitionId) {
Write-Warning "Apply classification for role $($CurrentAadRbacClassification.RoleDefinitionName) without role actions..."
$Classification = $ControlPlaneRolesWithoutRoleActions | Where-Object { $_.RoleId -contains $CurrentAadRbacClassification.RoleDefinitionId }
$ClassifiedAadRbacRoleWithoutActions = [PSCustomObject]@{
'AdminTierLevel' = "0"
'AdminTierLevelName' = "ControlPlane"
'Service' = $Classification.Service
'TaggedBy' = "ControlPlaneWithoutRoleActions"
}
$CurrentAadRbacClassification.Classification.Add( $ClassifiedAadRbacRoleWithoutActions ) | Out-Null
}

if (($AadRoleActionsInJsonDefinition.Count -gt 0)) {
$ClassifiedAadRbacRoleWithActions = @()
foreach ($AadRoleAction in $AadRoleActions.rolePermissions.allowedResourceActions) {
$ClassifiedAadRbacRoleWithActions += $AadRoleActionsInJsonDefinition | Where-Object { $AadRoleAction -in $_.RoleDefinitionActions }
}
$ClassifiedAadRbacRoleWithActions = $ClassifiedAadRbacRoleWithActions | select-object -Unique EAMTierLevelName, EAMTierLevelTagValue, Service | Sort-Object EAMTierLevelTagValue, Service
$CurrentAadRbacClassification.Classification = New-Object System.Collections.ArrayList
$ClassifiedAadRbacRoleWithActions | ForEach-Object {
$ClassifiedRoleAction = [PSCustomObject]@{
'AdminTierLevel' = $_.EAMTierLevelTagValue
Expand All @@ -167,8 +169,9 @@ function Get-EntraOpsPrivilegedEamEntraId {
}
$CurrentAadRbacClassification.Classification.Add( $ClassifiedRoleAction ) | Out-Null
}
}
$CurrentAadRbacClassification | sort-object AdminTierLevel, AdminTierLevelName, Service
}

$CurrentAadRbacClassification
}
#endregion

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,9 @@ function Get-EntraOpsPrivilegedEamResourceApps {
$ClassificationFileName = "Classification_AppRoles.json"
if (Test-Path -Path "$($DefaultFolderClassification)/$($TenantNameContext)/$($ClassificationFileName)") {
$ResourceAppsClassificationFilePath = "$($DefaultFolderClassification)/$($TenantNameContext)/$($ClassificationFileName)"
}
elseif (Test-Path -Path "$($DefaultFolderClassification)/Templates/$($ClassificationFileName)") {
} elseif (Test-Path -Path "$($DefaultFolderClassification)/Templates/$($ClassificationFileName)") {
$ResourceAppsClassificationFilePath = "$($DefaultFolderClassification)/Templates/$($ClassificationFileName)"
}
else {
} else {
Write-Error "Classification file $($ClassificationFileName) not found in $($DefaultFolderClassification). Please run Update-EntraOpsClassificationFiles to download the latest classification files from AzurePrivilegedIAM repository."
}

Expand All @@ -52,14 +50,12 @@ function Get-EntraOpsPrivilegedEamResourceApps {
# Get all role assignments and global exclusions
if ($SampleMode -ne $True) {
$AppRoleAssignments = Get-EntraOpsPrivilegedAppRoles -TenantId $TenantId
}
else {
} else {
Write-Warning "Currently not supported!"
}
if ($GlobalExclusion -eq $true) {
$GlobalExclusionList = (Get-Content -Path "$DefaultFolderClassification/Global.json" | ConvertFrom-Json -Depth 10).ExcludedPrincipalId
}
else {
} else {
$GlobalExclusionList = $null
}
#endregion
Expand All @@ -78,7 +74,7 @@ function Get-EntraOpsPrivilegedEamResourceApps {
$Classification = @()
if (($AppRoleInJsonDefinition.Count -gt 0)) {
$ClassifiedAppRole = @()
$ClassifiedAppRole += $AppRoleInJsonDefinition | select-object -Unique EAMTierLevelName, EAMTierLevelTagValue, Service
$ClassifiedAppRole += $AppRoleInJsonDefinition | select-object -Unique EAMTierLevelName, EAMTierLevelTagValue, Service | Sort-Object EAMTierLevelTagValue, EAMTierLevelName, Service
$Classification += $ClassifiedAppRole | ForEach-Object {
[PSCustomObject]@{
'AdminTierLevel' = $_.EAMTierLevelTagValue
Expand All @@ -87,8 +83,7 @@ function Get-EntraOpsPrivilegedEamResourceApps {
'TaggedBy' = "JSONwithAction"
}
}
}
else {
} else {
$Classification += [PSCustomObject]@{
'AdminTierLevel' = "Unclassified"
'AdminTierLevelName' = "Unclassified"
Expand All @@ -102,18 +97,23 @@ function Get-EntraOpsPrivilegedEamResourceApps {
'Classification' = $Classification
}
}
$AppRoleClassificationsByJSON = $AppRoleClassificationsByJSON | sort-object -property @{e = { $_.Classification.AdminTierLevel } }
#endregion

#region Classify App Role Assignments
$AppRoleClassifications = foreach ($AppRoleAssignment in $AppRoleAssignments) {
$AppRoleAssignment = $AppRoleAssignment | Select-Object -ExcludeProperty Classification
$Classification = @()
$ClassificationCollection = ($AppRoleClassificationsByJSON | Where-Object { $_.RoleAssignmentScope -eq $AppRoleAssignment.RoleAssignmentScope -and $_.RoleDefinitionId -eq $AppRoleAssignment.RoleDefinitionId })
$Classification += $ClassificationCollection.Classification | select-object -Unique AdminTierLevel, AdminTierLevelName, Service, TaggedBy | Sort-Object -Unique AdminTierLevel, AdminTierLevelName, Service, TaggedBy
if ($ClassificationCollection.Classification.Count -gt 0) {
$Classification += $ClassificationCollection.Classification | Sort-Object AdminTierLevel, AdminTierLevelName, Service
$Classification += $ClassificationCollection.Classification | select-object -Unique AdminTierLevel, AdminTierLevelName, Service, TaggedBy
}
$AppRoleAssignment | Add-Member -NotePropertyName "Classification" -NotePropertyValue $Classification -Force
$AppRoleAssignment
}
#endregion
$AppRoleClassifications = $AppRoleClassifications | sort-object -property @{e = { $_.Classification.AdminTierLevel } }, RoleDefinitionName

#region Add classification and details of Service Principals to output
Write-Host "Classifiying of all assigned privileged app roles to service principals..."
Expand All @@ -131,7 +131,7 @@ function Get-EntraOpsPrivilegedEamResourceApps {

# Classification
$Classification = @()
$Classification += $AppRoleClassification
$Classification += $AppRoleClassification | Sort-Object AdminTierLevel, AdminTierLevelName, Service
if ($Classification.Count -eq 0) {
$Classification += [PSCustomObject]@{
'AdminTierLevel' = "Unclassified"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,18 +77,38 @@ function Save-EntraOpsPrivilegedEAMInsightsCustomTable {

try {
$EamFiles = (Get-ChildItem -Path "$($ImportPath)\$($RbacSystem)\$($ObjectType)" -Filter "*.json").FullName
}
catch {
} catch {
Write-Warning "No $($RbacSystem).json found!"
}

if ($EamFiles.Count -gt 0) {
Write-Host "Upload classification data for object type: $($ObjectType)"
$EamSummary = @()
$EamSummary += $EamFiles | ForEach-Object {
Get-Content $_ | ConvertFrom-Json -Depth 10

# Loop through files in batches of 50 to avoid errors hitting the 1Mb file limit for DCRs
for ($i = 0; $i -lt $EamFiles.Count; $i += 50) {
# Select the current batch of 50 files
$Batch = $EamFiles[$i..([math]::Min($i + 49, $EamFiles.Count - 1))]

# Process the batch
$EamSummary = @()
$EamSummary += $Batch | ForEach-Object {
# Check that each item is indeed a file before processing
if (Test-Path $_ -PathType Leaf) {
Get-Content $_ | ConvertFrom-Json -Depth 10
} else {
Write-Warning "Skipped non-file item: $_"
}
}

if ($EamSummary.Count -ne 0) {
$Json = $EamSummary | ConvertTo-Json -Depth 10

# Send the batch to the API
Push-EntraOpsLogsIngestionAPI -TableName $TableName -JsonContent $json -DataCollectionRuleName $DataCollectionRuleName -DataCollectionResourceGroupName $DataCollectionResourceGroupName -DataCollectionRuleSubscriptionId $DataCollectionRuleSubscriptionId
}

Write-Host "Processed batch of $($EamSummary.Count) files starting at index $i."
}
$Json = $EamSummary | ConvertTo-Json -Depth 10
Push-EntraOpsLogsIngestionAPI -TableName $TableName -JsonContent $json -DataCollectionRuleName $DataCollectionRuleName -DataCollectionResourceGroupName $DataCollectionResourceGroupName -DataCollectionRuleSubscriptionId $DataCollectionRuleSubscriptionId
}
}
}
Expand Down
Loading