Skip to content

Commit a34e00a

Browse files
anwatherAnthony Watherston
and
Anthony Watherston
authored
Added new ALZ process and 892 fix (#945)
Co-authored-by: Anthony Watherston <Anthony.Watherston@microsoft.com>
1 parent c82f0e2 commit a34e00a

File tree

4 files changed

+370
-5
lines changed

4 files changed

+370
-5
lines changed

Docs/integrating-with-alz-library.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,20 @@ In both cases it is now recommended that if you have the default ALZ policies de
3232

3333
## Using the new Azure Landing Zone Library sync process
3434

35+
### Pre-requisites
36+
37+
To use the ALZ policies in an environment successfully there are some Azure Resources that need to be created. This is normally completed by using one of the ALZ accelerators to deploy the environment however if you have written your own code or modified the default deployment ensure you have the following resources in place to support the ALZ policies.
38+
39+
- Log Analytics workspace
40+
- DCR rules to support monitoring - [data collection rule templates](https://github.com/Azure/Enterprise-Scale/tree/main/eslzArm/resourceGroupTemplates)
41+
- User Assigned Managed Identity to support Azure Monitor Agent - [sample template](https://github.com/Azure/Enterprise-Scale/blob/main/eslzArm/resourceGroupTemplates/userAssignedIdentity.json)
42+
3543
### Create a policy default structure file
3644

37-
This file contains information that drives the sync process. The file includes management group IDs, default enforcement mode, and parameter values. It must be generated at least once before executing the sync process.
45+
This file contains information that drives the sync process. The file includes management group IDs, default enforcement mode, and parameter values. **It must be generated at least once before executing the sync process.**
3846

39-
1. Ensure that the EPAC module is up to date.
40-
2. Follow the example below to clone the library repository and create the default file. There are examples below on how to run this commnand.
47+
1. Ensure that the EPAC module is up to date - required minimum version to use these features is 10.9.0.
48+
2. Use to code to clone the library repository and create the default file. There are examples below on how to run this commnand - you will only need to run one of these depending on your requirements.
4149

4250
```ps1
4351
# Create a default file for ALZ policies using the latest version of the ALZ Library
@@ -89,7 +97,7 @@ Modify the default enforcement mode
8997

9098
The next command will generate policy assignments based on the values in this file so ensure they are correct for your environment.
9199

92-
4. Follow the example below to sync the policy files and update scopes and parameters based on the information in the previously created file.
100+
4. Use to code to sync the policy files and update scopes and parameters based on the information in the previously created file. There are examples below on how to run this command - you will only need to run one of these depending on your requirements. The files will be copied into their own folder to separate them from any definitions already in the repository.
93101

94102
```ps1
95103
# Sync the ALZ policies and assign to the "epac-dev" PAC environment.
@@ -111,4 +119,7 @@ Carefully review the generated policy assigments and ensure all parameter and sc
111119

112120
## Advanced Scenarios
113121

114-
Using the format of the Azure Landing Zones repository it is possible to extend the management groups defined and provide your own archetypes. You must maintain a local copy of the library for this purpose. Details will be provided at a later stage on how to customize this.
122+
Using the format of the Azure Landing Zones repository it is possible to extend the management groups defined and provide your own archetypes. You must maintain a local copy of the library for this purpose. Details will be provided at a later stage on how to customize this for different scenarios including:
123+
124+
- Modifying the management group structure (add new groups and archetypes)
125+
- Add/Remove policies from an archetype
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
Param(
2+
3+
[Parameter(Mandatory = $false)]
4+
[string] $DefinitionsRootFolder,
5+
6+
[ValidateSet('ALZ', 'FSI', 'AMBA', 'SLZ')]
7+
[string]$Type = 'ALZ',
8+
9+
[string]$LibraryPath,
10+
11+
[string]$Tag
12+
)
13+
14+
if ($DefinitionsRootFolder -eq "") {
15+
if ($null -eq $env:PAC_DEFINITIONS_FOLDER) {
16+
if ($ModuleRoot) {
17+
$DefinitionsRootFolder = "./Definitions"
18+
}
19+
else {
20+
$DefinitionsRootFolder = "$PSScriptRoot/../../Definitions"
21+
}
22+
}
23+
else {
24+
$DefinitionsRootFolder = $env:PAC_DEFINITIONS_FOLDER
25+
}
26+
}
27+
28+
if ($LibraryPath -eq "") {
29+
if ($Tag) {
30+
git clone --depth 1 --branch $Tag https://github.com/anwather/Azure-Landing-Zones-Library.git .\temp
31+
$LibraryPath = ".\temp"
32+
}
33+
else {
34+
git clone --depth 1 https://github.com/anwather/Azure-Landing-Zones-Library.git .\temp
35+
$LibraryPath = ".\temp"
36+
}
37+
}
38+
39+
40+
$jsonOutput = @{
41+
managementGroupNameMappings = @{}
42+
defaultParameterValues = @{}
43+
enforcementMode = "Default"
44+
}
45+
46+
# Get Management Group Names
47+
48+
$archetypeDefinitionFile = Get-Content -Path "$LibraryPath\platform\$($Type.ToLower())\architecture_definitions\$($Type.ToLower()).alz_architecture_definition.json" | `
49+
ConvertFrom-Json
50+
51+
foreach ($mg in $archetypeDefinitionFile.management_groups) {
52+
$obj = @{
53+
management_group_function = $mg.display_Name
54+
value = "/providers/Microsoft.Management/managementGroups/$($mg.id)"
55+
}
56+
57+
$jsonOutput.managementGroupNameMappings.Add($mg.id, $obj)
58+
}
59+
60+
# Build Parameter Values
61+
62+
$policyDefaultFile = Get-Content -Path "$LibraryPath\platform\$($Type.ToLower())\alz_policy_default_values.json" | ConvertFrom-Json
63+
64+
foreach ($parameter in $policyDefaultFile.defaults) {
65+
$obj = @{
66+
description = $parameter.description
67+
policy_assignment_name = $parameter.policy_assignments.policy_assignment_name
68+
parameters = @{
69+
parameter_name = $parameter.policy_assignments[0].parameter_names[0]
70+
value = ""
71+
}
72+
}
73+
74+
$jsonOutput.defaultParameterValues.Add($parameter.default_name, $obj)
75+
}
76+
77+
Out-File "$DefinitionsRootFolder\$($Type.ToLower()).policy_default_structure.json" -InputObject ($jsonOutput | ConvertTo-Json -Depth 10) -Encoding utf8 -Force
78+
79+
80+
if ($LibraryPath -eq ".\temp") {
81+
Remove-Item .\temp -Recurse -Force -ErrorAction SilentlyContinue
82+
}
83+
84+
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
Param(
2+
3+
[Parameter(Mandatory = $false)]
4+
[string] $DefinitionsRootFolder,
5+
6+
[ValidateSet("ALZ", "AMBA")]
7+
[string]$Type = "ALZ",
8+
9+
[Parameter(Mandatory = $true)]
10+
[string]$PacEnvironmentSelector,
11+
12+
[string]$LibraryPath
13+
)
14+
15+
if ($LibraryPath -eq "") {
16+
if ($Tag) {
17+
git clone --depth 1 --branch $Tag https://github.com/anwather/Azure-Landing-Zones-Library.git .\temp
18+
$LibraryPath = ".\temp"
19+
}
20+
else {
21+
git clone --depth 1 https://github.com/anwather/Azure-Landing-Zones-Library.git .\temp
22+
$LibraryPath = ".\temp"
23+
}
24+
}
25+
26+
if ($DefinitionsRootFolder -eq "") {
27+
if ($null -eq $env:PAC_DEFINITIONS_FOLDER) {
28+
if ($ModuleRoot) {
29+
$DefinitionsRootFolder = "./Definitions"
30+
}
31+
else {
32+
$DefinitionsRootFolder = "$PSScriptRoot/../../Definitions"
33+
}
34+
}
35+
else {
36+
$DefinitionsRootFolder = $env:PAC_DEFINITIONS_FOLDER
37+
}
38+
}
39+
40+
try {
41+
$telemetryEnabled = (Get-Content $DefinitionsRootFolder/global-settings.jsonc | ConvertFrom-Json).telemetryOptOut
42+
$deploymentRootScope = (Get-Content $DefinitionsRootFolder/global-settings.jsonc | ConvertFrom-Json).pacEnvironments[0]
43+
if (!($telemetryEnabled)) {
44+
Write-Information "Telemetry is enabled"
45+
Submit-EPACTelemetry -Cuapid "pid-adaa7564-1962-46e6-92b4-735e91f76d43" -DeploymentRootScope $deploymentRootScope
46+
}
47+
else {
48+
Write-Information "Telemetry is disabled"
49+
}
50+
}
51+
catch {}
52+
53+
New-Item -Path "$DefinitionsRootFolder\policyDefinitions" -ItemType Directory -Force -ErrorAction SilentlyContinue
54+
New-Item -Path "$DefinitionsRootFolder\policyDefinitions\$Type" -ItemType Directory -Force -ErrorAction SilentlyContinue
55+
New-Item -Path "$DefinitionsRootFolder\policySetDefinitions" -ItemType Directory -Force -ErrorAction SilentlyContinue
56+
New-Item -Path "$DefinitionsRootFolder\policySetDefinitions\$Type" -ItemType Directory -Force -ErrorAction SilentlyContinue
57+
New-Item -Path "$DefinitionsRootFolder\policyAssignments" -ItemType Directory -Force -ErrorAction SilentlyContinue
58+
New-Item -Path "$DefinitionsRootFolder\policyAssignments\$Type" -ItemType Directory -Force -ErrorAction SilentlyContinue
59+
60+
# Create policy definition objects
61+
62+
foreach ($file in Get-ChildItem -Path "$LibraryPath\platform\$($Type.ToLower())\policy_definitions" -Recurse -File -Include *.json) {
63+
$fileContent = Get-Content -Path $file.FullName -Raw | ConvertFrom-Json
64+
$baseTemplate = @{
65+
name = $fileContent.name
66+
properties = $fileContent.properties
67+
}
68+
$category = $baseTemplate.properties.Metadata.category
69+
if (!(Test-Path $DefinitionsRootFolder\policyDefinitions\$Type\$category)) {
70+
New-Item -Path $DefinitionsRootFolder\policyDefinitions\$Type\$category -ItemType Directory -Force -ErrorAction SilentlyContinue
71+
}
72+
$baseTemplate | Select-Object name, properties | ConvertTo-Json -Depth 50 | Out-File -FilePath $DefinitionsRootFolder\policyDefinitions\$Type\$category\$($fileContent.name).json -Force
73+
(Get-Content $DefinitionsRootFolder\policyDefinitions\$Type\$category\$($fileContent.name).json) -replace "\[\[", "[" | Set-Content $DefinitionsRootFolder\policyDefinitions\$Type\$category\$($fileContent.name).json
74+
}
75+
76+
# Create policy set definition objects
77+
78+
foreach ($file in Get-ChildItem -Path "$LibraryPath\platform\$($Type.ToLower())\policy_set_definitions" -Recurse -File -Include *.json) {
79+
$fileContent = Get-Content -Path $file.FullName -Raw | ConvertFrom-Json
80+
$baseTemplate = @{
81+
name = $fileContent.name
82+
properties = @{
83+
description = $fileContent.properties.description
84+
displayName = $fileContent.properties.displayName
85+
metadata = $fileContent.properties.metadata
86+
parameters = $fileContent.properties.parameters
87+
policyType = $fileContent.properties.policyType
88+
policyDefinitionGroups = $fileContent.properties.policyDefinitionGroups
89+
}
90+
}
91+
$policyDefinitions = @()
92+
# Fix the policyDefinitionIds for custom policies
93+
foreach ($policyDefinition in $fileContent.properties.policyDefinitions) {
94+
$obj = @{
95+
parameters = $policyDefinition.parameters
96+
groupNames = $policyDefinition.groupNames
97+
policyDefinitionReferenceId = $policyDefinition.policyDefinitionReferenceId
98+
}
99+
if ($policyDefinition.policyDefinitionId -match "managementGroups") {
100+
$obj.Add("policyDefinitionName", $policyDefinition.policyDefinitionId.split("/")[-1])
101+
}
102+
else {
103+
$obj.Add("policyDefinitionId", $policyDefinition.policyDefinitionId)
104+
}
105+
$policyDefinitions += $obj
106+
}
107+
$baseTemplate.properties.policyDefinitions = $policyDefinitions
108+
109+
$category = $baseTemplate.properties.Metadata.category
110+
if (!(Test-Path $DefinitionsRootFolder\policySetDefinitions\$Type\$category)) {
111+
New-Item -Path $DefinitionsRootFolder\policySetDefinitions\$Type\$category -ItemType Directory -Force -ErrorAction SilentlyContinue
112+
}
113+
$baseTemplate | Select-Object name, properties | ConvertTo-Json -Depth 50 | Out-File -FilePath $DefinitionsRootFolder\policySetDefinitions\$Type\$category\$($fileContent.name).json -Force
114+
(Get-Content $DefinitionsRootFolder\policySetDefinitions\$Type\$category\$($fileContent.name).json) -replace "\[\[", "[" | Set-Content $DefinitionsRootFolder\policySetDefinitions\$Type\$category\$($fileContent.name).json
115+
(Get-Content $DefinitionsRootFolder\policySetDefinitions\$Type\$category\$($fileContent.name).json) -replace "variables\('scope'\)", "'/providers/Microsoft.Management/managementGroups/$managementGroupId'" | Set-Content $DefinitionsRootFolder\policySetDefinitions\$Type\$category\$($fileContent.name).json
116+
(Get-Content $DefinitionsRootFolder\policySetDefinitions\$Type\$category\$($fileContent.name).json) -replace "', '", "" | Set-Content $DefinitionsRootFolder\policySetDefinitions\$Type\$category\$($fileContent.name).json
117+
(Get-Content $DefinitionsRootFolder\policySetDefinitions\$Type\$category\$($fileContent.name).json) -replace "\[concat\(('(.+)')\)\]", "`$2" | Set-Content $DefinitionsRootFolder\policySetDefinitions\$Type\$category\$($fileContent.name).json
118+
}
119+
120+
# Create assignment objects
121+
122+
try {
123+
$structureFile = Get-Content .\$Type.policy_default_structure.json -ErrorAction Stop | ConvertFrom-Json
124+
switch ($structureFile.enforcementMode) {
125+
"Default" { $enforcementModeText = "must" }
126+
"DoNotEnforce" { $enforcementModeText = "should" }
127+
}
128+
}
129+
catch {
130+
Write-Host "No policy default structure file found. Please run Create-ALZPolicyDefaultStructure.ps1 first."
131+
exit
132+
}
133+
134+
foreach ($file in Get-ChildItem -Path "$LibraryPath\platform\$($Type.ToLower())\archetype_definitions" -Recurse -File -Include *.json) {
135+
$archetypeContent = Get-Content -Path $file.FullName -Raw | ConvertFrom-Json
136+
foreach ($requiredAssignment in $archetypeContent.policy_assignments) {
137+
switch ($Type) {
138+
"ALZ" { $fileContent = Get-ChildItem -Path $LibraryPath\platform\$Type\policy_assignments | Where-Object { $_.BaseName.Split(".")[0] -eq $requiredAssignment } | Get-Content -Raw | ConvertFrom-Json }
139+
"AMBA" { $fileContent = Get-ChildItem -Path $LibraryPath\platform\$Type\policy_assignments | Where-Object { $_.BaseName.Split(".")[0].Replace("_", "-") -eq $requiredAssignment } | Get-Content -Raw | ConvertFrom-Json }
140+
default { $fileContent = Get-ChildItem -Path $LibraryPath\platform\$Type\policy_assignments | Where-Object { $_.BaseName.Split(".")[0] -eq $requiredAssignment } | Get-Content -Raw | ConvertFrom-Json }
141+
}
142+
143+
144+
$baseTemplate = @{
145+
nodeName = "$($archetypeContent.name)/$($fileContent.name)"
146+
assignment = @{
147+
name = $fileContent.Name
148+
displayName = $fileContent.properties.displayName
149+
description = $fileContent.properties.description
150+
}
151+
definitionEntry = @{
152+
displayName = $fileContent.properties.displayName
153+
}
154+
parameters = @{}
155+
enforcementMode = $structureFile.enforcementMode
156+
}
157+
158+
# Definition Version
159+
if ($null -ne $fileContent.properties.definitionVersion) {
160+
$baseTemplate.Add("definitionVersion", $fileContent.properties.definitionVersion)
161+
}
162+
163+
# Definition Entry
164+
if ($fileContent.properties.policyDefinitionId -match "placeholder.+policySetDefinition") {
165+
$baseTemplate.definitionEntry.Add("policySetName", ($fileContent.properties.policyDefinitionId).Split("/")[-1])
166+
}
167+
elseif ($fileContent.properties.policyDefinitionId -match "placeholder.+policyDefinition") {
168+
$baseTemplate.definitionEntry.Add("policyName", ($fileContent.properties.policyDefinitionId).Split("/")[-1])
169+
}
170+
else {
171+
if ($fileContent.properties.policyDefinitionId -match "policySetDefinitions") {
172+
$baseTemplate.definitionEntry.Add("policySetId", ($fileContent.properties.policyDefinitionId))
173+
}
174+
else {
175+
$baseTemplate.definitionEntry.Add("policyId", ($fileContent.properties.policyDefinitionId))
176+
}
177+
178+
}
179+
180+
#Scope
181+
$scopeTrim = $file.BaseName.split(".")[0]
182+
if ($scopeTrim -eq "root") {
183+
$scopeTrim = "alz"
184+
}
185+
if ($scopeTrim -eq "landing_zones") {
186+
$scopeTrim = "landingzones"
187+
}
188+
$scope = @{
189+
$PacEnvironmentSelector = @(
190+
$structureFile.managementGroupNameMappings.$scopeTrim.value
191+
)
192+
}
193+
$baseTemplate.Add("scope", $scope)
194+
195+
# Base Parameters
196+
if ($fileContent.name -ne "Deploy-Private-DNS-Zones") {
197+
foreach ($parameter in $fileContent.properties.parameters.psObject.Properties.Name) {
198+
$baseTemplate.parameters.Add($parameter, $fileContent.properties.parameters.$parameter.value)
199+
}
200+
}
201+
202+
# Non-compliance messages
203+
if ($null -ne $fileContent.properties.nonComplianceMessages) {
204+
$obj = @(
205+
@{
206+
message = $fileContent.properties.nonComplianceMessages.message -replace "{enforcementMode}", $enforcementModeText
207+
}
208+
)
209+
$baseTemplate.Add("nonComplianceMessages", $obj)
210+
}
211+
212+
213+
# Check for explicit parameters
214+
if ($fileContent.name -ne "Deploy-Private-DNS-Zones") {
215+
foreach ($key in $structureFile.defaultParameterValues.psObject.Properties.Name) {
216+
if ($structureFile.defaultParameterValues.$key.policy_assignment_name -eq $fileContent.name) {
217+
$keyName = $structureFile.defaultParameterValues.$key.parameters.parameter_name
218+
$baseTemplate.parameters.$keyName = $structureFile.defaultParameterValues.$key.parameters.value
219+
}
220+
}
221+
}
222+
else {
223+
$dnsZoneRegion = $structureFile.defaultParameterValues.private_dns_zone_region.parameters.value
224+
$dnzZoneSubscription = $structureFile.defaultParameterValues.private_dns_zone_subscription_id.parameters.value
225+
$dnzZoneResourceGroupName = $structureFile.defaultParameterValues.private_dns_zone_resource_group_name.parameters.value
226+
foreach ($parameter in $fileContent.properties.parameters.psObject.Properties.Name) {
227+
$value = "/subscriptions/$dnzZoneSubscription/resourceGroups/$dnzZoneResourceGroupName/providers/Microsoft.Network/privateDnsZones/$($fileContent.properties.parameters.$parameter.value.split("/")[-1])"
228+
#$value = $fileContent.properties.parameters.$parameter.value -replace "00000000-0000-0000-0000-000000000000", $dnzZoneSubscription -replace "placeholder", $dnzZoneResourceGroupName
229+
$baseTemplate.parameters.Add($parameter, $value)
230+
}
231+
}
232+
233+
234+
$category = $structureFile.managementGroupNameMappings.$scopeTrim.management_group_function
235+
if (!(Test-Path $DefinitionsRootFolder\policyAssignments\$Type\$category)) {
236+
New-Item -Path $DefinitionsRootFolder\policyAssignments\$Type\$category -ItemType Directory -Force -ErrorAction SilentlyContinue
237+
}
238+
$baseTemplate | Select-Object nodeName, assignment, definitionEntry, definitionVersion, enforcementMode, parameters, nonComplianceMessages, scope | ConvertTo-Json -Depth 50 | Out-File -FilePath $DefinitionsRootFolder\policyAssignments\$Type\$category\$($fileContent.name).json -Force
239+
(Get-Content $DefinitionsRootFolder\policyAssignments\$Type\$category\$($fileContent.name).json) -replace "\[\[", "[" | Set-Content $DefinitionsRootFolder\policyAssignments\$Type\$category\$($fileContent.name).json
240+
if ($fileContent.name -eq "Deploy-Private-DNS-Zones") {
241+
(Get-Content $DefinitionsRootFolder\policyAssignments\$Type\$category\$($fileContent.name).json) -replace "\.ne\.", ".$dnsZoneRegion." | Set-Content $DefinitionsRootFolder\policyAssignments\$Type\$category\$($fileContent.name).json
242+
}
243+
}
244+
245+
246+
}
247+
248+
if ($LibraryPath -eq ".\temp") {
249+
Remove-Item .\temp -Recurse -Force -ErrorAction SilentlyContinue
250+
}
251+

0 commit comments

Comments
 (0)