diff --git a/config/variables/CanadaESLZ-main.yml b/config/variables/CanadaESLZ-main.yml index 0d22f73e..d24941dd 100644 --- a/config/variables/CanadaESLZ-main.yml +++ b/config/variables/CanadaESLZ-main.yml @@ -58,7 +58,7 @@ variables: } # Logging - var-logging-region: $(deploymentRegion) + var-logging-region: canadacentral var-logging-managementGroupId: pubsecPlatformManagement var-logging-subscriptionId: bc0a4f9f-07fa-4284-b1bd-fbad38578d3a var-logging-configurationFileName: logging.parameters.json @@ -67,7 +67,7 @@ variables: var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix: pubsecnsg # Hub Networking - var-hubnetwork-region: $(deploymentRegion) + var-hubnetwork-region: canadacentral var-hubnetwork-managementGroupId: pubsecPlatformConnectivity var-hubnetwork-subscriptionId: ed7f4eed-9010-4227-b115-2a5e37728f27 diff --git a/scripts/deployments/E2E.ps1 b/scripts/deployments/E2E.ps1 new file mode 100644 index 00000000..4cb83f77 --- /dev/null +++ b/scripts/deployments/E2E.ps1 @@ -0,0 +1,174 @@ +#Requires -Modules Az, powershell-yaml + +. ".\Functions\EnvironmentContext.ps1" +. ".\Functions\ManagementGroups.ps1" +. ".\Functions\Roles.ps1" +. ".\Functions\Logging.ps1" +. ".\Functions\Policy.ps1" +. ".\Functions\HubNetworkWithNVA.ps1" +. ".\Functions\HubNetworkWithAzureFirewall.ps1" +. ".\Functions\Subscriptions.ps1" + +$EnvironmentName = "CanadaESLZ-main" +$WorkingDirectory = Resolve-Path "../.." + +# Replace the Tenant ID with the GUID for your Azure Active Directory instance. +# It can be found through https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview +$AzureADTenantId = "343ddfdb-bef5-46d9-99cf-ed67d5948783" + +$Features = @{ + # Prompt to login to Azure AD and set the context for Azure deployments + PromptForLogin = $false + + # Resource Organization + DeployManagementGroups = $false + + # Access Control + DeployRoles = $false + + # Logging + DeployLogging = $false + + # Guardrail & Compliance + DeployPolicy = $false + + # Hub Networking - With Network Virtual Appliance + DeployHubNetworkWithNVA = $false + + # Hub Networking - With Azure Firewall + DeployHubNetworkWithAzureFirewall = $false +} + +Write-Output "Features configured for deployment:" +$Features + +# Az Login +if ($Features.PromptForLogin) { + Connect-AzAccount ` + -UseDeviceAuthentication ` + -TenantId $AzureADTenantId +} + +# Set Azure Landing Zones Context +$Context = New-EnvironmentContext -Environment $EnvironmentName -WorkingDirectory $WorkingDirectory + +# Deploy Management Groups +if ($Features.DeployManagementGroups) { + Set-ManagementGroups ` + -Context $Context ` + -ManagementGroupHierarchy $Context.ManagementGroupHierarchy +} + +# Deploy Roles +if ($Features.DeployRoles) { + Set-Roles ` + -Context $Context ` + -RolesDirectory $Context.RolesDirectory ` + -ManagementGroupId $Context.TopLevelManagementGroupId +} + +# Deploy Logging +if ($Features.DeployLogging) { + Set-Logging ` + -Region $Context.Variables['var-logging-region'] ` + -ManagementGroupId $Context.Variables['var-logging-managementGroupId'] ` + -SubscriptionId $Context.Variables['var-logging-subscriptionId'] ` + -ConfigurationFilePath "$($Context.LoggingDirectory)/$($Context.Variables['var-logging-configurationFileName'])" +} + +# Deploy Policy +if ($Features.DeployPolicy) { + # Get Logging information using logging config file + $LoggingConfiguration = Get-LoggingConfiguration ` + -ConfigurationFilePath "$($Context.LoggingDirectory)/$($Context.Variables['var-logging-configurationFileName'])" ` + -SubscriptionId $Context.Variables['var-logging-subscriptionId'] + + # Custom Policy Definitions + Set-Policy-Definitions ` + -PolicyDefinitionsDirectory $Context.PolicyCustomDefinitionDirectory ` + -ManagementGroupId $Context.TopLevelManagementGroupId + + # Custom Policy Set Definitions + Set-PolicySet-Defintions ` + -Context $Context ` + -PolicySetDefinitionsDirectory $Context.PolicySetCustomDefinitionDirectory ` + -ManagementGroupId $Context.TopLevelManagementGroupId ` + -PolicySetDefinitionNames $('AKS', 'DefenderForCloud', 'LogAnalytics', 'Network', 'DNSPrivateEndpoints', 'Tags') + + # Built In Policy Set Assignments + Set-PolicySet-Assignments ` + -Context $Context ` + -PolicySetAssignmentsDirectory $Context.PolicySetBuiltInAssignmentsDirectory ` + -PolicySetAssignmentManagementGroupId $Context.TopLevelManagementGroupId ` + -PolicySetAssignmentNames $('asb', 'nist80053r4', 'nist80053r5', 'pbmm', 'cis-msft-130', 'fedramp-moderate', 'hitrust-hipaa', 'location') ` + -LogAnalyticsWorkspaceResourceGroupName $LoggingConfiguration.ResourceGroupName ` + -LogAnalyticsWorkspaceResourceId $LoggingConfiguration.LogAnalyticsWorkspaceResourceId ` + -LogAnalyticsWorkspaceId $LoggingConfiguration.LogAnalyticsWorkspaceId ` + -LogAnalyticsWorkspaceRetentionInDays $LoggingConfiguration.LogRetentionInDays + + # Custom Policy Sets Assignments + Set-PolicySet-Assignments ` + -Context $Context ` + -PolicySetAssignmentsDirectory $Context.PolicySetCustomAssignmentsDirectory ` + -PolicySetAssignmentManagementGroupId $Context.TopLevelManagementGroupId ` + -PolicySetAssignmentNames $('AKS', 'DefenderForCloud', 'LogAnalytics', 'Network', 'Tags') ` + -LogAnalyticsWorkspaceResourceGroupName $LoggingConfiguration.ResourceGroupName ` + -LogAnalyticsWorkspaceResourceId $LoggingConfiguration.LogAnalyticsWorkspaceResourceId ` + -LogAnalyticsWorkspaceId $LoggingConfiguration.LogAnalyticsWorkspaceId ` + -LogAnalyticsWorkspaceRetentionInDays $LoggingConfiguration.LogRetentionInDays +} + +# Deploy Hub Networking with NVA +if ($Features.DeployHubNetworkWithNVA) { + # Get Logging information using logging config file + $LoggingConfiguration = Get-LoggingConfiguration ` + -ConfigurationFilePath "$($Context.LoggingDirectory)/$($Context.Variables['var-logging-configurationFileName'])" ` + -SubscriptionId $Context.Variables['var-logging-subscriptionId'] + + Set-HubNetwork-With-NVA ` + -Context $Context ` + -Region $Context.Variables['var-hubnetwork-region'] ` + -ManagementGroupId $Context.Variables['var-hubnetwork-managementGroupId'] ` + -SubscriptionId $Context.Variables['var-hubnetwork-subscriptionId'] ` + -ConfigurationFilePath "$($Context.NetworkingDirectory)/$($Context.Variables['var-hubnetwork-nva-configurationFileName'])" ` + -LogAnalyticsWorkspaceResourceId $LoggingConfiguration.LogAnalyticsWorkspaceResourceId +} + +# Hub Networking with Azure Firewall +if ($Features.DeployHubNetworkWithAzureFirewall) { + # Get Logging information using logging config file + $LoggingConfiguration = Get-LoggingConfiguration ` + -ConfigurationFilePath "$($Context.LoggingDirectory)/$($Context.Variables['var-logging-configurationFileName'])" ` + -SubscriptionId $Context.Variables['var-logging-subscriptionId'] + + # Create Azure Firewall Policy + Set-AzureFirewallPolicy ` + -Region $Context.Variables['var-hubnetwork-region'] ` + -SubscriptionId $Context.Variables['var-hubnetwork-subscriptionId'] ` + -ConfigurationFilePath "$($Context.NetworkingDirectory)/$($Context.Variables['var-hubnetwork-azfwPolicy-configurationFileName'])" + + # Retrieve Azure Firewall Policy + $AzureFirewallPolicyConfiguration = Get-AzureFirewallPolicy ` + -SubscriptionId $Context.Variables['var-hubnetwork-subscriptionId'] ` + -ConfigurationFilePath "$($Context.NetworkingDirectory)/$($Context.Variables['var-hubnetwork-azfwPolicy-configurationFileName'])" + + # Create Hub Networking with Azure Firewall + Set-HubNetwork-With-AzureFirewall ` + -Context $Context ` + -Region $Context.Variables['var-hubnetwork-region'] ` + -ManagementGroupId $Context.Variables['var-hubnetwork-managementGroupId'] ` + -SubscriptionId $Context.Variables['var-hubnetwork-subscriptionId'] ` + -ConfigurationFilePath "$($Context.NetworkingDirectory)/$($Context.Variables['var-hubnetwork-azfw-configurationFileName'])" ` + -AzureFirewallPolicyResourceId $AzureFirewallPolicyConfiguration.AzureFirewallPolicyResourceId ` + -LogAnalyticsWorkspaceResourceId $LoggingConfiguration.LogAnalyticsWorkspaceResourceId +} + +<# + +# Subscriptions +Set-Subscriptions ` + -Region "canadacentral" ` + -SubscriptionIds $("4f9", "ec6") ` + -LogAnalyticsWorkspaceResourceId $LoggingConfiguration.LogAnalyticsWorkspaceResourceId + +#> \ No newline at end of file diff --git a/scripts/deployments/Functions/EnvironmentContext.ps1 b/scripts/deployments/Functions/EnvironmentContext.ps1 new file mode 100644 index 00000000..2a6b66ac --- /dev/null +++ b/scripts/deployments/Functions/EnvironmentContext.ps1 @@ -0,0 +1,48 @@ +#Requires -Modules powershell-yaml + +Import-Module powershell-yaml + +function New-EnvironmentContext { + param ( + [Parameter(Mandatory = $true)] + [string] $WorkingDirectory, + + [Parameter(Mandatory = $true)] + [string] $Environment + ) + + $EnvironmentConfigurationYamlFilePath = "$WorkingDirectory/config/variables/$Environment.yml" + + # Load main environment variables file as YAML + $EnvironmentConfiguration = Get-Content $EnvironmentConfigurationYamlFilePath | ConvertFrom-Yaml + $Variables = $EnvironmentConfiguration.variables + + # Retrieve the management group hierarchy variable as JSON + $ManagementGroupHierarchy = $Variables['var-managementgroup-hierarchy'] | ConvertFrom-Json + + $PolicyDirectory = "$WorkingDirectory/policy" + + # Create a new context object + return [PSCustomObject]@{ + WorkingDirectory = $WorkingDirectory + + RolesDirectory = "$WorkingDirectory/roles" + + PolicyCustomDefinitionDirectory = "$PolicyDirectory/custom/definitions/policy" + PolicySetCustomDefinitionDirectory = "$PolicyDirectory/custom/definitions/policyset" + PolicySetCustomAssignmentsDirectory = "$PolicyDirectory/custom/assignments" + PolicySetBuiltInAssignmentsDirectory = "$PolicyDirectory/builtin/assignments" + + LoggingDirectory = "$WorkingDirectory/config/logging/$Environment" + NetworkingDirectory = "$WorkingDirectory/config/networking/$Environment" + + Variables = $Variables + ManagementGroupHierarchy = $ManagementGroupHierarchy + + # Identify the top level management group (the first child underneath Tenant Root Group) + TopLevelManagementGroupId = $ManagementGroupHierarchy.children[0].id + + # TODO: Retrieve from common.yml + DeploymentRegion = "canadacentral" + } +} \ No newline at end of file diff --git a/scripts/deployments/Functions/HubNetworkWithAzureFirewall.ps1 b/scripts/deployments/Functions/HubNetworkWithAzureFirewall.ps1 new file mode 100644 index 00000000..a6af024c --- /dev/null +++ b/scripts/deployments/Functions/HubNetworkWithAzureFirewall.ps1 @@ -0,0 +1,174 @@ +function Get-AzureFirewallPolicy { + param ( + [Parameter(Mandatory = $true)] + [String]$ConfigurationFilePath, + + [Parameter(Mandatory = $true)] + [String]$SubscriptionId + ) + + Set-AzContext -Subscription $SubscriptionId + + $Configuration = Get-Content $ConfigurationFilePath | ConvertFrom-Json + + $policy = Get-AzFirewallPolicy ` + -ResourceGroupName $Configuration.parameters.resourceGroupName.value ` + -Name $Configuration.parameters.policyName.value + + return [PSCustomObject]@{ + AzureFirewallPolicyResourceId = $policy.Id + } +} + +function Set-AzureFirewallPolicy { + param ( + [Parameter(Mandatory = $true)] + [String]$Region, + + [Parameter(Mandatory = $true)] + [String]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [String]$ConfigurationFilePath + ) + + Set-AzContext -Subscription $SubscriptionId + + Write-Output "Deploying to $SubscriptionId in $Region using $ConfigurationFilePath" + + New-AzSubscriptionDeployment ` + -Name "main-$Region" ` + -Location $Region ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.bicep" ` + -TemplateParameterFile $ConfigurationFilePath +} + +function Set-HubNetwork-With-AzureFirewall { + param ( + [Parameter(Mandatory = $true)] + $Context, + + [Parameter(Mandatory = $true)] + [String]$Region, + + [Parameter(Mandatory = $true)] + [String]$ManagementGroupId, + + [Parameter(Mandatory = $true)] + [String]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [String]$ConfigurationFilePath, + + [Parameter(Mandatory = $true)] + [String]$AzureFirewallPolicyResourceId, + + [Parameter(Mandatory = $true)] + [String]$LogAnalyticsWorkspaceResourceId + ) + + Set-AzContext -Subscription $SubscriptionId + + # Load networking configuration + $Configuration = Get-Content $ConfigurationFilePath | ConvertFrom-Json -Depth 100 + + #region Check if Log Analytics Workspace Id is provided. Otherwise set it. + $LogAnalyticsWorkspaceResourceIdInFile = $Configuration.parameters | Get-Member -Name logAnalyticsWorkspaceResourceId + + if ($null -eq $LogAnalyticsWorkspaceResourceIdInFile -or $Configuration.parameters.logAnalyticsWorkspaceResourceId.value -eq "") { + $LogAnalyticsWorkspaceIdElement = @{ + logAnalyticsWorkspaceResourceId = @{ + value = $LogAnalyticsWorkspaceResourceId + } + } + + $Configuration.parameters | Add-Member $LogAnalyticsWorkspaceIdElement -Force + } + #endregion + + #region Check if Azure Firewall Policy Id is provided. Otherwise set it. + $AzureFirewallPolicyResourceIdInFile = $Configuration.parameters.hub.value.azureFirewall | Get-Member -Name firewallPolicyId + + if ($null -eq $AzureFirewallPolicyResourceIdInFile -or $Configuration.parameters.hub.value.azureFirewall.firewallPolicyId -eq "") { + $Configuration.parameters.hub.value.azureFirewall | Add-Member -MemberType NoteProperty -Name firewallPolicyId -Value $AzureFirewallPolicyResourceId -Force + } + #endregion + + $PopulatedParametersFilePath = $ConfigurationFilePath.Split('.')[0] + '-populated.json' + + Write-Output "Creating new file with runtime populated parameters: $PopulatedParametersFilePath" + $Configuration | ConvertTo-Json -Depth 100 | Set-Content $PopulatedParametersFilePath + + Write-Output "Moving Subscription ($SubscriptionId) to Management Group ($ManagementGroupId)" + New-AzManagementGroupDeployment ` + -ManagementGroupId $ManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/utils/mg-move/move-subscription.bicep" ` + -TemplateParameterObject @{ + managementGroupId = $ManagementGroupId + subscriptionId = $SubscriptionId + } + + Write-Output "Deploying $PopulatedParametersFilePath to $SubscriptionId in $Region" + New-AzSubscriptionDeployment ` + -Name "main-$Region" ` + -Location $Region ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/lz-platform-connectivity-hub-azfw/main.bicep" ` + -TemplateParameterFile $PopulatedParametersFilePath ` + -Verbose + + #region Check if Private DNS Zones are managed in the Hub. If so, enable Private DNS Zones policy assignment + if ($Configuration.parameters.privateDnsZones.value.enabled -eq $true) { + $PolicyAssignmentFilePath = "$($Context.PolicySetCustomAssignmentsDirectory)/DNSPrivateEndpoints.bicep" + + Write-Output "Hub Network will manage private dns zones, creating Azure Policy assignment to automatically create Private Endpoint DNS Zones." + Write-Output "Deploying policy assignment using $PolicyAssignmentFilePath" + + $Parameters = @{ + policyAssignmentManagementGroupId = $Context.TopLevelManagementGroupId + policyDefinitionManagementGroupId = $Context.TopLevelManagementGroupId + privateDNSZoneSubscriptionId = $SubscriptionId + privateDNSZoneResourceGroupName = $Configuration.parameters.privateDnsZones.value.resourceGroupName + } + + New-AzManagementGroupDeployment ` + -ManagementGroupId $Context.TopLevelManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile $PolicyAssignmentFilePath ` + -TemplateParameterObject $Parameters + } + else { + Write-Output "Hub Network will not manage private dns zones. Azure Policy assignment will be skipped." + } + #endregion + + #region Check if DDOS Standard is deployed in the Hub. If so, enable DDOS Standard policy assignment + if ($Configuration.parameters.ddosStandard.value.enabled -eq $true) { + $DDoSPlan = Get-AzDdosProtectionPlan ` + -ResourceGroupName $Configuration.parameters.ddosStandard.value.resourceGroupName ` + -Name $Configuration.parameters.ddosStandard.value.planName + + $PolicyAssignmentFilePath = "$($Context.PolicySetCustomAssignmentsDirectory)/DDoS.bicep" + + Write-Output "DDoS Standard is enabled, creating Azure Policy assignment to protect for all Virtual Networks in '$($Context.TopLevelManagementGroupId)' management group." + Write-Output "Deploying policy assignment using $PolicyAssignmentFilePath" + + $Parameters = @{ + policyAssignmentManagementGroupId = $Context.TopLevelManagementGroupId + policyDefinitionManagementGroupId = $Context.TopLevelManagementGroupId + ddosStandardPlanId = $DDoSPlan.Id + } + + New-AzManagementGroupDeployment ` + -ManagementGroupId $Context.TopLevelManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile $PolicyAssignmentFilePath ` + -TemplateParameterObject $Parameters + } + else { + Write-Output "DDoS Standard is not enabled. Azure Policy assignment will be skipped." + } + #endregion + + Remove-Item $PopulatedParametersFilePath +} \ No newline at end of file diff --git a/scripts/deployments/Functions/HubNetworkWithNVA.ps1 b/scripts/deployments/Functions/HubNetworkWithNVA.ps1 new file mode 100644 index 00000000..55482fa4 --- /dev/null +++ b/scripts/deployments/Functions/HubNetworkWithNVA.ps1 @@ -0,0 +1,118 @@ +function Set-HubNetwork-With-NVA { + param ( + [Parameter(Mandatory = $true)] + $Context, + + [Parameter(Mandatory = $true)] + [String]$Region, + + [Parameter(Mandatory = $true)] + [String]$ManagementGroupId, + + [Parameter(Mandatory = $true)] + [String]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [String]$ConfigurationFilePath, + + [Parameter(Mandatory = $true)] + [String]$LogAnalyticsWorkspaceResourceId + ) + + Set-AzContext -Subscription $SubscriptionId + + # Load networking configuration + $Configuration = Get-Content $ConfigurationFilePath | ConvertFrom-Json -Depth 100 + + #region Check if Log Analytics Workspace Id is provided. Otherwise set it. + $LogAnalyticsWorkspaceResourceIdInFile = $Configuration.parameters | Get-Member -Name logAnalyticsWorkspaceResourceId + + if ($null -eq $LogAnalyticsWorkspaceResourceIdInFile -or $Configuration.parameters.logAnalyticsWorkspaceResourceId.value -eq "") { + $LogAnalyticsWorkspaceIdElement = @{ + logAnalyticsWorkspaceResourceId = @{ + value = $LogAnalyticsWorkspaceResourceId + } + } + + $Configuration.parameters | Add-Member $LogAnalyticsWorkspaceIdElement -Force + } + #endregion + + $PopulatedParametersFilePath = $ConfigurationFilePath.Split('.')[0] + '-populated.json' + + Write-Output "Creating new file with runtime populated parameters: $PopulatedParametersFilePath" + $Configuration | ConvertTo-Json -Depth 100 | Set-Content $PopulatedParametersFilePath + + Write-Output "Moving Subscription ($SubscriptionId) to Management Group ($ManagementGroupId)" + New-AzManagementGroupDeployment ` + -ManagementGroupId $ManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/utils/mg-move/move-subscription.bicep" ` + -TemplateParameterObject @{ + managementGroupId = $ManagementGroupId + subscriptionId = $SubscriptionId + } + + Write-Output "Deploying $PopulatedParametersFilePath to $SubscriptionId in $Region" + New-AzSubscriptionDeployment ` + -Name "main-$Region" ` + -Location $Region ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/lz-platform-connectivity-hub-nva/main.bicep" ` + -TemplateParameterFile $PopulatedParametersFilePath ` + -Verbose + + #region Check if Private DNS Zones are managed in the Hub. If so, enable Private DNS Zones policy assignment + if ($Configuration.parameters.privateDnsZones.value.enabled -eq $true) { + $PolicyAssignmentFilePath = "$($Context.PolicySetCustomAssignmentsDirectory)/DNSPrivateEndpoints.bicep" + + Write-Output "Hub Network will manage private dns zones, creating Azure Policy assignment to automatically create Private Endpoint DNS Zones." + Write-Output "Deploying policy assignment using $PolicyAssignmentFilePath" + + $Parameters = @{ + policyAssignmentManagementGroupId = $Context.TopLevelManagementGroupId + policyDefinitionManagementGroupId = $Context.TopLevelManagementGroupId + privateDNSZoneSubscriptionId = $SubscriptionId + privateDNSZoneResourceGroupName = $Configuration.parameters.privateDnsZones.value.resourceGroupName + } + + New-AzManagementGroupDeployment ` + -ManagementGroupId $Context.TopLevelManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile $PolicyAssignmentFilePath ` + -TemplateParameterObject $Parameters + } + else { + Write-Output "Hub Network will not manage private dns zones. Azure Policy assignment will be skipped." + } + #endregion + + #region Check if DDOS Standard is deployed in the Hub. If so, enable DDOS Standard policy assignment + if ($Configuration.parameters.ddosStandard.value.enabled -eq $true) { + $DDoSPlan = Get-AzDdosProtectionPlan ` + -ResourceGroupName $Configuration.parameters.ddosStandard.value.resourceGroupName ` + -Name $Configuration.parameters.ddosStandard.value.planName + + $PolicyAssignmentFilePath = "$($Context.PolicySetCustomAssignmentsDirectory)/DDoS.bicep" + + Write-Output "DDoS Standard is enabled, creating Azure Policy assignment to protect for all Virtual Networks in '$($Context.TopLevelManagementGroupId)' management group." + Write-Output "Deploying policy assignment using $PolicyAssignmentFilePath" + + $Parameters = @{ + policyAssignmentManagementGroupId = $Context.TopLevelManagementGroupId + policyDefinitionManagementGroupId = $Context.TopLevelManagementGroupId + ddosStandardPlanId = $DDoSPlan.Id + } + + New-AzManagementGroupDeployment ` + -ManagementGroupId $Context.TopLevelManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile $PolicyAssignmentFilePath ` + -TemplateParameterObject $Parameters + } + else { + Write-Output "DDoS Standard is not enabled. Azure Policy assignment will be skipped." + } + #endregion + + Remove-Item $PopulatedParametersFilePath +} \ No newline at end of file diff --git a/scripts/deployments/Functions/Logging.ps1 b/scripts/deployments/Functions/Logging.ps1 new file mode 100644 index 00000000..c90dbaba --- /dev/null +++ b/scripts/deployments/Functions/Logging.ps1 @@ -0,0 +1,60 @@ +function Get-LoggingConfiguration { + param ( + [Parameter(Mandatory = $true)] + [String]$ConfigurationFilePath, + + [Parameter(Mandatory = $true)] + [String]$SubscriptionId + ) + + $Configuration = Get-Content $ConfigurationFilePath | ConvertFrom-Json + + Set-AzContext -Subscription $SubscriptionId + + $LogAnalyticsWorkspace = Get-AzOperationalInsightsWorkspace ` + -Name $Configuration.parameters.logAnalyticsWorkspaceName.value ` + -ResourceGroupName $Configuration.parameters.logAnalyticsResourceGroupName.value + + return [PSCustomObject]@{ + ResourceGroupName = $Configuration.parameters.logAnalyticsResourceGroupName.value + LogAnalyticsWorkspaceName = $Configuration.parameters.logAnalyticsWorkspaceName.value + LogRetentionInDays = $Configuration.parameters.logAnalyticsRetentionInDays.value + LogAnalyticsWorkspaceResourceId = $LogAnalyticsWorkspace.ResourceId + LogAnalyticsWorkspaceId = $LogAnalyticsWorkspace.CustomerId + } +} + +function Set-Logging { + param ( + [Parameter(Mandatory = $true)] + [String]$Region, + + [Parameter(Mandatory = $true)] + [String]$ManagementGroupId, + + [Parameter(Mandatory = $true)] + [String]$SubscriptionId, + + [Parameter(Mandatory = $true)] + [String]$ConfigurationFilePath + ) + + Set-AzContext -Subscription $SubscriptionId + + Write-Output "Moving Subscription ($SubscriptionId) to Management Group ($ManagementGroupId)" + New-AzManagementGroupDeployment ` + -ManagementGroupId $ManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/utils/mg-move/move-subscription.bicep" ` + -TemplateParameterObject @{ + managementGroupId = $ManagementGroupId + subscriptionId = $SubscriptionId + } + + Write-Output "Deploying Logging to $SubscriptionId in $Region with $ConfigurationFilePath" + New-AzSubscriptionDeployment ` + -Name "main-$Region" ` + -Location $Region ` + -TemplateFile "$($Context.WorkingDirectory)/landingzones/lz-platform-logging/main.bicep" ` + -TemplateParameterFile $ConfigurationFilePath +} \ No newline at end of file diff --git a/scripts/deployments/Functions/ManagementGroups.ps1 b/scripts/deployments/Functions/ManagementGroups.ps1 new file mode 100644 index 00000000..259f5a77 --- /dev/null +++ b/scripts/deployments/Functions/ManagementGroups.ps1 @@ -0,0 +1,53 @@ +function Set-ManagementGroups { + param ( + [Parameter(Mandatory = $true)] + $Context, + + [Parameter(Mandatory = $true)] + $ManagementGroupHierarchy + ) + + function Set-ChildManagementGroups { + param ( + [Parameter(Mandatory = $true)] + $Context, + + [Parameter(Mandatory = $true)] + $RootManagementGroupId, + + [Parameter(Mandatory = $true)] + $ParentNode + ) + + ForEach ($childNode in $ParentNode.children) { + $parentManagementGroupId = $ParentNode.id + $childManagementGroupId = $childNode.id + $childManagementGroupName = $childNode.name + + $DeploymentParameters = @{ + topLevelManagementGroupName = $RootManagementGroupId + parentManagementGroupId = $parentManagementGroupId + childManagementGroupId = $childManagementGroupId + childManagementGroupName = $childManagementGroupName + } + + Write-Output "Creating $childManagementGroupName [$childManagementGroupId] under $parentManagementGroupId" + + New-AzManagementGroupDeployment ` + -ManagementGroupId $parentManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile "$($Context.WorkingDirectory)/management-groups/structure-v2.bicep" ` + -TemplateParameterObject $DeploymentParameters + + Set-ChildManagementGroups ` + -Context $Context ` + -RootManagementGroupId $RootManagementGroupId ` + -ParentNode $childNode + } + } + + Set-ChildManagementGroups ` + -Context $Context ` + -RootManagementGroupId $ManagementGroupHierarchy.id ` + -ParentNode $ManagementGroupHierarchy +} \ No newline at end of file diff --git a/scripts/deployments/Functions/Policy.ps1 b/scripts/deployments/Functions/Policy.ps1 new file mode 100644 index 00000000..32d208f8 --- /dev/null +++ b/scripts/deployments/Functions/Policy.ps1 @@ -0,0 +1,150 @@ +function Set-Policy-Definitions { + param( + [Parameter(Mandatory = $true)] + [String] $PolicyDefinitionsDirectory, + + [Parameter(Mandatory = $true)] + [PSCustomObject] $ManagementGroupId + ) + Get-ChildItem -Directory -Path $PolicyDefinitionsDirectory | + Foreach-Object { + $PolicyDefinitionName = $_.Name + $PolicyConfigFilePath = "$($_.FullName)/azurepolicy.config.json" + $PolicyRuleFilePath = "$($_.FullName)/azurepolicy.rules.json" + $PolicyParametersFilePath = "$($_.FullName)/azurepolicy.parameters.json" + + $PolicyConfig = Get-Content $PolicyConfigFilePath | ConvertFrom-Json + + Write-Output "Policy: $PolicyDefinitionName" + Write-Output " - Rule: $PolicyRuleFilePath" + Write-Output " - Parameters: $PolicyParametersFilePath" + Write-Output " - Id: $PolicyDefinitionName" + Write-Output " - Display Name: $($PolicyConfig.name)" + Write-Output " - Mode: $($PolicyConfig.mode)" + + New-AzPolicyDefinition ` + -ManagementGroupName $ManagementGroupId ` + -Name $PolicyDefinitionName ` + -DisplayName $($PolicyConfig.name) ` + -Mode $PolicyConfig.mode ` + -Policy $PolicyRuleFilePath ` + -Parameter $PolicyParametersFilePath + } +} + +function Set-PolicySet-Defintions { + param( + [Parameter(Mandatory = $true)] + $Context, + + [Parameter(Mandatory = $true)] + [String] $PolicySetDefinitionsDirectory, + + [Parameter(Mandatory = $true)] + [String[]] $PolicySetDefinitionNames, + + [Parameter(Mandatory = $true)] + [PSCustomObject] $ManagementGroupId + ) + + foreach ($policySetDefinitionName in $PolicySetDefinitionNames) { + $PolicySetDefinitionFilePath = "$($PolicySetDefinitionsDirectory)/$($policySetDefinitionName).bicep" + $PolicySetDefinitionParametersFilePath = "$($PolicySetDefinitionsDirectory)/$($policySetDefinitionName).parameters.json" + + # Replace templated parameters & create temp file for deployment + $ParametersContent = Get-Content $PolicySetDefinitionParametersFilePath + $ParametersContent = $ParametersContent -Replace '{{var-topLevelManagementGroupName}}', $ManagementGroupId + + $PopulatedParametersFilePath = "$($PolicySetDefinitionsDirectory)/$($policySetDefinitionName)-populated.parameters.json" + $ParametersContent | Set-Content -Path $PopulatedParametersFilePath + + Write-Output "Policy Set: $policySetDefinitionName" + Write-Output " - Definition: $PolicySetDefinitionFilePath" + Write-Output " - Parameters: $PolicySetDefinitionParametersFilePath" + Write-Output " - Populated (temp): $PopulatedParametersFilePath" + + # Deploy Policy Set + New-AzManagementGroupDeployment ` + -ManagementGroupId $ManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile $PolicySetDefinitionFilePath ` + -TemplateParameterFile $PopulatedParametersFilePath + + # Remove temporary file + Remove-Item $PopulatedParametersFilePath + } +} + +function Set-PolicySet-Assignments { + param( + [Parameter(Mandatory = $true)] + $Context, + + [Parameter(Mandatory = $true)] + [String] $PolicySetAssignmentsDirectory, + + [Parameter(Mandatory = $true)] + [String] $PolicySetAssignmentManagementGroupId, + + [Parameter(Mandatory = $true)] + [String[]] $PolicySetAssignmentNames, + + [Parameter(Mandatory = $true)] + [String] $LogAnalyticsWorkspaceResourceGroupName, + + [Parameter(Mandatory = $true)] + [String] $LogAnalyticsWorkspaceResourceId, + + [Parameter(Mandatory = $true)] + [String] $LogAnalyticsWorkspaceId, + + [Parameter(Mandatory = $true)] + [Int32] $LogAnalyticsWorkspaceRetentionInDays + ) + + foreach ($policySetAssignmentName in $PolicySetAssignmentNames) { + Write-Output "Policy Set assignment Name: $($policySetAssignmentName)" + + $PolicySetAssignmentFilePath = "$($PolicySetAssignmentsDirectory)/$($policySetAssignmentName).bicep" + + $DefaultPolicyParameterFilePath = "$PolicySetAssignmentsDirectory/$policySetAssignmentName.parameters.json" + $AssignmentScopeParameterFilePath = "$PolicySetAssignmentsDirectory/$policySetAssignmentName-$PolicySetAssignmentManagementGroupId.parameters.json" + + # Check if there is an assignment scope specific parameter file. + # The file will have the syntax -.parameters.json + # If not found, then use the default parameter file with syntax .parameters.json + if (Test-Path $AssignmentScopeParameterFilePath -PathType Leaf) { + $PolicySetParameterFilePath = $AssignmentScopeParameterFilePath + } else { + $PolicySetParameterFilePath = $DefaultPolicyParameterFilePath + } + + # Replace templated parameters & create temp file for deployment + $ParametersContent = Get-Content $PolicySetParameterFilePath + $ParametersContent = $ParametersContent -Replace '{{var-topLevelManagementGroupName}}', $Context.TopLevelManagementGroupId + $ParametersContent = $ParametersContent -Replace '{{var-logging-logAnalyticsWorkspaceResourceId}}', $LogAnalyticsWorkspaceResourceId + $ParametersContent = $ParametersContent -Replace '{{var-logging-logAnalyticsWorkspaceId}}', $LogAnalyticsWorkspaceId + $ParametersContent = $ParametersContent -Replace '{{var-logging-logAnalyticsResourceGroupName}}', $LogAnalyticsWorkspaceResourceGroupName + $ParametersContent = $ParametersContent -Replace '{{var-logging-logAnalyticsRetentionInDays}}', $LogAnalyticsWorkspaceRetentionInDays + $ParametersContent = $ParametersContent -Replace '{{var-policyAssignmentManagementGroupId}}', $PolicySetAssignmentManagementGroupId + $ParametersContent = $ParametersContent -Replace '{{var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix}}', $Context.Variables['var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix'] + + $PopulatedParametersFilePath = "$($PolicySetAssignmentsDirectory)/$($policySetAssignmentName)-populated.parameters.json" + $ParametersContent | Set-Content -Path $PopulatedParametersFilePath + + Write-Output "Policy: $policy" + Write-Output " - Definition: $PolicySetAssignmentFilePath" + Write-Output " - Parameters: $PolicySetParameterFilePath" + Write-Output " - Populated (temp): $PopulatedParametersFilePath" + + # Deploy Policy Set + New-AzManagementGroupDeployment ` + -ManagementGroupId $PolicySetAssignmentManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile $PolicySetAssignmentFilePath ` + -TemplateParameterFile $PopulatedParametersFilePath + + # Remove temporary file + Remove-Item $PopulatedParametersFilePath + } +} diff --git a/scripts/deployments/Functions/Roles.ps1 b/scripts/deployments/Functions/Roles.ps1 new file mode 100644 index 00000000..2117f6f9 --- /dev/null +++ b/scripts/deployments/Functions/Roles.ps1 @@ -0,0 +1,30 @@ +function Set-Roles { + param ( + [Parameter(Mandatory = $true)] + $Context, + + [Parameter(Mandatory = $true)] + [String] $RolesDirectory, + + [Parameter(Mandatory = $true)] + [String] $ManagementGroupId + ) + + # Deployment + Write-Output "Deploying roles to management group: $ManagementGroupId" + Write-Output "Deploying role definitions from $RolesDirectory" + + $DeploymentParameters = @{ + assignableMgId = $ManagementGroupId + } + + foreach ($roleDefinition in Get-ChildItem -Path $RolesDirectory) { + Write-Output "Deploying $($roleDefinition.FullName)" + + New-AzManagementGroupDeployment ` + -ManagementGroupId $ManagementGroupId ` + -Location $Context.DeploymentRegion ` + -TemplateFile $roleDefinition.FullName ` + -TemplateParameterObject $DeploymentParameters + } +} \ No newline at end of file diff --git a/scripts/deployments/Functions/Subscriptions.ps1 b/scripts/deployments/Functions/Subscriptions.ps1 new file mode 100644 index 00000000..4c0d0524 --- /dev/null +++ b/scripts/deployments/Functions/Subscriptions.ps1 @@ -0,0 +1,26 @@ +function Set-Subscriptions { + param ( + [Parameter(Mandatory = $true)] + [String] $Region, + + [Parameter(Mandatory = $true)] + [String[]] $SubscriptionIds, + + [Parameter(Mandatory = $true)] + [String] $LogAnalyticsWorkspaceResourceId + ) + + foreach ($subscriptionId in $SubscriptionIds) { + Write-Output "Deploying Subscription: $subscriptionId" + + # TODO: Find the ARM JSON parameters + + # TODO: Ensure there's only 1 parameters file for each subscription + + # TODO: Parse the file name to determine archetype, region and subscription id + + # TODO: Load subscription configuration and check if Log Analytics Workspace Id is provided. Otherwise set it. + + # TODO: Add Azure PS deployment command + } +} \ No newline at end of file diff --git a/scripts/deployments/Install-Prerequisites.ps1 b/scripts/deployments/Install-Prerequisites.ps1 new file mode 100644 index 00000000..8c34b6a8 --- /dev/null +++ b/scripts/deployments/Install-Prerequisites.ps1 @@ -0,0 +1 @@ +Install-Module powershell-yaml -Force