From 33d5050afa71b4ae5e9333ff14437cd1f70a6887 Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Wed, 12 Oct 2022 08:14:11 +0100 Subject: [PATCH 1/7] Improve deployment name uniqueness and fix vNet naming for ALZ Portal Accelerator (#1078) --- docs/wiki/Whats-new.md | 12 ++++++++++++ eslzArm/eslzArm.json | 19 +++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/wiki/Whats-new.md b/docs/wiki/Whats-new.md index afe4b965db..b2f329e8ae 100644 --- a/docs/wiki/Whats-new.md +++ b/docs/wiki/Whats-new.md @@ -56,6 +56,18 @@ Here's what's changed in Enterprise Scale/Azure Landing Zones: - Release [`v2.4.1`](https://github.com/Azure/terraform-azurerm-caf-enterprise-scale/releases/tag/v2.4.1) of the Azure landing zones Terraform module adds a new diagnostic category for Azure Firewall, as reported in issue [#1063](https://github.com/Azure/Enterprise-Scale/issues/1063) - Update the Azure landing zone portal accelerator to use Resource Graph with a generic drop down UI element to improve user experience for subscription selection. +- Update the Azure landing zone portal accelerator to have more unique naming for deployment names in same tenant, using `utcNow()` function in `deploymentSuffix` variable - fixes [#1077](https://github.com/Azure/Enterprise-Scale/issues/1077) +- Update the Azure landing zone portal accelerator to have more unique naming for vNet names - fixes [#881](https://github.com/Azure/Enterprise-Scale/issues/881) + - vNet naming pattern changed: + - **From:** + - Identity vNet: `--vnet-` + - Corp vNets: `--vnet-` + - **To:** + - Identity vNet: `-vnet--` (then trimmed to 64 characters, using `take()` function, starting at front - so Subscription ID will get trimmed) + - Corp vNets: `-vnet--` (then trimmed to 64 characters, using `take()` function, starting at front - so Subscription ID will get trimmed) + - **⚠️This is a breaking change, only if you attempt to redeploy the Azure landing zone portal accelerator over the top of an existing Azure landing zone portal accelerator deployment that was deployed prior to 12/10/2022 (12th October 2022)⚠️** + - The outcome if you do this will be that new vNets will be created based on what you input into the Azure landing zone portal accelerator form when you fill it out. Even if you input exactly the same inputs and details as the first time you deployed it. + - However, this is a very uncommon action and if you are impacted [please raise an issue](https://github.com/Azure/Enterprise-Scale/issues) on the repo and we can assist further ### Policy diff --git a/eslzArm/eslzArm.json b/eslzArm/eslzArm.json index 728b31d699..6a478db436 100644 --- a/eslzArm/eslzArm.json +++ b/eslzArm/eslzArm.json @@ -653,6 +653,13 @@ "metadata": { "description": "Configure the count of empty deployments used to introduce a delay after policy deployment. Used to increase reliability of deployment, but can be reduced when re-deploying to an existing environment." } + }, + "currentDateTimeUtcNow": { + "type": "string", + "defaultValue": "[utcNow()]", + "metadata": { + "description": "The current date and time using the utcNow function. Used for deployment name uniqueness" + } } }, "variables": { @@ -731,7 +738,7 @@ "govMdfcPolicyAssignment": "[uri(deployment().properties.templateLink.uri, 'managementGroupTemplates/policyAssignments/gov/fairfaxDINE-MDFCConfigPolicyAssignment.json')]" }, // Declaring deterministic deployment names - "deploymentSuffix": "[concat('-', deployment().location, '-', guid(parameters('enterpriseScaleCompanyPrefix')))]", + "deploymentSuffix": "[concat('-', deployment().location, '-', guid(parameters('enterpriseScaleCompanyPrefix'), parameters('currentDateTimeUtcNow')))]", "deploymentNames": { "mgmtGroupDeploymentName": "[take(concat('alz-Mgs', variables('deploymentSuffix')), 64)]", "mgmtSubscriptionPlacement": "[take(concat('alz-MgmtSub', variables('deploymentSuffix')), 64)]", @@ -790,7 +797,7 @@ "identityPeeringDeploymentName": "[take(concat('alz-IDPeering', variables('deploymentSuffix')), 64)]", "identityVwanPeeringDeploymentName": "[take(concat('alz-IDVwanPeering', variables('deploymentSuffix')), 64)]", "corpConnectedLzVwanSubs": "[take(concat('alz-CorpConnLzsVwan', variables('deploymentSuffix')), 50)]", - "pidCuaDeploymentName": "[take(concat('pid-', variables('cuaid'), '-' , uniqueString(deployment().location, parameters('enterpriseScaleCompanyPrefix'))), 64)]" + "pidCuaDeploymentName": "[take(concat('pid-', variables('cuaid'), '-' , uniqueString(deployment().location, parameters('enterpriseScaleCompanyPrefix'), parameters('currentDateTimeUtcNow'))), 64)]" }, "esLiteDeploymentNames": { "mgmtGroupLiteDeploymentName": "[take(concat('alz-MgsLite', variables('deploymentSuffix')), 64)]", @@ -2721,7 +2728,7 @@ "value": "[variables('platformRgNames').identityVnetRg]" }, "vNetName": { - "value": "[concat(parameters('identitySubscriptionId'), variables('platformResourceNames').identityVnet)]" + "value": "[take(concat(variables('platformResourceNames').identityVnet, '-', uniqueString(parameters('identitySubscriptionId'))), 64)]" }, "vNetLocation": { "value": "[parameters('connectivityLocation')]" @@ -2768,7 +2775,7 @@ "value": "[variables('platformRgNames').identityVnetRg]" }, "vNetName": { - "value": "[concat(parameters('identitySubscriptionId'), variables('platformResourceNames').identityVnet)]" + "value": "[take(concat(variables('platformResourceNames').identityVnet, '-', uniqueString(parameters('identitySubscriptionId'))), 64)]" }, "vNetLocation": { "value": "[parameters('connectivityLocation')]" @@ -2931,7 +2938,7 @@ "value": "[variables('platformRgNames').lzVnetRg]" }, "vNetName": { - "value": "[concat(parameters('corpConnectedLzSubscriptionId')[copyIndex()].subs, '-', variables('platformResourceNames').lzVnet)]" + "value": "[take(concat(variables('platformResourceNames').lzVnet, '-', parameters('corpConnectedLzSubscriptionId')[copyIndex()].subs), 64)]" }, "vNetLocation": { "value": "[parameters('connectivityLocation')]" @@ -2988,7 +2995,7 @@ "value": "[variables('platformRgNames').lzVnetRg]" }, "vNetName": { - "value": "[concat(parameters('corpConnectedLzSubscriptionId')[copyIndex()].subs, '-', variables('platformResourceNames').lzVnet)]" + "value": "[take(concat(variables('platformResourceNames').lzVnet, '-', parameters('corpConnectedLzSubscriptionId')[copyIndex()].subs), 64)]" }, "vNetLocation": { "value": "[parameters('connectivityLocation')]" From 2a3fc6265e840e633bb27ebef7036998a4490b2b Mon Sep 17 00:00:00 2001 From: Jack Tracey <41163455+jtracey93@users.noreply.github.com> Date: Thu, 13 Oct 2022 22:13:26 +0100 Subject: [PATCH 2/7] Fix GH to ADO Sync Bugs (#1083) * Update gh-ado-sync.yml * Auto-update Portal experience [jtracey93/33d5050a] * Update gh-ado-sync.yml * Auto-update Portal experience [jtracey93/33d5050a] Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/gh-ado-sync.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/gh-ado-sync.yml b/.github/workflows/gh-ado-sync.yml index 2a8e161296..69268db91a 100644 --- a/.github/workflows/gh-ado-sync.yml +++ b/.github/workflows/gh-ado-sync.yml @@ -1,13 +1,10 @@ -name: Sync Issues with Azure DevOps Work Items +name: Sync Issues to Azure DevOps Work Items on: - schedule: - - cron: '*/15 * * * *' issues: types: [opened, closed, deleted, reopened, edited, labeled, unlabeled, assigned, unassigned] issue_comment: types: [created] - workflow_dispatch: {} jobs: alert: @@ -22,10 +19,9 @@ jobs: fetch-depth: 0 - name: GitHub/ADO Sync - uses: a11smiles/GitSync@main + uses: a11smiles/GitSync@v1.1.4 env: ado_token: '${{ secrets.ADO_PERSONAL_ACCESS_TOKEN }}' - github_token: '${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}' config_file: './.github/actions-config/gh-ado-sync-config.json' with: ado: ${{ secrets.ADO_MAPPINGS_HANDLES }} From 92942de4ab37b52d86f007c8d426e977b40ea066 Mon Sep 17 00:00:00 2001 From: Kevin Rowlandson Date: Thu, 20 Oct 2022 08:38:42 +0100 Subject: [PATCH 3/7] Add Bicep support to `Alz.Tools` (#1086) * Add `Bicep` to export formats * Don't escape Policy Sets for Bicep * Add explicit converters for policies and initiatives --- src/Alz.Tools/Alz.Classes/Alz.Classes.psm1 | 1 + src/Alz.Tools/Alz.Enums/Alz.Enums.psm1 | 1 + src/Alz.Tools/functions/Alz.Tools.ps1 | 39 ++++++++++++++++++---- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/Alz.Tools/Alz.Classes/Alz.Classes.psm1 b/src/Alz.Tools/Alz.Classes/Alz.Classes.psm1 index b7acf18747..d30a62a2dd 100644 --- a/src/Alz.Tools/Alz.Classes/Alz.Classes.psm1 +++ b/src/Alz.Tools/Alz.Classes/Alz.Classes.psm1 @@ -477,6 +477,7 @@ class ArmTemplateResource : ALZBase { $policyDefinitionId = switch ($ExportFormat) { "ArmResource" { "/providers/Microsoft.Management/managementGroups/contoso$($regexMatches.Value)" } "ArmVariable" { "[concat(variables('scope'), '$($regexMatches.Value)')]" } + "Bicep" { "`${varTargetManagementGroupResourceId}$($regexMatches.Value)" } "Raw" { "$($policyDefinition.policyDefinitionId)" } "Jinja2" { "$([ArmTemplateResource]::ConvertToTemplateVariable("root_scope_resource_id", $ExportFormat))$($regexMatches.Value)" } "Terraform" { "$([ArmTemplateResource]::ConvertToTemplateVariable("root_scope_resource_id", $ExportFormat))$($regexMatches.Value)" } diff --git a/src/Alz.Tools/Alz.Enums/Alz.Enums.psm1 b/src/Alz.Tools/Alz.Enums/Alz.Enums.psm1 index 762b7387f7..0343b3c25e 100644 --- a/src/Alz.Tools/Alz.Enums/Alz.Enums.psm1 +++ b/src/Alz.Tools/Alz.Enums/Alz.Enums.psm1 @@ -44,4 +44,5 @@ enum ExportFormat { Raw Jinja2 Terraform + Bicep } diff --git a/src/Alz.Tools/functions/Alz.Tools.ps1 b/src/Alz.Tools/functions/Alz.Tools.ps1 index efc224e1b5..8fc4c66d54 100644 --- a/src/Alz.Tools/functions/Alz.Tools.ps1 +++ b/src/Alz.Tools/functions/Alz.Tools.ps1 @@ -37,6 +37,19 @@ param () "Microsoft.Management/managementGroups/subscriptions" ) +[String[]]$removePolicyEscapingByFormat = @( + "Terraform" + "Bicep" +) + +[String[]]$removePolicySetEscapingByFormat = @( + "Terraform" +) + +[String[]]$removeResourceEscapingByFormat = @( + "Terraform" +) + ################################ # Functions used within module # ################################ @@ -146,21 +159,21 @@ function GetObjectByResourceTypeFromJson { elseif ($regex_schema_managementGroupDeploymentTemplate.IsMatch($objectFromJson."`$schema")) { foreach ($policyDefinition in $objectFromJson.variables.policies.policyDefinitions) { ProcessObjectByResourceType ` - -ResourceObject ($ExportFormat -eq "Terraform" ? (Remove-Escaping -InputObject $policyDefinition) : $policyDefinition) ` + -ResourceObject ($ExportFormat -in $removePolicyEscapingByFormat ? (Remove-Escaping -InputObject $policyDefinition) : $policyDefinition) ` -ResourceType ("Microsoft.Authorization/policyDefinitions") } foreach ($policySetDefinition in $objectFromJson.variables.initiatives.policySetDefinitions) { ProcessObjectByResourceType ` - -ResourceObject ($ExportFormat -eq "Terraform" ? (Remove-Escaping -InputObject $policySetDefinition) : $policySetDefinition) ` + -ResourceObject ($ExportFormat -in $removePolicySetEscapingByFormat ? (Remove-Escaping -InputObject $policySetDefinition) : $policySetDefinition) ` -ResourceType ("Microsoft.Authorization/policySetDefinitions") } foreach ( - $policySetDefinition in $objectFromJson.resources | + $policyDefinition in $objectFromJson.resources | Where-Object { $_.type -eq "Microsoft.Authorization/policyDefinitions" } | Where-Object { $_.name -ne "[variables('policies').policyDefinitions[copyIndex()].name]" } ) { ProcessObjectByResourceType ` - -ResourceObject ($ExportFormat -eq "Terraform" ? (Remove-Escaping -InputObject $policySetDefinition) : $policySetDefinition) ` + -ResourceObject ($ExportFormat -in $removePolicyEscapingByFormat ? (Remove-Escaping -InputObject $policyDefinition) : $policyDefinition) ` -ResourceType ("Microsoft.Authorization/policyDefinitions") } foreach ( @@ -169,14 +182,26 @@ function GetObjectByResourceTypeFromJson { Where-Object { $_.name -ne "[variables('initiatives').policySetDefinitions[copyIndex()].name]" } ) { ProcessObjectByResourceType ` - -ResourceObject ($ExportFormat -eq "Terraform" ? (Remove-Escaping -InputObject $policySetDefinition) : $policySetDefinition) ` + -ResourceObject ($ExportFormat -in $removePolicySetEscapingByFormat ? (Remove-Escaping -InputObject $policySetDefinition) : $policySetDefinition) ` -ResourceType ("Microsoft.Authorization/policySetDefinitions") } } - # The following elseif block handles resource files stored in ARM template format + # The following elseif block handles all policy definitions stored in ARM template format + elseif ($objectFromJson.type -eq "Microsoft.Authorization/policyDefinitions") { + ProcessObjectByResourceType ` + -ResourceObject ($ExportFormat -in $removePolicyEscapingByFormat ? (Remove-Escaping -InputObject $objectFromJson) : $objectFromJson) ` + -ResourceType $objectFromJson.type + } + # The following elseif block handles all policy set definitions stored in ARM template format + elseif ($objectFromJson.type -eq "Microsoft.Authorization/policySetDefinitions") { + ProcessObjectByResourceType ` + -ResourceObject ($ExportFormat -in $removePolicySetEscapingByFormat ? (Remove-Escaping -InputObject $objectFromJson) : $objectFromJson) ` + -ResourceType $objectFromJson.type + } + # The following elseif block handles all other allowed resource types stored in ARM template format elseif ($objectFromJson.type -in $allowedResourceTypes) { ProcessObjectByResourceType ` - -ResourceObject ($ExportFormat -eq "Terraform" ? (Remove-Escaping -InputObject $objectFromJson) : $objectFromJson) ` + -ResourceObject ($ExportFormat -in $removeResourceEscapingByFormat ? (Remove-Escaping -InputObject $objectFromJson) : $objectFromJson) ` -ResourceType $objectFromJson.type } # The following block handles processing generic files where the source content is unknown From 03d0a8730b4eee34815d68b8385b70c665dede92 Mon Sep 17 00:00:00 2001 From: Kevin Rowlandson Date: Fri, 21 Oct 2022 08:59:35 +0100 Subject: [PATCH 4/7] Update workflows to fix permissions error (#1087) * Update workflows * Auto-update Portal experience [krowlandson/92942de4] Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/test-portal.yml | 2 +- .github/workflows/update-portal.yml | 4 ++-- .github/workflows/wiki-sync.yml | 2 +- .../managementGroupTemplates/policyDefinitions/policies.json | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-portal.yml b/.github/workflows/test-portal.yml index ce24790c86..63ebdac57d 100644 --- a/.github/workflows/test-portal.yml +++ b/.github/workflows/test-portal.yml @@ -9,10 +9,10 @@ name: Test Portal Experience on: pull_request_target: types: - - edited - opened - reopened - synchronize + - ready_for_review paths: - "eslzArm/**.json" - "src/**.json" diff --git a/.github/workflows/update-portal.yml b/.github/workflows/update-portal.yml index 01a329207a..e1a290ddd8 100644 --- a/.github/workflows/update-portal.yml +++ b/.github/workflows/update-portal.yml @@ -9,10 +9,10 @@ name: Update Portal Experience on: pull_request_target: types: - - edited - opened - reopened - synchronize + - ready_for_review env: github_user_name: 'github-actions' @@ -87,7 +87,7 @@ jobs: echo "==> Push changes..." echo "Pushing changes to: $github_pr_repo" - git push "https://$GITHUB_TOKEN@github.com/$github_pr_repo.git" + git push "https://$GITHUB_TOKEN@github.com/$github_pr_repo.git" "HEAD:$GITHUB_BASE_REF" else echo "No changes found." diff --git a/.github/workflows/wiki-sync.yml b/.github/workflows/wiki-sync.yml index 715a27e5cc..7da0dfd133 100644 --- a/.github/workflows/wiki-sync.yml +++ b/.github/workflows/wiki-sync.yml @@ -52,7 +52,7 @@ jobs: run: | mapfile -t CHECK_GIT_STATUS < <(git status -s) printf "%s\n" "${CHECK_GIT_STATUS[@]}" - echo "::set-output name=changes::${#CHECK_GIT_STATUS[@]}" + echo "changes=${#CHECK_GIT_STATUS[@]}" >> $GITHUB_OUTPUT working-directory: ${{ env.wiki_target_repo }} - name: Add files, commit and push into Wiki diff --git a/eslzArm/managementGroupTemplates/policyDefinitions/policies.json b/eslzArm/managementGroupTemplates/policyDefinitions/policies.json index 0a6c183a52..94ab081773 100644 --- a/eslzArm/managementGroupTemplates/policyDefinitions/policies.json +++ b/eslzArm/managementGroupTemplates/policyDefinitions/policies.json @@ -4,8 +4,8 @@ "metadata": { "_generator": { "name": "bicep", - "version": "0.10.61.36676", - "templateHash": "12718734228119532752" + "version": "0.11.1.770", + "templateHash": "12625835499431513826" } }, "parameters": { From 205b304b486e5fb67cc37409ddef28666d28231d Mon Sep 17 00:00:00 2001 From: Kevin Rowlandson Date: Fri, 21 Oct 2022 09:24:34 +0100 Subject: [PATCH 5/7] Update to target HEAD ref (#1088) * Update workflows * Update to HEAD ref --- .github/workflows/update-portal.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-portal.yml b/.github/workflows/update-portal.yml index e1a290ddd8..c48bc82a3d 100644 --- a/.github/workflows/update-portal.yml +++ b/.github/workflows/update-portal.yml @@ -87,7 +87,7 @@ jobs: echo "==> Push changes..." echo "Pushing changes to: $github_pr_repo" - git push "https://$GITHUB_TOKEN@github.com/$github_pr_repo.git" "HEAD:$GITHUB_BASE_REF" + git push "https://$GITHUB_TOKEN@github.com/$github_pr_repo.git" "HEAD:$GITHUB_HEAD_REF" else echo "No changes found." From 1f2455b96ed815bd36ce8ddeef7dd2db70200527 Mon Sep 17 00:00:00 2001 From: Kevin Rowlandson Date: Fri, 21 Oct 2022 13:44:30 +0100 Subject: [PATCH 6/7] Fix multiline input for `Edit-LineEndings` function (#1090) --- src/Alz.Tools/functions/Alz.Tools.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Alz.Tools/functions/Alz.Tools.ps1 b/src/Alz.Tools/functions/Alz.Tools.ps1 index 8fc4c66d54..8198aebdd6 100644 --- a/src/Alz.Tools/functions/Alz.Tools.ps1 +++ b/src/Alz.Tools/functions/Alz.Tools.ps1 @@ -264,7 +264,7 @@ function Edit-LineEndings { Process { - [String[]]$outputText = $InputText | + [String[]]$outputText += $InputText | ForEach-Object { $_ -replace "`r`n", "`n" } | ForEach-Object { $_ -replace "`r", "`n" } | ForEach-Object { $_ -replace "`n", "$eol" } From dc8a4177ad90a1d2b45c658ee35b152ac30db881 Mon Sep 17 00:00:00 2001 From: quoteee <45695032+JulianHayward@users.noreply.github.com> Date: Fri, 21 Oct 2022 19:58:08 +0200 Subject: [PATCH 7/7] Create Invoke-AlzCustomPolicyCheckAgainstBuiltIn.ps1 (#1091) * Create Invoke-AlzCustomPolicyCheckAgainstBuiltIn.ps1 detect ALZ policy definitions where * policyRule is equal to a BuiltIn Policy definition * policyRuleIf is equal to a BuiltIn Policy definition * policyRuleThen is equal to a BuiltIn Policy definition Next iteration should include the BuiltIn Policy definition state (GA, preview, deprecated) * linter fixes 1 * add PSScriptAnalyser Suppression * additional suppression Co-authored-by: Jack Tracey <41163455+jtracey93@users.noreply.github.com> --- ...oke-AlzCustomPolicyCheckAgainstBuiltIn.ps1 | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 src/scripts/Invoke-AlzCustomPolicyCheckAgainstBuiltIn.ps1 diff --git a/src/scripts/Invoke-AlzCustomPolicyCheckAgainstBuiltIn.ps1 b/src/scripts/Invoke-AlzCustomPolicyCheckAgainstBuiltIn.ps1 new file mode 100644 index 0000000000..64fd57bb19 --- /dev/null +++ b/src/scripts/Invoke-AlzCustomPolicyCheckAgainstBuiltIn.ps1 @@ -0,0 +1,206 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingWriteHost", "", Justification = "Coloured output required in this script")] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseProcessBlockForPipelineCommand", "", Justification = "Not required for this script")] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "", Justification = "Not required for this script")] + +[CmdletBinding()] +Param +( + [string] + [parameter(ValueFromPipeline)][ValidateSet(';', ',')][string]$CsvDelimiter = ';', + + [string] + $FileTimeStampFormat = 'yyyyMMdd_HHmmss' +) + +#region helper +if ($CsvDelimiter -eq ';') { + $CsvDelimiterOpposite = ',' +} +if ($CsvDelimiter -eq ',') { + $CsvDelimiterOpposite = ';' +} +#endregion helper + +#region AzAPICall +try { + $azAPICallConf = initAzAPICall -DebugAzAPICall $True +} +catch { + Write-Host "Install AzAPICall Powershell Module https://www.powershellgallery.com/packages/AzAPICall (aka.ms/AzAPICall)" -ForegroundColor DarkRed + Write-Host "Command: Install-Module -Name AzAPICall" -ForegroundColor Yellow + throw +} +#endregion AzAPICall + +#region get ALZ policies.json +$ALZRetryMax = 5 +$ALZRetryCount = 0 +do { + $ALZRetryCount++ + $ALZPoliciesRaw = Invoke-WebRequest -uri "https://raw.githubusercontent.com/Azure/Enterprise-Scale/main/eslzArm/managementGroupTemplates/policyDefinitions/policies.json" + + if ($ALZPoliciesRaw.StatusCode -ne 200) { + Write-Output "getALZPolicies: $($ALZPoliciesRaw.StatusCode -eq 200) - try again in $($ALZRetryCount * 2) seconds" + start-sleep -seconds ($ALZRetryCount * 2) + } +} +until($ALZPoliciesRaw.StatusCode -eq 200 -or $ALZRetryCount -gt $ALZRetryMax) +if ($ALZRetryCount -gt 10 -and $ALZPoliciesRaw.StatusCode -ne 200) { + Write-Output "ALZ Policies failed" + throw +} +#endregion get ALZ policies.json + +$jsonALZPolicies = $ALZPoliciesRaw.Content -replace "\[\[", '[' | ConvertFrom-Json +[regex]$extractVariableName = "(?<=\[variables\(')[^']+" +$refsPolicyDefinitionsAll = $extractVariableName.Matches($jsonALZPolicies.variables.loadPolicyDefinitions.All).Value +$refsPolicyDefinitions = $extractVariableName.Matches($jsonALZPolicies.variables.loadPolicyDefinitions.$($azapicallconf['checkContext'].Environment.Name)).Value +$listPolicyDefinitions = $refsPolicyDefinitionsAll + $refsPolicyDefinitions +$policyDefinitionsALZ = $listPolicyDefinitions.ForEach({ $jsonALZPolicies.variables.$_ }) + +if ($policyDefinitionsALZ.Count -eq 0) { + throw "Found $($policyDefinitionsALZ.Count) ALZ Policy definitions for $($azapicallconf['checkContext'].Environment.Name)" +} +else { + function getHash { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [object] + $object + ) + return [System.BitConverter]::ToString([System.Security.Cryptography.HashAlgorithm]::Create("sha256").ComputeHash([System.Text.Encoding]::UTF8.GetBytes($object))) + } + + $currentTask = 'Getting BuiltIn Policy definitions' + $uri = "$($azAPICallConf['azAPIEndpointUrls'].ARM)/providers/Microsoft.Authorization/policyDefinitions?api-version=2021-06-01&`$filter=policyType eq 'BuiltIn'" + $method = 'GET' + $policyDefinitionsBuiltIn = AzAPICall -AzAPICallConfiguration $azAPICallConf -uri $uri -method $method -currentTask $currentTask + + Write-Host "Found $($policyDefinitionsALZ.Count) ALZ Policy definitions for $($azapicallconf['checkContext'].Environment.Name)" + Write-Host "Found $($policyDefinitionsBuiltIn.Count) BuiltIn Policy definitions for $($azapicallconf['checkContext'].Environment.Name)" + + $htHashesBuiltIn = @{} + foreach ($policyDefinitionBuiltIn in $policyDefinitionsBuiltIn) { + $policyObject = $policyDefinitionBuiltIn + + if ($policyObject.properties.parameters.effect.defaultvalue) { + $arrEff = foreach ($eff in $policyObject.properties.parameters.effect.allowedValues) { + $eff + } + $arrEff += $policyObject.properties.parameters.effect.defaultvalue + $effectBuiltIn = ($arrEff | Sort-Object -Unique) -join "$CsvDelimiterOpposite " + } + else { + if ($policyObject.properties.parameters.policyEffect.defaultValue) { + $arrEff = foreach ($eff in $policyObject.properties.parameters.policyEffect.allowedValues) { + $eff + } + $arrEff += $policyObject.properties.parameters.policyEffect.defaultvalue + $effectBuiltIn = ($arrEff | Sort-Object -Unique) -join "$CsvDelimiterOpposite " + } + else { + $effectBuiltIn = $policyObject.Properties.policyRule.then.effect + } + } + + $htHashesBuiltIn.($policyObject.name) = @{} + $htHashesBuiltIn.($policyObject.name).policy = $policyObject + $htHashesBuiltIn.($policyObject.name).effectBuiltIn = $effectBuiltIn + + $htHashesBuiltIn.($policyObject.name).policyRuleHash = getHash -object ($policyObject.properties.policyRule | ConvertTo-Json -depth 99) + $htHashesBuiltIn.($policyObject.name).policyRuleIfHash = getHash -object ($policyObject.properties.policyRule.if | ConvertTo-Json -depth 99) + $htHashesBuiltIn.($policyObject.name).policyRuleThenHash = getHash -object ($policyObject.properties.policyRule.then | ConvertTo-Json -depth 99) + } + + $arrayResults = [System.Collections.ArrayList]@() + foreach ($policyDefinitionALZ in $policyDefinitionsALZ) { + $policyObject = $policyDefinitionALZ | ConvertFrom-Json + if ($policyObject.properties.parameters.effect.defaultvalue) { + $arrEff = foreach ($eff in $policyObject.properties.parameters.effect.allowedValues) { + $eff + } + $arrEff += $policyObject.properties.parameters.effect.defaultvalue + $effectALZ = ($arrEff | Sort-Object -Unique) -join "$CsvDelimiterOpposite " + } + else { + if ($policyObject.properties.parameters.policyEffect.defaultValue) { + $arrEff = foreach ($eff in $policyObject.properties.parameters.policyEffect.allowedValues) { + $eff + } + $arrEff += $policyObject.properties.parameters.policyEffect.defaultvalue + $effectALZ = ($arrEff | Sort-Object -Unique) -join "$CsvDelimiterOpposite " + } + else { + $effectALZ = $policyObject.Properties.policyRule.then.effect + } + } + + $policyRuleHash = getHash -object ($policyObject.properties.policyRule | ConvertTo-Json -depth 99) + if ($htHashesBuiltIn.values.policyRuleHash -contains $policyRuleHash) { + $ref = ($htHashesBuiltIn.values.where({ $_.policyRuleHash -eq $policyRuleHash }) | Select-Object effectBuiltIn, @{Label = 'name'; Expression = { $_.policy.Name } }, @{Label = 'displayName'; Expression = { $_.policy.properties.displayName } }) + Write-Host "ALZ '$($policyObject.name)' policy-Rule matches a BuiltIn policy def: id:'$($ref.Name)' displayName: '$($ref.displayName)'" -ForegroundColor Magenta + Write-Host " - AzA ALZ Link: https://www.azadvertizer.net/azpolicyadvertizer/$($policyObject.name).html" -ForegroundColor Magenta + Write-Host " - AzA BuiltIn Link: https://www.azadvertizer.net/azpolicyadvertizer/$($ref.Name).html" -ForegroundColor Magenta + + $null = $arrayResults.Add([PSCustomObject]@{ + ALZEffect = $effectALZ + ALZPolicy = $policyObject.name + ALZPolicyDisplayName = $policyObject.properties.displayName + ALZPolicyLink = "https://www.azadvertizer.net/azpolicyadvertizer/$($policyObject.name).html" + Match = 'policyRule' + MatchCount = $ref.Count + BuilTinEffect = $ref.effectBuiltIn + BuiltinPolicy = $ref.Name + BuiltinPolicyDisplayName = $ref.displayName + BuiltinPolicyLink = "https://www.azadvertizer.net/azpolicyadvertizer/$($ref.Name).html" + }) + } + + $policyRuleIfHash = getHash -object ($policyObject.properties.policyRule.if | ConvertTo-Json -depth 99) + if ($htHashesBuiltIn.values.policyRuleIfHash -contains $policyRuleIfHash) { + $ref = ($htHashesBuiltIn.values.where({ $_.policyRuleIfHash -eq $policyRuleIfHash }) | Select-Object effectBuiltIn, @{Label = 'name'; Expression = { $_.policy.Name } }, @{Label = 'displayName'; Expression = { $_.policy.properties.displayName } }) + Write-Host "ALZ '$($policyObject.name)' policy-Rule-If match in $($ref.count) Builtin Policy defs" + + foreach ($entry in $ref) { + + $null = $arrayResults.Add([PSCustomObject]@{ + ALZEffect = $effectALZ + ALZPolicy = $policyObject.name + ALZPolicyDisplayName = $policyObject.properties.displayName + ALZPolicyLink = "https://www.azadvertizer.net/azpolicyadvertizer/$($policyObject.name).html" + Match = 'policyRuleIf' + MatchCount = $ref.Count + BuilTinEffect = $entry.effectBuiltIn + BuiltinPolicy = $entry.Name + BuiltinPolicyDisplayName = $entry.displayName + BuiltinPolicyLink = "https://www.azadvertizer.net/azpolicyadvertizer/$($entry.Name).html" + }) + } + } + + $policyRuleThenHash = getHash -object ($policyObject.properties.policyRule.then | ConvertTo-Json -depth 99) + if ($htHashesBuiltIn.values.policyRuleThenHash -contains $policyRuleThenHash) { + $ref = ($htHashesBuiltIn.values.where({ $_.policyRuleThenHash -eq $policyRuleThenHash }) | Select-Object effectBuiltIn, @{Label = 'name'; Expression = { $_.policy.Name } }, @{Label = 'displayName'; Expression = { $_.policy.properties.displayName } }) + Write-Host "ALZ '$($policyObject.name)' policy-Rule-Then match in $($ref.count) Builtin Policy defs" + + foreach ($entry in $ref) { + $null = $arrayResults.Add([PSCustomObject]@{ + ALZEffect = $effectALZ + ALZPolicy = $policyObject.name + ALZPolicyDisplayName = $policyObject.properties.displayName + ALZPolicyLink = "https://www.azadvertizer.net/azpolicyadvertizer/$($policyObject.name).html" + Match = 'policyRuleThen' + MatchCount = $ref.Count + BuilTinEffect = $entry.effectBuiltIn + BuiltinPolicy = $entry.Name + BuiltinPolicyDisplayName = $entry.displayName + BuiltinPolicyLink = "https://www.azadvertizer.net/azpolicyadvertizer/$($entry.Name).html" + }) + } + } + } + + $fileTimestamp = (Get-Date -Format $FileTimeStampFormat) + $arrayResults | Export-Csv -delimiter $CsvDelimiter -path "alzvsbuiltin_$($fileTimestamp).csv"-Encoding utf8 +}