diff --git a/Docs/Images/dfc-defender-plans-settings.png b/Docs/Images/dfc-defender-plans-settings.png new file mode 100644 index 00000000..2944857b Binary files /dev/null and b/Docs/Images/dfc-defender-plans-settings.png differ diff --git a/Docs/Images/dfc-security-policy-sets-settings.png b/Docs/Images/dfc-security-policy-sets-settings.png new file mode 100644 index 00000000..3c2953f4 Binary files /dev/null and b/Docs/Images/dfc-security-policy-sets-settings.png differ diff --git a/Docs/code-promotion-module-release-process.md b/Docs/code-promotion-module-release-process.md new file mode 100644 index 00000000..54014ad3 --- /dev/null +++ b/Docs/code-promotion-module-release-process.md @@ -0,0 +1,64 @@ +# EPAC Development to Production Promotion Process + +A guide for maintainers on how to move internal EPAC development (ADO) to production (GitHub). + +Assumption: You have completed PR in for EPAC Development in ADO ([https://secinfra.visualstudio.com/\_git/epac-development](https://secinfra.visualstudio.com/_git/epac-development)) and are ready to release to public GitHub EPAC project. + +You are using known local path names for EPAC Development repo and GitHub repo, for example: + +EPAC Development local repo: `C:\GitRepoClones\epac-development` +EPAC GitHub local repo: `C:\GitRepoClones\enterprise-azure-policy-as-code` + +## Code Promotion Process + +1. Create a branch in GitHub ([https://github.com/Azure/enterprise-azure-policy-as-code](https://github.com/Azure/enterprise-azure-policy-as-code)). + +2. Update local production repo with content from local development repo. In local VS code repo for EPAC GitHub, open terminal: + `PS C:\GitRepoClones\enterprise-azure-policy-as-code> .\Sync-ToGH.ps1`. + +3. Commit changes and sync. + +4. Go to [https://github.com/Azure/enterprise-azure-policy-as-code](https://github.com/Azure/enterprise-azure-policy-as-code), go to `Compare and Pull Request` + +5. Add PR title and create PR. + +6. Complete GitHub Review and merge PR process. + +7. Delete branch from GitHub. + +8. Go to VSCode for EPAC Release (GitHub) (ex `C:\GitRepoClones\enterprise-azure-policy-as-code`) In Source Control, select main branch. Move to Remotes and fetch, then sync changes. + +9. Move to branches, delete local branch (force delete may be required). + +10. Open terminal, type `git remote prune origin` + +# Module Release Process + +This is a guide on how to release a new version of the project - including automated PowerShell module publish. It is used by the EPAC maintainers only. + +## GitHub Release Process + +1. Navigate to https://github.com/Azure/enterprise-azure-policy-as-code/releases +2. Click on **Draft a new release** +3. Click on **Choose a tag** and enter in the new release version - it should be in the format "v(major).(minor).(build)" i.e. v7.3.4 **Don't forget the v** +4. When prompted click on **Create new tag: vX.X.X on publish** +5. Add a release title - you can just use the new version number. +6. Click on **Generate release notes** to pull all the notes in from related PRs. Update if necessary. +7. Click **Publish Release** + +Now just verify the module publish action has run + +## Verify Action + +1. Click on **Actions** +2. Verify that a workflow run has started with the same name as the release. + +It should finish successfully - if there is a failure review the build logs. + +# Documentation Release Process + +A guide for maintainers on how to update documentation.. + +1. Modify files in the Docs folder following the format of other files. For a list of acceptable admonitions please see [here](https://squidfunk.github.io/mkdocs-material/reference/admonitions/#supported-types) +2. If you are adding a new file ensure it is added to the `mkdocs.yml` file in the appropriate section. Use the built site to determine where a new document should be placed. +3. Create a PR and merge - the actions will commence automatically. There are two actions which run in the background to update the GitHub Pages site. diff --git a/Docs/desired-state-strategy.md b/Docs/desired-state-strategy.md index a866c0c0..0edfe6b9 100644 --- a/Docs/desired-state-strategy.md +++ b/Docs/desired-state-strategy.md @@ -1,6 +1,6 @@ # Desired state strategy -Desired State strategy enables shared responsibility scenarios. the following documents the archetypical use cases. For complex scenarios it is possible to combine multiple use cases (e.g., Use case 2a and 3, use case 1 and 2a, ...). +Desired State strategy enables shared responsibility scenarios. the following documents the archetypical use cases. For complex scenarios it is possible to combine multiple use cases. ## Use Case 1: Centralized Team diff --git a/Docs/dfc-assignments.md b/Docs/dfc-assignments.md new file mode 100644 index 00000000..bd24c306 --- /dev/null +++ b/Docs/dfc-assignments.md @@ -0,0 +1,38 @@ +# Managing Defender for Cloud Assignments + +Defender for Cloud (DFC) is a suite of Azure Security Center (ASC) capabilities that helps you prevent, detect, and respond to threats. It provides you with integration of Microsoft's threat protection technology and expertise. For more information, see [Azure Defender for Cloud](https://docs.microsoft.com/en-us/azure/security-center/defender-for-cloud). + +## Behavior of EPAC Prior to v9.0.0 + +Defender for Cloud uses Azure Policy Assignments to enable and configure the various capabilities. These assignments are created at the subscription level. + +* Policy Assignments required for [Defender plans](#defender-for-cloud-settings-for-defender-plans) (e.g., SQL, App Service, ...) +* Policy Assignments required for [Security policies](#defender-for-cloud-settings-for-security-policy-sets) (e.g., Microsoft Cloud Security Benchmark, NIST 800-53 Rev 5, NIST 800-171, ...) + +Prior to v9.0.0 of EPAC, Defender for Cloud Assignments were removed by EPAC. This was a problem for Microsoft's customers, especially for Defender Plans. + +## Revised behavior of EPAC Starting with v9.0.0: + +* EPAC **no longer manages** Defender for Cloud Assignments required for Defender Plans. +* EPAC behavior for Security Policy **is controlled by** the `keepDfcSecurityAssignments` in `desiredState` setting per `pacEnvironment` in `global-settings.jsonc`. + * If set to `true`, EPAC will **not** remove Security Policy Set Assignments created by Defender for Cloud. + * If **omitted** or **set to `false`**, EPAC will remove Security Policy Set Assignments created by Defender for Cloud. + + ```json + "desiredState": { + "strategy": "full", + "keepDfcSecurityAssignments": true + } + ``` + +**Security Policies should be manged by EPAC at the Management Group level.**; This is the recommended approach for managing Security Policies instead of relying on the auto-assignments. + +## Defender for Cloud Settings + +### Defender for Cloud settings for Defender Plans + +![image.png](Images/dfc-defender-plans-settings.png) + +### Defender for Cloud settings for Security Policy Sets + +![image.png](Images/dfc-security-policy-sets-settings.png) diff --git a/Docs/index.md b/Docs/index.md index f1f8239c..99c81989 100644 --- a/Docs/index.md +++ b/Docs/index.md @@ -37,9 +37,14 @@ You have the right and means to **opt-out**; see [Usage Tracking](usage-tracking - [Azure Enterprise Policy as Code – A New Approach](https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/azure-enterprise-policy-as-code-a-new-approach/ba-p/3607843) - [Azure Enterprise Policy as Code – Azure Landing Zones Integration](https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/azure-enterprise-policy-as-code-azure-landing-zones-integration/ba-p/3642784) -## Microsoft's Security & Compliance for Cloud Infrastructure +## EPAC and Defender for Cloud Assignments -This `enterprise-policy-as-code` **(EPAC)** repo has been developed in partnership with the Security & Compliance for Cloud Infrastructure (S&C4CI) offering available from Microsoft's Industry Solutions (Consulting Services). Microsoft Industry Solutions can assist you with securing your cloud. S&C4CI improves your new or existing security posture in Azure by securing platforms, services, and workloads at scale. +Defender for Cloud uses Azure Policy Assignments to enable and configure the various capabilities. Prior to v9.0.0 of EPAC removed these subscription-level assignments. + +* Policy Assignments required for Defender plans (e.g., SQL, App Service, ...). These is no longer managed (removed) by EPAC v9.0.0 and later. +* Policy Assignments required for Security policies (e.g., Microsoft Cloud Security Benchmark, NIST 800-53 Rev 5, NIST 800-171, ...). The defult `desiredState` behavior is to manage (remove) these assignments. Setting `keepDfcSecurityAssignments` to `true` disables the management (removal) by EPAC. + +Carefully review [Managing Defender for Cloud Assignments](dfc-assignments.md) . ## Terminology diff --git a/Docs/module-release-process.md b/Docs/module-release-process.md deleted file mode 100644 index 9cd0b5c5..00000000 --- a/Docs/module-release-process.md +++ /dev/null @@ -1,30 +0,0 @@ -# Module Release Process - -This is a guide on how to release a new version of the project - including automated PowerShell module publish. It is used by the EPAC maintainers only. - -## GitHub Release Process - -1. Navigate to https://github.com/Azure/enterprise-azure-policy-as-code/releases -2. Click on **Draft a new release** -3. Click on **Choose a tag** and enter in the new release version - it should be in the format "v(major).(minor).(build)" i.e. v7.3.4 **Don't forget the v** -4. When prompted click on **Create new tag: vX.X.X on publish** -5. Add a release title - you can just use the new version number. -6. Click on **Generate release notes** to pull all the notes in from related PRs. Update if necessary. -7. Click **Publish Release** - -Now just verify the module publish action has run - -## Verify Action - -1. Click on **Actions** -2. Verify that a workflow run has started with the same name as the release. - -It should finish successfully - if there is a failure review the build logs. - -# Documentation Release Process - -A guide for maintainers on how to update documentation.. - -1. Modify files in the Docs folder following the format of other files. For a list of acceptable admonitions please see [here](https://squidfunk.github.io/mkdocs-material/reference/admonitions/#supported-types) -2. If you are adding a new file ensure it is added to the ```mkdocs.yml``` file in the appropriate section. Use the built site to determine where a new document should be placed. -3. Create a PR and merge - the actions will commence automatically. There are two actions which run in the background to update the GitHub Pages site. diff --git a/Schemas/global-settings-schema.json b/Schemas/global-settings-schema.json index c8b20de6..9d58ea60 100644 --- a/Schemas/global-settings-schema.json +++ b/Schemas/global-settings-schema.json @@ -47,6 +47,9 @@ "includeResourceGroups": { "type": "boolean" }, + "keepDfcSecurityAssignments": { + "type": "boolean" + }, "excludedScopes": { "type": "array", "items": [ diff --git a/Scripts/Helpers/Build-AssignmentPlan.ps1 b/Scripts/Helpers/Build-AssignmentPlan.ps1 index 4acc964c..aaa66e4c 100644 --- a/Scripts/Helpers/Build-AssignmentPlan.ps1 +++ b/Scripts/Helpers/Build-AssignmentPlan.ps1 @@ -161,17 +161,17 @@ function Build-AssignmentPlan { $resourceSelectors ` -HandleRandomOrderArray - $IdentityStatus = Build-AssignmentIdentityChanges ` + $identityStatus = Build-AssignmentIdentityChanges ` -Existing $deployedPolicyAssignment ` -Assignment $assignment ` -ReplacedAssignment ($replacedDefinition -or $changedPolicyDefinitionId) ` -DeployedRoleAssignmentsByPrincipalId $deployedRoleAssignmentsByPrincipalId - if ($IdentityStatus.requiresRoleChanges) { - $RoleAssignments.added += ($IdentityStatus.added) - $RoleAssignments.removed += ($IdentityStatus.removed) - $RoleAssignments.numberOfChanges += ($IdentityStatus.numberOfChanges) + if ($identityStatus.requiresRoleChanges) { + $RoleAssignments.added += ($identityStatus.added) + $RoleAssignments.removed += ($identityStatus.removed) + $RoleAssignments.numberOfChanges += ($identityStatus.numberOfChanges) } - if ($IdentityStatus.isUserAssigned) { + if ($identityStatus.isUserAssigned) { $isUserAssignedAny = $true } @@ -179,21 +179,21 @@ function Build-AssignmentPlan { $changesStrings = @() $match = $displayNameMatches -and $descriptionMatches -and $parametersMatch -and $metadataMatches -and !$changePacOwnerId ` - -and $enforcementModeMatches -and $notScopesMatch -and $nonComplianceMessagesMatches -and $overridesMatch -and $resourceSelectorsMatch -and !$IdentityStatus.replaced + -and $enforcementModeMatches -and $notScopesMatch -and $nonComplianceMessagesMatches -and $overridesMatch -and $resourceSelectorsMatch -and !$identityStatus.replaced if ($match) { # no Assignment properties changed $Assignments.numberUnchanged++ - if ($IdentityStatus.requiresRoleChanges) { + if ($identityStatus.requiresRoleChanges) { # role assignments for Managed Identity changed - caused by a mangedIdentityLocation changed or a previously failed role assignment failure - Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "Update($($IdentityStatus.changedIdentityStrings -join ','))" -IdentityStatus $IdentityStatus + Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "Update($($identityStatus.changedIdentityStrings -join ','))" -IdentityStatus $identityStatus } else { - # Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "Unchanged" -IdentityStatus $IdentityStatus + # Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "Unchanged" -IdentityStatus $identityStatus } } else { # One or more properties have changed - if ($IdentityStatus.replaced) { + if ($identityStatus.replaced) { # Assignment must be deleted and recreated (new) if ($changedPolicyDefinitionId) { $changesStrings += "definitionId" @@ -201,7 +201,7 @@ function Build-AssignmentPlan { if ($replacedDefinition) { $changesStrings += "replacedDefinition" } - $changesStrings += ($IdentityStatus.changedIdentityStrings) + $changesStrings += ($identityStatus.changedIdentityStrings) } if (!$displayNameMatches) { @@ -236,14 +236,14 @@ function Build-AssignmentPlan { } $changesString = $changesStrings -join "," - if ($IdentityStatus.replaced) { + if ($identityStatus.replaced) { # Assignment must be deleted and recreated (new) $null = $Assignments.replace.Add($id, $assignment) - Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "Replace($changesString)" -IdentityStatus $IdentityStatus + Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "Replace($changesString)" -IdentityStatus $identityStatus } else { $null = $Assignments.update.Add($id, $assignment) - Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "Update($changesString)" -IdentityStatus $IdentityStatus + Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "Update($changesString)" -IdentityStatus $identityStatus } $Assignments.numberOfChanges++ } @@ -252,24 +252,25 @@ function Build-AssignmentPlan { # New Assignment $null = $Assignments.new.Add($id, $assignment) $Assignments.numberOfChanges++ - $IdentityStatus = Build-AssignmentIdentityChanges ` + $identityStatus = Build-AssignmentIdentityChanges ` -Existing $null ` -Assignment $assignment ` -ReplacedAssignment $false ` -DeployedRoleAssignmentsByPrincipalId $deployedRoleAssignmentsByPrincipalId - if ($IdentityStatus.requiresRoleChanges) { - $RoleAssignments.added += ($IdentityStatus.added) - $RoleAssignments.numberOfChanges += ($IdentityStatus.numberOfChanges) + if ($identityStatus.requiresRoleChanges) { + $RoleAssignments.added += ($identityStatus.added) + $RoleAssignments.numberOfChanges += ($identityStatus.numberOfChanges) } - if ($IdentityStatus.isUserAssigned) { + if ($identityStatus.isUserAssigned) { $isUserAssignedAny = $true } - Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "New" -IdentityStatus $IdentityStatus + Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "New" -IdentityStatus $identityStatus } } } $strategy = $PacEnvironment.desiredState.strategy + $keepDfcSecurityAssignments = $PacEnvironment.desiredState.keepDfcSecurityAssignments if ($deleteCandidates.psbase.Count -gt 0) { foreach ($id in $deleteCandidates.Keys) { $deleteCandidate = $deleteCandidates.$id @@ -278,24 +279,24 @@ function Build-AssignmentPlan { $DisplayName = $deleteCandidateProperties.displayName $Scope = $deleteCandidateProperties.scope $pacOwner = $deleteCandidate.pacOwner - $shallDelete = Confirm-DeleteForStrategy -PacOwner $pacOwner -Strategy $strategy + $shallDelete = Confirm-DeleteForStrategy -PacOwner $pacOwner -Strategy $strategy -KeepDfcSecurityAssignments $keepDfcSecurityAssignments if ($shallDelete) { # always delete if owned by this Policy as Code solution # never delete if owned by another Policy as Code solution # if strategy is "full", delete with unknown owner (missing pacOwnerId) - $IdentityStatus = Build-AssignmentIdentityChanges ` + $identityStatus = Build-AssignmentIdentityChanges ` -Existing $deleteCandidate ` -Assignment $null ` -ReplacedAssignment $false ` -DeployedRoleAssignmentsByPrincipalId $deployedRoleAssignmentsByPrincipalId - if ($IdentityStatus.requiresRoleChanges) { - $RoleAssignments.removed += ($IdentityStatus.removed) - $RoleAssignments.numberOfChanges += ($IdentityStatus.numberOfChanges) + if ($identityStatus.requiresRoleChanges) { + $RoleAssignments.removed += ($identityStatus.removed) + $RoleAssignments.numberOfChanges += ($identityStatus.numberOfChanges) } - if ($IdentityStatus.isUserAssigned) { + if ($identityStatus.isUserAssigned) { $isUserAssignedAny = $true } - Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "Delete" -IdentityStatus $IdentityStatus + Write-AssignmentDetails -DisplayName $DisplayName -Scope $Scope -Prefix "Delete" -IdentityStatus $identityStatus $splat = @{ id = $id name = $name @@ -308,9 +309,38 @@ function Build-AssignmentPlan { $Assignments.numberOfChanges++ } - # else { - # Write-AssignmentDetails -DisplayName $name -Scope $Scope -Prefix "Desired State($pacOwner,$strategy) - no delete" -IdentityStatus $IdentityStatus - # } + else { + $identityStatus = @{ + requiresRoleChanges = $false + numberOfChanges = 0 + added = @() + removed = @() + changedIdentityStrings = @() + replaced = $false + isUserAssigned = $false + } + $shortScope = $Scope -replace "/providers/Microsoft.Management", "" + switch ($pacOwner) { + thisPaC { + Write-Error "Policy Assignment '$DisplayName' at $shortScope owned by this Policy as Code solution should have been deleted." -ErrorAction Stop + } + otherPaC { + if ($VerbosePreference -eq "Continue") { + Write-AssignmentDetails -DisplayName $name -Scope $Scope -Prefix "Skipping delete (owened by other PaC):" -IdentityStatus $identityStatus + } + } + unknownOwner { + Write-AssignmentDetails -DisplayName $name -Scope $Scope -Prefix "Skipping delete (strategy $strategy):" -IdentityStatus $identityStatus + } + managedByDfcSecurityPolicies { + Write-AssignmentDetails -DisplayName $name -Scope $Scope -Prefix "Skipping delete (DfC Security Policies):" -IdentityStatus $identityStatus + } + managedByDfcDefenderPlans { + Write-AssignmentDetails -DisplayName $name -Scope $Scope -Prefix "Skipping delete (DfC Defender Plans):" -IdentityStatus $identityStatus + } + } + + } } } diff --git a/Scripts/Helpers/Confirm-DeleteForStrategy.ps1 b/Scripts/Helpers/Confirm-DeleteForStrategy.ps1 index 9b0c53c3..da140599 100644 --- a/Scripts/Helpers/Confirm-DeleteForStrategy.ps1 +++ b/Scripts/Helpers/Confirm-DeleteForStrategy.ps1 @@ -6,7 +6,10 @@ function Confirm-DeleteForStrategy { [string] $Status, [string] $DeleteExpired, [string] $DeleteOrphaned, - [string] $Removed + [string] $Removed, + + [Parameter(Mandatory = $false)] + $KeepDfcSecurityAssignments = $false ) $shallDelete = switch ($PacOwner) { @@ -28,6 +31,14 @@ function Confirm-DeleteForStrategy { $Strategy -eq "full" break } + "managedByDfcSecurityPolicies" { + !$KeepDfcSecurityAssignments -and $Strategy -eq "full" + break + } + "managedByDfcDefenderPlans" { + $false + break + } } return $shallDelete } diff --git a/Scripts/Helpers/Confirm-PacOwner.ps1 b/Scripts/Helpers/Confirm-PacOwner.ps1 index 0e4825d9..6b87e214 100644 --- a/Scripts/Helpers/Confirm-PacOwner.ps1 +++ b/Scripts/Helpers/Confirm-PacOwner.ps1 @@ -4,29 +4,48 @@ function Confirm-PacOwner { [Parameter(Mandatory = $true)] $ThisPacOwnerId, - [Parameter(Mandatory = $false)] - $Metadata = $null, + [Parameter(Mandatory = $true)] + $PolicyResource, [Parameter(Mandatory = $false)] - $ManagedByCounters = $null + $Scope = $null, + + [Parameter(Mandatory = $true)] + $ManagedByCounters ) - if ($null -eq $Metadata -or $null -eq $Metadata.pacOwnerId) { - if ($null -ne $ManagedByCounters) { - $ManagedByCounters.unknown += 1 + $properties = $PolicyResource.properties + $metadata = $properties.metadata + if ($null -eq $metadata -or $null -eq $metadata.pacOwnerId) { + $kind = $PolicyResource.kind + if ($kind -eq "policyassignments" -and $Scope.StartsWith("/subscriptions/")) { + $definitionIdParts = Split-AzPolicyResourceId -Id $properties.policyDefinitionId + if ($definitionIdParts.scopeType -eq "builtin") { + # Check if the owner is a special case, either managed by DfC's "Security Policies" or one of the "Defender Plans" + # This didcult due to inconsistent naming and createdBy users. + # At present, the only way to identify these is by string comparing the description field. This is not ideal. + # "Security Policies" (e.g., MCSB, NIST, ...) use a description "This object has been generated by Microsoft Defender for Cloud. To make changes, navigate to the security policies management page.", + # "Defender Plans" (e.g., Servers, App Service, Databases, ...) use a description srtaing with "This policy assignment was automatically created by " + $description = $properties.description + if ($description.StartsWith("This object has been generated by ")) { + $ManagedByCounters.dfcSecurityPolicies += 1 + return "managedByDfcSecurityPolicies" + } + elseif ($description.StartsWith("This policy assignment was automatically created by ")) { + $ManagedByCounters.dfcDefenderPlans += 1 + return "managedByDfcDefenderPlans" + } + } } + $ManagedByCounters.unknown += 1 return "unknownOwner" } elseif ($ThisPacOwnerId -eq $Metadata.pacOwnerId) { - if ($null -ne $ManagedByCounters) { - $ManagedByCounters.thisPaC += 1 - } + $ManagedByCounters.thisPaC += 1 return "thisPaC" } else { - if ($null -ne $ManagedByCounters) { - $ManagedByCounters.otherPaC += 1 - } + $ManagedByCounters.otherPaC += 1 return "otherPaC" } } diff --git a/Scripts/Helpers/Confirm-ValidPolicyResourceName.ps1 b/Scripts/Helpers/Confirm-ValidPolicyResourceName.ps1 index 6655f79c..59d991b8 100644 --- a/Scripts/Helpers/Confirm-ValidPolicyResourceName.ps1 +++ b/Scripts/Helpers/Confirm-ValidPolicyResourceName.ps1 @@ -6,7 +6,7 @@ function Confirm-ValidPolicyResourceName { ) # Test is the Name has any charcters from thisn string of characters "<>*%&:?.+/" in it or ends with a space - if ($Name -match "[\<\>\*\%\&\:\?\.\+\/\\]" -or $Name.EndsWith(" ")) { + if ($Name -match "[\<\>\*\%\&\:\?\+\/\\]" -or $Name.EndsWith(" ")) { return $false } else { diff --git a/Scripts/Helpers/Get-AzPolicyResources.ps1 b/Scripts/Helpers/Get-AzPolicyResources.ps1 index a03576ad..8c8c86f1 100644 --- a/Scripts/Helpers/Get-AzPolicyResources.ps1 +++ b/Scripts/Helpers/Get-AzPolicyResources.ps1 @@ -63,6 +63,8 @@ function Get-AzPolicyResources { managedBy = @{ thisPaC = 0 otherPaC = 0 + dfcSecurityPolicies = 0 + dfcDefenderPlans = 0 unknown = 0 } excluded = 0 @@ -118,12 +120,6 @@ function Get-AzPolicyResources { $scopesLast = $scopesLength - 1 $policyAssignmentsTable = $deployed.policyassignments $thisPacOwnerId = $PacEnvironment.pacOwnerId - $uniqueRoleAssignmentScopes = @{ - resources = @{} - resourceGroups = @{} - subscriptions = @{} - managementGroups = @{} - } $uniquePrincipalIds = @{} $assignmentsWithIdentity = @{} $numberPolicyResourcesProcessed = 0 @@ -147,7 +143,7 @@ function Get-AzPolicyResources { if ($included) { $scope = $resourceIdParts.scope $policyResource.resourceIdParts = $resourceIdParts - $policyResource.pacOwner = Confirm-PacOwner -ThisPacOwnerId $thisPacOwnerId -Metadata $policyResource.properties.metadata -ManagedByCounters $policyAssignmentsTable.counters.managedBy + $policyResource.pacOwner = Confirm-PacOwner -ThisPacOwnerId $thisPacOwnerId -PolicyResource $policyResource -Scope $scope -ManagedByCounters $policyAssignmentsTable.counters.managedBy $null = $policyAssignmentsTable.managed.Add($id, $policyResource) if ($policyResource.identity -and $policyResource.identity.type -ne "None") { $principalId = "" @@ -158,18 +154,7 @@ function Get-AzPolicyResources { $userAssignedIdentityId = $policyResource.identity.userAssignedIdentities.PSObject.Properties.Name $principalId = $policyResource.identity.userAssignedIdentities.$userAssignedIdentityId.principalId } - Set-UniqueRoleAssignmentScopes ` - -ScopeId $scope ` - -UniqueRoleAssignmentScopes $uniqueRoleAssignmentScopes $uniquePrincipalIds[$principalId] = $true - if ($policyResource.properties.metadata.roles) { - $roles = $policyResource.properties.metadata.roles - foreach ($role in $roles) { - Set-UniqueRoleAssignmentScopes ` - -ScopeId $role.scope ` - -UniqueRoleAssignmentScopes $uniqueRoleAssignmentScopes - } - } $null = $assignmentsWithIdentity.Add($id, $policyResource) } } @@ -201,7 +186,7 @@ function Get-AzPolicyResources { switch ($i) { 0 { # deploymentRootScope - $policyResource.pacOwner = Confirm-PacOwner -ThisPacOwnerId $thisPacOwnerId -Metadata $policyResource.properties.metadata -ManagedByCounters $deployedPolicyTable.counters.managedBy + $policyResource.pacOwner = Confirm-PacOwner -ThisPacOwnerId $thisPacOwnerId -PolicyResource $policyResource -ManagedByCounters $deployedPolicyTable.counters.managedBy $null = $deployedPolicyTable.all.Add($id, $policyResource) $null = $deployedPolicyTable.managed.Add($id, $policyResource) $found = $true @@ -225,7 +210,7 @@ function Get-AzPolicyResources { } if (!$found) { if ($CollectAllPolicies) { - $policyResource.pacOwner = Confirm-PacOwner -ThisPacOwnerId $thisPacOwnerId -Metadata $policyResource.properties.metadata -ManagedByCounters $deployedPolicyTable.counters.managedBy + $policyResource.pacOwner = Confirm-PacOwner -ThisPacOwnerId $thisPacOwnerId -PolicyResource $policyResource -ManagedByCounters $deployedPolicyTable.counters.managedBy $null = $deployedPolicyTable.all.Add($id, $policyResource) $null = $deployedPolicyTable.managed.Add($id, $policyResource) } @@ -311,23 +296,12 @@ function Get-AzPolicyResources { -PolicyResourceTable $exemptionsTable if ($included) { $status = "unknown" - $pacOwner = "unknownOwner" - $assignmentPacOwner = "unknownOwner" - $exemptionPacOwner = Confirm-PacOwner -ThisPacOwnerId $thisPacOwnerId -Metadata $metadata -ManagedByCounters $managedByCounters + $pacOwner = Confirm-PacOwner -ThisPacOwnerId $thisPacOwnerId -PolicyResource $policyResourceRaw -ManagedByCounters $managedByCounters if ($managedPolicyAssignmentsTable.ContainsKey($policyAssignmentId)) { $status = "active" - $policyAssignment = $managedPolicyAssignmentsTable.$policyAssignmentId - $assignmentPacOwner = $policyAssignment.pacOwner - if ($exemptionPacOwner -eq "unknownOwner") { - $pacOwner = $assignmentPacOwner - } - else { - $pacOwner = $exemptionPacOwner - } } else { $status = "orphaned" - $pacOwner = $exemptionPacOwner } $expiresInDays = [Int32]::MaxValue if ($expiresOn) { @@ -475,16 +449,18 @@ function Get-AzPolicyResources { $counters = $deployed.policyassignments.counters $managedBy = $counters.managedBy - $managedByAny = $managedBy.thisPaC + $managedBy.otherPaC + $managedBy.unknown + $managedByAny = $managedBy.thisPaC + $managedBy.otherPaC + $managedBy.unknown + $managedBy.dfcSecurityPolicies + $managedBy.dfcDefenderPlans Write-Information "" Write-Information "Policy Assignment counts:" Write-Information " Managed ($($managedByAny)) by:" - Write-Information " This PaC = $($managedBy.thisPaC)" - Write-Information " Other PaC = $($managedBy.otherPaC)" - Write-Information " Unknown = $($managedBy.unknown)" - Write-Information " With identity = $($assignmentsWithIdentity.psbase.Count)" - Write-Information " Excluded = $($counters.excluded)" - Write-Verbose " Not our scopes = $($counters.unmanagedScopes)" + Write-Information " This PaC = $($managedBy.thisPaC)" + Write-Information " Other PaC = $($managedBy.otherPaC)" + Write-Information " Unknown = $($managedBy.unknown)" + Write-Information " DfC Security Policies = $($managedBy.dfcSecurityPolicies)" + Write-Information " DfC Defender Plans = $($managedBy.dfcDefenderPlans)" + Write-Information " With identity = $($assignmentsWithIdentity.psbase.Count)" + Write-Information " Excluded = $($counters.excluded)" + Write-Verbose " Not our scopes = $($counters.unmanagedScopes)" if (!$SkipExemptions) { $counters = $exemptionsTable.counters diff --git a/Scripts/Helpers/Get-GlobalSettings.ps1 b/Scripts/Helpers/Get-GlobalSettings.ps1 index d30ef3de..d89a44e6 100644 --- a/Scripts/Helpers/Get-GlobalSettings.ps1 +++ b/Scripts/Helpers/Get-GlobalSettings.ps1 @@ -113,6 +113,7 @@ function Get-GlobalSettings { excludedPolicyAssignments = @() deleteExpiredExemptions = $true deleteOrphanedExemptions = $true + keepDfcSecurityAssignments = $false } if ($null -ne $pacEnvironment.desiredState) { $desired = $pacEnvironment.desiredState @@ -133,6 +134,15 @@ function Get-GlobalSettings { Write-Error "Policy as Code environment $pacSelector field desiredState.includeResourceGroups ($includeResourceGroups) must be a boolean value." -ErrorAction Stop } } + $keepDfcSecurityAssignments = $desired.keepDfcSecurityAssignments + if ($null -ne $keepDfcSecurityAssignments) { + if ($keepDfcSecurityAssignments -is [bool]) { + $desiredState.keepDfcSecurityAssignments = $keepDfcSecurityAssignments + } + else { + Write-Error "Policy as Code environment $pacSelector field desiredState.keepDfcSecurityAssignments ($keepDfcSecurityAssignments) must be a boolean value." -ErrorAction Stop + } + } $excluded = $desired.excludedScopes if ($null -ne $excluded) { if ($excluded -isnot [array]) { diff --git a/mkdocs.yml b/mkdocs.yml index d6c974b6..09336d94 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,6 +29,7 @@ nav: - Usage Tracking: usage-tracking.md - Operating Environment: operating-environment.md - Global Settings and Definitions: definitions-and-global-settings.md + - Managing Defender for Cloud Assignments: dfc-assignments.md - Desired State Strategy: desired-state-strategy.md - Tips: tips.md - Alternate Script Installation: clone-github.md