diff --git a/Docs/settings-desired-state.md b/Docs/settings-desired-state.md index a6cf8464..b46e1763 100644 --- a/Docs/settings-desired-state.md +++ b/Docs/settings-desired-state.md @@ -14,11 +14,13 @@ Desired State strategy enables you to adjust the default behavior to fit more co - `full`: EPAC manages all Policy resources in the `deploymentRootScope` and its children. EPAC deletes any Policy resources not defined in the EPAC repo. - `ownedOnly`: EPAC manages only Policy resources defined in the EPAC repo. EPAC does not delete any Policy resources not defined in the EPAC repo. - `keepDfcSecurityAssignments`: It is recommended that Security and Compliance Initiatives are managed at management group levels with EPAC. Please read [Managing Defender for Cloud Assignments](settings-dfc-assignments.md). + - Optional: - `excludedScopes`: An array of scopes to exclude from management by EPAC. The default is an empty array. Wild cards are supported. - `excludedPolicyDefinitions`: An array of Policy Definitions to exclude from management by EPAC. The default is an empty array. Wild cards are supported. - `excludedPolicySetDefinitions`: An array of Policy Set Definitions to exclude from management by EPAC. The default is an empty array. Wild cards are supported. - `excludedPolicyAssignments`: An array of Policy Assignments to exclude from management by EPAC. The default is an empty array. Wild cards are supported. + - `doNotDisableDeprecatedPolicies`: Automatically set deprecated policies' policy effect to "Disabled" The following example shows the `desiredState` element with all properties set: @@ -26,6 +28,7 @@ The following example shows the `desiredState` element with all properties set: "desiredState": { "strategy": "full", "keepDfcSecurityAssignments": false, + "doNotDisableDeprecatedPolicies": false, "excludedScopes": [], "excludedPolicyDefinitions": [], "excludedPolicySetDefinitions": [], @@ -148,6 +151,8 @@ You can exclude any combination of `excludedScopes`, `excludedPolicyDefinitions` ```json "desiredState": { "strategy": "full", + "keepDfcSecurityAssignments": false, + "doNotDisableDeprecatedPolicies": false, "excludedScopes": [ // Management Groups, Subscriptions, Resource Groups "/providers/Microsoft.Management/managementGroups/mg-policy-as-code/childScope" ], diff --git a/Docs/settings-global-setting-file.md b/Docs/settings-global-setting-file.md index ee7e6520..b83fb730 100644 --- a/Docs/settings-global-setting-file.md +++ b/Docs/settings-global-setting-file.md @@ -22,7 +22,8 @@ "deploymentRootScope": "/providers/Microsoft.Management/managementGroups/mg-Epac-Dev", "desiredState": { "strategy": "full", - "keepDfcSecurityAssignments": false + "keepDfcSecurityAssignments": false, + "doNotDisableDeprecatedPolicies": false }, "managedIdentityLocation": "eastus2" }, @@ -33,7 +34,8 @@ "deploymentRootScope": "/providers/Microsoft.Management/managementGroups/mg-Enterprise", "desiredState": { "strategy": "full", - "keepDfcSecurityAssignments": false + "keepDfcSecurityAssignments": false, + "doNotDisableDeprecatedPolicies": false }, "managedIdentityLocation": "eastus2", "globalNotScopes": [ @@ -146,7 +148,8 @@ Resource Group patterns allow us to exclude "special" managed Resource Groups. T "deploymentRootScope": "/providers/Microsoft.Management/managementGroups/PAC-Heinrich-Dev", "desiredState": { "strategy": "full", - "keepDfcSecurityAssignments": false + "keepDfcSecurityAssignments": false, + "doNotDisableDeprecatedPolicies": false }, "mangedIdentityLocation": "eastus2" }, @@ -157,7 +160,8 @@ Resource Group patterns allow us to exclude "special" managed Resource Groups. T "deploymentRootScope": "/providers/Microsoft.Management/managementGroups/Contoso-Root", "desiredState": { "strategy": "full", - "keepDfcSecurityAssignments": false + "keepDfcSecurityAssignments": false, + "doNotDisableDeprecatedPolicies": false }, "globalNotScopes": [ "/providers/Microsoft.Management/managementGroups/PAC-Heinrich-Dev" @@ -178,7 +182,8 @@ Resource Group patterns allow us to exclude "special" managed Resource Groups. T "deploymentRootScope": "/providers/Microsoft.Management/managementGroups/Contoso-Root", "desiredState": { "strategy": "full", - "keepDfcSecurityAssignments": false + "keepDfcSecurityAssignments": false, + "doNotDisableDeprecatedPolicies": false }, "managedIdentityLocation": "eastus2" } diff --git a/Schemas/global-settings-schema.json b/Schemas/global-settings-schema.json index 7a774660..43bf81bf 100644 --- a/Schemas/global-settings-schema.json +++ b/Schemas/global-settings-schema.json @@ -72,6 +72,9 @@ "keepDfcSecurityAssignments": { "type": "boolean" }, + "doNotDisableDeprecatedPolicies": { + "type": "boolean" + }, "excludedScopes": { "type": "array", "items": [ diff --git a/Scripts/Helpers/Build-AssignmentDefinitionNode.ps1 b/Scripts/Helpers/Build-AssignmentDefinitionNode.ps1 index 8d5c2b45..96cd5667 100644 --- a/Scripts/Helpers/Build-AssignmentDefinitionNode.ps1 +++ b/Scripts/Helpers/Build-AssignmentDefinitionNode.ps1 @@ -153,6 +153,14 @@ function Build-AssignmentDefinitionNode { } #endregion definitionEntry or definitionEntryList (required exactly once per branch) + #region Process Deprecated + $deprecatedHash = @{} + foreach ($key in $CombinedPolicyDetails.policies.keys) { + if ($true -eq $CombinedPolicyDetails.policies.$key.isDeprecated) { + $deprecatedHash[$CombinedPolicyDetails.policies.$key.name] = $CombinedPolicyDetails.policies.$key + } + } + #region metadata if ($DefinitionNode.metadata) { # merge metadata @@ -166,13 +174,36 @@ function Build-AssignmentDefinitionNode { #endregion metadata #region parameters in JSON; parameters defined at a deeper level override previous parameters (union operator) + + # create parameter Hash to Policy Def + $parameterHash = @{} + foreach ($key in $flatPolicyList.keys) { + foreach ($paramKey in $flatPolicyList.$key.parameters.keys) { + $parameterHash.$paramKey = $flatPolicyList.$key + } + } + + $deprecatedInJSON = [System.Collections.ArrayList]::new() if ($DefinitionNode.parameters) { $allParameters = $definition.parameters $addedParameters = $DefinitionNode.parameters foreach ($parameterName in $addedParameters.Keys) { $rawParameterValue = $addedParameters.$parameterName + $currentParameterHash = $parameterHash.$parameterName + if ($deprecatedHash.ContainsKey($($currentParameterHash.name)) -and $currentParameterHash.parameters.$parameterName.isEffect) { + $null = $deprecatedInJSON.Add("Assignment: '$($assignment.name)' with Parameter: '$parameterName' ($($currentParameterHash))") + if (!$PacEnvironment.desiredState.doNotDisableDeprecatedPolicies) { + $rawParameterValue = "Disabled" + } + } $parameterValue = Get-DeepCloneAsOrderedHashtable $rawParameterValue - $allParameters[$parameterName] = $parameterValue + $allParameters.$parameterName = $parameterValue + } + } + if ($deprecatedInJSON.Count -gt 0) { + Write-Warning "Node $($nodeName): Assignment contains JSON effect parameter for Policies that has been deprecated in the Policy Sets. Update Policy Sets." + foreach ($deprecated in $deprecatedInJSON) { + Write-Information " $($deprecated)" } } #endregion parameters in JSON; parameters defined at a deeper level override previous parameters (union operator) @@ -184,6 +215,7 @@ function Build-AssignmentDefinitionNode { $definition.effectColumn = "$($parameterSelector)Effect" $definition.parametersColumn = "$($parameterSelector)Parameters" } + $deprecatedInCSV = [System.Collections.ArrayList]::new() if ($DefinitionNode.parameterFile) { $parameterFileName = $DefinitionNode.parameterFile if ($ParameterFilesCsv.ContainsKey($parameterFileName)) { @@ -191,6 +223,28 @@ function Build-AssignmentDefinitionNode { $content = Get-Content -Path $fullName -Raw -ErrorAction Stop $xlsArray = @() + ($content | ConvertFrom-Csv -ErrorAction Stop) $csvParameterArray = Get-DeepCloneAsOrderedHashtable $xlsArray + # Replace CSV effect with Disabled if Deprecated + foreach ($entry in $csvParameterArray) { + # If policy in csv is found to be deprecated + if ($deprecatedHash.ContainsKey($entry.name)) { + # For each child in the assignment + foreach ($child in $DefinitionNode.children) { + # If that child is using a parameterSelector with the CSV + if ($child.ContainsKey('parameterSelector')) { + $key = "$($child.parameterSelector)" + "Effect" + # If the parameter is not set to Disabled already + if ($entry.$key -ne "Disabled") { + if (!$PacEnvironment.desiredState.doNotDisableDeprecatedPolicies) { + $entry.$key = 'Disabled' + } + $null = $deprecatedInCSV.Add("$($entry.displayName) ($($entry.name))") + } + } + } + break + } + } + $definition.parameterFileName = $parameterFileName $definition.csvParameterArray = $csvParameterArray $definition.csvRowsValidated = $false @@ -272,6 +326,12 @@ function Build-AssignmentDefinitionNode { Write-Information " $($missing)" } } + if ($deprecatedInCSV.Count -gt 0) { + Write-Warning "Node $($nodeName): CSV parameterFile '$parameterFileName' contains rows for Policies that have been deprecated in the Policy Sets. Update Policy Sets." + foreach ($deprecated in $deprecatedInCSV) { + Write-Information " $($deprecated)" + } + } } #endregion validate CSV rows diff --git a/Scripts/Helpers/Get-GlobalSettings.ps1 b/Scripts/Helpers/Get-GlobalSettings.ps1 index 8700c186..ca2e6d10 100644 --- a/Scripts/Helpers/Get-GlobalSettings.ps1 +++ b/Scripts/Helpers/Get-GlobalSettings.ps1 @@ -209,6 +209,7 @@ function Get-GlobalSettings { excludedPolicyDefinitions = @() excludedPolicySetDefinitions = @() excludedPolicyAssignments = @() + doNotDisableDeprecatedPolicies = $false } $desired = $pacEnvironment.desiredState @@ -311,6 +312,15 @@ function Get-GlobalSettings { if ($null -ne $deleteOrphaned) { Add-ErrorMessage -ErrorInfo $errorInfo -ErrorString "Global settings error: pacEnvironment $pacSelector field desiredState.deleteOrphanedExemptions is deprecated. Remove it!" } + $doNotDisableDeprecatedPolicies = $desired.doNotDisableDeprecatedPolicies + if ($null -ne $doNotDisableDeprecatedPolicies) { + if ($doNotDisableDeprecatedPolicies -is [bool]) { + $desiredState.doNotDisableDeprecatedPolicies = $doNotDisableDeprecatedPolicies + } + else { + Add-ErrorMessage -ErrorInfo $errorInfo -ErrorString "Global settings error: pacEnvironment $pacSelector field desiredState.doNotDisableDeprecatedPolicies ($doNotDisableDeprecatedPolicies) must be a boolean value." + } + } } $pacEnvironmentDefinition = @{ diff --git a/Scripts/Helpers/Out-DocumentationForPolicyAssignments.ps1 b/Scripts/Helpers/Out-DocumentationForPolicyAssignments.ps1 index da2882be..63ec30f2 100644 --- a/Scripts/Helpers/Out-DocumentationForPolicyAssignments.ps1 +++ b/Scripts/Helpers/Out-DocumentationForPolicyAssignments.ps1 @@ -5,7 +5,8 @@ function Out-DocumentationForPolicyAssignments { [switch] $WindowsNewLineCells, $DocumentationSpecification, [hashtable] $AssignmentsByEnvironment, - [switch] $IncludeManualPolicies + [switch] $IncludeManualPolicies, + [bool] $doNotDisableDeprecatedPolicies ) [string] $fileNameStem = $DocumentationSpecification.fileNameStem @@ -150,6 +151,13 @@ function Out-DocumentationForPolicyAssignments { } } + #region Process Deprecated + $deprecatedHash = @{} + foreach ($key in $CombinedPolicyDetails.policies.keys) { + if ($true -eq $CombinedPolicyDetails.policies.$key.isDeprecated) { + $deprecatedHash[$CombinedPolicyDetails.policies.$key.name] = $CombinedPolicyDetails.policies.$key + } + } #region Review Duplicates # Iterate over each key-value pair in the hashtable @@ -441,67 +449,69 @@ function Out-DocumentationForPolicyAssignments { # Process the table $flatPolicyListAcrossEnvironments.Values | Sort-Object -Property { $_.category }, { $_.displayName } | ForEach-Object -Process { - # If statement to skip over duplicates + # If statement to skip over duplicates and ensure not to include Deprecated Policies if ( $true -ne $_.isReferencePathMatch) { - # Initialize row - with empty strings - $rowObj = [ordered]@{} - foreach ($key in $columnHeaders) { - $null = $rowObj.Add($key, "") - } + if (!$deprecatedHash.ContainsKey($_.name) -or $doNotDisableDeprecatedPolicies) { + # Initialize row - with empty strings + $rowObj = [ordered]@{} + foreach ($key in $columnHeaders) { + $null = $rowObj.Add($key, "") + } - # Cache loop values - # $effectAllowedValues = $_.effectAllowedValues - # $groupNames = $_.groupNames - # $policySetEffectStrings = $_.policySetEffectStrings - $effectAllowedValues = $_.effectAllowedValues - $isEffectParameterized = $_.isEffectParameterized - $effectAllowedOverrides = $_.effectAllowedOverrides - $groupNames = $_.groupNames - $effectDefault = $_.effectDefault - $policySetEffectStrings = $_.policySetEffectStrings - - # Build common columns - $rowObj.name = $_.name - $rowObj.referencePath = $_.referencePath - $rowObj.policyType = $_.policyType - $rowObj.category = $_.category - $rowObj.displayName = $_.displayName - $rowObj.description = $_.description - $groupNames = $_.groupNames - if ($groupNames.Count -gt 0) { - $sortedGroupNameList = $groupNames | Sort-Object -Unique - $rowObj.groupNames = $sortedGroupNameList -join $inCellSeparator3 - } - if ($policySetEffectStrings.Count -gt 0) { - $rowObj.policySets = $policySetEffectStrings -join $inCellSeparator3 - } - $rowObj.allowedEffects = Convert-AllowedEffectsToCsvString ` - -DefaultEffect $effectDefault ` - -IsEffectParameterized $isEffectParameterized ` - -EffectAllowedValues $effectAllowedValues.Keys ` - -EffectAllowedOverrides $effectAllowedOverrides ` - -InCellSeparator1 $inCellSeparator1 ` - -InCellSeparator2 $inCellSeparator2 + # Cache loop values + # $effectAllowedValues = $_.effectAllowedValues + # $groupNames = $_.groupNames + # $policySetEffectStrings = $_.policySetEffectStrings + $effectAllowedValues = $_.effectAllowedValues + $isEffectParameterized = $_.isEffectParameterized + $effectAllowedOverrides = $_.effectAllowedOverrides + $groupNames = $_.groupNames + $effectDefault = $_.effectDefault + $policySetEffectStrings = $_.policySetEffectStrings + + # Build common columns + $rowObj.name = $_.name + $rowObj.referencePath = $_.referencePath + $rowObj.policyType = $_.policyType + $rowObj.category = $_.category + $rowObj.displayName = $_.displayName + $rowObj.description = $_.description + $groupNames = $_.groupNames + if ($groupNames.Count -gt 0) { + $sortedGroupNameList = $groupNames | Sort-Object -Unique + $rowObj.groupNames = $sortedGroupNameList -join $inCellSeparator3 + } + if ($policySetEffectStrings.Count -gt 0) { + $rowObj.policySets = $policySetEffectStrings -join $inCellSeparator3 + } + $rowObj.allowedEffects = Convert-AllowedEffectsToCsvString ` + -DefaultEffect $effectDefault ` + -IsEffectParameterized $isEffectParameterized ` + -EffectAllowedValues $effectAllowedValues.Keys ` + -EffectAllowedOverrides $effectAllowedOverrides ` + -InCellSeparator1 $inCellSeparator1 ` + -InCellSeparator2 $inCellSeparator2 - $environmentList = $_.environmentList - # Build environmentCategory columns - foreach ($environmentCategory in $environmentCategories) { - if ($environmentList.ContainsKey($environmentCategory)) { - $perEnvironment = $environmentList.$environmentCategory - if ($null -ne $perEnvironment.effectValue) { - $rowObj["$($environmentCategory)Effect"] = Convert-EffectToCsvString $perEnvironment.effectValue - } - else { - $rowObj["$($environmentCategory)Effect"] = Convert-EffectToCsvString $_.effectDefault - } + $environmentList = $_.environmentList + # Build environmentCategory columns + foreach ($environmentCategory in $environmentCategories) { + if ($environmentList.ContainsKey($environmentCategory)) { + $perEnvironment = $environmentList.$environmentCategory + if ($null -ne $perEnvironment.effectValue) { + $rowObj["$($environmentCategory)Effect"] = Convert-EffectToCsvString $perEnvironment.effectValue + } + else { + $rowObj["$($environmentCategory)Effect"] = Convert-EffectToCsvString $_.effectDefault + } - $text = Convert-ParametersToString -Parameters $perEnvironment.parameters -OutputType "csvValues" - $rowObj["$($environmentCategory)Parameters"] = $text + $text = Convert-ParametersToString -Parameters $perEnvironment.parameters -OutputType "csvValues" + $rowObj["$($environmentCategory)Parameters"] = $text + } } - } - # Add row to spreadsheet - $null = $allRows.Add($rowObj) + # Add row to spreadsheet + $null = $allRows.Add($rowObj) + } } } diff --git a/Scripts/Operations/Build-PolicyDocumentation.ps1 b/Scripts/Operations/Build-PolicyDocumentation.ps1 index 51d46000..7aabb2ec 100644 --- a/Scripts/Operations/Build-PolicyDocumentation.ps1 +++ b/Scripts/Operations/Build-PolicyDocumentation.ps1 @@ -299,8 +299,9 @@ foreach ($file in $files) { } ) } - + # Build documents + $doNotDisableDeprecatedPolicies = $pacEnvironments[$($globalSettings.pacEnvironmentPrompt)].'doNotDisableDeprecatedPolicies' $documentationSpecifications = $documentAssignments.documentationSpecifications foreach ($documentationSpecification in $documentationSpecifications) { $documentationType = $documentationSpecification.type @@ -312,7 +313,8 @@ foreach ($file in $files) { -WindowsNewLineCells:$WindowsNewLineCells ` -DocumentationSpecification $documentationSpecification ` -AssignmentsByEnvironment $assignmentsByEnvironment ` - -IncludeManualPolicies:$IncludeManualPolicies + -IncludeManualPolicies:$IncludeManualPolicies ` + -doNotDisableDeprecatedPolicies:$doNotDisableDeprecatedPolicies # Out-DocumentationForPolicyAssignments ` # -OutputPath $outputPath ` # -WindowsNewLineCells:$true `