From 51e58f3a385762761b835375618d631e7344e992 Mon Sep 17 00:00:00 2001 From: Anthony Watherston Date: Thu, 17 Oct 2024 08:30:04 +1100 Subject: [PATCH] Updates to docs, global settings function, export and policy refresh (#782) Co-authored-by: Anthony Watherston --- Docs/ci-cd-github-actions.md | 21 +- Docs/integrating-with-alz.md | 2 +- Docs/operational-scripts.md | 10 +- Docs/settings-desired-state.md | 8 +- .../ALZ-LandingZones-Default.jsonc | 33 +- .../ALZ-Platform-Default.jsonc | 26 +- .../policyAssignments/ALZ-Root-Default.jsonc | 5 +- .../ALZ-WorkloadGuardRails.jsonc | 17 + ...Build-ScopeTableForDeploymentRootScope.ps1 | 7 +- .../Build-ScopeTableForSubscription.ps1 | 3 + Scripts/Helpers/Get-GlobalSettings.ps1 | 6 +- .../Operations/Export-AzAdvertizerPolicy.ps1 | 394 --------- Scripts/Operations/Export-PolicyToEPAC.ps1 | 778 ++++++++++++++++++ 13 files changed, 883 insertions(+), 427 deletions(-) delete mode 100644 Scripts/Operations/Export-AzAdvertizerPolicy.ps1 create mode 100644 Scripts/Operations/Export-PolicyToEPAC.ps1 diff --git a/Docs/ci-cd-github-actions.md b/Docs/ci-cd-github-actions.md index e4745c66..b08f9196 100644 --- a/Docs/ci-cd-github-actions.md +++ b/Docs/ci-cd-github-actions.md @@ -11,7 +11,9 @@ The previous version is still available in the starter kit in folder `Legacy` an ### Create GitHub Deployment Environments -You will need one [GitHub deployment environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) for the `epac-dev` workflow and three environments each for your epac-prod or tenant workflows. This documentation assumes the include starter kit pipelines. +Create two labels in the project called `PolicyDeployment` and `RoleDeployment`. Instructions to create new labels are [here](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels#creating-a-label). + +You will need one [GitHub deployment environment](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) for the `epac-dev` workflow and three environments each for your epac-prod or tenant workflows. This documentation assumes the use of the included starter kit pipelines. | Environment | Purpose | [App Registration](ci-cd-app-registrations.md) (SPN) | |---|---|---| @@ -20,7 +22,12 @@ You will need one [GitHub deployment environment](https://docs.github.com/en/act | TENANT-DEPLOY-POLICY | Deploy Policy resources for `tenant` | ci-cd-root-policy-contributor | | TENANT-DEPLOY-ROLES | Deploy Roles for `tenant` | ci-cd-root-user-assignments | -For each environment, [add to the environment secrets](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#environment-secrets) for the tenant id, client id and client secret for the SPN. The secrets must be named `TENANT_ID`, `CLIENT_ID` and `CLIENT_SECRET` respectively. +[Add the environment secrets for](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#environment-secrets) the Service Principal listed below to the GitHub repository. These are used to authenticate to Azure, and should be added to each Environment listed above. + +| Secret Name | Value | +|---------|---------| +| AZURE_CLIENT_ID | Application ID for SPN | +| AZURE_TENANT_ID | Azure AD Tenant | ### Hardening each Environment @@ -52,14 +59,12 @@ This section is retained from the previous documentation to enable you to contin There are some steps to be performed in GitHub to ensure the action runs correctly. 1. Create two labels in the project called `PolicyDeployment` and `RoleDeployment`. Instructions to create new labels are [here](https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels#creating-a-label). -2. Create secrets representing an SPN in GitHub for the repository as below - these are used to authenticate to Azure +2. An Environment should be created for each [SPN created](Docs/ci-cd-app-registrations.md) | Secret Name | Value | - |---|---| - | CLIENT_ID | Application ID for SPN | - | CLIENT_SECRET | Client secret | - | TENANT_ID | Azure AD Tenant | - | SUBSCRIPTION_ID | Any subscription ID. Used to login but not in the process | + |---------|---------| + | AZURE_CLIENT_ID | Application ID for SPN | + | AZURE_TENANT_ID | Azure AD Tenant | 3. In the `.github\workflows\build.yaml` and `.github\workflows\deploy.yaml` file updated the `env:` section as below. diff --git a/Docs/integrating-with-alz.md b/Docs/integrating-with-alz.md index ea9117d4..0db9293f 100644 --- a/Docs/integrating-with-alz.md +++ b/Docs/integrating-with-alz.md @@ -128,7 +128,7 @@ To deploy the ALZ policies using EPAC follow the steps below. New-HydrationDefinitionFolder -DefinitionsRootFolder .\Definitions ``` -3. Update the `global-settings.json` file in the Definitions folder as described [here](definitions-and-global-settings.md#global-settings) +3. Update the `global-settings.json` file in the Definitions folder as described [here](definitions-and-global-settings.md) 4. Synchronize the policies from the upstream repository. You should ensure that you are running the latest version of the EPAC module before running this script each time. diff --git a/Docs/operational-scripts.md b/Docs/operational-scripts.md index 8823790f..50fec594 100644 --- a/Docs/operational-scripts.md +++ b/Docs/operational-scripts.md @@ -38,11 +38,17 @@ The scripts `New-AzureDevOpsBug` and `New-GitHubIssue` create a Bug or Issue whe ## Export from AzAdvertizer -The script `Export-AzAdvertizerPolicy.ps1` creates for you the policyAssignments, policyDefinitions, and policySetDefinitions based on the provided URL in an Output folder under 'ALZ-Export'. +The script `Export-PolicyToEPAC.ps1` creates for you the policyAssignments, policyDefinitions, and policySetDefinitions based on the provided URL in an Output folder under 'ALZ-Export'. Parameters: -* **AzAdvertizerUrl**: Mandatory url of the policy or policy set from AzAdvertizer. +* **PolicyDefinitionId**: Mandatory url of the policy or policy set from AzAdvertizer. + +* **PolicySetDefinitionId**: Mandatory url of the policy or policy set from AzAdvertizer. + +* **ALZPolicyDefinitionId**: Mandatory url of the policy or policy set from AzAdvertizer. + +* **ALZPolicySetDefinitionId**: Mandatory url of the policy or policy set from AzAdvertizer. * **OutputFolder**: Output Folder. Defaults to the path 'Output'. diff --git a/Docs/settings-desired-state.md b/Docs/settings-desired-state.md index 96ebc6d5..2847f2fe 100644 --- a/Docs/settings-desired-state.md +++ b/Docs/settings-desired-state.md @@ -21,6 +21,7 @@ Desired State strategy enables you to adjust the default behavior to fit more co - `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". This setting can be used to override that behavior by setting it to `true`. Default is `false`. + - `excludeSubscriptions`: Exclude all subscription under the deployment root scope. Designed for environments containing many frequently updated subscriptions that are not requiring management and where using ```excludedScopes``` would be impractical to maintain. If resource groups are added ```excludedScopes``` they will be ignored as this setting will take precedence. It will not effect excluded management group scopes. Default is `false` The following example shows the `desiredState` element with all properties set: @@ -40,7 +41,6 @@ The following example shows the `desiredState` element with all properties set: While transitioning to EPAC, existing Policy resources may need to be kept. Setting `desiredState` to `ownedOnly` allows EPAC to remove its own resources while preserving instances requiring (temporary) preservation. - ```json "desiredState": { "strategy": "ownedOnly", @@ -62,7 +62,7 @@ After short transitioning period (weeks), it is recommended to set `desiredState > [!WARNING] > **Breaking Change in v10.0.0:** Policy Assignments at resource groups are **managed** by EPAC. The element `includeResourceGroups` has been deprecated and removed. -To exclude resource groups from management by EPAC, add an `excludedScopes` array element with a wild card for the subscription and resourceGroups to `desiredState`. +To exclude resource groups from management by EPAC, add an `excludedScopes` array element with a wild card for the subscription and resourceGroups to `desiredState`. ```json "desiredState": { @@ -90,8 +90,8 @@ In some organizations the lifecycle of different parts may be managed separately EPAC only manages items with a directory in the `Definitions` folder. Therefore, you can use the same `pacOwnerId` from two repos and remove the folders to separate them. In this example: -* Repo1: `Definitions` contains `policyDefinitions`, `policySetDefinitions` and `policyAssignments` folders. -* Repo2: `Definitions` contains `policyExemptions` folder. +- Repo1: `Definitions` contains `policyDefinitions`, `policySetDefinitions` and `policyAssignments` folders. +- Repo2: `Definitions` contains `policyExemptions` folder. Policy resource that would be defined in the folder. It is important to remove the folders. GitHub repos remove empty folder automatically. diff --git a/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-LandingZones-Default.jsonc b/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-LandingZones-Default.jsonc index 3d5c9966..f098d202 100644 --- a/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-LandingZones-Default.jsonc +++ b/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-LandingZones-Default.jsonc @@ -58,18 +58,6 @@ "policyId": "/providers/Microsoft.Authorization/policyDefinitions/1a5b4dca-0b6f-4cf5-907c-56316bc1bf3d", "displayName": "AKS HTTPS Access" } - }, - { - "nodeName": "Security", - "assignment": { - "name": "Deploy-AKS-Policy", - "displayName": "Deploy Azure Policy Add-on to Azure Kubernetes Service clusters", - "description": "Use Azure Policy Add-on to manage and report on the compliance state of your Azure Kubernetes Service (AKS) clusters. For more information, see https://aka.ms/akspolicydoc." - }, - "definitionEntry": { - "policyId": "/providers/Microsoft.Authorization/policyDefinitions/a8eff44f-8c92-45c3-a3fb-9880802d67a7", - "displayName": "Deploy AKS Policy" - } } ] }, @@ -146,6 +134,25 @@ "message": "Web Application Firewall (WAF) must be enabled for Application Gateway." } ] + }, + { + "nodeName": "Subnets", + "assignment": { + "name": "Enforce-Snet-Private-LZ", + "displayName": "Subnets should be private", + "description": "Ensure your subnets are secure by default by preventing default outbound access. For more information go to https://aka.ms/defaultoutboundaccessretirement" + }, + "definitionEntry": { + "policyId": "/providers/Microsoft.Authorization/policyDefinitions/7bca8353-aa3b-429b-904a-9229c4385837", + "displayName": "Subnets should be private", + "nonComplianceMessages": [ + { + "policyDefinitionReferenceId": null, + "message": "Subnets should be private." + } + ] + }, + "enforcementMode": "DoNotEnforce" } ] }, @@ -528,4 +535,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-Platform-Default.jsonc b/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-Platform-Default.jsonc index 96a121a9..a1fa00e0 100644 --- a/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-Platform-Default.jsonc +++ b/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-Platform-Default.jsonc @@ -272,6 +272,30 @@ } } ] + }, + { + "nodeName": "Networking", + "children": [ + { + "nodeName": "Subnets", + "assignment": { + "name": "Enforce-Snet-Private-PLT", + "displayName": "Subnets should be private", + "description": "Ensure your subnets are secure by default by preventing default outbound access. For more information go to https://aka.ms/defaultoutboundaccessretirement" + }, + "definitionEntry": { + "policyId": "/providers/Microsoft.Authorization/policyDefinitions/7bca8353-aa3b-429b-904a-9229c4385837", + "displayName": "Subnets should be private", + "nonComplianceMessages": [ + { + "policyDefinitionReferenceId": null, + "message": "Subnets should be private." + } + ] + }, + "enforcementMode": "DoNotEnforce" + } + ] } ] -} +} \ No newline at end of file diff --git a/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-Root-Default.jsonc b/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-Root-Default.jsonc index d2641af5..d6aa74f0 100644 --- a/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-Root-Default.jsonc +++ b/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-Root-Default.jsonc @@ -11,7 +11,8 @@ "logAnalytics_1": "", // Replace with your central Log Analytics workspace ID "emailSecurityContact": "", // Security contact email address for Microsoft Defender for Cloud "ascExportResourceGroupName": "asc-export", // Resource group to export Microsoft Defender for Cloud data to - "ascExportResourceGroupLocation": "" // Location of the resource group to export Microsoft Defender for Cloud data to + "ascExportResourceGroupLocation": "", // Location of the resource group to export Microsoft Defender for Cloud data to + "createResourceGroup": "true" // A new resource group will be created if set to true for ascExportResourceGroupName }, "children": [ { @@ -241,7 +242,7 @@ "nonComplianceMessages": [ { "policyDefinitionReferenceId": null, - "message": "Trust Launch must be used on supported virtual machines for enhanced security." + "message": "Trusted Launch must be used on supported virtual machines for enhanced security." } ] } diff --git a/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-WorkloadGuardRails.jsonc b/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-WorkloadGuardRails.jsonc index 040fe2cd..2033bdd5 100644 --- a/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-WorkloadGuardRails.jsonc +++ b/Scripts/CloudAdoptionFramework/policyAssignments/ALZ-WorkloadGuardRails.jsonc @@ -60,6 +60,23 @@ } ] }, + { + "nodeName": "BotServices", + "assignment": { + "name": "Enforce-GR-BotService", + "displayName": "Enforce recommended guardrails for Bot Service", + "description": "This initiative assignment enables additional ALZ guardrails for Bot Service." + }, + "definitionEntry": { + "policySetName": "Enforce-Guardrails-BotService", + "displayName": "Enforce recommended guardrails for Bot Service" + }, + "nonComplianceMessages": [ + { + "message": "Recommended guardrails must be enforced for Bot Service." + } + ] + }, { "nodeName": "CogServ", "assignment": { diff --git a/Scripts/Helpers/Build-ScopeTableForDeploymentRootScope.ps1 b/Scripts/Helpers/Build-ScopeTableForDeploymentRootScope.ps1 index 77fb7979..0c3d39ba 100644 --- a/Scripts/Helpers/Build-ScopeTableForDeploymentRootScope.ps1 +++ b/Scripts/Helpers/Build-ScopeTableForDeploymentRootScope.ps1 @@ -44,7 +44,12 @@ function Build-ScopeTableForDeploymentRootScope { foreach ($resourceGroup in $resourceGroups) { $subscriptionId = $resourceGroup.subscriptionId $id = $resourceGroup.id - $isExcluded = $false + if ($PacEnvironment.desiredState.excludeSubscriptions) { + $isExcluded = $true + } + else { + $isExcluded = $false + } $isInGlobalNotScope = $false foreach ($globalNotScope in $PacEnvironment.globalNotScopesResourceGroups) { if ($id -like $globalNotScope) { diff --git a/Scripts/Helpers/Build-ScopeTableForSubscription.ps1 b/Scripts/Helpers/Build-ScopeTableForSubscription.ps1 index 052be9f9..bdf48f1b 100644 --- a/Scripts/Helpers/Build-ScopeTableForSubscription.ps1 +++ b/Scripts/Helpers/Build-ScopeTableForSubscription.ps1 @@ -27,6 +27,9 @@ function Build-ScopeTableForSubscription { #region build scope details $thisNotScope = $null + if ($PacEnvironment.desiredState.excludeSubscriptions) { + $IsExcluded = $true + } if ($null -ne $ParentScopeDetails) { # the root node is never not in scope or excluded if (!$IsInGlobalNotScope) { diff --git a/Scripts/Helpers/Get-GlobalSettings.ps1 b/Scripts/Helpers/Get-GlobalSettings.ps1 index ff7cfeda..a7ac80e2 100644 --- a/Scripts/Helpers/Get-GlobalSettings.ps1 +++ b/Scripts/Helpers/Get-GlobalSettings.ps1 @@ -204,6 +204,7 @@ function Get-GlobalSettings { excludedPolicyDefinitions = @() excludedPolicySetDefinitions = @() excludedPolicyAssignments = @() + excludeSubscriptions = $false doNotDisableDeprecatedPolicies = $false } @@ -260,7 +261,7 @@ function Get-GlobalSettings { } else { $null = $excludedScopesList.Add($excludedScope) - if ($excludedScope.StartsWith("/subscriptions/")) { + if ($excludedScope.StartsWith("/subscriptions/") -and $desired.excludeSubscriptions -eq $false) { if ($excludedScope.Contains("/resourceGroups/", [System.StringComparison]::OrdinalIgnoreCase)) { $null = $globalExcludedScopesResourceGroupsList.Add($excludedScope) } @@ -299,6 +300,9 @@ function Get-GlobalSettings { } $desiredState.excludedPolicyAssignments = $excluded } + if ($desired.excludeSubscriptions) { + $desiredState.excludeSubscriptions = $true + } $deleteExpired = $desired.deleteExpiredExemptions if ($null -ne $deleteExpired) { Add-ErrorMessage -ErrorInfo $errorInfo -ErrorString "Global settings error: pacEnvironment $pacSelector field desiredState.deleteExpiredExemptions is deprecated. Remove it!" diff --git a/Scripts/Operations/Export-AzAdvertizerPolicy.ps1 b/Scripts/Operations/Export-AzAdvertizerPolicy.ps1 deleted file mode 100644 index 85f0dcb3..00000000 --- a/Scripts/Operations/Export-AzAdvertizerPolicy.ps1 +++ /dev/null @@ -1,394 +0,0 @@ -<# -.SYNOPSIS - Exports Azure Policy from https://www.azadvertizer.net/ and builds EPAC templates for deploying PolicySets and Assignments. - -.PARAMETER AzAdvertizerUrl - Mandatory url of the policy or policy set from AzAdvertizer. - -.PARAMETER OutputFolder - Output Folder. Defaults to the path 'Output'. - -.PARAMETER AutoCreateParameters - Automatically create parameters for Azure Policy Sets and Assignment Files. - -.PARAMETER UseBuiltIn - Default to using builtin policies rather than local versions. - -.PARAMETER Scope - Used to set scope value on each assignment file. - -.PARAMETER PacSelector - Used to set PacEnvironment for each assignment file. - -.PARAMETER OverwriteOutput - Used to Overwrite the contents of the output folder with each run. Helpful when running consecutively. - -.EXAMPLE - "./Out-AzAdvertizerPolicy.ps1" -AzAdvertizerUrl "https://www.azadvertizer.net/azpolicyinitiativesadvertizer/Deny-PublicPaaSEndpoints.html" -AutoCreateParameters $True -UseBuiltIn $True - Retrieves Policy from AzAdvertizer, auto creates parameters to be manipulated in the assignment and sets assignment and policy set to use built-in policies rather than self hosted. - -.EXAMPLE - "./Out-AzAdvertizerPolicy.ps1" -AzAdvertizerUrl "https://www.azadvertizer.net/azpolicyinitiativesadvertizer/Deny-PublicPaaSEndpoints.html" -PacSelector "EPAC-Prod" -Scope "/providers/Microsoft.Management/managementGroups/4fb849a3-3ff3-4362-af8e-45174cd753dd" - Retrieves Policy from AzAdvertizer, sets the PacSelector in the assignment files to "EPAC-Prod" and the scope to the management group path provided. - -.LINK - https://azure.github.io/enterprise-azure-policy-as-code/policy-exemptions/ -#> - -[CmdletBinding()] -param ( - [Parameter(Mandatory = $true, HelpMessage = "Mandatory url of the policy or policy set from AzAdvertizer")] - [string] $AzAdvertizerUrl, - - [Parameter(Mandatory = $false, HelpMessage = "Output Folder. Defaults to the path 'Output'")] - [string] $OutputFolder, - - [Parameter(Mandatory = $false, HelpMessage = "Automatically create parameters for Azure Policy Sets and Assignment Files")] - [bool] $AutoCreateParameters = $true, - - [Parameter(Mandatory = $false, HelpMessage = "Default to using builtin policies rather than local versions")] - [bool] $UseBuiltIn = $true, - - [Parameter(Mandatory = $false, HelpMessage = "Used to set scope value on each assignment file")] - [string] $Scope, - - [Parameter(Mandatory = $false, HelpMessage = "Used to set PacEnvironment for each assignment file")] - [string] $PacSelector, - - [Parameter(Mandatory = $false, HelpMessage = "Used to Overwrite the contents of the output folder with each run. Helpful when running consecutively")] - [bool] $OverwriteOutput = $true -) - -#region Policy - -# Validate session with Azure exists -if (-not (Get-AzContext)) { - $null = Connect-AzAccount -} - -# Pull HTML -try { - $restResponse = Invoke-RestMethod -Uri "$AzAdvertizerUrl" -Method Get -} -catch { - if ($_.Exception.Response.StatusCode -lt 200 -or $_.Exception.Response.StatusCode -ge 300) { - Write-Error "Error gathering Policy Information, please validate AzAdvertizerUrl is valid." -ErrorAction Stop - } -} - -# Determine if PolicySet or Policy Assignment -if ($AzAdvertizerUrl -match "azpolicyadvertizer") { - $policyType = "policyDefinitions" - # Pull Built-In Policies and Policy Sets - $builtInPolicies = Get-AzPolicyDefinition -Builtin - $builtInPolicyNames = $builtInPolicies.name -} -elseif ($AzAdvertizerUrl -match "azpolicyinitiativesadvertizer") { - $policyType = "policySetDefinitions" - # Pull Built-In Policies and Policy Sets - $builtInPolicies = Get-AzPolicyDefinition -Builtin - $builtInPolicyNames = $builtInPolicies.name - $builtInPolicySets = Get-AzPolicySetDefinition -Builtin - $builtInPolicySetNames = $builtInPolicySets.name -} -else { - Write-Error "AzAdvertizerUrl is not referencing Policy or Policy Initiative" -} - -# Parse HTML response to find copyEPACDef -Write-Information "" -InformationAction Continue -$response = $restResponse.split("function copyEPACDef() ")[1] - -try { - $response = $response.split("const obj = ")[1] - if ($null -eq $response) { - throw "Split resulted in null" - } -} -catch { - try { - $response = $restResponse.split("function copyEPACDef() ")[1] - $response = $response.split("const objEPAC = ")[1] - if ($null -eq $response) { - throw "Both splits resulted in null" - } - } - catch { - Write-Information "Both splits failed and resulted in null." -InformationAction Continue - } -} - -$response = $response.split("};")[0] + "}" - -# Convert from JSON to get object -$policyObject = $response | ConvertFrom-Json - -# Use Object to get policy name -$policyName = $policyObject.name - -# Get Display Name -$policyDisplayName = $policyObject.properties.displayName - -# Get Description -$policyDescription = $policyObject.properties.description - -# Export policy definition or policy set definition -$policyJson = $policyObject | ConvertTo-Json -Depth 100 - -# Overwrite Output folder -if ($OutputFolder -eq "") { - $OutputFolder = "Output" -} -if ($OverwriteOutput) { - if (Test-Path -Path "$OutputFolder/ALZ-Export") { - Remove-Item -Path "$OutputFolder/ALZ-Export" -Recurse -Force - } -} - -# Export File -if ($policyType -eq "policyDefinitions") { - if ($UseBuiltIn -and $builtInPolicyNames -contains $policyName) { - } - else { - # Check Output folder exists - if (-not (Test-Path -Path "$OutputFolder/ALZ-Export/$policyType")) { - # Create folder if does not exist - $null = New-Item -Path "$OutputFolder/ALZ-Export/$policyType" -ItemType Directory - } - Write-Information "Created Policy Definition - $policyName.jsonc" -InformationAction Continue - $policyJson | Out-File -FilePath "$OutputFolder/ALZ-Export/$policyType/$policyName.jsonc" - } -} -if ($policyType -eq "policySetDefinitions") { - if ($UseBuiltIn -and $builtInPolicySetNames -contains $policyName) { - } - else { - # Check Output folder exists - if (-not (Test-Path -Path "$OutputFolder/ALZ-Export/$policyType")) { - # Create folder if does not exist - $null = New-Item -Path "$OutputFolder/ALZ-Export/$policyType" -ItemType Directory - } - Write-Information "Created Policy Set Definition - $policyName.jsonc" -InformationAction Continue - $policyJson | Out-File -FilePath "$OutputFolder/ALZ-Export/$policyType/$policyName.jsonc" - } -} - -#region Policy Set individual Custom Definitions -if ($policyType -eq "policySetDefinitions") { - # Get policy names that are used - $policySetDefinitions = $policyObject.properties.policyDefinitions.policyDefinitionName - - foreach ($definition in $policySetDefinitions) { - if ($UseBuiltIn -and $builtInPolicyNames -contains $definition) { - } - else { - # Set URL - $tempURL = "https://www.azadvertizer.net/azpolicyadvertizer/$definition.html" - # Pull HTML - $tempRestResponse = Invoke-RestMethod -Uri $tempURL - - # Parse HTML response to find copyEPACDef - $tempResponse = $tempRestResponse.split("function copyEPACDef() ")[1] - - try { - $tempResponse = $tempResponse.split("const obj = ")[1] - if ($null -eq $tempResponse) { - throw "Split resulted in null" - } - } - catch { - try { - $tempResponse = $tempRestResponse.split("function copyEPACDef() ")[1] - $tempResponse = $tempResponse.split("const objEPAC = ")[1] - if ($null -eq $tempResponse) { - throw "Both splits resulted in null" - } - } - catch { - Write-Information "Error parsing response for $definition." -InformationAction Continue - } - } - - $tempResponse = $tempResponse.split("};")[0] + "}" - - # Convert from JSON to get object - $tempPolicyObject = $tempResponse | ConvertFrom-Json - - # Use Object to get policy name - $tempPolicyName = $tempPolicyObject.name - - # Export policy definition or policy set definition - $tempPolicyJson = $tempPolicyObject | ConvertTo-Json -Depth 100 - - # Check Output folder exists - if (-not (Test-Path -Path "$OutputFolder/ALZ-Export/policyDefinitions")) { - # Create folder if does not exist - $null = New-Item -Path "$OutputFolder/ALZ-Export/policyDefinitions" -ItemType Directory - } - - # Export File - Write-Information " - Created Policy Definition - $tempPolicyName.jsonc" -InformationAction Continue - $tempPolicyJson | Out-File -FilePath "$OutputFolder/ALZ-Export/policyDefinitions/$tempPolicyName.jsonc" - } - } -} - -$assignmentTemplate = @" -{ - "`$schema" : "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-assignment-schema.json", - "nodeName": "/Security/", - "definitionEntry": { - "policyName": "" - }, - "children": [ - { - "nodeName": "EPAC-Dev", - "assignment": { - "name": "", - "displayName": "", - "description": "" - }, - "enforcementMode": "Default", - "parameters": {}, - "scope": { - "EPAC-Dev": [ - "/providers/Microsoft.Management/managementGroups/EPAC-Dev" - ] - } - } - ] -} -"@ - -#region Assignment - -$assignmentObject = $assignmentTemplate | ConvertFrom-Json - -# Set name -if ($policyType -eq "policyDefinitions" -and !$UseBuiltIn) { - $assignmentObject.definitionEntry.policyName = "$policyName" -} -if ($policyType -eq "policyDefinitions" -and $UseBuiltIn) { - if ($builtInPolicyNames -contains $policyName) { - $assignmentObject.definitionEntry | Add-Member -MemberType NoteProperty -Name "policyId" -Value "/providers/Microsoft.Authorization/policyDefinitions/$policyName" - $assignmentObject.definitionEntry.PSObject.Properties.Remove("policyName") - } - else { - $assignmentObject.definitionEntry.policyName = "$policyName" - } -} -if ($policyType -eq "policySetDefinitions" -and !$UseBuiltIn) { - $assignmentObject.definitionEntry | Add-Member -MemberType NoteProperty -Name "policySetName" -Value "$policyName" - $assignmentObject.definitionEntry.PSObject.Properties.Remove("policyName") -} -if ($policyType -eq "policySetDefinitions" -and $UseBuiltIn) { - if ($builtInPolicySetNames -contains $policyName) { - $assignmentObject.definitionEntry | Add-Member -MemberType NoteProperty -Name "policySetId" -Value "/providers/Microsoft.Authorization/policySetDefinitions/$policyName" - $assignmentObject.definitionEntry.PSObject.Properties.Remove("policyName") - } - else { - $assignmentObject.definitionEntry | Add-Member -MemberType NoteProperty -Name "policySetName" -Value "$policyName" - $assignmentObject.definitionEntry.PSObject.Properties.Remove("policyName") - } -} - -# Set Assignment Properties -$tempGuid = New-Guid -$assignmentObject.children.assignment.name = $tempGuid.Guid.split("-")[-1] -$assignmentObject.children.assignment.displayName = "$policyDisplayName" -$assignmentObject.children.assignment.description = "$policyDescription" - -# Overwrite PacSelector is given -if ($PacSelector -and $PacSelector -ne "EPAC-Dev") { - $assignmentObject.children.scope | Add-Member -MemberType NoteProperty -Name "$PacSelector" -Value "" - $assignmentObject.children.scope.$PacSelector = $assignmentObject.children.scope.'EPAC-Dev' - $assignmentObject.children.scope.PSObject.Properties.Remove("EPAC-Dev") -} - -# Overwrite Scope if given -if ($Scope -and $PacSelector) { - $assignmentObject.children.scope.$PacSelector = "$Scope" -} -elseif ($Scope -and !$PacSelector) { - $assignmentObject.children.scope.'EPAC-Dev' = "$Scope" - -} - -#region AutoParameter -if ($AutoCreateParameters) { - if ($UseBuiltIn) { - if ($policyType -eq "policySetDefinitions" -and $builtInPolicySetNames -contains $policyName) { - $policyObject = $builtInPolicySets | Where-Object { $_.Name -eq $policyName } - $policySetParameters = $policyObject.parameter - $parameterNames = $policySetParameters | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name - foreach ($parameter in $parameterNames) { - $defaultEffect = "" - if ($null -eq $($policySetParameters).$($parameter).defaultValue) { - Write-Warning "Default Value not found for '$parameter' in PolicySet '$policyName'" -InformationAction Continue - } - else { - $defaultEffect = $($policySetParameters).$($parameter).defaultValue - } - $assignmentObject.children.parameters | Add-Member -MemberType NoteProperty -Name "$parameter" -Value $defaultEffect - } - } - elseif ($policyType -eq "policyDefinitions" -and $builtInPolicyNames -contains $policyName) { - $policyObject = $builtInPolicies | Where-Object { $_.Name -eq $policyName } - $policyParameters = $policyObject.parameter - $parameterNames = $policyParameters | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name - foreach ($parameter in $parameterNames) { - $defaultEffect = "" - if ($null -eq $($policyParameters).$($parameter).defaultValue) { - Write-Warning "Default Value not found for '$parameter' in PolicySet '$policyName'" -InformationAction Continue - } - else { - $defaultEffect = $($policyParameters).$($parameter).defaultValue - } - $assignmentObject.children.parameters | Add-Member -MemberType NoteProperty -Name "$parameter" -Value $defaultEffect - } - } - else { - $policySetParameters = $policyObject.properties.parameters - $parameterNames = $policySetParameters | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name - - foreach ($parameter in $parameterNames) { - $defaultEffect = "" - if ($null -eq $($policySetParameters).$($parameter).defaultValue) { - Write-Warning "Default Value not found for '$parameter' in PolicySet '$policyName'" -InformationAction Continue - } - else { - $defaultEffect = $($policySetParameters).$($parameter).defaultValue - } - $assignmentObject.children.parameters | Add-Member -MemberType NoteProperty -Name "$parameter" -Value $defaultEffect - } - } - } - else { - $policySetParameters = $policyObject.properties.parameters - $parameterNames = $policySetParameters | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name - - foreach ($parameter in $parameterNames) { - $defaultEffect = "" - if ($null -eq $($policySetParameters).$($parameter).defaultValue) { - Write-Warning "Default Value not found for '$parameter' in PolicySet '$policyName'" -InformationAction Continue - } - else { - $defaultEffect = $($policySetParameters).$($parameter).defaultValue - } - $assignmentObject.children.parameters | Add-Member -MemberType NoteProperty -Name "$parameter" -Value $defaultEffect - } - } -} - -# Convert from PSObject to Json -$assignmentJson = $assignmentObject | ConvertTo-Json -Depth 100 - -# Check Assignment Output folder exists -if (-not (Test-Path -Path "$OutputFolder/ALZ-Export/policyAssignments")) { - # Create folder if does not exist - $null = New-Item -Path "$OutputFolder/ALZ-Export/policyAssignments" -ItemType Directory -} - -# Export File -Write-Information "Created Policy Assignment - $policyName.jsonc" -InformationAction Continue -$assignmentJson | Out-File -FilePath "$OutputFolder/ALZ-Export/policyAssignments/$policyName.jsonc" diff --git a/Scripts/Operations/Export-PolicyToEPAC.ps1 b/Scripts/Operations/Export-PolicyToEPAC.ps1 new file mode 100644 index 00000000..c41ab95c --- /dev/null +++ b/Scripts/Operations/Export-PolicyToEPAC.ps1 @@ -0,0 +1,778 @@ +<# +.SYNOPSIS + Exports Policies and Policy Sets from Azure Portal as well as ALZ Policies and builds EPAC templates for deploying PolicySets and Assignments. + +.PARAMETER PolicyDefinitionId + Policy Definition ID - example: '/providers/Microsoft.Authorization/policyDefinitions/72f8cee7-2937-403d-84a1-a4e3e57f3c21' + +.PARAMETER PolicySetDefinitionId + "PolicySet Definition ID - example: '/providers/Microsoft.Authorization/policySetDefinitions/f08c57cd-dbd6-49a4-a85e-9ae77ac959b0' + +.PARAMETER ALZPolicyDefinitionId + ALZ Definition ID - example: 'Deny-APIM-TLS" + +.PARAMETER ALZPolicySetDefinitionId + ALZ PolicySet Definition ID - example: 'Enforce-Guardrails-OpenAI' + +.PARAMETER OutputFolder + Output Folder. Defaults to the path 'Output'. + +.PARAMETER AutoCreateParameters + Automatically create parameters for Azure Policy Sets and Assignment Files. + +.PARAMETER UseBuiltIn + Default to using builtin policies rather than local versions. + +.PARAMETER Scope + Used to set scope value on each assignment file. + +.PARAMETER PacSelector + Used to set PacEnvironment for each assignment file. + +.PARAMETER OverwriteOutput + Used to Overwrite the contents of the output folder with each run. Helpful when running consecutively. + +.EXAMPLE + "./Export-PolicyToEPAC.ps1" -PolicyDefinitionID "/providers/Microsoft.Authorization/policySetDefinitions/051cba44-2429-45b9-9649-46cec11c7119" -AutoCreateParameters $True -UseBuiltIn $True + Retrieves Policy from Azure Portal, auto creates parameters to be manipulated in the assignment and sets assignment and policy set to use built-in policies rather than self hosted. + +.EXAMPLE + "./Export-PolicyToEPAC.ps1" -ALZPolicySetDefinitionId "Enforce-Guardrails-OpenAI" -PacSelector "EPAC-Prod" -Scope "/providers/Microsoft.Management/managementGroups/4fb849a3-3ff3-4362-af8e-45174cd753dd" + Retrieves Policy from ALZ Repo, sets the PacSelector in the assignment files to "EPAC-Prod" and the scope to the management group path provided. +#> + +[CmdletBinding()] +param ( + [Parameter(Mandatory = $false, HelpMessage = "Policy Definition ID - example: '/providers/Microsoft.Authorization/policyDefinitions/051cba44-2429-45b9-9649-46cec11c7119'")] + [string] $PolicyDefinitionId, + + [Parameter(Mandatory = $false, HelpMessage = "PolicySet Definition ID - example: '/providers/Microsoft.Authorization/policySetDefinitions/f08c57cd-dbd6-49a4-a85e-9ae77ac959b0'")] + [string] $PolicySetDefinitionId, + + [Parameter(Mandatory = $false, HelpMessage = "ALZ Definition ID - example: 'Deny-APIM-TLS")] + [string] $ALZPolicyDefinitionId, + + [Parameter(Mandatory = $false, HelpMessage = "ALZ PolicySet Definition ID - example: 'Enforce-Guardrails-OpenAI'")] + [string] $ALZPolicySetDefinitionId, + + [Parameter(Mandatory = $false, HelpMessage = "Output Folder. Defaults to the path 'Output'")] + [string] $OutputFolder, + + [Parameter(Mandatory = $false, HelpMessage = "Automatically create parameters for Azure Policy Sets and Assignment Files")] + [bool] $AutoCreateParameters = $true, + + [Parameter(Mandatory = $false, HelpMessage = "Default to using builtin policies rather than local versions")] + [bool] $UseBuiltIn = $true, + + [Parameter(Mandatory = $false, HelpMessage = "Used to set scope value on each assignment file")] + [string] $Scope, + + [Parameter(Mandatory = $false, HelpMessage = "Used to set PacEnvironment for each assignment file")] + [string] $PacSelector, + + [Parameter(Mandatory = $false, HelpMessage = "Used to Overwrite the contents of the output folder with each run. Helpful when running consecutively")] + [bool] $OverwriteOutput = $true +) + +# Validate session with Azure exists +if (-not (Get-AzContext)) { + $null = Connect-AzAccount +} + +# Overwrite Output folder +if ($OutputFolder -eq "") { + $OutputFolder = "Output" +} +if ($OverwriteOutput) { + if (Test-Path -Path "$OutputFolder/Export") { + Remove-Item -Path "$OutputFolder/Export" -Recurse -Force + } +} + +Write-Information "" -InformationAction Continue + +#region PolicyDefinition +if ($PolicyDefinitionId) { + # Pull Built-In Policies + $builtInPolicies = Get-AzPolicyDefinition -Builtin + $builtInPolicyNames = $builtInPolicies.name + + # Create Policy Definition File + if ($PolicySetDefinitionId -match "/") { + $policyName = $PolicySetDefinitionId.split("/")[-1] + } + else { + $policyName = $PolicySetDefinitionId + } + + try { + $policyResponse = Get-AzPolicyDefinition -Id "$PolicyDefinitionId" | Select-Object -Property * + } + catch { + $policyResponse = Get-AzPolicyDefinition -Id "/providers/Microsoft.Authorization/policyDefinitions/$PolicyDefinitionId" | Select-Object -Property * + } + if ($null -eq $policyResponse) { + Write-Error "Policy Definition Not Found!" + } + + $policyType = "policyDefinitions" + $policyDisplayName = $policyResponse.displayName + $policyDescription = $policyResponse.description + $policyBuiltInType = $policyResponse.policyType + $orderedPolicy = [ordered]@{ + "displayName" = $policyResponse.displayName + "policyType" = $policyResponse.policyType + "mode" = $policyResponse.mode + "description" = $policyResponse.description + "metadata" = $policyResponse.metadata + "parameters" = $policyResponse.parameter + "policyRule" = $policyResponse.policyRule + } + $policyObject = [ordered]@{ + "`$schema" = "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-definition-schema.json" + "name" = $policyName + "properties" = $orderedPolicy + } + $policyObjectProperties = $policyObject.properties.metadata + if ($policyObjectProperties.pacOwnerId) { + $policyObjectProperties.PSObject.Properties.Remove('pacOwnerId') + } + if ($policyObjectProperties.deployedBy) { + $policyObjectProperties.PSObject.Properties.Remove('deployedBy') + } + if ($policyObjectProperties.createdBy) { + $policyObjectProperties.PSObject.Properties.Remove('createdBy') + } + if ($policyObjectProperties.createdOn) { + $policyObjectProperties.PSObject.Properties.Remove('createdOn') + } + if ($policyObjectProperties.updatedBy) { + $policyObjectProperties.PSObject.Properties.Remove('updatedBy') + } + if ($policyObjectProperties.updatedOn) { + $policyObjectProperties.PSObject.Properties.Remove('updatedOn') + } + $policyJson = $policyObject | ConvertTo-Json -Depth 100 + + if ($UseBuiltIn -and $policyResponse.policyType -eq "BuiltIn") { + } + else { + # Check Output folder exists + if (-not (Test-Path -Path "$OutputFolder/Export/policyDefinitions")) { + # Create folder if does not exist + $null = New-Item -Path "$OutputFolder/Export/policyDefinitions" -ItemType Directory + } + Write-Information "Created Policy Definition - $policyName.jsonc" -InformationAction Continue + $policyJson | Out-File -FilePath "$OutputFolder/Export/policyDefinitions/$policyName.jsonc" + } +} +#region PolicySetDefinition +elseif ($PolicySetDefinitionId) { + # Pull Built-In Policies and Policy Sets + $builtInPolicies = Get-AzPolicyDefinition -Builtin + $builtInPolicyNames = $builtInPolicies.name + $builtInPolicySets = Get-AzPolicySetDefinition -Builtin + $builtInPolicySetNames = $builtInPolicySets.name + + # Create PolicySet Definition File + if ($PolicySetDefinitionId -match "/") { + $policyName = $PolicySetDefinitionId.split("/")[-1] + } + else { + $policyName = $PolicySetDefinitionId + } + + try { + $policyResponse = Get-AzPolicySetDefinition -Id "$PolicySetDefinitionId" | Select-Object -Property * + } + catch { + $policyResponse = Get-AzPolicySetDefinition -Id "/providers/Microsoft.Authorization/policySetDefinitions/$PolicySetDefinitionId" | Select-Object -Property * + } + if ($null -eq $policyResponse) { + Write-Error "Policy Definition Not Found!" + } + + $policyType = "policySetDefinitions" + $policyDisplayName = $policyResponse.displayName + $policyDescription = $policyResponse.description + $policyBuiltInType = $policyResponse.policyType + $policyDefinitionArray = @() + foreach ($policyDef in $policyResponse.PolicyDefinition) { + $tempParam = $policyDef.parameters + $orderedPolicyDefinitions = [ordered]@{ + "policyDefinitionReferenceId" = "$($policyDef.policyDefinitionReferenceId)" + "PolicyDefinitionId" = "$($policyDef.PolicyDefinitionId)" + "definitionVersion" = "$($policyDef.definitionVersion)" + "parameters" = $tempParam + "groupNames" = "$($policyDef.groupNames)" + } + $policyDefinitionArray += $orderedPolicyDefinitions + } + $orderedPolicy = [ordered]@{ + "displayName" = $policyResponse.displayName + "policyType" = $policyResponse.policyType + "description" = $policyResponse.description + "metadata" = $policyResponse.metadata + "parameters" = $policyResponse.parameter + "policyDefinitions" = $policyDefinitionArray + "policyDefinitionGroups" = $policyResponse.PolicyDefinitionGroup + } + $policyObject = [ordered]@{ + "`$schema" = "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-set-definition-schema.json" + "name" = $policyName + "properties" = $orderedPolicy + } + $policyObjectProperties = $policyObject.properties.metadata + if ($policyObjectProperties.pacOwnerId) { + $policyObjectProperties.PSObject.Properties.Remove('pacOwnerId') + } + if ($policyObjectProperties.deployedBy) { + $policyObjectProperties.PSObject.Properties.Remove('deployedBy') + } + if ($policyObjectProperties.createdBy) { + $policyObjectProperties.PSObject.Properties.Remove('createdBy') + } + if ($policyObjectProperties.createdOn) { + $policyObjectProperties.PSObject.Properties.Remove('createdOn') + } + if ($policyObjectProperties.updatedBy) { + $policyObjectProperties.PSObject.Properties.Remove('updatedBy') + } + if ($policyObjectProperties.updatedOn) { + $policyObjectProperties.PSObject.Properties.Remove('updatedOn') + } + $policyJson = $policyObject | ConvertTo-Json -Depth 100 + + if ($UseBuiltIn -and $policyResponse.policyType -eq "BuiltIn") { + } + else { + # Check Output folder exists + if (-not (Test-Path -Path "$OutputFolder/Export/policySetDefinitions")) { + # Create folder if does not exist + $null = New-Item -Path "$OutputFolder/Export/policySetDefinitions" -ItemType Directory + } + Write-Information "Created PolicySet Definition - $policyName.jsonc" -InformationAction Continue + $policyJson | Out-File -FilePath "$OutputFolder/Export/policySetDefinitions/$policyName.jsonc" + } + + # Get individual policies within PolicySet + $policySetDefinitions = $policyObject.properties.policyDefinitions.PolicyDefinitionId + + foreach ($definition in $policySetDefinitions) { + if ($UseBuiltIn -and $builtInPolicyNames -contains $definition) { + } + else { + # Create Policy Definition File + $tempPolicyName = $definition.split("/")[-1] + + $tempPolicyResponse = Get-AzPolicyDefinition -Id "$definition" | Select-Object -Property * + $tempOrderedPolicy = [ordered]@{ + "displayName" = $tempPolicyResponse.displayName + "policyType" = $tempPolicyResponse.policyType + "mode" = $tempPolicyResponse.mode + "description" = $tempPolicyResponse.description + "metadata" = $tempPolicyResponse.metadata + "parameters" = $tempPolicyResponse.parameter + "policyRule" = $tempPolicyResponse.policyRule + } + $tempPolicyObject = [ordered]@{ + "`$schema" = "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-definition-schema.json" + "name" = $tempPolicyName + "properties" = $tempOrderedPolicy + } + $tempPolicyObjectProperties = $tempPolicyObject.properties.metadata + if ($tempPolicyObjectProperties.pacOwnerId) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('pacOwnerId') + } + if ($tempPolicyObjectProperties.deployedBy) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('deployedBy') + } + if ($tempPolicyObjectProperties.createdBy) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('createdBy') + } + if ($tempPolicyObjectProperties.createdOn) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('createdOn') + } + if ($tempPolicyObjectProperties.updatedBy) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('updatedBy') + } + if ($tempPolicyObjectProperties.updatedOn) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('updatedOn') + } + $tempPolicyJson = $tempPolicyObject | ConvertTo-Json -Depth 100 + + if ($UseBuiltIn -and $builtInPolicyNames -contains $tempPolicyName) { + } + else { + # Check Output folder exists + if (-not (Test-Path -Path "$OutputFolder/Export/policyDefinitions")) { + # Create folder if does not exist + $null = New-Item -Path "$OutputFolder/Export/policyDefinitions" -ItemType Directory + } + Write-Information " - Created Policy Definition - $tempPolicyName.jsonc" -InformationAction Continue + $tempPolicyJson | Out-File -FilePath "$OutputFolder/Export/policyDefinitions/$tempPolicyName.jsonc" + } + } + } +} +#region ALZ Definitions +elseif ($ALZPolicyDefinitionId) { + $GithubReleaseTag = Invoke-RestMethod -Method Get -Uri "https://api.github.com/repos/Azure/Enterprise-Scale/releases/latest" -ErrorAction Stop | Select-Object -ExpandProperty tag_name + $defaultPolicyURI = "https://raw.githubusercontent.com/Azure/Enterprise-Scale/$GithubReleaseTag/eslzArm/managementGroupTemplates/policyDefinitions/policies.json" + $rawContent = (Invoke-WebRequest -Uri $defaultPolicyURI).Content | ConvertFrom-Json + $variables = $rawContent.variables + [hashtable] $jsonPolicyDefsHash = @{} + if ($null -ne $variables) { + if ($variables -is [System.Collections.IDictionary]) { + if ($variables -is [hashtable]) { + return $variables + } + else { + foreach ($key in $variables.Keys) { + $null = $jsonPolicyDefsHash[$key] = $variables[$key] + } + } + } + elseif ($variables.psobject.Properties) { + foreach ($property in $variables.psobject.Properties) { + $jsonPolicyDefsHash[$property.Name] = $property.Value + } + } + } + $alzHash = @{} + $jsonPolicyDefsHash.GetEnumerator() | Foreach-Object { + if ($_.Key -match 'fxv') { + $type = $_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Type + if ($type -eq 'Microsoft.Authorization/policyDefinitions') { + $environments = ($_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Properties).metadata.alzCloudEnvironments + if ($environments -contains "AzureCloud") { + $name = $_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Name + $properties = $_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Properties + $alzHash[$name] = $properties + } + } + } + } + $policyName = $ALZPolicyDefinitionId + $policyType = "policyDefinitions" + $policyResponse = $alzHash[$ALZPolicyDefinitionId] + $policyDisplayName = $policyResponse.displayName + $policyDescription = $policyResponse.description + $policyBuiltInType = $policyResponse.policyType + $orderedPolicy = [ordered]@{ + "displayName" = $policyResponse.displayName + "policyType" = $policyResponse.policyType + "mode" = $policyResponse.mode + "description" = $policyResponse.description + "metadata" = $policyResponse.metadata + "parameters" = $policyResponse.parameters + "policyRule" = $policyResponse.policyRule + } + $policyObject = [ordered]@{ + "`$schema" = "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-definition-schema.json" + "name" = $ALZPolicyDefinitionId + "properties" = $orderedPolicy + } + + $policyJson = $policyObject | ConvertTo-Json -Depth 100 + $policyJson = $policyJson -replace "\[\[", "[" + + if ($UseBuiltIn -and $policyResponse.policyType -eq "BuiltIn") { + } + else { + # Check Output folder exists + if (-not (Test-Path -Path "$OutputFolder/Export/policyDefinitions")) { + # Create folder if does not exist + $null = New-Item -Path "$OutputFolder/Export/policyDefinitions" -ItemType Directory + } + Write-Information "Created Policy Definition - $policyName.jsonc" -InformationAction Continue + $policyJson | Out-File -FilePath "$OutputFolder/Export/policyDefinitions/$policyName.jsonc" + } +} +#region ALZ SetDefinitions +elseif ($ALZPolicySetDefinitionId) { + $builtInPolicies = Get-AzPolicyDefinition -Builtin + $builtInPolicyNames = $builtInPolicies.name + $GithubReleaseTag = Invoke-RestMethod -Method Get -Uri "https://api.github.com/repos/Azure/Enterprise-Scale/releases/latest" -ErrorAction Stop | Select-Object -ExpandProperty tag_name + $defaultPolicyURI = "https://raw.githubusercontent.com/Azure/Enterprise-Scale/$GithubReleaseTag/eslzArm/managementGroupTemplates/policyDefinitions/policies.json" + $rawContent = (Invoke-WebRequest -Uri $defaultPolicyURI).Content | ConvertFrom-Json + $variables = $rawContent.variables + [hashtable] $jsonPolicyDefsHash = @{} + if ($null -ne $variables) { + if ($variables -is [System.Collections.IDictionary]) { + if ($variables -is [hashtable]) { + return $variables + } + else { + foreach ($key in $variables.Keys) { + $null = $jsonPolicyDefsHash[$key] = $variables[$key] + } + } + } + elseif ($variables.psobject.Properties) { + foreach ($property in $variables.psobject.Properties) { + $jsonPolicyDefsHash[$property.Name] = $property.Value + } + } + } + $alzHash = @{} + $jsonPolicyDefsHash.GetEnumerator() | Foreach-Object { + if ($_.Key -match 'fxv') { + $type = $_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Type + if ($type -eq 'Microsoft.Authorization/policyDefinitions') { + $environments = ($_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Properties).metadata.alzCloudEnvironments + if ($environments -contains "AzureCloud") { + $name = $_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Name + $properties = $_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Properties + $alzHash[$name] = $properties + } + } + } + } + + $defaultPolicySetURI = "https://raw.githubusercontent.com/Azure/Enterprise-Scale/$GithubReleaseTag/eslzArm/managementGroupTemplates/policyDefinitions/initiatives.json" + $rawContent = (Invoke-WebRequest -Uri $defaultPolicySetURI).Content | ConvertFrom-Json + $variables = $rawContent.variables + [hashtable] $jsonPolicySetDefsHash = @{} + if ($null -ne $variables) { + if ($variables -is [System.Collections.IDictionary]) { + if ($variables -is [hashtable]) { + return $variables + } + else { + foreach ($key in $variables.Keys) { + $null = $jsonPolicySetDefsHash[$key] = $variables[$key] + } + } + } + elseif ($variables.psobject.Properties) { + foreach ($property in $variables.psobject.Properties) { + $jsonPolicySetDefsHash[$property.Name] = $property.Value + } + } + } + $alzSetHash = @{} + $jsonPolicySetDefsHash.GetEnumerator() | Foreach-Object { + if ($_.Key -match 'fxv') { + $type = $_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Type + if ($type -match 'Microsoft.Authorization/policySetDefinitions') { + $environments = ($_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Properties).metadata.alzCloudEnvironments + if ($environments -contains "AzureCloud") { + $name = $_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Name + $properties = $_.Value | ConvertFrom-Json | Select-Object -ExpandProperty Properties + $alzSetHash[$name] = $properties + } + } + } + } + + $policyName = $ALZPolicySetDefinitionId + $policyType = "policySetDefinitions" + $policyResponse = $alzSetHash[$ALZPolicySetDefinitionId] + $policyDisplayName = $policyResponse.displayName + $policyDescription = $policyResponse.description + $policyBuiltInType = $policyResponse.policyType + $policyDefinitionArray = @() + foreach ($policyDef in $policyResponse.PolicyDefinitions) { + $tempParam = $policyDef.parameters + $orderedPolicyDefinitions = [ordered]@{ + "policyDefinitionReferenceId" = "$($policyDef.policyDefinitionReferenceId)" + "PolicyDefinitionId" = "$($policyDef.PolicyDefinitionId)" + "definitionVersion" = "$($policyDef.definitionVersion)" + "parameters" = $tempParam + "groupNames" = "$($policyDef.groupNames)" + } + $policyDefinitionArray += $orderedPolicyDefinitions + } + $orderedPolicy = [ordered]@{ + "displayName" = $policyResponse.displayName + "policyType" = $policyResponse.policyType + "description" = $policyResponse.description + "metadata" = $policyResponse.metadata + "parameters" = $policyResponse.parameters + "policyDefinitions" = $policyDefinitionArray + "policyDefinitionGroups" = $policyResponse.PolicyDefinitionGroups + } + $policyObject = [ordered]@{ + "`$schema" = "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-set-definition-schema.json" + "name" = $policyName + "properties" = $orderedPolicy + } + $policyJson = $policyObject | ConvertTo-Json -Depth 100 + $policyJson = $policyJson -replace "\[\[", "[" + + if ($UseBuiltIn -and $policyResponse.policyType -eq "BuiltIn") { + } + else { + # Check Output folder exists + if (-not (Test-Path -Path "$OutputFolder/Export/policySetDefinitions")) { + # Create folder if does not exist + $null = New-Item -Path "$OutputFolder/Export/policySetDefinitions" -ItemType Directory + } + Write-Information "Created PolicySet Definition - $policyName.jsonc" -InformationAction Continue + $policyJson | Out-File -FilePath "$OutputFolder/Export/policySetDefinitions/$policyName.jsonc" + } + + # Get individual policies within PolicySet + $policySetDefinitions = $policyObject.properties.policyDefinitions.PolicyDefinitionId + + foreach ($definition in $policySetDefinitions) { + + if ($definition -match "/") { + $shortName = $definition.split("/")[-1] + } + + if ($UseBuiltIn -and $builtInPolicyNames -contains $definition -or $UseBuiltIn -and $builtInPolicyNames -contains $shortName) { + } + elseif (!$UseBuiltIn -and $builtInPolicyNames -contains $definition -or !$UseBuiltIn -and $builtInPolicyNames -contains $shortName) { + # Create Policy Definition File + $tempPolicyName = $definition.split("/")[-1] + + $tempPolicyResponse = Get-AzPolicyDefinition -Id "$definition" | Select-Object -Property * + $tempOrderedPolicy = [ordered]@{ + "displayName" = $tempPolicyResponse.displayName + "policyType" = $tempPolicyResponse.policyType + "mode" = $tempPolicyResponse.mode + "description" = $tempPolicyResponse.description + "metadata" = $tempPolicyResponse.metadata + "parameters" = $tempPolicyResponse.parameter + "policyRule" = $tempPolicyResponse.policyRule + } + $tempPolicyObject = [ordered]@{ + "`$schema" = "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-definition-schema.json" + "name" = $tempPolicyName + "properties" = $tempOrderedPolicy + } + $tempPolicyObjectProperties = $tempPolicyObject.properties.metadata + if ($tempPolicyObjectProperties.pacOwnerId) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('pacOwnerId') + } + if ($tempPolicyObjectProperties.deployedBy) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('deployedBy') + } + if ($tempPolicyObjectProperties.createdBy) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('createdBy') + } + if ($tempPolicyObjectProperties.createdOn) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('createdOn') + } + if ($tempPolicyObjectProperties.updatedBy) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('updatedBy') + } + if ($tempPolicyObjectProperties.updatedOn) { + $tempPolicyObjectProperties.PSObject.Properties.Remove('updatedOn') + } + $tempPolicyJson = $tempPolicyObject | ConvertTo-Json -Depth 100 + + if ($UseBuiltIn -and $builtInPolicyNames -contains $tempPolicyName) { + } + else { + # Check Output folder exists + if (-not (Test-Path -Path "$OutputFolder/Export/policyDefinitions")) { + # Create folder if does not exist + $null = New-Item -Path "$OutputFolder/Export/policyDefinitions" -ItemType Directory + } + Write-Information " - Created Policy Definition - $tempPolicyName.jsonc" -InformationAction Continue + $tempPolicyJson | Out-File -FilePath "$OutputFolder/Export/policyDefinitions/$tempPolicyName.jsonc" + } + } + else { + $tempPolicyName = $definition + $tempPolicyResponse = $alzHash[$definition] + if ($null -eq $tempPolicyResponse) { + $definition = $definition.split("/")[-1] + $tempPolicyName = $definition + $tempPolicyResponse = $alzHash[$definition] + } + $tempOrderedPolicy = [ordered]@{ + "displayName" = $tempPolicyResponse.displayName + "policyType" = $tempPolicyResponse.policyType + "mode" = $tempPolicyResponse.mode + "description" = $tempPolicyResponse.description + "metadata" = $tempPolicyResponse.metadata + "parameters" = $tempPolicyResponse.parameters + "policyRule" = $tempPolicyResponse.policyRule + } + $tempPolicyObject = [ordered]@{ + "`$schema" = "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-definition-schema.json" + "name" = $ALZPolicyDefinitionId + "properties" = $tempOrderedPolicy + } + $tempPolicyJson = $tempPolicyObject | ConvertTo-Json -Depth 100 + $tempPolicyJson = $tempPolicyJson -replace "\[\[", "[" + # Check Output folder exists + if (-not (Test-Path -Path "$OutputFolder/Export/policyDefinitions")) { + # Create folder if does not exist + $null = New-Item -Path "$OutputFolder/Export/policyDefinitions" -ItemType Directory + } + Write-Information " - Created Policy Definition - $tempPolicyName.jsonc" -InformationAction Continue + $tempPolicyJson | Out-File -FilePath "$OutputFolder/Export/policyDefinitions/$tempPolicyName.jsonc" + } + } +} +else { + Write-Error "Export-PolicyToEPAC requires at least one of the following: PolicyDefinitionId, PolicySetDefinitionId!" +} + + +#region Assignment +if ($policyObject) { + $assignmentTemplate = @" +{ + "`$schema" : "https://raw.githubusercontent.com/Azure/enterprise-azure-policy-as-code/main/Schemas/policy-assignment-schema.json", + "nodeName": "/Security/", + "definitionEntry": { + "policyName": "" + }, + "children": [ + { + "nodeName": "EPAC-Dev", + "assignment": { + "name": "", + "displayName": "", + "description": "" + }, + "enforcementMode": "Default", + "parameters": {}, + "scope": { + "EPAC-Dev": [ + "/providers/Microsoft.Management/managementGroups/EPAC-Dev" + ] + } + } + ] +} +"@ + + $assignmentObject = $assignmentTemplate | ConvertFrom-Json + + # Set name + if ($policyType -eq "policyDefinitions" -and !$UseBuiltIn) { + $assignmentObject.definitionEntry.policyName = "$policyName" + } + if ($policyType -eq "policyDefinitions" -and $UseBuiltIn) { + if ($policyBuiltInType -eq "BuiltIn") { + $assignmentObject.definitionEntry | Add-Member -MemberType NoteProperty -Name "policyId" -Value "/providers/Microsoft.Authorization/policyDefinitions/$policyName" + $assignmentObject.definitionEntry.PSObject.Properties.Remove("policyName") + } + else { + $assignmentObject.definitionEntry.policyName = "$policyName" + } + } + if ($policyType -eq "policySetDefinitions" -and !$UseBuiltIn) { + $assignmentObject.definitionEntry | Add-Member -MemberType NoteProperty -Name "policySetName" -Value "$policyName" + $assignmentObject.definitionEntry.PSObject.Properties.Remove("policyName") + } + if ($policyType -eq "policySetDefinitions" -and $UseBuiltIn) { + if ($policyBuiltInType -eq "BuiltIn") { + $assignmentObject.definitionEntry | Add-Member -MemberType NoteProperty -Name "policySetId" -Value "/providers/Microsoft.Authorization/policySetDefinitions/$policyName" + $assignmentObject.definitionEntry.PSObject.Properties.Remove("policyName") + } + else { + $assignmentObject.definitionEntry | Add-Member -MemberType NoteProperty -Name "policySetName" -Value "$policyName" + $assignmentObject.definitionEntry.PSObject.Properties.Remove("policyName") + } + } + + # Set Assignment Properties + $tempGuid = New-Guid + $assignmentObject.children.assignment.name = $tempGuid.Guid.split("-")[-1] + $assignmentObject.children.assignment.displayName = "$policyDisplayName" + $assignmentObject.children.assignment.description = "$policyDescription" + + # Overwrite PacSelector is given + if ($PacSelector -and $PacSelector -ne "EPAC-Dev") { + $assignmentObject.children.scope | Add-Member -MemberType NoteProperty -Name "$PacSelector" -Value "" + $assignmentObject.children.scope.$PacSelector = $assignmentObject.children.scope.'EPAC-Dev' + $assignmentObject.children.scope.PSObject.Properties.Remove("EPAC-Dev") + } + + # Overwrite Scope if given + if ($Scope -and $PacSelector) { + $assignmentObject.children.scope.$PacSelector = "$Scope" + } + elseif ($Scope -and !$PacSelector) { + $assignmentObject.children.scope.'EPAC-Dev' = "$Scope" + + } + + #region AutoParameter + if ($AutoCreateParameters) { + if ($UseBuiltIn) { + if ($policyType -eq "policySetDefinitions" -and $builtInPolicySetNames -contains $policyName) { + $policyObject = $builtInPolicySets | Where-Object { $_.Name -eq $policyName } + $policySetParameters = $policyObject.parameter + $parameterNames = $policySetParameters | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name + foreach ($parameter in $parameterNames) { + $defaultEffect = "" + if ($null -eq $($policySetParameters).$($parameter).defaultValue) { + Write-Warning "Default Value not found for '$parameter' in PolicySet '$policyName'" -InformationAction Continue + } + else { + $defaultEffect = $($policySetParameters).$($parameter).defaultValue + } + $assignmentObject.children.parameters | Add-Member -MemberType NoteProperty -Name "$parameter" -Value $defaultEffect + } + } + elseif ($policyType -eq "policyDefinitions" -and $builtInPolicyNames -contains $policyName) { + $policyObject = $builtInPolicies | Where-Object { $_.Name -eq $policyName } + $policyParameters = $policyObject.parameter + $parameterNames = $policyParameters | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name + foreach ($parameter in $parameterNames) { + $defaultEffect = "" + if ($null -eq $($policyParameters).$($parameter).defaultValue) { + Write-Warning "Default Value not found for '$parameter' in PolicySet '$policyName'" -InformationAction Continue + } + else { + $defaultEffect = $($policyParameters).$($parameter).defaultValue + } + $assignmentObject.children.parameters | Add-Member -MemberType NoteProperty -Name "$parameter" -Value $defaultEffect + } + } + else { + $policySetParameters = $policyObject.properties.parameters + $parameterNames = $policySetParameters | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name + + foreach ($parameter in $parameterNames) { + $defaultEffect = "" + if ($null -eq $($policySetParameters).$($parameter).defaultValue) { + Write-Warning "Default Value not found for '$parameter' in PolicySet '$policyName'" -InformationAction Continue + } + else { + $defaultEffect = $($policySetParameters).$($parameter).defaultValue + } + $assignmentObject.children.parameters | Add-Member -MemberType NoteProperty -Name "$parameter" -Value $defaultEffect + } + } + } + else { + $policySetParameters = $policyObject.properties.parameters + $parameterNames = $policySetParameters | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name + + foreach ($parameter in $parameterNames) { + $defaultEffect = "" + if ($null -eq $($policySetParameters).$($parameter).defaultValue) { + Write-Warning "Default Value not found for '$parameter' in PolicySet '$policyName'" -InformationAction Continue + } + else { + $defaultEffect = $($policySetParameters).$($parameter).defaultValue + } + $assignmentObject.children.parameters | Add-Member -MemberType NoteProperty -Name "$parameter" -Value $defaultEffect + } + } + } + + # Convert from PSObject to Json + $assignmentJson = $assignmentObject | ConvertTo-Json -Depth 100 + + # Check Assignment Output folder exists + if (-not (Test-Path -Path "$OutputFolder/Export/policyAssignments")) { + # Create folder if does not exist + $null = New-Item -Path "$OutputFolder/Export/policyAssignments" -ItemType Directory + } + + # Export File + Write-Information "Created Policy Assignment - $policyName.jsonc" -InformationAction Continue + Write-Information "" -InformationAction Continue + $assignmentJson | Out-File -FilePath "$OutputFolder/Export/policyAssignments/$policyName.jsonc" +} \ No newline at end of file