diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f59e187d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +experiments/* +**/*.swp +.vscode/* +/*.sh +/*.ps1 \ No newline at end of file diff --git a/.pipelines/README.md b/.pipelines/README.md new file mode 100644 index 00000000..4fb28630 --- /dev/null +++ b/.pipelines/README.md @@ -0,0 +1,58 @@ +# Azure Pipelines + +## Disclaimer + +Copyright (c) Microsoft Corporation. + +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + +## Pipeline definitions + +The following top-level pipelines are present in the `.pipelines/` repository folder: + +| # | Pipeline | File | CI Name +| :---: | ---------- | ---------- | ---------- +| 1 | Management Groups | `management-groups.yml` | management-groups-ci +| 2 | Platform Logging | `platform-logging.yml` | platform-logging-ci +| 3 | Policy | `policy.yml` | policy-ci +| 4 | Roles | `roles.yml` | roles-ci +| 5 | Networking | `platform-connectivity-hub-nva.yml` | platform-connectivity-hub-nva-ci +| 6 | Subscription | `subscription.yml` | subscription-ci + +These pipelines need to be run in the order specified. For example, the `Policy` pipeline is dependent on resources deployed by the `Platform Logging` pipeline. Think of it as a layered approach; once the layer is deployed, it only requires re-running if some configuration at that layer changes. + +In the default implementation, the `Management Groups`, `Platform Logging`, `Policy`, and `Roles` pipelines are run automatically (trigger) whenever a related code change is detected on the `main` branch. The `Networking` and `Subscription` pipelines do not run automatically (no trigger). This behavior can be changed by modifying the corresponding YAML pipeline definition files. + +In the default implementation, the `Roles` and `Platform Logging` pipelines are run automatically after a successful run of the `Management Groups` pipeline, and the `Policy` pipeline is run automatically after a successful run of the `Platform Logging` pipeline. Again, this behavior can be changed by modifying the corresponding YAML pipeline definition files. + +The top-level pipeline definitions are implemented in a modular way, using nested YAML templates defined in the `.pipelines/templates/jobs/` and `.pipelines/templates/steps/` paths. + +## Pipeline configuration + +The top-level pipelines use configuration values from these locations: + +- environment related configuration values are stored in the `config/variables/` path. +- subscription related configuration values are stored in the `config/subscriptions/` path. + +Additional information on configuration files is available here: + +- [Environment configuration files](../config/variables/README.md) +- [Subscription configuration files](../config/subscriptions/README.md) + +## Additional pipelines + +In addition to the top-level pipelines mentioned previously, there are several other pipeline definitions in the `./pipelines` path that may be useful. + +### Check Bicep files + +The `checks-bicep-compile.yml` pipeline can be used to configure a `Build Validation` branch policy in your repository and validate any Bicep code changes by compiling all Bicep files with built-in linting. + +### Manual approval + +The `demo-approval.yml` pipeline demonstrates how to implement a manual approval gate/check in your pipeline definition. + +### Linting source files + +The `linters.yml` pipeline demonstrates using the GitHub SuperLinter project to run a linting process on many common source code file types. diff --git a/.pipelines/checks-bicep-compile.yml b/.pipelines/checks-bicep-compile.yml new file mode 100644 index 00000000..054024ed --- /dev/null +++ b/.pipelines/checks-bicep-compile.yml @@ -0,0 +1,39 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +trigger: none + +pool: + vmImage: ubuntu-latest + +stages: + +- stage: CheckBicepCompileStage + displayName: Checks - Bicep Compile Stage + + jobs: + + - deployment: CheckBicepCompileJob + displayName: Checks - Bicep Compile Job + environment: ${{ variables['Build.SourceBranchName'] }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + + - task: Bash@3 + displayName: Compile all bicep templates + name: CompileBiceps + inputs: + targetType: 'inline' + script: | + find . -type f -name '*.bicep' | xargs -tn1 az bicep build -f + + workingDirectory: ${{ variables['Build.SourcesDirectory'] }} diff --git a/.pipelines/demo-approval.yml b/.pipelines/demo-approval.yml new file mode 100644 index 00000000..02a0d61a --- /dev/null +++ b/.pipelines/demo-approval.yml @@ -0,0 +1,71 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +# To setup a CI trigger so this pipeline is automatically run on new commits: +# 1. Remove the 'none' keyword after the 'trigger:' statement in the line below +# 2. Uncomment the indented lines following the 'trigger:' statement +trigger: none + # batch: true + # branches: + # include: + # - main + # paths: + # include: + # - config/* + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: variable-template-file + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }}.yml +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['variable-template-file'] }} + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: DemoApprovalStage + displayName: Demo Approval Stage + + jobs: + + # - job: WaitForApproval + # displayName: Wait For Approval + # pool: server + # timeoutInMinutes: 2 + # steps: + # - task: ManualValidation@0 + # timeoutInMinutes: 2 + # inputs: + # notifyUsers: | + # alzcanadapubsec@microsoft.com + # doic-release-approvers@microsoft.onmicrosoft.com + # instructions: 'Review release and resume or reject' + # onTimeout: 'reject' + + # - job: DemoApprovalJob + # displayName: Demo Approval Job + # dependsOn: WaitForApproval + # steps: + # - script: | + # echo "Demonstrating manual approval workflow" + + - deployment: DemoApprovalJob2 + displayName: Demo Approval Job 2 + environment: ${{ variables['Build.SourceBranchName'] }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + - script: | + echo "Demonstrating manual approval workflow for environment: $(var-environmentName)" + ls -al $(System.DefaultWorkingDirectory) diff --git a/.pipelines/linters.yml b/.pipelines/linters.yml new file mode 100644 index 00000000..e7ee8b2d --- /dev/null +++ b/.pipelines/linters.yml @@ -0,0 +1,54 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +# +# GitHub Super Linter : https://github.com/github/super-linter +# + +# To setup a CI trigger so this pipeline is automatically run on new commits: +# 1. Remove the 'none' keyword after the 'trigger:' statement in the line below +# 2. Uncomment the indented lines following the 'trigger:' statement +trigger: none + # batch: true + # branches: + # include: + # - main + # paths: + # include: + # - azresources/* + # - config/* + # - landingzones/* + # - .pipelines/* + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: variable-template-file + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }}.yml +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['variable-template-file'] }} + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: LinterStage + displayName: Linter Stage + + jobs: + + - job: LinterJob + displayName: Linter Job + + steps: + + - template: ./templates/steps/run-linter.yml + parameters: + validationTypes: 'ARM JSON YAML' diff --git a/.pipelines/management-groups.yml b/.pipelines/management-groups.yml new file mode 100644 index 00000000..3d87a70b --- /dev/null +++ b/.pipelines/management-groups.yml @@ -0,0 +1,57 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +trigger: + batch: true + branches: + include: + - main + paths: + include: + - management-groups + - .pipelines/management-groups.yml + - .pipelines/templates/steps/deploy-management-groups.yml + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: variable-template-file + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }}.yml +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['variable-template-file'] }} + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: DeployManagementGroupsStage + displayName: Deploy Management Groups Stage + + jobs: + + - deployment: DeployManagementGroupsJob + displayName: Deploy Management Groups Job + environment: ${{ variables['Build.SourceBranchName'] }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + + - template: templates/steps/show-variables.yml + parameters: + json: ${{ convertToJson(variables) }} + + - template: templates/steps/deploy-management-groups.yml + parameters: + description: 'Create/Update Management Groups' + templateFile: structure.bicep + deployOperation: ${{ variables['deployOperation'] }} + workingDir: $(System.DefaultWorkingDirectory)/management-groups diff --git a/.pipelines/platform-connectivity-hub-azfw-policy.yml b/.pipelines/platform-connectivity-hub-azfw-policy.yml new file mode 100644 index 00000000..8f16c51b --- /dev/null +++ b/.pipelines/platform-connectivity-hub-azfw-policy.yml @@ -0,0 +1,44 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +trigger: none + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: variable-template-file + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }}.yml +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['variable-template-file'] }} + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: DeployAzureFirewallPolicyStage + displayName: Deploy Azure Firewall Policy Stage + + jobs: + + - deployment: DeployAzureFirewallPolicyJob + displayName: Deploy Azure Firewall Policy Job + environment: ${{ variables['Build.SourceBranchName'] }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + + - template: templates/steps/deploy-platform-connectivity-hub-azfw-policy.yml + parameters: + description: 'Deploy Azure Firewall Policy' + templateFile: main-azfw-policy.bicep + deployOperation: ${{ variables['deployOperation'] }} + workingDir: $(System.DefaultWorkingDirectory)/landingzones diff --git a/.pipelines/platform-connectivity-hub-azfw.yml b/.pipelines/platform-connectivity-hub-azfw.yml new file mode 100644 index 00000000..6fc7813a --- /dev/null +++ b/.pipelines/platform-connectivity-hub-azfw.yml @@ -0,0 +1,60 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +trigger: none + # batch: true + # branches: + # include: + # - main + # paths: + # include: + # - landingzones/lz-platform-connectivity-hub-azfw + # - .pipelines/platform-connectivity-hub-azfw.yml + # - .pipelines/templates/steps/deploy-platform-connectivity-hub-azfw.yml + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: variable-template-file + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }}.yml +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['variable-template-file'] }} + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: DeployNetworkingStage + displayName: Deploy Networking Stage + + jobs: + + - deployment: DeployNetworkingJob + displayName: Deploy Networking Job + environment: ${{ variables['Build.SourceBranchName'] }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + + - template: templates/steps/load-log-analytics-vars.yml + + - template: templates/steps/show-variables.yml + parameters: + json: ${{ convertToJson(variables) }} + + - template: templates/steps/deploy-platform-connectivity-hub-azfw.yml + parameters: + description: 'Deploy Networking' + moveTemplate: move-subscription.bicep + templateFile: main.bicep + deployOperation: ${{ variables['deployOperation'] }} + workingDir: $(System.DefaultWorkingDirectory)/landingzones diff --git a/.pipelines/platform-connectivity-hub-nva.yml b/.pipelines/platform-connectivity-hub-nva.yml new file mode 100644 index 00000000..f8e2b694 --- /dev/null +++ b/.pipelines/platform-connectivity-hub-nva.yml @@ -0,0 +1,61 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +trigger: none + # batch: true + # branches: + # include: + # - main + # paths: + # include: + # - landingzones/lz-platform-connectivity-hub-nva + # - .pipelines/platform-connectivity-hub-nva.yml + # - .pipelines/templates/steps/deploy-platform-connectivity-hub-nva.yml + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: variable-template-file + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }}.yml +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['variable-template-file'] }} +- group: firewall-secrets + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: DeployNetworkingStage + displayName: Deploy Networking Stage + + jobs: + + - deployment: DeployNetworkingJob + displayName: Deploy Networking Job + environment: ${{ variables['Build.SourceBranchName'] }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + + - template: templates/steps/load-log-analytics-vars.yml + + - template: templates/steps/show-variables.yml + parameters: + json: ${{ convertToJson(variables) }} + + - template: templates/steps/deploy-platform-connectivity-hub-nva.yml + parameters: + description: 'Deploy Networking' + moveTemplate: move-subscription.bicep + templateFile: main.bicep + deployOperation: ${{ variables['deployOperation'] }} + workingDir: $(System.DefaultWorkingDirectory)/landingzones diff --git a/.pipelines/platform-logging.yml b/.pipelines/platform-logging.yml new file mode 100644 index 00000000..5b7f5b09 --- /dev/null +++ b/.pipelines/platform-logging.yml @@ -0,0 +1,68 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +trigger: + batch: true + branches: + include: + - main + paths: + include: + - landingzones/lz-platform-logging + - .pipelines/platform-logging.yml + - .pipelines/templates/steps/deploy-platform-logging.yml + +resources: + pipelines: + # Trigger this pipeline when management-groups-ci pipeline completes + - pipeline: ManagementGroups + source: management-groups-ci + trigger: + branches: + include: + - refs/heads/main + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: variable-template-file + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }}.yml +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['variable-template-file'] }} + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: DeployPlatformLoggingStage + displayName: Deploy Platform Logging Stage + + jobs: + + - deployment: DeployPlatformLoggingJob + displayName: Deploy Platform Logging Job + environment: ${{ variables['Build.SourceBranchName'] }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + + - template: templates/steps/show-variables.yml + parameters: + json: ${{ convertToJson(variables) }} + + - template: templates/steps/deploy-platform-logging.yml + parameters: + description: 'Deploy Logging' + moveTemplate: move-subscription.bicep + configTemplate: main.bicep + deployOperation: ${{ variables['deployOperation'] }} + workingDir: $(System.DefaultWorkingDirectory)/landingzones diff --git a/.pipelines/policy.yml b/.pipelines/policy.yml new file mode 100644 index 00000000..23c0a41f --- /dev/null +++ b/.pipelines/policy.yml @@ -0,0 +1,120 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +trigger: + batch: true + branches: + include: + - main + paths: + include: + - policy + - .pipelines/policy.yml + - .pipelines/templates/steps/deploy-policy.yml + +resources: + pipelines: + # Trigger this pipeline when platform-logging-ci pipeline completes + - pipeline: PlatformLogging + source: platform-logging-ci + trigger: + branches: + include: + - refs/heads/main + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: variable-template-file + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }}.yml +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['variable-template-file'] }} + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: DeployPolicyStage + displayName: Deploy Policy Stage + + # Policy deployment is divided into 2 jobs, one for Built-In and + # one for Custom policy definitions and assignments. Jobs are + # implicitly parallel, so these jobs may run concurrently if + # you have enough parallel job capacity. + # Added one more job (total 3) that runs before the two existing + # jobs to run Environment Approvals and Checks. The two policy + # jobs (built-in and custom) only run once any/all environment + # approvals and checks are satisfied. + + jobs: + + - deployment: EnvionmentApprovalsAndChecks + displayName: Environment Approvals and Checks + environment: ${{ variables['Build.SourceBranchName'] }} + strategy: + runOnce: + deploy: + steps: + - script: | + echo "Environment Approvals and Checks completed for environment: ${{ variables['Build.SourceBranchName'] }}" + + - job: CustomPolicyJob + displayName: Custom Policy Job + dependsOn: + - EnvionmentApprovalsAndChecks + condition: succeeded('EnvionmentApprovalsAndChecks') + + steps: + + - template: templates/steps/load-log-analytics-vars.yml + + - template: templates/steps/show-variables.yml + parameters: + json: ${{ convertToJson(variables) }} + + - template: templates/steps/define-policy.yml + parameters: + description: 'Define Policies' + workingDir: $(System.DefaultWorkingDirectory)/policy/custom/definitions/policy + + - template: templates/steps/define-policyset.yml + parameters: + description: 'Define Policy Set' + deployTemplates: [AKS, EnableAzureDefender, EnableLogAnalytics, Network, DNSPrivateEndpoints, Tags] + deployOperation: ${{ variables['deployOperation'] }} + workingDir: $(System.DefaultWorkingDirectory)/policy/custom/definitions/policyset + + - template: templates/steps/assign-policy.yml + parameters: + description: 'Assign Policy Set' + deployTemplates: [aks, asc, loganalytics, network, tags] + deployOperation: ${{ variables['deployOperation'] }} + workingDir: $(System.DefaultWorkingDirectory)/policy/custom/assignments + + - job: BuiltInPolicyJob + displayName: Built In Policy Job + dependsOn: + - EnvionmentApprovalsAndChecks + condition: succeeded('EnvionmentApprovalsAndChecks') + + steps: + + - template: templates/steps/load-log-analytics-vars.yml + + - template: templates/steps/show-variables.yml + parameters: + json: ${{ convertToJson(variables) }} + + - template: templates/steps/assign-policy.yml + parameters: + description: 'Assign Policy Set' + deployTemplates: [asb, cis-msft-130, location, nist80053r4, nist80053r5, pbmm, hitrust-hipaa, fedramp-moderate] + deployOperation: ${{ variables['deployOperation'] }} + workingDir: $(System.DefaultWorkingDirectory)/policy/builtin/assignments diff --git a/.pipelines/roles.yml b/.pipelines/roles.yml new file mode 100644 index 00000000..2c11bd0f --- /dev/null +++ b/.pipelines/roles.yml @@ -0,0 +1,56 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +trigger: + batch: true + branches: + include: + - main + paths: + include: + - roles + - .pipelines/roles.yml + +resources: + pipelines: + - pipeline: ManagementGroups + source: management-groups-ci + trigger: + branches: + include: + - refs/heads/main + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: variable-template-file + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }}.yml +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['variable-template-file'] }} + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: DeployRoleStage + displayName: Deploy Custom Roles Stage + + jobs: + + - job: CustomRolesJob + displayName: Custom Roles Job + + steps: + - template: templates/steps/create-roles.yml + parameters: + description: 'Create Custom Roles' + workingDir: $(System.DefaultWorkingDirectory)/roles + deployTemplates: [la-vminsights-readonly, lz-appowner] + deployOperation: ${{ variables['deployOperation'] }} diff --git a/.pipelines/subscriptions.yml b/.pipelines/subscriptions.yml new file mode 100644 index 00000000..50cedbfc --- /dev/null +++ b/.pipelines/subscriptions.yml @@ -0,0 +1,84 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: subscriptions + type: object + default: [] + - name: commit + type: string + default: 'optional-commit-id' + +# Remove 'none' and uncomment block to enable CI trigger +trigger: none + # batch: false + # branches: + # include: + # - main + # paths: + # include: + # - config/subscriptions/* + +variables: +- name: devops-org-name + value: ${{ replace(replace(variables['System.CollectionUri'], 'https://dev.azure.com/' , ''), '/', '') }} +- name: environment + value: ${{ variables['devops-org-name'] }}-${{ variables['Build.SourceBranchName'] }} +- template: ../config/variables/common.yml +- template: ../config/variables/${{ variables['environment'] }}.yml + +pool: + vmImage: $[ variables.vmImage ] + +stages: + +- stage: DeploySubscriptionsStage + displayName: Deploy Subscription Stage + + jobs: + + # This job is run when the CI trigger is fired based on a change to one or more + # subscription configuration files *OR* when the pipeline is run manually and the + # default empty array value is passed for the 'subscriptions' suparameter. + # In either of these cases, the array of subscriptions is determined by one of: + # + # 1) Inspecting configuration files included in the triggering Git commit *OR* + # the HEAD of the current branch if manually triggered + # + # 2) The (array) values entered by the user when manually triggered + # + # The template job invoked here will re-invoke this template (subscriptions.yml) + # once, passing it an array of subscriptions. When re-entered (and 'subscriptions' + # array parameter is not empty - i.e. []), this block will not run, rather the + # following block will run, using a template job to run subscription deployment + # for each subscription in the array - allowing parallel execution if you have + # sufficient parallel pipelines available. + + - ${{ if eq(length(parameters.subscriptions), 0) }}: + - template: templates/jobs/trigger-subscriptions.yml + parameters: + config: ${{ variables['subscriptionsPathFromRoot'] }} + enviro: ${{ variables['environment'] }} + ${{ if eq(parameters['commit'], 'optional-commit-id') }}: + commit: $(Build.SourceVersion) + ${{ if ne(parameters['commit'], 'optional-commit-id') }}: + commit: ${{ parameters.commit }} + + # Create a new job for each subscription in subscriptions parameter (if any). + # This job (template) will not be run on CI invocation where no subscriptions are passed by parameter. + # If mutliple subscription changes are detected *and* you have sufficient parallel pipelines, + # then the subscription deployments will occur in parallel. + + - ${{ each subscription in parameters.subscriptions }}: + - template: templates/jobs/deploy-subscription.yml + parameters: + config: ${{ variables['subscriptionsPathFromRoot'] }} + enviro: ${{ variables['environment'] }} + subscription: ${{ subscription }} + diff --git a/.pipelines/templates/jobs/deploy-subscription.yml b/.pipelines/templates/jobs/deploy-subscription.yml new file mode 100644 index 00000000..63f11c1e --- /dev/null +++ b/.pipelines/templates/jobs/deploy-subscription.yml @@ -0,0 +1,158 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: config + type: string + - name: enviro + type: string + - name: subscription + type: string + +jobs: + +- deployment: DeploySubscriptionJob_${{ replace(parameters.subscription, '-', '_') }} + displayName: Deploy Subscription Job - ${{ parameters.subscription }} + timeoutInMinutes: 0 + environment: ${{ variables['Build.SourceBranchName'] }} + strategy: + runOnce: + deploy: + steps: + - checkout: self + + - template: ../steps/show-variables.yml + parameters: + json: ${{ convertToJson(variables) }} + + - task: Bash@3 + displayName: Parse Subscription - ${{ parameters.subscription }} + name: ParseSubscription + inputs: + targetType: 'inline' + script: | + $(var-bashPreInjectScript) + + subscription=${{ parameters.subscription }} + config=${{ parameters.config }} + config_sed=$(echo ${{ parameters.config }} | sed 's/\//\\\//g') + enviro=${{ parameters.enviro }} + enviro_sed=$(echo ${{ parameters.enviro }} | sed 's/\//\\\//g') + + # Resolve partial configuration file specification + if [[ ! ${subscription} =~ .*/.* ]]; then + echo + echo "Resolving partial configuration file specifier: ${subscription} ..." + readarray -t lines < <(find ${config}/${enviro} -type f -name "*${subscription}*" | sed "s/^${config_sed}\///g") + if [[ ${#lines[@]} < 1 || ${#lines[@]} > 1 ]]; then + echo "##vso[task.logissue type=error]Found ${#lines[@]} subscription configuration file matches based on qualifier: ${subscription}. Partial configuration file name search must have exactly 1 match." + exit 1 + else + subscription=${lines[0]} + echo " - partial configuration file specifier resolved to: ${subscription}" + fi + else + echo + echo "Fully qualified configuration specified: ${subscription}" + fi + + # Deconstruct full path + echo + echo "Analyzing subscription configuration file path..." + readarray -t dparts < <(echo ${subscription} | sed "s/^${config_sed}\///g" | tr "/" "\n") + environment=${dparts[@]:0:1} + echo " - environment: $environment" + filename=${dparts[@]: -1:1} + echo " - configuration file: $filename" + pathToFile=$(printf "%s/" "${dparts[@]:0:${#dparts[@]}-1}") + pathToFile="${pathToFile%/}" + echo " - path to file: ${pathToFile}" + mgmt_group=$(printf "%s" "${dparts[@]:1:${#dparts[@]}-2}") + echo " - management Group: ${mgmt_group}" + + # Deconstruct file name + echo + echo "Analyzing subscription configuration file name..." + readarray -t fparts < <(echo ${filename} | sed 's/\.json$//g' | tr "_" "\n") + fparts_num=${#fparts[@]} + sub_guid='' + sub_type='' + sub_loc=$(deploymentRegion) + sub_guid_index=0 + sub_type_index=1 + sub_loc_index=2 + + # Parse required subscription GUID + if [[ ${fparts_num} > ${sub_guid_index} ]]; then + sub_guid=${fparts[$sub_guid_index]} + fi + + # Parse required template type + if [[ ${fparts_num} > ${sub_type_index} ]]; then + sub_type=${fparts[$sub_type_index]} + fi + + # Parse optional location + if [[ ${fparts_num} > ${sub_loc_index} ]]; then + if [[ -n ${fparts[$sub_loc_index]} && ${fparts[$sub_loc_index]} != 'default' ]]; then + sub_loc=${fparts[$sub_loc_index]} + fi + fi + + # Emit parsed values: Subscription GUID, Template Type, Location + echo " - subscription GUID: ${sub_guid}" + echo " - template type: ${sub_type}" + echo " - location: ${sub_loc}" + + # Validate JSON configuration file name by testing parts + if [[ -z ${sub_guid} || -z ${sub_type} ]]; then + echo + echo "##vso[task.logissue type=error]Invalid configuration file name format (${filename}). It must be in the form [SubGuid]_[TemplateType].json or [SubGuid]_[TemplateType]_[Location].json" + exit 1 + fi + + # Create output variables + echo "##vso[task.setvariable variable=Environment;isoutput=true]${environment}" + echo "##vso[task.setvariable variable=Filename;isoutput=true]${filename}" + echo "##vso[task.setvariable variable=FullPath;isoutput=true]${subscription}" + echo "##vso[task.setvariable variable=RelativePath;isoutput=true]${pathToFile}" + echo "##vso[task.setvariable variable=MgmtGroup;isoutput=true]${mgmt_group}" + echo "##vso[task.setvariable variable=SubGuid;isoutput=true]${sub_guid}" + echo "##vso[task.setvariable variable=SubType;isoutput=true]${sub_type}" + echo "##vso[task.setvariable variable=SubLocation;isoutput=true]${sub_loc}" + + $(var-bashPostInjectScript) + + workingDirectory: ${{ variables['Build.SourcesDirectory'] }} + + # -------------------------------------------------------------------------------- + # EXAMPLE OUTPUT VARIABLE VALUES FROM PREVIOUS STEP: + # -------------------------------------------------------------------------------- + # Environment = devopsincanada-main + # Filename = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_generic-subscription.json + # FullPath = devopsincanada-main/doic/LandingZones/Prod/DAPI/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_generic-subscription.json + # RelativePath = devopsincanada-main/doic/LandingZones/Prod/DAPI + # MgmtGroup = doicLandingZonesProdDAPI + # SubGuid = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + # SubType = generic-subscription + # -------------------------------------------------------------------------------- + + - template: ../steps/deploy-subscription.yml + parameters: + config: ${{ parameters.config }} + enviro: ${{ parameters.enviro }} + environment: $(ParseSubscription.Environment) + filename: $(ParseSubscription.Filename) + fullpath: $(ParseSubscription.FullPath) + relativepath: $(ParseSubscription.RelativePath) + mgmtgroup: $(ParseSubscription.MgmtGroup) + subguid: $(ParseSubscription.SubGuid) + subtype: $(ParseSubscription.SubType) + sublocation: $(ParseSubscription.SubLocation) + diff --git a/.pipelines/templates/jobs/trigger-subscriptions.yml b/.pipelines/templates/jobs/trigger-subscriptions.yml new file mode 100644 index 00000000..9591134c --- /dev/null +++ b/.pipelines/templates/jobs/trigger-subscriptions.yml @@ -0,0 +1,107 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: config + type: string + - name: enviro + type: string + - name: commit + type: string + +jobs: + +- job: TriggerSubscriptionsJob + displayName: Trigger Subscriptions Job + + steps: + + - template: ../steps/show-variables.yml + parameters: + json: ${{ convertToJson(variables) }} + + - task: Bash@3 + name: IdentifySubscriptions + displayName: Identify Subscriptions + inputs: + targetType: 'inline' + script: | + $(var-bashPreInjectScript) + + echo "Find changed subscription configuration files in: ${{ parameters.config }}" + echo " where environment is: ${{ parameters.enviro }}" + echo " and change status flag in set: [$(var-TriggerSubscriptionDeployOn)]" + echo " for Git commit id: ${{ parameters.commit }}" + echo + + # Escape any '/' characters in the config path (for sed's benefit) + config=$(echo ${{ parameters.config }} | sed 's/\//\\\//g') + enviro=${{ parameters.enviro }} + + # # Get array of subscription configuration files changed in this commit + # readarray -t lines < <(git show --pretty="" --name-only ${{ parameters.commit }} | sed "/^${{ parameters.config }}\//!d" | sed "s/^${{ parameters.config }}\///g") + + # Get array of subscription configuration files in this commit & enviro, [AMD] per global variable + readarray -t lines < <(git show --pretty="" --name-status ${{ parameters.commit }} | sed "/^[$(var-TriggerSubscriptionDeployOn)].*${config}\/${enviro}/!d" | cut -f 2 - | sed "s/^${config}\///g") + + # Output diagnostic information + echo "All subscription configuration Files changed in this commit: (${{ parameters.commit }})" + git show --pretty="" --name-status ${{ parameters.commit }} + echo + echo "Filtered subscription configuration files changed in this commit: (${{ parameters.commit }})" + echo $(printf " %s\n" "${lines[@]}") + echo + + # Convert array to comma-separated string of values + changes=$(printf "%s," "${lines[@]}") + + # # Convert array to comma-separated string of quoted values + # changes=$(printf "\"%s\"," "${lines[@]}") + + # Remove trailing comma and display result + changes="${changes%,}" + + # Create an output variable named 'LoadSubscriptions.Changes' + echo "Creating output variable: IdentifySubscriptions.Changes=${changes}" + echo + echo "##vso[task.setvariable variable=Changes;isoutput=true]${changes}" + + $(var-bashPostInjectScript) + + workingDirectory: ${{ variables['Build.SourcesDirectory'] }} + + - task: PowerShell@2 + name: TriggerSubscriptions + displayName: Trigger Subscriptions + inputs: + targetType: 'inline' + script: | + Write-Host "Subscription changes: $env:SUBSCRIPTION_CHANGES" + if (($env:SUBSCRIPTION_CHANGES).Length -gt 0) + { + $url = "$($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI)$env:SYSTEM_TEAMPROJECTID/_apis/pipelines/$($env:SYSTEM_DEFINITIONID)/runs?api-version=6.0-preview.1" + Write-Host "Invoking pipeline definition with URL: $url" + $body = @" + { + "templateParameters": { + "subscriptions":"[$env:SUBSCRIPTION_CHANGES]" + }, + } + "@ + $headers = @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" } + $pipeline = Invoke-RestMethod -Uri $url -Headers $headers -Method Post -Body $body -ContentType application/json + Write-Host "Pipeline invocation result = $($pipeline | ConvertTo-Json -Depth 100)" + } + else + { + Write-Host "No subscription configuration changes detected in current commit ($BUILD_SOURCEVERSION)" + } + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + SUBSCRIPTION_CHANGES: $(IdentifySubscriptions.Changes) diff --git a/.pipelines/templates/steps/assign-policy.yml b/.pipelines/templates/steps/assign-policy.yml new file mode 100644 index 00000000..b646cb32 --- /dev/null +++ b/.pipelines/templates/steps/assign-policy.yml @@ -0,0 +1,56 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: description + type: string + - name: deployTemplates + type: object + - name: deployOperation + type: string + default: create + values: + - create + - what-if + - name: workingDir + type: string + +steps: + +- ${{ each policy in parameters.deployTemplates }}: + - task: AzureCLI@2 + displayName: '${{ parameters.description }} - ${{ policy }}.bicep' + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + echo "Populating templated parameters in ${{ policy }}.parameters.json" + sed -i 's~{{var-topLevelManagementGroupName}}~$(var-topLevelManagementGroupName)~g' ${{ policy }}.parameters.json + + sed -i 's~{{var-logging-logAnalyticsWorkspaceResourceId}}~$(var-logging-logAnalyticsWorkspaceResourceId)~g' ${{ policy }}.parameters.json + sed -i 's~{{var-logging-logAnalyticsWorkspaceId}}~$(var-logging-logAnalyticsWorkspaceId)~g' ${{ policy }}.parameters.json + sed -i 's~{{var-logging-logAnalyticsResourceGroupName}}~$(var-logging-logAnalyticsResourceGroupName)~g' ${{ policy }}.parameters.json + + sed -i 's~{{var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix}}~$(var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix)~g' ${{ policy }}.parameters.json + + cat ${{ policy }}.parameters.json ; echo + + echo "Deploying ${{ policy }}.bicep using ${{ parameters.deployOperation}} operation..." + + az deployment mg ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --management-group-id $(var-topLevelManagementGroupName) \ + --template-file ${{ policy }}.bicep \ + --parameters ${{ policy }}.parameters.json + + $(var-bashPostInjectScript) + workingDirectory: ${{ parameters.workingDir }} diff --git a/.pipelines/templates/steps/config-subscription.yml b/.pipelines/templates/steps/config-subscription.yml new file mode 100644 index 00000000..118abc67 --- /dev/null +++ b/.pipelines/templates/steps/config-subscription.yml @@ -0,0 +1,47 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: subscriptionGuid + type: string + - name: subscriptionType + type: string + - name: subscriptionLocation + type: string + - name: filename + type: string + - name: workingDir + type: string + +steps: + +- task: AzureCLI@2 + displayName: Configure Subscription + inputs: + azureSubscription: $(serviceConnection) + workingDirectory: '${{ parameters.workingDir }}' + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + template="landingzones/lz-${{ parameters.subscriptionType}}/main.bicep" + deployName='main-${{ parameters.subscriptionGuid }}-${{ parameters.subscriptionLocation }}' + deployName=${deployName:0:63} + + echo "Configuring subscription ${{ parameters.subscriptionGuid }} using template ${template} ..." + az deployment sub $(deployOperation) \ + --subscription ${{ parameters.subscriptionGuid }} \ + --location ${{ parameters.subscriptionLocation }} \ + --template-file $(Build.SourcesDirectory)/${template} \ + --name ${deployName} \ + --parameters @${{ parameters.filename }} \ + logAnalyticsWorkspaceResourceId='$(var-logging-logAnalyticsWorkspaceResourceId)' + + $(var-bashPostInjectScript) diff --git a/.pipelines/templates/steps/create-roles.yml b/.pipelines/templates/steps/create-roles.yml new file mode 100644 index 00000000..7b3c3df3 --- /dev/null +++ b/.pipelines/templates/steps/create-roles.yml @@ -0,0 +1,46 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: description + type: string + - name: workingDir + type: string + - name: deployTemplates + type: object + - name: deployOperation + type: string + default: create + values: + - create + - what-if + +steps: + +- ${{ each role in parameters.deployTemplates }}: + - task: AzureCLI@2 + displayName: '${{ parameters.description }} - ${{ role }}.bicep' + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + managementgroup: $(var-topLevelManagementGroupName) + inlineScript: | + $(var-bashPreInjectScript) + + echo "Deploying ${{ role }}.bicep using ${{ parameters.deployOperation}} operation..." + + az deployment mg ${{ parameters.deployOperation }} \ + --template-file ${{ role }}.bicep \ + --location $(deploymentRegion) \ + --management-group-id $(var-topLevelManagementGroupName) \ + --parameters assignableMgId=$(var-topLevelManagementGroupName) + + $(var-bashPostInjectScript) + workingDirectory: ${{ parameters.workingDir }} diff --git a/.pipelines/templates/steps/define-policy.yml b/.pipelines/templates/steps/define-policy.yml new file mode 100644 index 00000000..0a7c596d --- /dev/null +++ b/.pipelines/templates/steps/define-policy.yml @@ -0,0 +1,44 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: description + type: string + - name: workingDir + type: string + +steps: + +- task: AzureCLI@2 + displayName: ${{ parameters.description }} + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + echo "Creating policy definitions based on subfolders..." + + for defn in `ls -d */` + do + echo "Create/Update: $defn" + name=`jq -r .name $defn'azurepolicy.config.json'` + mode=`jq -r .mode $defn'azurepolicy.config.json'` + az policy definition create \ + -n $defn \ + --display-name "$name" \ + --mode "$mode" \ + --rules @$defn'azurepolicy.rules.json' \ + --params @$defn'azurepolicy.parameters.json' \ + --management-group $(var-topLevelManagementGroupName) + done + + $(var-bashPostInjectScript) + workingDirectory: ${{ parameters.workingDir }} diff --git a/.pipelines/templates/steps/define-policyset.yml b/.pipelines/templates/steps/define-policyset.yml new file mode 100644 index 00000000..4c191b91 --- /dev/null +++ b/.pipelines/templates/steps/define-policyset.yml @@ -0,0 +1,50 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: description + type: string + - name: deployTemplates + type: object + - name: deployOperation + type: string + default: create + values: + - create + - what-if + - name: workingDir + type: string + +steps: + +- ${{ each policy in parameters.deployTemplates }}: + - task: AzureCLI@2 + displayName: '${{ parameters.description }} - ${{ policy }}.bicep' + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + echo "Populating templated properties in ${{ policy }}.parameters.json" + sed -i 's~{{var-topLevelManagementGroupName}}~$(var-topLevelManagementGroupName)~g' ${{ policy }}.parameters.json + + cat ${{ policy }}.parameters.json ; echo + + echo "Deploying ${{ policy }}.bicep using ${{ parameters.deployOperation}} operation..." + + az deployment mg ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --management-group-id $(var-topLevelManagementGroupName) \ + --template-file ${{ policy }}.bicep \ + --parameters ${{ policy }}.parameters.json + + $(var-bashPostInjectScript) + workingDirectory: ${{ parameters.workingDir }} diff --git a/.pipelines/templates/steps/deploy-management-groups.yml b/.pipelines/templates/steps/deploy-management-groups.yml new file mode 100644 index 00000000..106618f7 --- /dev/null +++ b/.pipelines/templates/steps/deploy-management-groups.yml @@ -0,0 +1,46 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: description + type: string + - name: templateFile + type: string + - name: workingDir + type: string + - name: deployOperation + type: string + default: create + values: + - create + - what-if + +steps: + +- task: AzureCLI@2 + displayName: ${{ parameters.description }} + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + echo "Deploying ${{ parameters.templateFile }} using ${{ parameters.deployOperation }} operation..." + + az deployment mg ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --management-group-id $(var-parentManagementGroupId) \ + --template-file ${{ parameters.templateFile }} \ + --parameters \ + parentManagementGroupId=$(var-parentManagementGroupId) \ + topLevelManagementGroupName=$(var-topLevelManagementGroupName) + + $(var-bashPostInjectScript) + workingDirectory: '${{ parameters.workingDir }}' diff --git a/.pipelines/templates/steps/deploy-platform-connectivity-hub-azfw-policy.yml b/.pipelines/templates/steps/deploy-platform-connectivity-hub-azfw-policy.yml new file mode 100644 index 00000000..6cd6b02d --- /dev/null +++ b/.pipelines/templates/steps/deploy-platform-connectivity-hub-azfw-policy.yml @@ -0,0 +1,47 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: description + type: string + - name: templateFile + type: string + - name: workingDir + type: string + - name: deployOperation + type: string + default: create + values: + - create + - what-if + +steps: + +- task: AzureCLI@2 + displayName: Configure Azure Firewall Policy + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + echo "Deploying ${{ parameters.templateFile }} using ${{ parameters.deployOperation}} operation..." + + az deployment sub ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --subscription $(var-hubnetwork-subscriptionId) \ + --template-file ${{ parameters.templateFile }} \ + --parameters \ + resourceTags='$(var-hubnetwork-resourceTags)' \ + resourceGroupName='$(var-hubnetwork-azfw-rgPolicyName)' \ + policyName='$(var-hubnetwork-azfw-policyName)' + + $(var-bashPostInjectScript) + workingDirectory: '${{ parameters.workingDir }}/lz-platform-connectivity-hub-azfw' \ No newline at end of file diff --git a/.pipelines/templates/steps/deploy-platform-connectivity-hub-azfw.yml b/.pipelines/templates/steps/deploy-platform-connectivity-hub-azfw.yml new file mode 100644 index 00000000..cfcddda9 --- /dev/null +++ b/.pipelines/templates/steps/deploy-platform-connectivity-hub-azfw.yml @@ -0,0 +1,191 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: description + type: string + - name: moveTemplate + type: string + - name: templateFile + type: string + - name: workingDir + type: string + - name: deployOperation + type: string + default: create + values: + - create + - what-if + +steps: + +- template: ./move-subscription.yml + parameters: + managementGroup: $(var-hubnetwork-managementGroupId) + subscriptionGuid: $(var-hubnetwork-subscriptionId) + subscriptionLocation: $(deploymentRegion) + templateDirectory: $(Build.SourcesDirectory)/landingzones/utils/mg-move + templateFile: move-subscription.bicep + workingDir: ${{ parameters.workingDir }}/utils/mg-move + +- task: AzureCLI@2 + displayName: Register Resource Providers + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + az account set -s $(var-hubnetwork-subscriptionId) + + az provider register -n Microsoft.ContainerService --subscription '$(var-hubnetwork-subscriptionId)' + + $(var-bashPostInjectScript) + +- task: AzureCLI@2 + displayName: Configure Hub Networking + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + # Install Azure CLI extension for Azure Firewall + az extension add --name azure-firewall + + # Identify Azure Firewall Policy + AZURE_FIREWALL_POLICY_ID=`az network firewall policy show --subscription $(var-hubnetwork-subscriptionId) --resource-group $(var-hubnetwork-azfw-rgPolicyName) --name $(var-hubnetwork-azfw-policyName) --query id -o tsv` + echo "Azure Firewall Policy Id: $AZURE_FIREWALL_POLICY_ID" + + echo "Deploying ${{ parameters.templateFile }} using ${{ parameters.deployOperation}} operation..." + + az deployment sub ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --subscription $(var-hubnetwork-subscriptionId) \ + --template-file ${{ parameters.templateFile }} \ + --parameters \ + serviceHealthAlerts='$(var-hubnetwork-serviceHealthAlerts)' \ + securityCenter='$(var-hubnetwork-securityCenter)' \ + subscriptionRoleAssignments='$(var-hubnetwork-subscriptionRoleAssignments)' \ + subscriptionBudget='$(var-hubnetwork-subscriptionBudget)' \ + subscriptionTags='$(var-hubnetwork-subscriptionTags)' \ + resourceTags='$(var-hubnetwork-resourceTags)' \ + logAnalyticsWorkspaceResourceId='$(var-logging-logAnalyticsWorkspaceResourceId)' \ + deployPrivateDnsZones='$(var-hubnetwork-deployPrivateDnsZones)' \ + rgPrivateDnsZonesName='$(var-hubnetwork-rgPrivateDnsZonesName)' \ + deployDdosStandard='$(var-hubnetwork-deployDdosStandard)' \ + rgDdosName='$(var-hubnetwork-rgDdosName)' \ + ddosPlanName='$(var-hubnetwork-ddosPlanName)' \ + bastionName='$(var-hubnetwork-bastionName)' \ + rgPazName='$(var-hubnetwork-rgPazName)' \ + rgMrzName='$(var-hubnetwork-rgMrzName)' \ + mrzVnetName='$(var-hubnetwork-mrzVnetName)' \ + mrzVnetAddressPrefixRFC1918='$(var-hubnetwork-mrzVnetAddressPrefixRFC1918)' \ + mrzMazSubnetName='$(var-hubnetwork-mrzMazSubnetName)' \ + mrzMazSubnetAddressPrefix='$(var-hubnetwork-mrzMazSubnetAddressPrefix)' \ + mrzInfSubnetName='$(var-hubnetwork-mrzInfSubnetName)' \ + mrzInfSubnetAddressPrefix='$(var-hubnetwork-mrzInfSubnetAddressPrefix)' \ + mrzSecSubnetName='$(var-hubnetwork-mrzSecSubnetName)' \ + mrzSecSubnetAddressPrefix='$(var-hubnetwork-mrzSecSubnetAddressPrefix)' \ + mrzLogSubnetName='$(var-hubnetwork-mrzLogSubnetName)' \ + mrzLogSubnetAddressPrefix='$(var-hubnetwork-mrzLogSubnetAddressPrefix)' \ + mrzMgmtSubnetName='$(var-hubnetwork-mrzMgmtSubnetName)' \ + mrzMgmtSubnetAddressPrefix='$(var-hubnetwork-mrzMgmtSubnetAddressPrefix)' \ + rgHubName='$(var-hubnetwork-azfw-rgHubName)' \ + hubVnetName='$(var-hubnetwork-azfw-hubVnetName)' \ + hubVnetAddressPrefixRFC1918='$(var-hubnetwork-azfw-hubVnetAddressPrefixRFC1918)' \ + hubVnetAddressPrefixRFC6598='$(var-hubnetwork-azfw-hubVnetAddressPrefixRFC6598)' \ + hubVnetAddressPrefixBastion='$(var-hubnetwork-azfw-hubVnetAddressPrefixBastion)' \ + hubPazSubnetName='$(var-hubnetwork-azfw-hubPazSubnetName)' \ + hubPazSubnetAddressPrefix='$(var-hubnetwork-azfw-hubPazSubnetAddressPrefix)' \ + hubGatewaySubnetAddressPrefix='$(var-hubnetwork-azfw-hubGatewaySubnetPrefix)' \ + hubAzureFirewallSubnetAddressPrefix='$(var-hubnetwork-azfw-hubAzureFirewallSubnetAddressPrefix)' \ + hubAzureFirewallManagementSubnetAddressPrefix='$(var-hubnetwork-azfw-hubAzureFirewallManagementSubnetAddressPrefix)' \ + hubBastionSubnetAddressPrefix='$(var-hubnetwork-azfw-hubBastionSubnetAddressPrefix)' \ + azureFirewallName='$(var-hubnetwork-azfw-azureFirewallName)' \ + azureFirewallZones='$(var-hubnetwork-azfw-azureFirewallZones)' \ + azureFirewallForcedTunnelingEnabled='$(var-hubnetwork-azfw-azureFirewallForcedTunnelingEnabled)' \ + azureFirewallForcedTunnelingNextHop='$(var-hubnetwork-azfw-azureFirewallForcedTunnelingNextHop)' \ + azureFirewallExistingPolicyId="$AZURE_FIREWALL_POLICY_ID" + + $(var-bashPostInjectScript) + workingDirectory: '${{ parameters.workingDir }}/lz-platform-connectivity-hub-azfw' + +- task: AzureCLI@2 + displayName: Azure Policy - Enable Private DNS Zone Policies (if var-hubnetwork-deployPrivateDnsZones=true) + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + case $(var-hubnetwork-deployPrivateDnsZones) in + (true) + echo "Hub Network will manage private dns zones, creating Azure Policy assignment to automatically create Private Endpoint DNS Zones" + + # Apply the policy assignment + echo "Deploying policy assignment using policy/custom/assignments/dns-private-endpoints.bicep" + + az deployment mg ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --management-group-id $(var-topLevelManagementGroupName) \ + --template-file dns-private-endpoints.bicep \ + --parameters \ + policyAssignmentManagementGroupId='$(var-topLevelManagementGroupName)' \ + policyDefinitionManagementGroupId='$(var-topLevelManagementGroupName)' \ + privateDNSZoneSubscriptionId='$(var-hubnetwork-subscriptionId)' \ + privateDNSZoneResourceGroupName='$(var-hubnetwork-rgPrivateDnsZonesName)' + ;; + (*) + echo "Hub Network will not manage private dns zones. Azure Policy assignment will be skipped." + ;; + esac + + $(var-bashPostInjectScript) + workingDirectory: '$(System.DefaultWorkingDirectory)/policy/custom/assignments' + +- task: AzureCLI@2 + displayName: Azure Policy - Enable DDoS Standard (if var-hubnetwork-deployDdosStandard=true) + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + case $(var-hubnetwork-deployDdosStandard) in + (true) + echo "DDoS Standard is enabled, creating Azure Policy assignment to protect for all Virtual Networks in '$(var-topLevelManagementGroupName)' management group." + + # Identify the Resource Id for DDOS Standard Plan + DDOS_PLAN_ID=`az network ddos-protection show -g $(var-hubnetwork-rgDdosName) -n $(var-hubnetwork-ddosPlanName) --subscription $(var-hubnetwork-subscriptionId) --query id -o tsv` + echo "DDoS Standard Plan Id: $DDOS_PLAN_ID" + + # Apply the policy assignment + echo "Deploying policy assignment using policy/custom/assignments/ddos.bicep" + + az deployment mg ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --management-group-id $(var-topLevelManagementGroupName) \ + --template-file ddos.bicep \ + --parameters \ + policyAssignmentManagementGroupId='$(var-topLevelManagementGroupName)' \ + policyDefinitionManagementGroupId=$(var-topLevelManagementGroupName) \ + ddosStandardPlanId="$DDOS_PLAN_ID" + ;; + (*) + echo "DDoS Standard is not enabled. Azure Policy assignment will be skipped." + ;; + esac + + $(var-bashPostInjectScript) + workingDirectory: '$(System.DefaultWorkingDirectory)/policy/custom/assignments' \ No newline at end of file diff --git a/.pipelines/templates/steps/deploy-platform-connectivity-hub-nva.yml b/.pipelines/templates/steps/deploy-platform-connectivity-hub-nva.yml new file mode 100644 index 00000000..dbbd5563 --- /dev/null +++ b/.pipelines/templates/steps/deploy-platform-connectivity-hub-nva.yml @@ -0,0 +1,221 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: description + type: string + - name: moveTemplate + type: string + - name: templateFile + type: string + - name: workingDir + type: string + - name: deployOperation + type: string + default: create + values: + - create + - what-if + +steps: + +- template: ./move-subscription.yml + parameters: + managementGroup: $(var-hubnetwork-managementGroupId) + subscriptionGuid: $(var-hubnetwork-subscriptionId) + subscriptionLocation: $(deploymentRegion) + templateDirectory: $(Build.SourcesDirectory)/landingzones/utils/mg-move + templateFile: move-subscription.bicep + workingDir: ${{ parameters.workingDir }}/utils/mg-move + +- task: AzureCLI@2 + displayName: Register Resource Providers + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + az account set -s $(var-hubnetwork-subscriptionId) + + az provider register -n Microsoft.ContainerService --subscription '$(var-hubnetwork-subscriptionId)' + + $(var-bashPostInjectScript) + +- task: AzureCLI@2 + displayName: Configure Hub Networking + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + echo "Deploying ${{ parameters.templateFile }} using ${{ parameters.deployOperation}} operation..." + + az deployment sub ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --subscription $(var-hubnetwork-subscriptionId) \ + --template-file ${{ parameters.templateFile }} \ + --parameters \ + serviceHealthAlerts='$(var-hubnetwork-serviceHealthAlerts)' \ + securityCenter='$(var-hubnetwork-securityCenter)' \ + subscriptionRoleAssignments='$(var-hubnetwork-subscriptionRoleAssignments)' \ + subscriptionBudget='$(var-hubnetwork-subscriptionBudget)' \ + subscriptionTags='$(var-hubnetwork-subscriptionTags)' \ + resourceTags='$(var-hubnetwork-resourceTags)' \ + logAnalyticsWorkspaceResourceId='$(var-logging-logAnalyticsWorkspaceResourceId)' \ + deployPrivateDnsZones='$(var-hubnetwork-deployPrivateDnsZones)' \ + rgPrivateDnsZonesName='$(var-hubnetwork-rgPrivateDnsZonesName)' \ + deployDdosStandard='$(var-hubnetwork-deployDdosStandard)' \ + rgDdosName='$(var-hubnetwork-rgDdosName)' \ + ddosPlanName='$(var-hubnetwork-ddosPlanName)' \ + bastionName='$(var-hubnetwork-bastionName)' \ + rgPazName='$(var-hubnetwork-rgPazName)' \ + rgMrzName='$(var-hubnetwork-rgMrzName)' \ + mrzVnetName='$(var-hubnetwork-mrzVnetName)' \ + mrzVnetAddressPrefixRFC1918='$(var-hubnetwork-mrzVnetAddressPrefixRFC1918)' \ + mrzMazSubnetName='$(var-hubnetwork-mrzMazSubnetName)' \ + mrzMazSubnetAddressPrefix='$(var-hubnetwork-mrzMazSubnetAddressPrefix)' \ + mrzInfSubnetName='$(var-hubnetwork-mrzInfSubnetName)' \ + mrzInfSubnetAddressPrefix='$(var-hubnetwork-mrzInfSubnetAddressPrefix)' \ + mrzSecSubnetName='$(var-hubnetwork-mrzSecSubnetName)' \ + mrzSecSubnetAddressPrefix='$(var-hubnetwork-mrzSecSubnetAddressPrefix)' \ + mrzLogSubnetName='$(var-hubnetwork-mrzLogSubnetName)' \ + mrzLogSubnetAddressPrefix='$(var-hubnetwork-mrzLogSubnetAddressPrefix)' \ + mrzMgmtSubnetName='$(var-hubnetwork-mrzMgmtSubnetName)' \ + mrzMgmtSubnetAddressPrefix='$(var-hubnetwork-mrzMgmtSubnetAddressPrefix)' \ + rgHubName='$(var-hubnetwork-nva-rgHubName)' \ + hubVnetName='$(var-hubnetwork-nva-hubVnetName)' \ + hubVnetAddressPrefixRFC1918='$(var-hubnetwork-nva-hubVnetAddressPrefixRFC1918)' \ + hubVnetAddressPrefixRFC6598='$(var-hubnetwork-nva-hubVnetAddressPrefixRFC6598)' \ + hubVnetAddressPrefixBastion='$(var-hubnetwork-nva-hubVnetAddressPrefixBastion)' \ + hubEanSubnetName='$(var-hubnetwork-nva-hubEanSubnetName)' \ + hubEanSubnetAddressPrefix='$(var-hubnetwork-nva-hubEanSubnetAddressPrefix)' \ + hubPublicSubnetName='$(var-hubnetwork-nva-hubPublicSubnetName)' \ + hubPublicSubnetAddressPrefix='$(var-hubnetwork-nva-hubPublicSubnetAddressPrefix)' \ + hubPazSubnetName='$(var-hubnetwork-nva-hubPazSubnetName)' \ + hubPazSubnetAddressPrefix='$(var-hubnetwork-nva-hubPazSubnetAddressPrefix)' \ + hubDevIntSubnetName='$(var-hubnetwork-nva-hubDevIntSubnetName)' \ + hubDevIntSubnetAddressPrefix='$(var-hubnetwork-nva-hubDevIntSubnetAddressPrefix)' \ + hubProdIntSubnetName='$(var-hubnetwork-nva-hubProdIntSubnetName)' \ + hubProdIntSubnetAddressPrefix='$(var-hubnetwork-nva-hubProdIntSubnetAddressPrefix)' \ + hubMrzIntSubnetName='$(var-hubnetwork-nva-hubMrzIntSubnetName)' \ + hubMrzIntSubnetAddressPrefix='$(var-hubnetwork-nva-hubMrzIntSubnetAddressPrefix)' \ + hubHASubnetName='$(var-hubnetwork-nva-hubHASubnetName)' \ + hubHASubnetAddressPrefix='$(var-hubnetwork-nva-hubHASubnetAddressPrefix)' \ + hubGatewaySubnetPrefix='$(var-hubnetwork-nva-hubGatewaySubnetPrefix)' \ + hubBastionSubnetAddressPrefix='$(var-hubnetwork-nva-hubBastionSubnetAddressPrefix)' \ + deployFirewallVMs='$(var-hubnetwork-nva-deployFirewallVMs)' \ + useFortigateFW='$(var-hubnetwork-nva-useFortigateFW)' \ + fwDevILBName='$(var-hubnetwork-nva-fwDevILBName)' \ + fwDevVMSku='$(var-hubnetwork-nva-fwDevVMSku)' \ + fwDevVM1Name='$(var-hubnetwork-nva-fwDevVM1Name)' \ + fwDevVM2Name='$(var-hubnetwork-nva-fwDevVM2Name)' \ + fwDevILBExternalFacingIP='$(var-hubnetwork-nva-fwDevILBExternalFacingIP)' \ + fwDevVM1ExternalFacingIP='$(var-hubnetwork-nva-fwDevVM1ExternalFacingIP)' \ + fwDevVM2ExternalFacingIP='$(var-hubnetwork-nva-fwDevVM2ExternalFacingIP)' \ + fwDevVM1MrzIntIP='$(var-hubnetwork-nva-fwDevVM1MrzIntIP)' \ + fwDevVM2MrzIntIP='$(var-hubnetwork-nva-fwDevVM2MrzIntIP)' \ + fwDevILBDevIntIP='$(var-hubnetwork-nva-fwDevILBDevIntIP)' \ + fwDevVM1DevIntIP='$(var-hubnetwork-nva-fwDevVM1DevIntIP)' \ + fwDevVM2DevIntIP='$(var-hubnetwork-nva-fwDevVM2DevIntIP)' \ + fwDevVM1HAIP='$(var-hubnetwork-nva-fwDevVM1HAIP)' \ + fwDevVM2HAIP='$(var-hubnetwork-nva-fwDevVM2HAIP)' \ + fwProdILBName='$(var-hubnetwork-nva-fwProdILBName)' \ + fwProdVMSku='$(var-hubnetwork-nva-fwProdVMSku)' \ + fwProdVM1Name='$(var-hubnetwork-nva-fwProdVM1Name)' \ + fwProdVM2Name='$(var-hubnetwork-nva-fwProdVM2Name)' \ + fwProdILBExternalFacingIP='$(var-hubnetwork-nva-fwProdILBExternalFacingIP)' \ + fwProdVM1ExternalFacingIP='$(var-hubnetwork-nva-fwProdVM1ExternalFacingIP)' \ + fwProdVM2ExternalFacingIP='$(var-hubnetwork-nva-fwProdVM2ExternalFacingIP)' \ + fwProdVM1MrzIntIP='$(var-hubnetwork-nva-fwProdVM1MrzIntIP)' \ + fwProdVM2MrzIntIP='$(var-hubnetwork-nva-fwProdVM2MrzIntIP)' \ + fwProdILBPrdIntIP='$(var-hubnetwork-nva-fwProdILBPrdIntIP)' \ + fwProdVM1PrdIntIP='$(var-hubnetwork-nva-fwProdVM1PrdIntIP)' \ + fwProdVM2PrdIntIP='$(var-hubnetwork-nva-fwProdVM2PrdIntIP)' \ + fwProdVM1HAIP='$(var-hubnetwork-nva-fwProdVM1HAIP)' \ + fwProdVM2HAIP='$(var-hubnetwork-nva-fwProdVM2HAIP)' \ + fwUsername='$(var-hubnetwork-nva-fwUsername)' \ + fwPassword='$(var-hubnetwork-nva-fwPassword)' + + $(var-bashPostInjectScript) + workingDirectory: '${{ parameters.workingDir }}/lz-platform-connectivity-hub-nva' + +- task: AzureCLI@2 + displayName: Azure Policy - Enable Private DNS Zone Policies (if var-hubnetwork-deployPrivateDnsZones=true) + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + case $(var-hubnetwork-deployPrivateDnsZones) in + (true) + echo "Hub Network will manage private dns zones, creating Azure Policy assignment to automatically create Private Endpoint DNS Zones" + + # Apply the policy assignment + echo "Deploying policy assignment using policy/custom/assignments/dns-private-endpoints.bicep" + + az deployment mg ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --management-group-id $(var-topLevelManagementGroupName) \ + --template-file dns-private-endpoints.bicep \ + --parameters \ + policyAssignmentManagementGroupId='$(var-topLevelManagementGroupName)' \ + policyDefinitionManagementGroupId='$(var-topLevelManagementGroupName)' \ + privateDNSZoneSubscriptionId='$(var-hubnetwork-subscriptionId)' \ + privateDNSZoneResourceGroupName='$(var-hubnetwork-rgPrivateDnsZonesName)' + ;; + (*) + echo "Hub Network will not manage private dns zones. Azure Policy assignment will be skipped." + ;; + esac + + $(var-bashPostInjectScript) + workingDirectory: '$(System.DefaultWorkingDirectory)/policy/custom/assignments' + +- task: AzureCLI@2 + displayName: Azure Policy - Enable DDoS Standard (if var-hubnetwork-deployDdosStandard=true) + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + case $(var-hubnetwork-deployDdosStandard) in + (true) + echo "DDoS Standard is enabled, creating Azure Policy assignment to protect for all Virtual Networks in '$(var-topLevelManagementGroupName)' management group." + + # Identify the Resource Id for DDOS Standard Plan + DDOS_PLAN_ID=`az network ddos-protection show -g $(var-hubnetwork-rgDdosName) -n $(var-hubnetwork-ddosPlanName) --subscription $(var-hubnetwork-subscriptionId) --query id -o tsv` + echo "DDoS Standard Plan Id: $DDOS_PLAN_ID" + + # Apply the policy assignment + echo "Deploying policy assignment using policy/custom/assignments/ddos.bicep" + + az deployment mg ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --management-group-id $(var-topLevelManagementGroupName) \ + --template-file ddos.bicep \ + --parameters \ + policyAssignmentManagementGroupId='$(var-topLevelManagementGroupName)' \ + policyDefinitionManagementGroupId=$(var-topLevelManagementGroupName) \ + ddosStandardPlanId="$DDOS_PLAN_ID" + ;; + (*) + echo "DDoS Standard is not enabled. Azure Policy assignment will be skipped." + ;; + esac + + $(var-bashPostInjectScript) + workingDirectory: '$(System.DefaultWorkingDirectory)/policy/custom/assignments' \ No newline at end of file diff --git a/.pipelines/templates/steps/deploy-platform-logging.yml b/.pipelines/templates/steps/deploy-platform-logging.yml new file mode 100644 index 00000000..c40c5350 --- /dev/null +++ b/.pipelines/templates/steps/deploy-platform-logging.yml @@ -0,0 +1,81 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: description + type: string + - name: moveTemplate + type: string + - name: configTemplate + type: string + - name: workingDir + type: string + - name: deployOperation + type: string + default: create + values: + - create + - what-if + +steps: + +- template: ./move-subscription.yml + parameters: + managementGroup: $(var-logging-managementGroupId) + subscriptionGuid: $(var-logging-subscriptionId) + subscriptionLocation: $(deploymentRegion) + templateDirectory: $(Build.SourcesDirectory)/landingzones/utils/mg-move + templateFile: move-subscription.bicep + workingDir: ${{ parameters.workingDir }}/utils/mg-move + + +- task: AzureCLI@2 + displayName: Register Resource Providers + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + az account set -s $(var-logging-subscriptionId) + + az provider register -n Microsoft.ContainerService --subscription '$(var-logging-subscriptionId)' + + $(var-bashPostInjectScript) + + +- task: AzureCLI@2 + displayName: Configure Logging + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + echo "Deploying ${{ parameters.configTemplate }} using ${{ parameters.deployOperation}} operation..." + + az deployment sub ${{ parameters.deployOperation }} \ + --location $(deploymentRegion) \ + --subscription $(var-logging-subscriptionId) \ + --template-file ${{ parameters.configTemplate }} \ + --parameters \ + serviceHealthAlerts='$(var-logging-serviceHealthAlerts)' \ + securityCenter='$(var-logging-securityCenter)' \ + subscriptionRoleAssignments='$(var-logging-subscriptionRoleAssignments)' \ + subscriptionBudget='$(var-logging-subscriptionBudget)'\ + subscriptionTags='$(var-logging-subscriptionTags)' \ + resourceTags='$(var-logging-resourceTags)' \ + logAnalyticsResourceGroupName='$(var-logging-logAnalyticsResourceGroupName)' \ + logAnalyticsWorkspaceName='$(var-logging-logAnalyticsWorkspaceName)' \ + logAnalyticsAutomationAccountName='$(var-logging-logAnalyticsAutomationAccountName)' + + $(var-bashPostInjectScript) + workingDirectory: '${{ parameters.workingDir }}/lz-platform-logging' diff --git a/.pipelines/templates/steps/deploy-subscription.yml b/.pipelines/templates/steps/deploy-subscription.yml new file mode 100644 index 00000000..151937c5 --- /dev/null +++ b/.pipelines/templates/steps/deploy-subscription.yml @@ -0,0 +1,76 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: config + type: string + - name: enviro + type: string + - name: environment + type: string + - name: filename + type: string + - name: fullpath + type: string + - name: relativepath + type: string + - name: mgmtgroup + type: string + - name: subguid + type: string + - name: subtype + type: string + - name: sublocation + type: string + +steps: + +- template: ./move-subscription.yml + parameters: + managementGroup: ${{ parameters.mgmtgroup }} + subscriptionGuid: ${{ parameters.subguid }} + subscriptionLocation: ${{ parameters.sublocation }} + templateDirectory: $(Build.SourcesDirectory)/landingzones/utils/mg-move + templateFile: move-subscription.bicep + workingDir: $(Build.SourcesDirectory)/$(subscriptionsPathFromRoot)/${{ parameters.relativePath }} + +- template: ./load-log-analytics-vars.yml + +- task: AzureCLI@2 + displayName: Register Azure Provider Features + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + az account set -s ${{ parameters.subguid }} + + echo 'Registering feature Microsoft.Compute/EncryptionAtHost' + az feature register --namespace Microsoft.Compute --name EncryptionAtHost + + while [ "`az feature show --namespace Microsoft.Compute --name EncryptionAtHost --query properties.state -o tsv`" != "Registered" ] + do + echo 'Waiting for Microsoft.Compute/EncryptionAtHost to register, checking in 5s ...' + sleep 5s + done + + echo 'Registering Microsoft.Compute namespace' + az provider register -n Microsoft.Compute + + $(var-bashPostInjectScript) + +- template: ./config-subscription.yml + parameters: + subscriptionGuid: ${{ parameters.subguid }} + subscriptionType: ${{ parameters.subtype }} + subscriptionLocation: ${{ parameters.sublocation }} + filename: ${{ parameters.filename }} + workingDir: $(Build.SourcesDirectory)/$(subscriptionsPathFromRoot)/${{ parameters.relativePath }} diff --git a/.pipelines/templates/steps/load-log-analytics-vars.yml b/.pipelines/templates/steps/load-log-analytics-vars.yml new file mode 100644 index 00000000..c1ff38c8 --- /dev/null +++ b/.pipelines/templates/steps/load-log-analytics-vars.yml @@ -0,0 +1,38 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +steps: + +- task: AzureCLI@2 + displayName: Load Log Analytics variables + inputs: + azureSubscription: $(serviceConnection) + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + # Get workspace info via Azure CLI + echo 'Retrieving log analytics workspace info via Azure CLI...' + workspaceInfo=`az monitor log-analytics workspace show --subscription $(var-logging-subscriptionId) -g $(var-logging-logAnalyticsResourceGroupName) -n $(var-logging-logAnalyticsWorkspaceName) --query '{workspaceResourceId:id,workspaceId:customerId}' -o json` + + # Set local environment variables + LOG_ANALYTICS_WORKSPACE_RESOURCE_ID=`echo $workspaceInfo | jq -r .workspaceResourceId` + LOG_ANALYTICS_WORKSPACE_ID=`echo $workspaceInfo | jq -r .workspaceId` + + # Show found workspace info + echo " log analytics workspace resource ID: $LOG_ANALYTICS_WORKSPACE_RESOURCE_ID" + echo " log analytics workspace ID: $LOG_ANALYTICS_WORKSPACE_ID" + + # Set pipeline environment variables + echo 'Storing workspace info in pipeline variables: var-logging-logAnalyticsWorkspaceResourceId, var-logging-logAnalyticsWorkspaceId...' + echo "##vso[task.setvariable variable=var-logging-logAnalyticsWorkspaceResourceId]$LOG_ANALYTICS_WORKSPACE_RESOURCE_ID" + echo "##vso[task.setvariable variable=var-logging-logAnalyticsWorkspaceId]$LOG_ANALYTICS_WORKSPACE_ID" + + $(var-bashPostInjectScript) diff --git a/.pipelines/templates/steps/move-subscription.yml b/.pipelines/templates/steps/move-subscription.yml new file mode 100644 index 00000000..1eac192f --- /dev/null +++ b/.pipelines/templates/steps/move-subscription.yml @@ -0,0 +1,49 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: templateDirectory + type: string + - name: templateFile + type: string + - name: managementGroup + type: string + - name: subscriptionGuid + type: string + - name: subscriptionLocation + type: string + - name: workingDir + type: string + +steps: +- task: AzureCLI@2 + displayName: Move Subscription + inputs: + azureSubscription: $(serviceConnection) + workingDirectory: '${{ parameters.templateDirectory }}' + scriptType: 'bash' + scriptLocation: 'inlineScript' + inlineScript: | + $(var-bashPreInjectScript) + + echo "Moving subscription ${{ parameters.subscriptionGuid }} to management group ${{ parameters.managementGroup }}..." + + deployName='move-subscription-${{ parameters.subscriptionGuid }}-${{ parameters.subscriptionLocation }}' + deployName=${deployName:0:63} + + az deployment mg $(deployOperation) \ + --location ${{ parameters.subscriptionLocation }} \ + --management-group-id ${{ parameters.managementGroup }} \ + --template-file ${{ parameters.templateFile }} \ + --name ${deployName} \ + --parameters \ + managementGroupId='${{ parameters.managementGroup }}' \ + subscriptionId='${{ parameters.subscriptionGuid }}' + + $(var-bashPostInjectScript) diff --git a/.pipelines/templates/steps/run-linter.yml b/.pipelines/templates/steps/run-linter.yml new file mode 100644 index 00000000..16e42208 --- /dev/null +++ b/.pipelines/templates/steps/run-linter.yml @@ -0,0 +1,59 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: validationTypes + type: string + default: '' + +steps: + +- task: Bash@3 + displayName: Install Linter + inputs: + targetType: 'inline' + script: | + $(var-bashPreInjectScript) + + echo + echo "Pulling the github/super-linter:latest docker image..." + echo + + docker pull github/super-linter:latest + + $(var-bashPostInjectScript) + +- task: Bash@3 + displayName: Run Linter + inputs: + targetType: 'inline' + script: | + $(var-bashPreInjectScript) + + # Possible enhancments: + # * Detect and process only added or modified files (like trigger-subscriptions.yml) + + if [[ -n '${{ parameters.validationTypes }}' ]]; then + echo + echo "Running GitHub Super Linter with validation types: ${{ parameters.validationTypes }} ..." + echo + + declare -a vtypesArray=( ${{ parameters.validationTypes }} ) + declare vtypesArgs=$(printf ' -e VALIDATE_%s=true ' "${vtypesArray[@]}") + + docker run --name linter -e RUN_LOCAL=true -e LINTER_RULES_PATH=config/linters ${vtypesArgs} -v $(System.DefaultWorkingDirectory):/tmp/lint --name super_linter github/super-linter + else + echo + echo "Running GitHub Super Linter on all supported validation types ..." + echo + + docker run --name linter -e RUN_LOCAL=true -e LINTER_RULES_PATH=config/linters -v $(System.DefaultWorkingDirectory):/tmp/lint --name super_linter github/super-linter + fi + + $(var-bashPostInjectScript) diff --git a/.pipelines/templates/steps/show-variables.yml b/.pipelines/templates/steps/show-variables.yml new file mode 100644 index 00000000..3fd076d2 --- /dev/null +++ b/.pipelines/templates/steps/show-variables.yml @@ -0,0 +1,54 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +parameters: + - name: json + type: string + default: '' + +steps: + +# Show variables (hard-coded) +- task: Bash@3 + displayName: Variables + inputs: + targetType: inline + script: | + $(var-bashPreInjectScript) + + echo + echo "COMMON" + echo " deploymentRegion = $(deploymentRegion)" + echo " serviceConnection = $(serviceConnection)" + echo " vmImage = $(vmImage)" + echo " deployOperation = $(deployOperation)" + echo " subscriptionsPathFromRoot = $(subscriptionsPathFromRoot)" + echo " var-TriggerSubscriptionDeployOn = $(var-TriggerSubscriptionDeployOn)" + + # echo " var-bashPreInjectScript = $(var-bashPreInjectScript)" + # echo " var-bashPostInjectScript = $(var-bashPostInjectScript)" + + echo + echo "MANAGEMENT GROUPS" + echo " var-parentManagementGroupId = $(var-parentManagementGroupId)" + echo " var-topLevelManagementGroupName = $(var-topLevelManagementGroupName)" + + echo + echo "LOGGING" + echo " var-logging-managementGroupId = $(var-logging-managementGroupId)" + echo " var-logging-subscriptionId = $(var-logging-subscriptionId)" + echo " var-logging-logAnalyticsResourceGroupName = $(var-logging-logAnalyticsResourceGroupName)" + echo " var-logging-logAnalyticsWorkspaceName = $(var-logging-logAnalyticsWorkspaceName)" + + echo + echo "HUB NETWORKING" + echo " var-hubnetwork-managementGroupId = $(var-hubnetwork-managementGroupId)" + echo " var-hubnetwork-subscriptionId = $(var-hubnetwork-subscriptionId)" + + $(var-bashPostInjectScript) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..80b5ab06 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,43 @@ +# Contributing Reference Implementation + +We are very happy to accept community contributions to this reference implementation, whether those are Pull Requests, Feature Suggestions or Bug Reports. Please note that by participating in this project, you agree to abide by the [Code of Conduct](CODE_OF_CONDUCT.md), as well as the terms of the [CLA](#cla). + +## Getting Started + +* You are free to work on automation templates on any platform using any editor, but you may find it quickest to get started using [VSCode](https://code.visualstudio.com/Download) with the [Bicep extension](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep). + +* Fork this repository + +* Clone the forked repository to your local development environment + + +## Deployment to your environment + +See [Onboarding Guide for Azure DevOps](ONBOARDING_GUIDE_ADO.md) + +## Pull Requests + +* Ensure that a user story or an issue has been created to track the feature enhancement or bug that is being fixed. + +* In the PR description, make sure you've included "Fixes #{issue_number}" e.g. "Fixes #242" so that Azure DevOps or GitHub knows to link it to an issue. + +* To avoid multiple contributors working on the same issue, please add a comment to the issue to let us know you plan to work on it. + +* If a significant amount of design is required, please include a proposal in the issue and wait for approval before working on code. If there's anything you're not sure about, please feel free to discuss this in the issue. We'd much rather all be on the same page at the start, so that there's less chance that drastic changes will be needed when your pull request is reviewed. + +## Feature Suggestions + +* Ensure you have included a "What?" - what your feature entails, being as specific as possible, and giving use cases where possible. + +* Ensure you have included a "Why?" - what the benefit of including this feature will be. + +## Bug Reports + +* Be as specific as possible such as steps to reproduce the issue, and any example files or snippets of code needed to reproduce it. + + +## CLA + +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. \ No newline at end of file diff --git a/LICENSE b/LICENSE index 9e841e7a..b9b5a645 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,23 @@ - MIT License +Copyright (c) Microsoft Corporation. - Copyright (c) Microsoft Corporation. +MIT License - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: +Azure Landing Zones for Canadian Public Sector - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/ONBOARDING_GUIDE_ADO.md b/ONBOARDING_GUIDE_ADO.md new file mode 100644 index 00000000..420598d3 --- /dev/null +++ b/ONBOARDING_GUIDE_ADO.md @@ -0,0 +1,605 @@ +# Onboarding Guide for Azure DevOps + +This document provides steps required to onboard to the Azure Landing Zones design based on Azure DevOps Pipelines. + +**All steps will need to be repeated per Azure AD tenant.** + +## Step 1: Create Service Principal Account & Assign RBAC + +A service principal account is required to automate the Azure DevOps pipelines. + +* **Service Principal Name**: any name (i.e. spn-azure-platform-ops) + +* **RBAC Assignment** + + * Scope: Tenant Root Group (this is a management group) + + * Role: Owner + +## Step 2: Configure Service Connection in Azure DevOps Project Configuration + +* **Scope Level**: Management Group + +* **Service Connection Name**: spn-azure-platform-ops + + *Service Connection Name will be used to configure Azure DevOps Pipelines.* + +* **Instructions**: [Service connections in Azure Pipelines - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml) + + +## Step 3: Configure Management Group Deployment + +### Step: 3.1: Update common.yml in git repository + +Create/edit **.pipelines/templates/variables/common.yml** in Git. This file is used in all Azure DevOps pipelines. + +**Sample YAML** +```yaml +variables: + + deploymentRegion: canadacentral + serviceConnection: spn-azure-platform-ops + vmImage: ubuntu-latest + deployOperation: create + +``` + +### Step 3.2: Update environment config file in git repository + +1. Create/edit **./pipelines/templates/variables/-.yml** in Git (i.e. CanadaESLZ-main.yml). This file name is automatically inferred based on the Azure DevOps organization name and the branch. + + **Sample environment YAML** + + ```yaml + variables: + + # Management Groups + var-parentManagementGroupId: abcddfdb-bef5-46d9-99cf-ed67dabc8783 + var-topLevelManagementGroupName: pubsec + + ``` + +2. Commit the changes to git repository + +### Step 3.3: Configure Azure DevOps Pipeline + +1. Pipeline definition for Management Group. + + *Note: Pipelines are stored as YAML definitions in Git and imported into Azure DevOps Pipelines. This approach allows for portability and change tracking.* + + 1. Go to Pipelines + 2. New Pipeline + 3. Choose Azure Repos Git + 4. Select Repository + 5. Select Existing Azure Pipeline YAML file + 6. Identify the pipeline in `.pipelines/management-groups.yml`. + 7. Save the pipeline (don't run it yet) + 8. Rename the pipeline to `management-groups-ci` + + +2. Run pipeline and wait for completion. + + +## Step 4: Logging Landing Zone + +### Step 4.1: Setup Azure AD Security Group (Optional) + +At least one Azure AD Security Group is required for role assignment. Role assignment can be set for Owner, Contributor, and/or Reader roles. Note down the Security Group object id, it will be required for next step. + +### Step 4.2: Update configuration files in git repository + +Set the configuration parameters even if there's an existing central Log Analytics Workspace. These settings are used by other deployments such as Azure Policy for Log Analytics. In this case, use the values of the existing Log Analytics Workspace. + +When a Log Analytics Workspace & Automation account already exists, enter Subscription ID, Resource Group, Log Analytics Workspace name and Automation account name. The automation will update the existing deployment instead of creating new resources. + +1. Edit `.pipelines/templates/variables/-.yml` in Git. This configuration file was created in Step 3. + + **Sample environment YAML** + + ```yml + variables: + # Management Groups + var-parentManagementGroupId: 343ddfdb-bef5-46d9-99cf-ed67d5948783 + var-topLevelManagementGroupName: pubsec + + # Logging + var-logging-managementGroupId: pubsecPlatform + var-logging-subscriptionId: bc0a4f9f-07fa-4284-b1bd-fbad38578d3a + var-logging-logAnalyticsResourceGroupName: pubsec-central-logging-rg + var-logging-logAnalyticsWorkspaceName: log-analytics-workspace + var-logging-logAnalyticsAutomationAccountName: automation-account + var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix: pubsecnsg + var-logging-serviceHealthAlerts: > + { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + } + } + var-logging-securityCenter: > + { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + var-logging-subscriptionRoleAssignments: > + [ + { + "comments": "Built-in Contributor Role", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + } + ] + var-logging-subscriptionBudget: > + { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ "alzcanadapubsec@microsoft.com" ] + } + var-logging-subscriptionTags: > + { + "ISSO": "isso-tbd" + } + var-logging-resourceTags: > + { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + ``` + +2. Commit the changes to git repository. + +### Step 4.3: Configure Azure DevOps Pipeline (only required if a new central Log Analytics Workspace is required) + +1. Pipeline definition for Central Logging. + + *Note: Pipelines are stored as YAML definitions in Git and imported into Azure DevOps Pipelines. This approach allows for portability and change tracking.* + + 1. Go to Pipelines + 2. New Pipeline + 3. Choose Azure Repos Git + 4. Select Repository + 5. Select Existing Azure Pipeline YAML file + 6. Identify the pipeline in `.pipelines/platform-logging.yml`. + 7. Save the pipeline (don't run it yet) + 8. Rename the pipeline to `platform-logging-ci` + + +2. Run pipeline and wait for completion. + +## Step 5: Configure Azure Policies + +1. Pipeline definition for Azure Policies. + + *Note: Pipelines are stored as YAML definitions in Git and imported into Azure DevOps Pipelines. This approach allows for portability and change tracking.* + + 1. Go to Pipelines + 2. New Pipeline + 3. Choose Azure Repos Git + 4. Select Repository + 5. Select Existing Azure Pipeline YAML file + 6. Identify the pipeline in `.pipelines/policy.yml`. + 7. Save the pipeline (don't run it yet) + 8. Rename the pipeline to `policy-ci` + + +2. Run pipeline and wait for completion. + + +## Step 6: Configure Custom Roles + +1. Pipeline definition for Custom Roles. + + *Note: Pipelines are stored as YAML definitions in Git and imported into Azure DevOps Pipelines. This approach allows for portability and change tracking.* + + 1. Go to Pipelines + 2. New Pipeline + 3. Choose Azure Repos Git + 4. Select Repository + 5. Select Existing Azure Pipeline YAML file + 6. Identify the pipeline in `.pipelines/roles.yml`. + 7. Save the pipeline (don't run it yet) + 8. Rename the pipeline to `roles-ci` + + +2. Run pipeline and wait for completion. + +## Step 7: Configure Hub Networking using NVAs + +1. Edit `.pipelines/templates/variables/-.yml` in Git. This configuration file was created in Step 3. + + Update configuration with the networking section. There are two options for Hub Networking: + + 1. Hub Networking with Azure Firewall + 2. Hub Networking with Fortinet Firewall (NVA) + + Depending on the preference, you may delete/comment the configuration that is not required. + + **Sample environment YAML** + + ```yml + variables: + # Management Groups + var-parentManagementGroupId: 343ddfdb-bef5-46d9-99cf-ed67d5948783 + var-topLevelManagementGroupName: pubsec + + # Logging + var-logging-managementGroupId: pubsecPlatform + var-logging-subscriptionId: bc0a4f9f-07fa-4284-b1bd-fbad38578d3a + var-logging-logAnalyticsResourceGroupName: pubsec-central-logging-rg + var-logging-logAnalyticsWorkspaceName: log-analytics-workspace + var-logging-logAnalyticsAutomationAccountName: automation-account + var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix: pubsecnsg + var-logging-serviceHealthAlerts: > + { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + } + } + var-logging-securityCenter: > + { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + var-logging-subscriptionRoleAssignments: > + [ + { + "comments": "Built-in Contributor Role", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + } + ] + var-logging-subscriptionBudget: > + { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ "alzcanadapubsec@microsoft.com" ] + } + var-logging-subscriptionTags: > + { + "ISSO": "isso-tbd" + } + var-logging-resourceTags: > + { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + + # Hub Networking + var-hubnetwork-managementGroupId: pubsecPlatform + var-hubnetwork-subscriptionId: ed7f4eed-9010-4227-b115-2a5e37728f27 + var-hubnetwork-serviceHealthAlerts: > + { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + } + } + var-hubnetwork-securityCenter: > + { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + var-hubnetwork-subscriptionRoleAssignments: > + [ + { + "comments": "Built-in Contributor Role", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + } + ] + var-hubnetwork-subscriptionBudget: > + { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ "alzcanadapubsec@microsoft.com" ] + } + var-hubnetwork-subscriptionTags: > + { + "ISSO": "isso-tbd" + } + var-hubnetwork-resourceTags: > + { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + + ## Hub Networking - Private Dns Zones + var-hubnetwork-deployPrivateDnsZones: true + var-hubnetwork-rgPrivateDnsZonesName: pubsec-dns-rg + + ## Hub Networking - DDOS + var-hubnetwork-deployDdosStandard: false + var-hubnetwork-rgDdosName: pubsec-ddos-rg + var-hubnetwork-ddosPlanName: ddos-plan + + ## Hub Networking - Public Zone + var-hubnetwork-rgPazName: pubsec-public-access-zone-rg + + ## Hub Networking - Management Restricted Zone Virtual Network + var-hubnetwork-rgMrzName: pubsec-management-restricted-zone-rg + var-hubnetwork-mrzVnetName: management-restricted-vnet + var-hubnetwork-mrzVnetAddressPrefixRFC1918: 10.18.4.0/22 + + var-hubnetwork-mrzMazSubnetName: MazSubnet + var-hubnetwork-mrzMazSubnetAddressPrefix: 10.18.4.0/25 + + var-hubnetwork-mrzInfSubnetName: InfSubnet + var-hubnetwork-mrzInfSubnetAddressPrefix: 10.18.4.128/25 + + var-hubnetwork-mrzSecSubnetName: SecSubnet + var-hubnetwork-mrzSecSubnetAddressPrefix: 10.18.5.0/26 + + var-hubnetwork-mrzLogSubnetName: LogSubnet + var-hubnetwork-mrzLogSubnetAddressPrefix: 10.18.5.64/26 + + var-hubnetwork-mrzMgmtSubnetName: MgmtSubnet + var-hubnetwork-mrzMgmtSubnetAddressPrefix: 10.18.5.128/26 + + var-hubnetwork-bastionName: bastion + + #################################################################################### + ### Hub Networking with Azure Firewall ### + #################################################################################### + var-hubnetwork-azfw-rgPolicyName: pubsec-azure-firewall-policy-rg + var-hubnetwork-azfw-policyName: pubsecAzureFirewallPolicy + + var-hubnetwork-azfw-rgHubName: pubsec-hub-networking-rg + var-hubnetwork-azfw-hubVnetName: hub-vnet + var-hubnetwork-azfw-hubVnetAddressPrefixRFC1918: 10.18.0.0/22 + var-hubnetwork-azfw-hubVnetAddressPrefixRFC6598: 100.60.0.0/16 + var-hubnetwork-azfw-hubVnetAddressPrefixBastion: 192.168.0.0/16 + + var-hubnetwork-azfw-hubPazSubnetName: PAZSubnet + var-hubnetwork-azfw-hubPazSubnetAddressPrefix: 100.60.1.0/24 + + var-hubnetwork-azfw-hubGatewaySubnetPrefix: 10.18.0.0/27 + var-hubnetwork-azfw-hubAzureFirewallSubnetAddressPrefix: 10.18.1.0/24 + var-hubnetwork-azfw-hubAzureFirewallManagementSubnetAddressPrefix: 10.18.2.0/26 + var-hubnetwork-azfw-hubBastionSubnetAddressPrefix: 192.168.0.0/24 + + var-hubnetwork-azfw-azureFirewallName: pubsecAzureFirewall + var-hubnetwork-azfw-azureFirewallZones: '["1", "2", "3"]' + var-hubnetwork-azfw-azureFirewallForcedTunnelingEnabled: false + var-hubnetwork-azfw-azureFirewallForcedTunnelingNextHop: 10.17.1.4 + + #################################################################################### + ### Hub Networking with Fortinet Firewalls ### + #################################################################################### + + ## Hub Networking - Core Virtual Network + var-hubnetwork-nva-rgHubName: pubsec-hub-networking-rg + var-hubnetwork-nva-hubVnetName: hub-vnet + var-hubnetwork-nva-hubVnetAddressPrefixRFC1918: 10.18.0.0/22 + var-hubnetwork-nva-hubVnetAddressPrefixRFC6598: 100.60.0.0/16 + var-hubnetwork-nva-hubVnetAddressPrefixBastion: 192.168.0.0/16 + + var-hubnetwork-nva-hubEanSubnetName: EanSubnet + var-hubnetwork-nva-hubEanSubnetAddressPrefix: 10.18.0.0/27 + + var-hubnetwork-nva-hubPublicSubnetName: PublicSubnet + var-hubnetwork-nva-hubPublicSubnetAddressPrefix: 100.60.0.0/24 + + var-hubnetwork-nva-hubPazSubnetName: PAZSubnet + var-hubnetwork-nva-hubPazSubnetAddressPrefix: 100.60.1.0/24 + + var-hubnetwork-nva-hubDevIntSubnetName: DevIntSubnet + var-hubnetwork-nva-hubDevIntSubnetAddressPrefix: 10.18.0.64/27 + + var-hubnetwork-nva-hubProdIntSubnetName: PrdIntSubnet + var-hubnetwork-nva-hubProdIntSubnetAddressPrefix: 10.18.0.32/27 + + var-hubnetwork-nva-hubMrzIntSubnetName: MrzSubnet + var-hubnetwork-nva-hubMrzIntSubnetAddressPrefix: 10.18.0.96/27 + + var-hubnetwork-nva-hubHASubnetName: HASubnet + var-hubnetwork-nva-hubHASubnetAddressPrefix: 10.18.0.128/28 + + var-hubnetwork-nva-hubGatewaySubnetPrefix: 10.18.1.0/27 + + var-hubnetwork-nva-hubBastionSubnetAddressPrefix: 192.168.0.0/24 + + ## Hub Networking - Firewall Virtual Appliances + var-hubnetwork-nva-deployFirewallVMs: false + var-hubnetwork-nva-useFortigateFW: false + + ### Hub Networking - Firewall Virtual Appliances - For Non-production Traffic + var-hubnetwork-nva-fwDevILBName: pubsecDevFWILB + var-hubnetwork-nva-fwDevVMSku: Standard_D8s_v4 + var-hubnetwork-nva-fwDevVM1Name: pubsecDevFW1 + var-hubnetwork-nva-fwDevVM2Name: pubsecDevFW2 + var-hubnetwork-nva-fwDevILBExternalFacingIP: 100.60.0.7 + var-hubnetwork-nva-fwDevVM1ExternalFacingIP: 100.60.0.8 + var-hubnetwork-nva-fwDevVM2ExternalFacingIP: 100.60.0.9 + var-hubnetwork-nva-fwDevILBMrzIntIP: 10.18.0.103 + var-hubnetwork-nva-fwDevVM1MrzIntIP: 10.18.0.104 + var-hubnetwork-nva-fwDevVM2MrzIntIP: 10.18.0.105 + var-hubnetwork-nva-fwDevILBDevIntIP: 10.18.0.68 + var-hubnetwork-nva-fwDevVM1DevIntIP: 10.18.0.69 + var-hubnetwork-nva-fwDevVM2DevIntIP: 10.18.0.70 + var-hubnetwork-nva-fwDevVM1HAIP: 10.18.0.134 + var-hubnetwork-nva-fwDevVM2HAIP: 10.18.0.135 + + ### Hub Networking - Firewall Virtual Appliances - For Production Traffic + var-hubnetwork-nva-fwProdILBName: pubsecProdFWILB + var-hubnetwork-nva-fwProdVMSku: Standard_F8s_v2 + var-hubnetwork-nva-fwProdVM1Name: pubsecProdFW1 + var-hubnetwork-nva-fwProdVM2Name: pubsecProdFW2 + var-hubnetwork-nva-fwProdILBExternalFacingIP: 100.60.0.4 + var-hubnetwork-nva-fwProdVM1ExternalFacingIP: 100.60.0.5 + var-hubnetwork-nva-fwProdVM2ExternalFacingIP: 100.60.0.6 + var-hubnetwork-nva-fwProdILBMrzIntIP: 10.18.0.100 + var-hubnetwork-nva-fwProdVM1MrzIntIP: 10.18.0.101 + var-hubnetwork-nva-fwProdVM2MrzIntIP: 10.18.0.102 + var-hubnetwork-nva-fwProdILBPrdIntIP: 10.18.0.36 + var-hubnetwork-nva-fwProdVM1PrdIntIP: 10.18.0.37 + var-hubnetwork-nva-fwProdVM2PrdIntIP: 10.18.0.38 + var-hubnetwork-nva-fwProdVM1HAIP: 10.18.0.132 + var-hubnetwork-nva-fwProdVM2HAIP: 10.18.0.133 + ``` + +2. Configure Variable Group: firewall-secrets **(required for Fortinet Firewall deployment)** + + * In Azure DevOps, go to Pipelines -> Library + * Select + Variable group + * Set Variable group name: firewall-secrets + * Add two variables: + + These two variables are used when creating Firewall virtual machines. These are temporary passwords and recommended to be changed after creation. The same username and password are used for all virtual machines. + + When creating both variables, toggle the lock icon to make it a secret. This ensures that the values are not shown in logs nor to Azure DevOps users. + + Write down the username and password as it's not retrievable once saved. + + * var-hubnetwork-nva-fwUsername + * var-hubnetwork-nva-fwPassword + + * Click Save + +3. Configure Pipeline for Platform – Hub Networking using Azure Firewall (only if Azure Firewall based Hub Networking is used) + + > Note: Pipelines are stored as YAML definitions in Git and imported into Azure DevOps Pipelines. This approach allows for portability and change tracking. + + 1. Go to Pipelines + + 2. New Pipeline + + 1. Choose Azure Repos Git + 2. Select Repository + 3. Select Existing Azure Pipeline YAML file + 4. Identify the pipeline in `.pipelines/platform-connectivity-hub-azfw-policy.yml`. + 6. Save the pipeline (don't run it yet) + 7. Rename the pipeline to `platform-connectivity-hub-azfw-policy-ci` + + 3. New Pipeline + + 1. Choose Azure Repos Git + 2. Select Repository + 3. Select Existing Azure Pipeline YAML file + 4. Identify the pipeline in `.pipelines/platform-connectivity-hub-azfw.yml`. + 6. Save the pipeline (don't run it yet) + 7. Rename the pipeline to `platform-connectivity-hub-azfw-ci` + + +4. Configure Pipeline for Platform – Hub Networking using NVAs (only if Fortinet Firewall based Hub Networking is used) + + > Note: Pipelines are stored as YAML definitions in Git and imported into Azure DevOps Pipelines. This approach allows for portability and change tracking. + + 1. Go to Pipelines + 2. New Pipeline + 3. Choose Azure Repos Git + 4. Select Repository + 5. Select Existing Azure Pipeline YAML file + 6. Identify the pipeline in `.pipelines/platform-connectivity-hub-nva.yml`. + 7. Save the pipeline (don't run it yet) + 8. Rename the pipeline to `platform-connectivity-hub-nva-ci` + +5. If using Fortinet Firewalls, configure Pipeline permissions for the secrets. + + * In Azure DevOps, go to Pipelines -> Library + * Select variable group previously created (i.e. "firewall-secrets") + * Click "Pipeline Permissions", and in resulting dialog window: + * Click "Restrict permission" + * Click "+" button + * Select the "platform-connectivity-hub-nva-ci" pipeline + * Close the dialog window + +6. Run pipeline and wait for completion. + + * When using Hub Networking with Azure Firewall, run `platform-connectivity-hub-azfw-policy-ci` pipeline first. This ensures that the Azure Firewall Policy is deployed and can be used as a reference for Azure Firewall. This approach allows for Azure Firewall Policies (such as allow/deny rules) to be managed independently from the Hub Networking components. + +## Step 8: Configure Subscription Archetype + +1. Configure Pipeline definition for subscription archetypes + + > Pipelines are stored as YAML definitions in Git and imported into Azure DevOps Pipelines. This approach allows for portability and change tracking. + + 1. Go to Pipelines + 2. New Pipeline + 3. Choose Azure Repos Git + 4. Select Repository + 5. Select Existing Azure Pipeline YAML file + 6. Identify the pipeline in `.pipelines/subscriptions.yml`. + 7. Save the pipeline (don't run it yet) + 8. Rename the pipeline to `subscription-ci` + +2. Create a subscription configuration file (JSON) + + 1. Make a copy of an existing subscription configuration file under `config/subscriptions/CanadaESLZ-main` as a starting point + + 2. Be sure to rename the file in one of the following formats: + * `[GUID]_[TYPE].json` + * `[GUID]_[TYPE]_[LOCATION].json` + + Replace `[GUID]` with the subscription GUID. Replace `[TYPE]` with the subscription archetype. Optionally, add (replace) `[LOCATION]` with an Azure deployment location, e.g. `canadacentral`. If you do not specify a location in the configuration file name, the `deploymentRegion` variable will be used by default. + + > If a fourth specifier is added to the configuration filename at some future point and you do not want to supply an explicit deployment location in the third part of the configuration file name, you can either leave it empty (two consecutive underscore characters) or provide the case-sensitive value `default` to signal the `deploymentRegion` variable value should be used. + + 3. Save the subscription configuration file in a subfolder (under `config/subscriptions`) that is named for your Azure DevOps organization combined with the branch name corresponding to your deployment environment. For example, if your Azure DevOps organization name is `Contoso` and your Azure Repos branch for the target deployment environment is `main`, then the subfolder name would be `Contoso-main`. + + 4. Update the contents of the newly created subscription configuration file to match your deployment environment. + + 5. Commit the subscription file to Azure Repos. + +3. Run the subscription pipeline + + 1. In Azure DevOps, go to Pipelines + 2. Select the `subscription-ci` pipeline and run it. + + > The `subscription-ci` pipeline YAML is configured, by default, to **not** run automatically; you can change this if desired. + + 3. In the Run Pipelines dialog window, enter the first 4 digits of your new subscription configuration file name (4 is usually enough of the GUID to uniquely identify the subscription) between the square brackets in the `subscriptions` parameter field. For example: `[802e]`. + + 4. In the Run Pipelines dialog window, click the `Run` button to start the pipeline. + diff --git a/README.md b/README.md index 5cd7cecf..a79d567a 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,66 @@ -# Project +# Azure Landing Zones Reference Implementation for Canadian Public Sector -> This repo has been populated by an initial template to help get you started. Please -> make sure to update the content to build a great experience for community-building. +## Introduction -As the maintainer of this project, please make a few updates: +The purpose of the reference implementation is to guide Canadian Public Sector customers on building Landing Zones in their Azure environment. The reference implementation is based on [Cloud Adoption Framework for Azure](https://docs.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/) and provides an opininated implementation that enables ITSG-33 regulatory compliance by using [NIST SP 800-53 Rev. 4](https://docs.microsoft.com/azure/governance/policy/samples/nist-sp-800-53-r4) and [Canada Federal PBMM](https://docs.microsoft.com/azure/governance/policy/samples/canada-federal-pbmm) Regulatory Compliance Policy Sets. -- Improving this README.MD file to provide a great experience -- Updating SUPPORT.MD with content about this project's support experience -- Understanding the security reporting process in SECURITY.MD -- Remove this section from the README +Architecture supported up to Treasury Board of Canada Secretariat (TBS) Cloud Profile 3 - Cloud Only Applications. This proflie is applicable to Infrastructure as a Service (IaaS) and Platform as a Service (PaaS) with [characteristics](https://github.com/canada-ca/cloud-guardrails/blob/master/EN/00_Applicable-Scope.md): + +* Cloud-based services hosting sensitive (up to Protected B) information +* No direct system to system network interconnections required with GC data centers + +### Automation + +The automation is built with [Project Bicep](https://github.com/Azure/bicep/blob/main/README.md) and Azure Resource Manager template. + +This project provides 6 modules that can be further customized based on requirements: + +1. Management Groups +2. Centralized Logging +3. Azure Policies (built-in & custom Azure Policies) +4. Custom Roles +5. Hub Network Design based on Hub/Spoke topology +6. Subscription Archetypes (Generic Subscription, Machine Learning & Healthcare) + +Deployment to Azure is supported using Azure DevOps Pipelines and can be adopted for other automated deployment systems like GitHub Actions, Jenkins, etc. + + +## Goals + +* Support Treasury Board of Canada Secretariat (TBS) Cloud Profile 3 - Cloud Only Applications + +* Secure environment capable for Protected B workloads. + +* Accelerate the use of Azure in Public Sector through onboarding +multiple types of workloads including App Dev and Data & AI. + +* Simplify compliance management through a single source of compliance, audit reporting and auto remediation. + +* Deployment of DevOps frameworks & business processes to improve agility. + +## Non-Goals + +* Automation does not configure firewalls deployed as Network Virtual Appliance (NVA). In this reference implementation, Fortinet firewalls can be deployed but customer is expected to configure and manage upon deployment. + +* Automatic approval for Canada Federal PBMM nor Authority to Operate (ATO). Customers must collect evidence, customize to meet their departmental requirements and submit for Authority to Operate based on their risk profile, requirements and process. + +* Compliant on all Azure Policies when the reference implementation is deployed. This is due to the shared responsibility of cloud and customers can choose the Azure Policies to exclude. For example, using Azure Firewall is an Azure Policy that will be non-compliant since majority of the Public Sector customers use Network Virtual Appliances such as Fortinet. Customers must review [Azure Security Center Regulatory Compliance dashboard](https://docs.microsoft.com/azure/security-center/update-regulatory-compliance-packages) and apply appropriate exemptions. ## Contributing -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. +See [Contributing Reference Implementation](CONTRIBUTING.md) for information on building/running the code, contributing code, contributing examples and contributing feature requests or bug reports. + +## Telemetry + +This reference implementation does not collect any telemetry. However, Project Bicep [collects telemetry in some scenarios](https://github.com/Azure/bicep/blob/main/README.md#telemetry) as part of improving the product. + + +## License -When you submit a pull request, a CLA bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. +All files except for [Super-Linter](https://github.com/github/super-linter) in the repository are subject to the MIT license. -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +Super-Linter in this project is provided as an example for enabling source code linting capabilities. [It is subjected to the license based on it's repository](https://github.com/github/super-linter). -## Trademarks +## Trademark -This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft -trademarks or logos is subject to and must follow -[Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). -Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. -Any use of third-party trademarks or logos are subject to those third-party's policies. +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/legal/intellectualproperty/trademarks). Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party’s policies. diff --git a/SUPPORT.md b/SUPPORT.md index dc72f0e5..978e778a 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,13 +1,3 @@ -# TODO: The maintainer of this repo has not yet edited this file - -**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? - -- **No CSS support:** Fill out this template with information about how to file issues and get help. -- **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). -- **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide. - -*Then remove this first heading from this SUPPORT.MD file before publishing your repo.* - # Support ## How to file issues and get help @@ -15,11 +5,3 @@ This project uses GitHub Issues to track bugs and feature requests. Please search the existing issues before filing new issues to avoid duplicates. For new issues, file your bug or feature request as a new Issue. - -For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE -FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER -CHANNEL. WHERE WILL YOU HELP PEOPLE?**. - -## Microsoft Support Policy - -Support for this **PROJECT or PRODUCT** is limited to the resources listed above. diff --git a/azresources/analytics/adf/adf-with-cmk.bicep b/azresources/analytics/adf/adf-with-cmk.bicep new file mode 100644 index 00000000..cd414770 --- /dev/null +++ b/azresources/analytics/adf/adf-with-cmk.bicep @@ -0,0 +1,147 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Data Factory Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +// Private Endpoints +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id for Data Factory.') +param datafactoryPrivateZoneId string + +// User Assigned Identity +@description('User Assigned Managed Identity Resource Id.') +param userAssignedIdentityId string + +@description('User Assigned Managed Identity Principal Id.') +param userAssignedIdentityPrincipalId string + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name.') +param akvResourceGroupName string + +@description('Azure Key Vault Name.') +param akvName string + +// Reference existing Azure Key Vault & Assign user assigned identity +// 'Key Vault Crypto Service Encryption User' permission. +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(akvResourceGroupName) + name: akvName +} + +module akvRoleAssignmentForCMK '../../iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-${name}-key-vault' + scope: resourceGroup(akvResourceGroupName) + params: { + keyVaultName: akv.name + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + resourceSPObjectIds: array(userAssignedIdentityPrincipalId) + } +} + +// Define a RSA 2048 Key for CMK. +module akvKey '../../security/key-vault-key-rsa2048.bicep' = { + name: 'add-cmk-${name}' + scope: resourceGroup(akvResourceGroupName) + params: { + akvName: akvName + keyName: 'cmk-adf-${name}' + } +} + +// Deploy Azure Data Factory with Managed Virtual Network & Managed Integration Runtime +resource adf 'Microsoft.DataFactory/factories@2018-06-01' = { + dependsOn: [ + akvRoleAssignmentForCMK + ] + + location: resourceGroup().location + name: name + tags: tags + identity: { + type: 'SystemAssigned,UserAssigned' + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } + } + properties: { + publicNetworkAccess: 'Disabled' + encryption: { + identity: { + userAssignedIdentity: userAssignedIdentityId + } + vaultBaseUrl: akv.properties.vaultUri + keyName: akvKey.outputs.keyName + keyVersion: akvKey.outputs.keyVersion + } + } + + resource managedVnet 'managedVirtualNetworks@2018-06-01' = { + name: 'default' + properties: {} + } + + resource autoResolveIR 'integrationRuntimes@2018-06-01' = { + name: 'AutoResolveIntegrationRuntime' + properties: { + type: 'Managed' + managedVirtualNetwork: { + type: 'ManagedVirtualNetworkReference' + referenceName: managedVnet.name + } + typeProperties: { + computeProperties: { + location: 'AutoResolve' + } + } + } + } +} + +// Create Private Endpoints and register their IPs with Private DNS Zone +resource adf_datafactory_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${adf.name}-df-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${adf.name}-df-endpoint' + properties: { + privateLinkServiceId: adf.id + groupIds: [ + 'dataFactory' + ] + } + } + ] + } + + resource adf_datafactory_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink_datafactory_windows_net' + properties: { + privateDnsZoneId: datafactoryPrivateZoneId + } + } + ] + } + } +} diff --git a/azresources/analytics/adf/adf-without-cmk.bicep b/azresources/analytics/adf/adf-without-cmk.bicep new file mode 100644 index 00000000..5f486dc2 --- /dev/null +++ b/azresources/analytics/adf/adf-without-cmk.bicep @@ -0,0 +1,98 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Data Factory Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +// Private Endpoints +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id for Data Factory.') +param datafactoryPrivateZoneId string + +// User Assigned Identity +@description('User Assigned Managed Identity Resource Id.') +param userAssignedIdentityId string + +// Deploy Azure Data Factory with Managed Virtual Network & Managed Integration Runtime +resource adf 'Microsoft.DataFactory/factories@2018-06-01' = { + location: resourceGroup().location + name: name + tags: tags + identity: { + type: 'SystemAssigned,UserAssigned' + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } + } + properties: { + publicNetworkAccess: 'Disabled' + } + + resource managedVnet 'managedVirtualNetworks@2018-06-01' = { + name: 'default' + properties: {} + } + + resource autoResolveIR 'integrationRuntimes@2018-06-01' = { + name: 'AutoResolveIntegrationRuntime' + properties: { + type: 'Managed' + managedVirtualNetwork: { + type: 'ManagedVirtualNetworkReference' + referenceName: managedVnet.name + } + typeProperties: { + computeProperties: { + location: 'AutoResolve' + } + } + } + } +} + +// Create Private Endpoints and register their IPs with Private DNS Zone +resource adf_datafactory_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${adf.name}-df-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${adf.name}-df-endpoint' + properties: { + privateLinkServiceId: adf.id + groupIds: [ + 'dataFactory' + ] + } + } + ] + } + + resource adf_datafactory_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink_datafactory_windows_net' + properties: { + privateDnsZoneId: datafactoryPrivateZoneId + } + } + ] + } + } +} diff --git a/azresources/analytics/adf/main.bicep b/azresources/analytics/adf/main.bicep new file mode 100644 index 00000000..4b8c1a85 --- /dev/null +++ b/azresources/analytics/adf/main.bicep @@ -0,0 +1,74 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Data Factory Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +// Private Endpoints +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id for Data Factory.') +param datafactoryPrivateZoneId string + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// User Assigned Managed Identity +module identity '../../iam/user-assigned-identity.bicep' = { + name: 'deploy-create-user-assigned-identity' + params: { + name: '${name}-managed-identity' + } +} + +// Azure Data Factory without Customer Managed Key +module adfWithoutCMK 'adf-without-cmk.bicep' = if (!useCMK) { + name: 'deploy-adf-without-cmk' + params: { + name:name + tags: tags + + privateEndpointSubnetId: privateEndpointSubnetId + datafactoryPrivateZoneId: datafactoryPrivateZoneId + + userAssignedIdentityId: identity.outputs.identityId + } +} + +// Azure Data Factory with Customer Managed Key +module adfWithCMK 'adf-with-cmk.bicep' = if (useCMK) { + name: 'deploy-adf-with-cmk' + params: { + name:name + tags: tags + + privateEndpointSubnetId: privateEndpointSubnetId + datafactoryPrivateZoneId: datafactoryPrivateZoneId + + userAssignedIdentityId: identity.outputs.identityId + userAssignedIdentityPrincipalId: identity.outputs.identityPrincipalId + + akvResourceGroupName: akvResourceGroupName + akvName: akvName + } +} + +output identityPrincipalId string = identity.outputs.identityPrincipalId diff --git a/azresources/analytics/aml/aml-with-cmk.bicep b/azresources/analytics/aml/aml-with-cmk.bicep new file mode 100644 index 00000000..0c47d51b --- /dev/null +++ b/azresources/analytics/aml/aml-with-cmk.bicep @@ -0,0 +1,131 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Machine Learning name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Boolean flag to enable High Business Impact workspace. Default: false') +param enableHbiWorkspace bool = false + +@description('Azure Key Vault Resource Id') +param keyVaultId string + +@description('Azure Storage Account Resource Id.') +param storageAccountId string + +@description('Azure Container Registry Resource Id.') +param containerRegistryId string + +@description('Azure Application Insights Resource Id.') +param appInsightsId string + +// Private Endpoitns +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id for AML API.') +param privateZoneAzureMLApiId string + +@description('Private DNS Zone Resource Id for AML Notebooks.') +param privateZoneAzureMLNotebooksId string + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(akvResourceGroupName) + name: akvName +} + +module akvKey '../../security/key-vault-key-rsa2048.bicep' = { + name: 'add-cmk-${name}' + scope: resourceGroup(akvResourceGroupName) + params: { + akvName: akvName + keyName: 'cmk-aml-${name}' + } +} + +resource aml 'Microsoft.MachineLearningServices/workspaces@2020-08-01' = { + name: name + tags: tags + location: resourceGroup().location + identity: { + type: 'SystemAssigned' + } + sku: { + name: 'Enterprise' + tier: 'Enterprise' + } + properties: { + friendlyName: name + keyVault: keyVaultId + storageAccount: storageAccountId + applicationInsights: appInsightsId + containerRegistry: containerRegistryId + hbiWorkspace: enableHbiWorkspace + allowPublicAccessWhenBehindVnet: false + encryption: { + status: 'Enabled' + keyVaultProperties: { + keyVaultArmId: akv.id + keyIdentifier: akvKey.outputs.keyUriWithVersion + } + } + } +} + +// Create Private Endpoints and register their IPs with Private DNS Zone +resource aml_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${aml.name}-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${aml.name}-endpoint' + properties: { + privateLinkServiceId: aml.id + groupIds: [ + 'amlworkspace' + ] + } + } + ] + } + + resource aml_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-api-azureml-ms' + properties: { + privateDnsZoneId: privateZoneAzureMLApiId + } + } + { + name: 'privatelink-notebooks-azureml-ms' + properties: { + privateDnsZoneId: privateZoneAzureMLNotebooksId + } + } + ] + } + } +} diff --git a/azresources/analytics/aml/aml-without-cmk.bicep b/azresources/analytics/aml/aml-without-cmk.bicep new file mode 100644 index 00000000..fcb88e8a --- /dev/null +++ b/azresources/analytics/aml/aml-without-cmk.bicep @@ -0,0 +1,103 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Machine Learning name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Boolean flag to enable High Business Impact workspace. Default: false') +param enableHbiWorkspace bool = false + +@description('Azure Key Vault Resource Id') +param keyVaultId string + +@description('Azure Storage Account Resource Id.') +param storageAccountId string + +@description('Azure Container Registry Resource Id.') +param containerRegistryId string + +@description('Azure Application Insights Resource Id.') +param appInsightsId string + +// Private Endpoitns +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id for AML API.') +param privateZoneAzureMLApiId string + +@description('Private DNS Zone Resource Id for AML Notebooks.') +param privateZoneAzureMLNotebooksId string + +resource aml 'Microsoft.MachineLearningServices/workspaces@2020-08-01' = { + name: name + tags: tags + location: resourceGroup().location + identity: { + type: 'SystemAssigned' + } + sku: { + name: 'Enterprise' + tier: 'Enterprise' + } + properties: { + friendlyName: name + keyVault: keyVaultId + storageAccount: storageAccountId + applicationInsights: appInsightsId + containerRegistry: containerRegistryId + hbiWorkspace: enableHbiWorkspace + allowPublicAccessWhenBehindVnet: false + } +} + +// Create Private Endpoints and register their IPs with Private DNS Zone +resource aml_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${aml.name}-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${aml.name}-endpoint' + properties: { + privateLinkServiceId: aml.id + groupIds: [ + 'amlworkspace' + ] + } + } + ] + } + + resource aml_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-api-azureml-ms' + properties: { + privateDnsZoneId: privateZoneAzureMLApiId + } + } + { + name: 'privatelink-notebooks-azureml-ms' + properties: { + privateDnsZoneId: privateZoneAzureMLNotebooksId + } + } + ] + } + } +} diff --git a/azresources/analytics/aml/main.bicep b/azresources/analytics/aml/main.bicep new file mode 100644 index 00000000..6de53173 --- /dev/null +++ b/azresources/analytics/aml/main.bicep @@ -0,0 +1,90 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Machine Learning name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Boolean flag to enable High Business Impact workspace. Default: false') +param enableHbiWorkspace bool = false + +@description('Azure Storage Account Resource Id.') +param storageAccountId string + +@description('Azure Container Registry Resource Id.') +param containerRegistryId string + +@description('Azure Application Insights Resource Id.') +param appInsightsId string + +// Private Endpoints +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id for AML API.') +param privateZoneAzureMLApiId string + +@description('Private DNS Zone Resource Id for AML Notebooks.') +param privateZoneAzureMLNotebooksId string + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(akvResourceGroupName) + name: akvName +} + +// Azure Machine Learning without Customer Managed Key +module amlWithoutCMK 'aml-without-cmk.bicep' = if (!useCMK) { + name: 'deploy-aml-without-cmk' + params: { + name: name + tags: tags + keyVaultId: akv.id + containerRegistryId: containerRegistryId + storageAccountId: storageAccountId + appInsightsId: appInsightsId + privateZoneAzureMLApiId: privateZoneAzureMLApiId + privateZoneAzureMLNotebooksId: privateZoneAzureMLNotebooksId + privateEndpointSubnetId: privateEndpointSubnetId + enableHbiWorkspace: enableHbiWorkspace + } +} + +// Azure Machine Learning with Customer Managed Key +module amlWithCMK 'aml-with-cmk.bicep' = if (useCMK) { + name: 'deploy-aml-with-cmk' + params: { + name: name + tags: tags + keyVaultId: akv.id + containerRegistryId: containerRegistryId + storageAccountId: storageAccountId + appInsightsId: appInsightsId + privateZoneAzureMLApiId: privateZoneAzureMLApiId + privateZoneAzureMLNotebooksId: privateZoneAzureMLNotebooksId + privateEndpointSubnetId: privateEndpointSubnetId + + enableHbiWorkspace: enableHbiWorkspace + + akvResourceGroupName: akvResourceGroupName + akvName: akvName + } +} diff --git a/azresources/analytics/databricks/main.bicep b/azresources/analytics/databricks/main.bicep new file mode 100644 index 00000000..3bf06927 --- /dev/null +++ b/azresources/analytics/databricks/main.bicep @@ -0,0 +1,74 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Databricks Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Azure Databricks Managed Resource Group Id') +param managedResourceGroupId string + +@description('Azure Databricks Pricing Tier.') +@allowed([ + 'trial' + 'standard' + 'premium' +]) +param pricingTier string + +// Networking +@description('Virtual Network Resource Id') +param vnetId string + +@description('Public Subnet Name.') +param publicSubnetName string + +@description('Private Subnet Name.') +param privateSubnetName string + +@description('Egress Azure Load Balancer Resource Id.') +param loadbalancerId string + +@description('Egress Azure Load Balancer Backend Pool Name.') +param loadBalancerBackendPoolName string + +// Create Azure Databricks without Public IPs and use Egress Load Balancer for integrating with Azure. +resource databricks 'Microsoft.Databricks/workspaces@2018-04-01' = { + name: name + tags: tags + location: resourceGroup().location + sku: { + name: pricingTier + } + properties: { + managedResourceGroupId: managedResourceGroupId + parameters: { + customVirtualNetworkId: { + value: vnetId + } + customPrivateSubnetName: { + value: privateSubnetName + } + customPublicSubnetName: { + value: publicSubnetName + } + enableNoPublicIp: { + value: true + } + loadBalancerId: { + value: loadbalancerId + } + loadBalancerBackendPoolName: { + value: loadBalancerBackendPoolName + } + } + } +} diff --git a/azresources/analytics/stream-analytics/main.bicep b/azresources/analytics/stream-analytics/main.bicep new file mode 100644 index 00000000..7f28d24e --- /dev/null +++ b/azresources/analytics/stream-analytics/main.bicep @@ -0,0 +1,28 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Stream Analytics name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +resource streamanalytics 'Microsoft.StreamAnalytics/streamingjobs@2017-04-01-preview' = { + name: name + tags: tags + location: resourceGroup().location + identity: { + type: 'SystemAssigned' + } + properties: { + sku: { + name: 'Standard' + } + } +} diff --git a/azresources/analytics/synapse/main.bicep b/azresources/analytics/synapse/main.bicep new file mode 100644 index 00000000..77cd09f3 --- /dev/null +++ b/azresources/analytics/synapse/main.bicep @@ -0,0 +1,142 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Synapse Analytics name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Synapse Analytics Managed Resource Group Name.') +param managedResourceGroupName string + +// ADLS Gen 2 +@description('Azure Data Lake Store Gen2 Resource Group Name.') +param adlsResourceGroupName string + +@description('Azure Data Lake Store Gen2 Name.') +param adlsName string + +@description('Azure Data Lake Store File System Name.') +param adlsFSName string + +// Credentials +@description('Synapse Analytics Username.') +@secure() +param synapseUsername string + +@description('Synapse Analytics Password.') +@secure() +param synapsePassword string + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id.') +param synapsePrivateZoneId string + +@description('Private DNS Zone Resource Id for Dev.') +param synapseDevPrivateZoneId string + +@description('Private DNS Zone Resource Id for Sql.') +param synapseSqlPrivateZoneId string + +// SQL Vulnerability Scanning +@description('SQL Vulnerability Scanning - Security Contact email address for alerts.') +param sqlVulnerabilitySecurityContactEmail string + +@description('SQL Vulnerability Scanning - Storage Account Resource Group.') +param sqlVulnerabilityLoggingStorageAccounResourceGroupName string + +@description('SQL Vulnerability Scanning - Storage Account Name.') +param sqlVulnerabilityLoggingStorageAccountName string + +@description('SQL Vulnerability Scanning - Storage Account Path to store the vulnerability scan results.') +param sqlVulnerabilityLoggingStoragePath string + +// Deployment Script Identity +@description('Deployment Script Identity Resource Id. This identity is used to execute Azure CLI as part of the deployment.') +param deploymentScriptIdentityId string + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// Synapse Analytics without Customer Managed Key +module synapseWithoutCMK 'synapse-without-cmk.bicep' = if (!useCMK) { + name: 'deploy-synapse-without-cmk' + params: { + name: name + tags: tags + + adlsResourceGroupName: adlsResourceGroupName + adlsName: adlsName + adlsFSName: adlsFSName + + managedResourceGroupName: managedResourceGroupName + + synapseUsername: synapseUsername + synapsePassword: synapsePassword + + privateEndpointSubnetId: privateEndpointSubnetId + synapsePrivateZoneId: synapsePrivateZoneId + synapseDevPrivateZoneId: synapseDevPrivateZoneId + synapseSqlPrivateZoneId: synapseSqlPrivateZoneId + + sqlVulnerabilitySecurityContactEmail: sqlVulnerabilitySecurityContactEmail + + sqlVulnerabilityLoggingStorageAccounResourceGroupName: sqlVulnerabilityLoggingStorageAccounResourceGroupName + sqlVulnerabilityLoggingStorageAccountName: sqlVulnerabilityLoggingStorageAccountName + sqlVulnerabilityLoggingStoragePath: sqlVulnerabilityLoggingStoragePath + + deploymentScriptIdentityId: deploymentScriptIdentityId + } +} + +// Synapse Analytics with Customer Managed Key +module synapseWithCMK 'synapse-with-cmk.bicep' = if (useCMK) { + name: 'deploy-synapse-with-cmk' + params: { + name: name + tags: tags + + adlsResourceGroupName: adlsResourceGroupName + adlsName: adlsName + adlsFSName: adlsFSName + + managedResourceGroupName: managedResourceGroupName + + synapseUsername: synapseUsername + synapsePassword: synapsePassword + + privateEndpointSubnetId: privateEndpointSubnetId + synapsePrivateZoneId: synapsePrivateZoneId + synapseDevPrivateZoneId: synapseDevPrivateZoneId + synapseSqlPrivateZoneId: synapseSqlPrivateZoneId + + sqlVulnerabilitySecurityContactEmail: sqlVulnerabilitySecurityContactEmail + + sqlVulnerabilityLoggingStorageAccounResourceGroupName: sqlVulnerabilityLoggingStorageAccounResourceGroupName + sqlVulnerabilityLoggingStorageAccountName: sqlVulnerabilityLoggingStorageAccountName + sqlVulnerabilityLoggingStoragePath: sqlVulnerabilityLoggingStoragePath + + deploymentScriptIdentityId: deploymentScriptIdentityId + + akvResourceGroupName: akvResourceGroupName + akvName: akvName + } +} diff --git a/azresources/analytics/synapse/synapse-with-cmk.bicep b/azresources/analytics/synapse/synapse-with-cmk.bicep new file mode 100644 index 00000000..e64b1e7e --- /dev/null +++ b/azresources/analytics/synapse/synapse-with-cmk.bicep @@ -0,0 +1,418 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Synapse Analytics name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Synapse Analytics Managed Resource Group Name.') +param managedResourceGroupName string + +// ADLS Gen 2 +@description('Azure Data Lake Store Gen2 Resource Group Name.') +param adlsResourceGroupName string + +@description('Azure Data Lake Store Gen2 Name.') +param adlsName string + +@description('Azure Data Lake Store File System Name.') +param adlsFSName string + +// Credentials +@description('Synapse Analytics Username.') +@secure() +param synapseUsername string + +@description('Synapse Analytics Password.') +@secure() +param synapsePassword string + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id.') +param synapsePrivateZoneId string + +@description('Private DNS Zone Resource Id for Dev.') +param synapseDevPrivateZoneId string + +@description('Private DNS Zone Resource Id for Sql.') +param synapseSqlPrivateZoneId string + +// SQL Vulnerability Scanning +@description('SQL Vulnerability Scanning - Security Contact email address for alerts.') +param sqlVulnerabilitySecurityContactEmail string + +@description('SQL Vulnerability Scanning - Storage Account Resource Group.') +param sqlVulnerabilityLoggingStorageAccounResourceGroupName string + +@description('SQL Vulnerability Scanning - Storage Account Name.') +param sqlVulnerabilityLoggingStorageAccountName string + +@description('SQL Vulnerability Scanning - Storage Account Path to store the vulnerability scan results.') +param sqlVulnerabilityLoggingStoragePath string + +// Deployment Script Identity +@description('Deployment Script Identity Resource Id. This identity is used to execute Azure CLI as part of the deployment.') +param deploymentScriptIdentityId string + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(akvResourceGroupName) + name: akvName +} + +module akvKey '../../security/key-vault-key-rsa2048.bicep' = { + name: 'add-cmk-${name}' + scope: resourceGroup(akvResourceGroupName) + params: { + akvName: akvName + keyName: 'cmk-synapse-${name}' + } +} + +resource adls 'Microsoft.Storage/storageAccounts@2019-06-01' existing = { + scope: resourceGroup(adlsResourceGroupName) + name: adlsName +} + +module dataLakeSynapseFS '../../storage/storage-adlsgen2-fs.bicep' = { + name: 'deploy-datalake-fs-for-synapse' + scope: resourceGroup(adlsResourceGroupName) + params: { + adlsName: adlsName + fsName: adlsFSName + } +} + +resource synapsePrivateLinkHub 'Microsoft.Synapse/privateLinkHubs@2021-03-01' = { + name: '${toLower(name)}plhub' + tags: tags + location: resourceGroup().location +} + +resource synapse 'Microsoft.Synapse/workspaces@2021-03-01' = { + dependsOn: [ + dataLakeSynapseFS + ] + + name: name + tags: tags + location: resourceGroup().location + properties: { + sqlAdministratorLoginPassword: synapsePassword + managedResourceGroupName: managedResourceGroupName + sqlAdministratorLogin: synapseUsername + + managedVirtualNetwork: 'default' + managedVirtualNetworkSettings: { + preventDataExfiltration: true + } + + publicNetworkAccess: 'Disabled' + + encryption: { + cmk: { + key : { + name: 'cmk-synapse-${name}' + keyVaultUrl: akvKey.outputs.keyUri + } + } + } + + defaultDataLakeStorage: { + accountUrl: adls.properties.primaryEndpoints.dfs + filesystem: adlsFSName + } + } + identity: { + type: 'SystemAssigned' + } + + // Assign the workspace's system-assigned managed identity CONTROL permissions to SQL pools for pipeline integration + resource synapse_msi_sql_control_settings 'managedIdentitySqlControlSettings@2021-05-01' = { + name: 'default' + properties: { + grantSqlControlToManagedIdentity: { + desiredState: 'Enabled' + } + } + } +} + +module roleAssignSynapseToSALogging '../../iam/resource/storage-role-assignment-to-sp.bicep' = { + name: 'rbac-${synapse.name}-logging-storage-account' + scope: resourceGroup(sqlVulnerabilityLoggingStorageAccounResourceGroupName) + params: { + storageAccountName: sqlVulnerabilityLoggingStorageAccountName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + resourceSPObjectIds: array(synapse.identity.principalId) + } +} + +resource synapse_workspace_web_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${synapse.name}-web-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${synapse.name}-web-endpoint' + properties: { + privateLinkServiceId: synapsePrivateLinkHub.id + groupIds: [ + 'web' + ] + } + } + ] + } + + resource synapse_workspace_web_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-synapse-workspace-web' + properties: { + privateDnsZoneId: synapsePrivateZoneId + } + } + ] + } + } +} + +resource synapse_workspace_dev_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${synapse.name}-workspace-dev-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${synapse.name}-workspace-dev-endpoint' + properties: { + privateLinkServiceId: synapse.id + groupIds: [ + 'dev' + ] + } + } + ] + } + + resource synapse_workspace_dev_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-synapse-workspace-dev' + properties: { + privateDnsZoneId: synapseDevPrivateZoneId + } + } + ] + } + } +} + +resource synapse_workspace_sql_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${synapse.name}-workspace-sql-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${synapse.name}-workspace-sql-endpoint' + properties: { + privateLinkServiceId: synapse.id + groupIds: [ + 'sql' + ] + } + } + ] + } + + resource synapse_workspace_sql_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-synapse-workspace-sql' + properties: { + privateDnsZoneId: synapseSqlPrivateZoneId + } + } + ] + } + } +} + +resource synapse_workspace_sql_on_demand_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${synapse.name}-workspace-sql-ondemand-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${synapse.name}-workspace-sql-ondemand-endpoint' + properties: { + privateLinkServiceId: synapse.id + groupIds: [ + 'sqlondemand' + ] + } + } + ] + } + + resource synapse_workspace_sql_on_demand_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-synapse-workspace-sql-ondemand' + properties: { + privateDnsZoneId: synapseSqlPrivateZoneId + } + } + ] + } + } +} + +// Grant Synapse access to ADLS Gen2 as Storage Blob Data Contributor +module roleAssignSynapseToADLSGen2 '../../iam/resource/storage-role-assignment-to-sp.bicep' = { + name: 'rbac-${synapse.name}-${adls.name}' + scope: resourceGroup(adlsResourceGroupName) + params: { + storageAccountName: adlsName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') // Storage Blob Data Contributor + resourceSPObjectIds: array(synapse.identity.principalId) + } +} + +// Grant access from Azure resource instances +var azCliCommand = ''' + az extension add -n storage-preview + + az storage account network-rule add \ + --resource-id {0} \ + --tenant-id {1} \ + -g {2} \ + --account-name {3} +''' + +module addResourceAccess '../../util/deployment-script.bicep' = { + name: 'grant-resource-instance-access-${adlsName}' + params: { + deploymentScript: format(azCliCommand, synapse.id, subscription().tenantId, adlsResourceGroupName, adlsName) + deploymentScriptName: 'grant-access-${synapse.name}-${adlsName}' + deploymentScriptIdentityId: deploymentScriptIdentityId + } +} + +module akvRoleAssignmentForCMK '../../iam/resource/key-vault-role-assignment-to-sp.bicep' = { + dependsOn: [ + synapse + ] + name: 'rbac-${synapse.name}-key-vault' + scope: resourceGroup(akvResourceGroupName) + params: { + keyVaultName: akv.name + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + resourceSPObjectIds: array(synapse.identity.principalId) + } +} + + +resource cmkActivation 'Microsoft.Synapse/workspaces/keys@2021-06-01-preview' = { + dependsOn: [ + akvRoleAssignmentForCMK + ] + name: '${name}/cmk-synapse-${name}' + properties: { + isActiveCMK: true + keyVaultUrl: akvKey.outputs.keyUri + } +} + +module wait '../../util/wait.bicep' = { + dependsOn: [ + cmkActivation + ] + name: 'wait-for-cmk-activation' + params: { + loopCounter: 120 + waitNamePrefix: 'wait-for-cmk-activation' + } +} + +resource synapse_audit 'Microsoft.Synapse/workspaces/auditingSettings@2021-05-01' = { + dependsOn: [ + cmkActivation + wait + ] + name: '${name}/default' + properties: { + isAzureMonitorTargetEnabled: true + state: 'Enabled' + } +} + +resource synapse_securityAlertPolicies 'Microsoft.Synapse/workspaces/securityAlertPolicies@2021-05-01' = { + dependsOn: [ + cmkActivation + wait + ] + name: '${name}/Default' + properties: { + state: 'Enabled' + emailAccountAdmins: false + } +} + +resource synapse_va 'Microsoft.Synapse/workspaces/vulnerabilityAssessments@2021-05-01' = { + dependsOn: [ + cmkActivation + wait + synapse_audit + synapse_securityAlertPolicies + roleAssignSynapseToSALogging + ] + name: '${name}/default' + properties: { + storageContainerPath: '${sqlVulnerabilityLoggingStoragePath}vulnerability-assessment' + recurringScans: { + isEnabled: true + emailSubscriptionAdmins: true + emails: [ + sqlVulnerabilitySecurityContactEmail + ] + } + } +} diff --git a/azresources/analytics/synapse/synapse-without-cmk.bicep b/azresources/analytics/synapse/synapse-without-cmk.bicep new file mode 100644 index 00000000..009686d4 --- /dev/null +++ b/azresources/analytics/synapse/synapse-without-cmk.bicep @@ -0,0 +1,340 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Synapse Analytics name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Synapse Analytics Managed Resource Group Name.') +param managedResourceGroupName string + +// ADLS Gen 2 +@description('Azure Data Lake Store Gen2 Resource Group Name.') +param adlsResourceGroupName string + +@description('Azure Data Lake Store Gen2 Name.') +param adlsName string + +@description('Azure Data Lake Store File System Name.') +param adlsFSName string + +// Credentials +@description('Synapse Analytics Username.') +@secure() +param synapseUsername string + +@description('Synapse Analytics Password.') +@secure() +param synapsePassword string + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id.') +param synapsePrivateZoneId string + +@description('Private DNS Zone Resource Id for Dev.') +param synapseDevPrivateZoneId string + +@description('Private DNS Zone Resource Id for Sql.') +param synapseSqlPrivateZoneId string + +// SQL Vulnerability Scanning +@description('SQL Vulnerability Scanning - Security Contact email address for alerts.') +param sqlVulnerabilitySecurityContactEmail string + +@description('SQL Vulnerability Scanning - Storage Account Resource Group.') +param sqlVulnerabilityLoggingStorageAccounResourceGroupName string + +@description('SQL Vulnerability Scanning - Storage Account Name.') +param sqlVulnerabilityLoggingStorageAccountName string + +@description('SQL Vulnerability Scanning - Storage Account Path to store the vulnerability scan results.') +param sqlVulnerabilityLoggingStoragePath string + +// Deployment Script Identity +@description('Deployment Script Identity Resource Id. This identity is used to execute Azure CLI as part of the deployment.') +param deploymentScriptIdentityId string + +resource adls 'Microsoft.Storage/storageAccounts@2019-06-01' existing = { + scope: resourceGroup(adlsResourceGroupName) + name: adlsName +} + +module dataLakeSynapseFS '../../storage/storage-adlsgen2-fs.bicep' = { + name: 'deploy-datalake-fs-for-synapse' + scope: resourceGroup(adlsResourceGroupName) + params: { + adlsName: adlsName + fsName: adlsFSName + } +} + +resource synapsePrivateLinkHub 'Microsoft.Synapse/privateLinkHubs@2021-03-01' = { + name: '${toLower(name)}plhub' + tags: tags + location: resourceGroup().location +} + +resource synapse 'Microsoft.Synapse/workspaces@2021-03-01' = { + dependsOn: [ + dataLakeSynapseFS + ] + + name: name + tags: tags + location: resourceGroup().location + properties: { + sqlAdministratorLoginPassword: synapsePassword + managedResourceGroupName: managedResourceGroupName + sqlAdministratorLogin: synapseUsername + + managedVirtualNetwork: 'default' + managedVirtualNetworkSettings: { + preventDataExfiltration: true + } + + publicNetworkAccess: 'Disabled' + + defaultDataLakeStorage: { + accountUrl: adls.properties.primaryEndpoints.dfs + filesystem: adlsFSName + } + } + identity: { + type: 'SystemAssigned' + } + + // Assign the workspace's system-assigned managed identity CONTROL permissions to SQL pools for pipeline integration + resource synapse_msi_sql_control_settings 'managedIdentitySqlControlSettings@2021-05-01' = { + name: 'default' + properties: { + grantSqlControlToManagedIdentity: { + desiredState: 'Enabled' + } + } + } + + resource synapse_audit 'auditingSettings@2021-05-01' = { + name: 'default' + properties: { + isAzureMonitorTargetEnabled: true + state: 'Enabled' + } + } + + resource synapse_securityAlertPolicies 'securityAlertPolicies@2021-05-01' = { + name: 'Default' + properties: { + state: 'Enabled' + emailAccountAdmins: false + } + } +} + +resource synapse_va 'Microsoft.Synapse/workspaces/vulnerabilityAssessments@2021-05-01' = { + name: '${synapse.name}/default' + dependsOn: [ + roleAssignSynapseToSALogging + ] + properties: { + storageContainerPath: '${sqlVulnerabilityLoggingStoragePath}vulnerability-assessment' + recurringScans: { + isEnabled: true + emailSubscriptionAdmins: true + emails: [ + sqlVulnerabilitySecurityContactEmail + ] + } + } +} + +module roleAssignSynapseToSALogging '../../iam/resource/storage-role-assignment-to-sp.bicep' = { + name: 'rbac-${synapse.name}-logging-storage-account' + scope: resourceGroup(sqlVulnerabilityLoggingStorageAccounResourceGroupName) + params: { + storageAccountName: sqlVulnerabilityLoggingStorageAccountName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + resourceSPObjectIds: array(synapse.identity.principalId) + } +} + +resource synapse_workspace_web_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${synapse.name}-web-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${synapse.name}-web-endpoint' + properties: { + privateLinkServiceId: synapsePrivateLinkHub.id + groupIds: [ + 'web' + ] + } + } + ] + } + + resource synapse_workspace_web_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-synapse-workspace-web' + properties: { + privateDnsZoneId: synapsePrivateZoneId + } + } + ] + } + } +} + +resource synapse_workspace_dev_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${synapse.name}-workspace-dev-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${synapse.name}-workspace-dev-endpoint' + properties: { + privateLinkServiceId: synapse.id + groupIds: [ + 'dev' + ] + } + } + ] + } + + resource synapse_workspace_dev_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-synapse-workspace-dev' + properties: { + privateDnsZoneId: synapseDevPrivateZoneId + } + } + ] + } + } +} + +resource synapse_workspace_sql_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${synapse.name}-workspace-sql-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${synapse.name}-workspace-sql-endpoint' + properties: { + privateLinkServiceId: synapse.id + groupIds: [ + 'sql' + ] + } + } + ] + } + + resource synapse_workspace_sql_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-synapse-workspace-sql' + properties: { + privateDnsZoneId: synapseSqlPrivateZoneId + } + } + ] + } + } +} + +resource synapse_workspace_sql_on_demand_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${synapse.name}-workspace-sql-ondemand-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${synapse.name}-workspace-sql-ondemand-endpoint' + properties: { + privateLinkServiceId: synapse.id + groupIds: [ + 'sqlondemand' + ] + } + } + ] + } + + resource synapse_workspace_sql_on_demand_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-synapse-workspace-sql-ondemand' + properties: { + privateDnsZoneId: synapseSqlPrivateZoneId + } + } + ] + } + } +} + +// Grant Synapse access to ADLS Gen2 as Storage Blob Data Contributor +module roleAssignSynapseToADLSGen2 '../../iam/resource/storage-role-assignment-to-sp.bicep' = { + name: 'rbac-${synapse.name}-${adls.name}' + scope: resourceGroup(adlsResourceGroupName) + params: { + storageAccountName: adlsName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') // Storage Blob Data Contributor + resourceSPObjectIds: array(synapse.identity.principalId) + } +} + +// Grant access from Azure resource instances +var azCliCommand = ''' + az extension add -n storage-preview + + az storage account network-rule add \ + --resource-id {0} \ + --tenant-id {1} \ + -g {2} \ + --account-name {3} +''' + +module addResourceAccess '../../util/deployment-script.bicep' = { + name: 'grant-resource-instance-access-${adlsName}' + params: { + deploymentScript: format(azCliCommand, synapse.id, subscription().tenantId, adlsResourceGroupName, adlsName) + deploymentScriptName: 'grant-access-${synapse.name}-${adlsName}' + deploymentScriptIdentityId: deploymentScriptIdentityId + } +} diff --git a/azresources/automation/automation-account.bicep b/azresources/automation/automation-account.bicep new file mode 100644 index 00000000..96c1ac21 --- /dev/null +++ b/azresources/automation/automation-account.bicep @@ -0,0 +1,27 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Automation Account name.') +param automationAccountName string + +@description('Key/Value pair of tags.') +param tags object = {} + +resource automationAccount 'Microsoft.Automation/automationAccounts@2015-10-31' = { + name: automationAccountName + tags: tags + location: resourceGroup().location + properties: { + sku: { + name: 'Basic' + } + } +} + +output automationAccountId string = automationAccount.id diff --git a/azresources/compute/fhir.bicep b/azresources/compute/fhir.bicep new file mode 100644 index 00000000..3d7c365b --- /dev/null +++ b/azresources/compute/fhir.bicep @@ -0,0 +1,72 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('FHIR Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('FHIR API Version. Default: fhir-R4') +param version string = 'fhir-R4' // fhir version + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private Zone Resource Id.') +param privateZoneId string + +resource fhir 'Microsoft.HealthcareApis/services@2021-01-11' = { + location: resourceGroup().location + name: name + tags: tags + kind: version + properties: { + authenticationConfiguration: { + audience: 'https://${name}.azurehealthcareapis.com' + authority: uri(environment().authentication.loginEndpoint, subscription().tenantId) + } + } +} + +resource fhir_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${fhir.name}-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${fhir.name}-endpoint' + properties: { + privateLinkServiceId: fhir.id + groupIds: [ + 'fhir' + ] + } + } + ] + } + + resource fhir_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-api-fhir-ms' + properties: { + privateDnsZoneId: privateZoneId + } + } + ] + } + } +} diff --git a/azresources/compute/vm-ubuntu1804/main.bicep b/azresources/compute/vm-ubuntu1804/main.bicep new file mode 100644 index 00000000..4b08bf58 --- /dev/null +++ b/azresources/compute/vm-ubuntu1804/main.bicep @@ -0,0 +1,92 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Virtual Machine Name.') +param vmName string + +@description('Virtual Machine SKU.') +param vmSize string + +@description('Azure Availability Zone for VM.') +param availabilityZone string + +// Credentials +@description('Virtual Machine Username.') +@secure() +param username string + +@description('Virtual Machine Password') +@secure() +param password string + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +@description('Boolean flag that enables Accelerated Networking.') +param enableAcceleratedNetworking bool + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// Host Encryption +@description('Boolean flag to enable encryption at host (double encryption). This feature can not be used with Azure Disk Encryption.') +param encryptionAtHost bool = true + +module vmWithoutCMK 'vm-ubuntu1804-without-cmk.bicep' = if (!useCMK) { + name: 'deploy-vm-without-cmk' + params: { + vmName: vmName + vmSize: vmSize + + subnetId: subnetId + + availabilityZone: availabilityZone + enableAcceleratedNetworking: enableAcceleratedNetworking + + username: username + password: password + + encryptionAtHost: encryptionAtHost + } +} + +module vmWithCMK 'vm-ubuntu1804-with-cmk.bicep' = if (useCMK) { + name: 'deploy-vm-with-cmk' + params: { + vmName: vmName + vmSize: vmSize + + subnetId: subnetId + + availabilityZone: availabilityZone + enableAcceleratedNetworking: enableAcceleratedNetworking + + username: username + password: password + + encryptionAtHost: encryptionAtHost + + akvName: akvName + akvResourceGroupName: akvResourceGroupName + } +} + +// Outputs +output vmName string = (useCMK) ? vmWithCMK.outputs.vmName : vmWithoutCMK.outputs.vmName +output vmId string = (useCMK) ? vmWithCMK.outputs.vmId : vmWithoutCMK.outputs.vmId +output nicId string = (useCMK) ? vmWithCMK.outputs.nicId : vmWithoutCMK.outputs.nicId diff --git a/azresources/compute/vm-ubuntu1804/vm-ubuntu1804-with-cmk.bicep b/azresources/compute/vm-ubuntu1804/vm-ubuntu1804-with-cmk.bicep new file mode 100644 index 00000000..a3753c5e --- /dev/null +++ b/azresources/compute/vm-ubuntu1804/vm-ubuntu1804-with-cmk.bicep @@ -0,0 +1,192 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Virtual Machine Name.') +param vmName string + +@description('Virtual Machine SKU.') +param vmSize string + +@description('Azure Availability Zone for VM.') +param availabilityZone string + +// Credentials +@description('Virtual Machine Username.') +@secure() +param username string + +@description('Virtual Machine Password') +@secure() +param password string + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +@description('Boolean flag that enables Accelerated Networking.') +param enableAcceleratedNetworking bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// Host Encryption +@description('Boolean flag to enable encryption at host (double encryption). This feature can not be used with Azure Disk Encryption.') +param encryptionAtHost bool = true + +// VM Image +@description('VM Publisher. Default: Canonical') +param publisher string = 'Canonical' + +@description('VM Offer. Default: UbuntuServer') +param offer string = 'UbuntuServer' + +@description('VM SKU. Default: 18.04-LTS') +param sku string = '18.04-LTS' + +@description('VM Version. Default: latest') +param version string = 'latest' + +@description('VM Managed Disk Storage Account Type. Default: StandardSSD_LRS') +param storageAccountType string = 'StandardSSD_LRS' + +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(akvResourceGroupName) + name: akvName +} + +module akvKey '../../security/key-vault-key-rsa2048.bicep' = { + name: 'add-cmk-${vmName}' + scope: resourceGroup(akvResourceGroupName) + params: { + akvName: akvName + keyName: 'cmk-vmdisks-${vmName}' + } +} + +resource diskEncryptionSet 'Microsoft.Compute/diskEncryptionSets@2020-12-01' = { + name: '${vmName}-disk-encryption-set' + location: resourceGroup().location + identity: { + type: 'SystemAssigned' + } + + properties: { + rotationToLatestKeyVersionEnabled: true + encryptionType: 'EncryptionAtRestWithPlatformAndCustomerKeys' + activeKey: { + keyUrl: akvKey.outputs.keyUriWithVersion + } + } +} + +module diskEncryptionSetRoleAssignmentForCMK '../../iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-${diskEncryptionSet.name}-key-vault' + scope: resourceGroup(akvResourceGroupName) + params: { + keyVaultName: akv.name + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + resourceSPObjectIds: array(diskEncryptionSet.identity.principalId) + } +} + +resource nic 'Microsoft.Network/networkInterfaces@2020-06-01' = { + name: '${vmName}-nic' + location: resourceGroup().location + properties: { + enableAcceleratedNetworking: enableAcceleratedNetworking + ipConfigurations: [ + { + name: 'IpConf' + properties: { + subnet: { + id: subnetId + } + privateIPAllocationMethod: 'Dynamic' + privateIPAddressVersion: 'IPv4' + primary: true + } + } + ] + } +} + +resource vm 'Microsoft.Compute/virtualMachines@2020-06-01' = { + dependsOn: [ + diskEncryptionSetRoleAssignmentForCMK + ] + + name: vmName + location: resourceGroup().location + zones: [ + availabilityZone + ] + properties: { + hardwareProfile: { + vmSize: vmSize + } + networkProfile: { + networkInterfaces: [ + { + id: nic.id + } + ] + } + storageProfile: { + imageReference: { + publisher: publisher + offer: offer + sku: sku + version: version + } + osDisk: { + name: '${vmName}-os' + caching: 'ReadWrite' + createOption: 'FromImage' + managedDisk: { + storageAccountType: storageAccountType + diskEncryptionSet: { + id: diskEncryptionSet.id + } + } + } + dataDisks: [ + { + caching: 'None' + name: '${vmName}-data-1' + diskSizeGB: 128 + lun: 0 + managedDisk: { + storageAccountType: 'Premium_LRS' + diskEncryptionSet: { + id: diskEncryptionSet.id + } + } + createOption: 'Empty' + } + ] + } + osProfile: { + computerName: vmName + adminUsername: username + adminPassword: password + } + securityProfile: { + encryptionAtHost: encryptionAtHost + } + } +} + +// Outputs +output vmName string = vm.name +output vmId string = vm.id +output nicId string = nic.id diff --git a/azresources/compute/vm-ubuntu1804/vm-ubuntu1804-without-cmk.bicep b/azresources/compute/vm-ubuntu1804/vm-ubuntu1804-without-cmk.bicep new file mode 100644 index 00000000..13c160d6 --- /dev/null +++ b/azresources/compute/vm-ubuntu1804/vm-ubuntu1804-without-cmk.bicep @@ -0,0 +1,135 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Virtual Machine Name.') +param vmName string + +@description('Virtual Machine SKU.') +param vmSize string + +@description('Azure Availability Zone for VM.') +param availabilityZone string + +// Credentials +@description('Virtual Machine Username.') +@secure() +param username string + +@description('Virtual Machine Password') +@secure() +param password string + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +@description('Boolean flag that enables Accelerated Networking.') +param enableAcceleratedNetworking bool + +// Host Encryption +@description('Boolean flag to enable encryption at host (double encryption). This feature can not be used with Azure Disk Encryption.') +param encryptionAtHost bool = true + +// VM Image +@description('VM Publisher. Default: Canonical') +param publisher string = 'Canonical' + +@description('VM Offer. Default: UbuntuServer') +param offer string = 'UbuntuServer' + +@description('VM SKU. Default: 18.04-LTS') +param sku string = '18.04-LTS' + +@description('VM Version. Default: latest') +param version string = 'latest' + +@description('VM Managed Disk Storage Account Type. Default: StandardSSD_LRS') +param storageAccountType string = 'StandardSSD_LRS' + +resource nic 'Microsoft.Network/networkInterfaces@2020-06-01' = { + name: '${vmName}-nic' + location: resourceGroup().location + properties: { + enableAcceleratedNetworking: enableAcceleratedNetworking + ipConfigurations: [ + { + name: 'IpConf' + properties: { + subnet: { + id: subnetId + } + privateIPAllocationMethod: 'Dynamic' + privateIPAddressVersion: 'IPv4' + primary: true + } + } + ] + } +} + +resource vm 'Microsoft.Compute/virtualMachines@2020-06-01' = { + name: vmName + location: resourceGroup().location + zones: [ + availabilityZone + ] + properties: { + hardwareProfile: { + vmSize: vmSize + } + networkProfile: { + networkInterfaces: [ + { + id: nic.id + } + ] + } + storageProfile: { + imageReference: { + publisher: publisher + offer: offer + sku: sku + version: version + } + osDisk: { + name: '${vmName}-os' + caching: 'ReadWrite' + createOption: 'FromImage' + managedDisk: { + storageAccountType: storageAccountType + } + } + dataDisks: [ + { + caching: 'None' + name: '${vmName}-data-1' + diskSizeGB: 128 + lun: 0 + managedDisk: { + storageAccountType: 'Premium_LRS' + } + createOption: 'Empty' + } + ] + } + osProfile: { + computerName: vmName + adminUsername: username + adminPassword: password + } + securityProfile: { + encryptionAtHost: encryptionAtHost + } + } +} + +// Outputs +output vmName string = vm.name +output vmId string = vm.id +output nicId string = nic.id diff --git a/azresources/compute/vm-win2019/main.bicep b/azresources/compute/vm-win2019/main.bicep new file mode 100644 index 00000000..e5a57c28 --- /dev/null +++ b/azresources/compute/vm-win2019/main.bicep @@ -0,0 +1,94 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Virtual Machine Name.') +param vmName string + +@description('Virtual Machine SKU.') +param vmSize string + +@description('Azure Availability Zone for VM.') +param availabilityZone string + +// Credentials +@description('Virtual Machine Username.') +@secure() +param username string + +@description('Virtual Machine Password') +@secure() +param password string + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +@description('Boolean flag that enables Accelerated Networking.') +param enableAcceleratedNetworking bool + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// Host Encryption +@description('Boolean flag to enable encryption at host (double encryption). This feature can not be used with Azure Disk Encryption.') +param encryptionAtHost bool = true + +// Deploy VM without Customer Managed Key for Managed Disks. +module vmWithoutCMK 'vm-win2019-without-cmk.bicep' = if (!useCMK) { + name: 'deploy-vm-without-cmk' + params: { + vmName: vmName + vmSize: vmSize + + subnetId: subnetId + + availabilityZone: availabilityZone + enableAcceleratedNetworking: enableAcceleratedNetworking + + username: username + password: password + + encryptionAtHost: encryptionAtHost + } +} + +// Deploy VM with Customer Managed Key for Managed Disks. +module vmWithCMK 'vm-win2019-with-cmk.bicep' = if (useCMK) { + name: 'deploy-vm-with-cmk' + params: { + vmName: vmName + vmSize: vmSize + + subnetId: subnetId + + availabilityZone: availabilityZone + enableAcceleratedNetworking: enableAcceleratedNetworking + + username: username + password: password + + encryptionAtHost: encryptionAtHost + + akvName: akvName + akvResourceGroupName: akvResourceGroupName + } +} + +// Outputs +output vmName string = (useCMK) ? vmWithCMK.outputs.vmName : vmWithoutCMK.outputs.vmName +output vmId string = (useCMK) ? vmWithCMK.outputs.vmId : vmWithoutCMK.outputs.vmId +output nicId string = (useCMK) ? vmWithCMK.outputs.nicId : vmWithoutCMK.outputs.nicId diff --git a/azresources/compute/vm-win2019/vm-win2019-with-cmk.bicep b/azresources/compute/vm-win2019/vm-win2019-with-cmk.bicep new file mode 100644 index 00000000..221eab6b --- /dev/null +++ b/azresources/compute/vm-win2019/vm-win2019-with-cmk.bicep @@ -0,0 +1,176 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Virtual Machine Name.') +param vmName string + +@description('Virtual Machine SKU.') +param vmSize string + +@description('Azure Availability Zone for VM.') +param availabilityZone string + +// Credentials +@description('Virtual Machine Username.') +@secure() +param username string + +@description('Virtual Machine Password') +@secure() +param password string + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +@description('Boolean flag that enables Accelerated Networking.') +param enableAcceleratedNetworking bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// Host Encryption +@description('Boolean flag to enable encryption at host (double encryption). This feature can not be used with Azure Disk Encryption.') +param encryptionAtHost bool = true + +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(akvResourceGroupName) + name: akvName +} + +module akvKey '../../security/key-vault-key-rsa2048.bicep' = { + name: 'add-cmk-${vmName}' + scope: resourceGroup(akvResourceGroupName) + params: { + akvName: akvName + keyName: 'cmk-vmdisks-${vmName}' + } +} + +resource diskEncryptionSet 'Microsoft.Compute/diskEncryptionSets@2020-12-01' = { + name: '${vmName}-disk-encryption-set' + location: resourceGroup().location + identity: { + type: 'SystemAssigned' + } + + properties: { + rotationToLatestKeyVersionEnabled: true + encryptionType: 'EncryptionAtRestWithPlatformAndCustomerKeys' + activeKey: { + keyUrl: akvKey.outputs.keyUriWithVersion + } + } +} + +module diskEncryptionSetRoleAssignmentForCMK '../../iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-${diskEncryptionSet.name}-key-vault' + scope: resourceGroup(akvResourceGroupName) + params: { + keyVaultName: akv.name + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + resourceSPObjectIds: array(diskEncryptionSet.identity.principalId) + } +} + +resource nic 'Microsoft.Network/networkInterfaces@2020-06-01' = { + name: '${vmName}-nic' + location: resourceGroup().location + properties: { + enableAcceleratedNetworking: enableAcceleratedNetworking + ipConfigurations: [ + { + name: 'IpConf' + properties: { + subnet: { + id: subnetId + } + privateIPAllocationMethod: 'Dynamic' + privateIPAddressVersion: 'IPv4' + primary: true + } + } + ] + } +} + +resource vm 'Microsoft.Compute/virtualMachines@2020-06-01' = { + dependsOn: [ + diskEncryptionSetRoleAssignmentForCMK + ] + + name: vmName + location: resourceGroup().location + zones: [ + availabilityZone + ] + properties: { + hardwareProfile: { + vmSize: vmSize + } + networkProfile: { + networkInterfaces: [ + { + id: nic.id + } + ] + } + storageProfile: { + imageReference: { + publisher: 'MicrosoftWindowsServer' + offer: 'WindowsServer' + sku: '2019-Datacenter' + version: 'latest' + } + osDisk: { + name: '${vmName}-os' + caching: 'ReadWrite' + createOption: 'FromImage' + managedDisk: { + storageAccountType: 'Premium_LRS' + diskEncryptionSet: { + id: diskEncryptionSet.id + } + } + } + dataDisks: [ + { + caching: 'None' + name: '${vmName}-data-1' + diskSizeGB: 128 + lun: 0 + managedDisk: { + storageAccountType: 'Premium_LRS' + diskEncryptionSet: { + id: diskEncryptionSet.id + } + } + createOption: 'Empty' + } + ] + } + osProfile: { + computerName: vmName + adminUsername: username + adminPassword: password + } + securityProfile: { + encryptionAtHost: encryptionAtHost + } + } +} + +// Outputs +output vmName string = vm.name +output vmId string = vm.id +output nicId string = nic.id diff --git a/azresources/compute/vm-win2019/vm-win2019-without-cmk.bicep b/azresources/compute/vm-win2019/vm-win2019-without-cmk.bicep new file mode 100644 index 00000000..f857a97a --- /dev/null +++ b/azresources/compute/vm-win2019/vm-win2019-without-cmk.bicep @@ -0,0 +1,119 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Virtual Machine Name.') +param vmName string + +@description('Virtual Machine SKU.') +param vmSize string + +@description('Azure Availability Zone for VM.') +param availabilityZone string + +// Credentials +@description('Virtual Machine Username.') +@secure() +param username string + +@description('Virtual Machine Password') +@secure() +param password string + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +@description('Boolean flag that enables Accelerated Networking.') +param enableAcceleratedNetworking bool + +// Host Encryption +@description('Boolean flag to enable encryption at host (double encryption). This feature can not be used with Azure Disk Encryption.') +param encryptionAtHost bool = true + +resource nic 'Microsoft.Network/networkInterfaces@2020-06-01' = { + name: '${vmName}-nic' + location: resourceGroup().location + properties: { + enableAcceleratedNetworking: enableAcceleratedNetworking + ipConfigurations: [ + { + name: 'IpConf' + properties: { + subnet: { + id: subnetId + } + privateIPAllocationMethod: 'Dynamic' + privateIPAddressVersion: 'IPv4' + primary: true + } + } + ] + } +} + +resource vm 'Microsoft.Compute/virtualMachines@2020-06-01' = { + name: vmName + location: resourceGroup().location + zones: [ + availabilityZone + ] + properties: { + hardwareProfile: { + vmSize: vmSize + } + networkProfile: { + networkInterfaces: [ + { + id: nic.id + } + ] + } + storageProfile: { + imageReference: { + publisher: 'MicrosoftWindowsServer' + offer: 'WindowsServer' + sku: '2019-Datacenter' + version: 'latest' + } + osDisk: { + name: '${vmName}-os' + caching: 'ReadWrite' + createOption: 'FromImage' + managedDisk: { + storageAccountType: 'Premium_LRS' + } + } + dataDisks: [ + { + caching: 'None' + name: '${vmName}-data-1' + diskSizeGB: 128 + lun: 0 + managedDisk: { + storageAccountType: 'Premium_LRS' + } + createOption: 'Empty' + } + ] + } + osProfile: { + computerName: vmName + adminUsername: username + adminPassword: password + } + securityProfile: { + encryptionAtHost: encryptionAtHost + } + } +} + +// Outputs +output vmName string = vm.name +output vmId string = vm.id +output nicId string = nic.id diff --git a/azresources/compute/web/app-service-plan-linux.bicep b/azresources/compute/web/app-service-plan-linux.bicep new file mode 100644 index 00000000..9aa5d8bb --- /dev/null +++ b/azresources/compute/web/app-service-plan-linux.bicep @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure App Service Plan Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('App Service Plan SKU Name') +param skuName string + +@description('App Service Plan SKU Tier') +param skuTier string + +resource plan 'Microsoft.Web/serverfarms@2020-06-01' = { + name: name + location: resourceGroup().location + tags: tags + kind: 'Linux' + sku: { + name: skuName + tier: skuTier + } + properties: { + reserved: true + } +} + +output planId string = plan.id diff --git a/azresources/compute/web/appservice-linux.bicep b/azresources/compute/web/appservice-linux.bicep new file mode 100644 index 00000000..13e0ec2b --- /dev/null +++ b/azresources/compute/web/appservice-linux.bicep @@ -0,0 +1,89 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure App Service Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Technology Stack.') +@allowed([ + 'DOTNETCORE|3.1' + 'DOTNETCORE|2.1' + 'NODE|14-lts' + 'NODE|12-lts' + 'PYTHON|3.8' + 'PYTHON|3.7' + 'PYTHON|3.6' +]) +param stack string + +@description('App Service Plan Resource Id.') +param appServicePlanId string + +@description('Storage Account Name.') +param storageName string + +@description('Storage Account Resource Id.') +param storageId string + +@description('Application Insights Instrumentation Key.') +param aiIKey string + +@description('Virtual Network Integration Subnet Resource Id.') +param vnetIntegrationSubnetId string + +// Linux Web App with Virtual Network Integration +resource app 'Microsoft.Web/sites@2020-06-01' = { + name: name + tags: tags + location: resourceGroup().location + kind: 'linux' + identity: { + type: 'SystemAssigned' + } + properties: { + httpsOnly: true + serverFarmId: appServicePlanId + clientAffinityEnabled: true + siteConfig: { + // for Linux Apps Azure DNS private zones only works if Route All is enabled. + // https://docs.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#azure-dns-private-zones + vnetRouteAllEnabled: true + + linuxFxVersion: stack + use32BitWorkerProcess: false + minTlsVersion: '1.2' + scmMinTlsVersion: '1.2' + appSettings: [ + { + name: 'WEBSITE_DNS_SERVER' + value: '168.63.129.16' + } + { + name: 'APPINSIGHTS_INSTRUMENTATIONKEY' + value: aiIKey + } + { + name: 'AzureWebJobsStorage' + value: 'DefaultEndpointsProtocol=https;AccountName=${storageName};AccountKey=${listKeys(storageId, '2021-04-01').keys[0].value};EndpointSuffix=${environment().suffixes.storage}' + } + ] + } + } + + resource app_vnet 'networkConfig@2020-06-01' = { + name: 'virtualNetwork' + properties: { + subnetResourceId: vnetIntegrationSubnetId + swiftSupported: true + } + } +} diff --git a/azresources/compute/web/functions-python-linux.bicep b/azresources/compute/web/functions-python-linux.bicep new file mode 100644 index 00000000..e97a6a95 --- /dev/null +++ b/azresources/compute/web/functions-python-linux.bicep @@ -0,0 +1,92 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Function App Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Technology Stack. Default: PYTHON|3.9') +@allowed([ + 'PYTHON|3.9' + 'PYTHON|3.8' + 'PYTHON|3.7' + 'PYTHON|3.6' +]) +param stack string = 'PYTHON|3.9' + +@description('App Service Plan Resource Id.') +param appServicePlanId string + +@description('Storage Account Name.') +param storageName string + +@description('Storage Account Resource Id.') +param storageId string + +@description('Application Insights Instrumentation Key.') +param aiIKey string + +@description('Virtual Network Integration Subnet Resource Id.') +param vnetIntegrationSubnetId string + +// Function App with Virtual Network Integration +resource function_app 'Microsoft.Web/sites@2020-06-01' = { + name: name + tags: tags + location: resourceGroup().location + kind: 'functionapp,linux' + identity: { + type: 'SystemAssigned' + } + properties: { + httpsOnly: true + serverFarmId: appServicePlanId + clientAffinityEnabled: true + clientCertEnabled: true + siteConfig: { + linuxFxVersion: stack + use32BitWorkerProcess: false + vnetRouteAllEnabled: true + ftpsState: 'FtpsOnly' + http20Enabled: true + appSettings: [ + { + name: 'WEBSITE_DNS_SERVER' + value: '168.63.129.16' + } + { + name: 'APPINSIGHTS_INSTRUMENTATIONKEY' + value: aiIKey + } + { + name: 'FUNCTIONS_EXTENSION_VERSION' + value: '~3' + } + { + name: 'FUNCTIONS_WORKER_RUNTIME' + value: 'python' + } + { + name: 'AzureWebJobsStorage' + value: 'DefaultEndpointsProtocol=https;AccountName=${storageName};AccountKey=${listKeys(storageId, '2021-04-01').keys[0].value};EndpointSuffix=${environment().suffixes.storage}' + } + ] + } + } + + resource function_app_vnet 'networkConfig@2020-06-01' = { + name: 'virtualNetwork' + properties: { + subnetResourceId: vnetIntegrationSubnetId + swiftSupported: true + } + } +} diff --git a/azresources/containers/acr/acr-with-cmk.bicep b/azresources/containers/acr/acr-with-cmk.bicep new file mode 100644 index 00000000..7ef352ad --- /dev/null +++ b/azresources/containers/acr/acr-with-cmk.bicep @@ -0,0 +1,228 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Container Registry Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Quarantine Policy. Default: disabled') +param quarantinePolicy string = 'disabled' + +@description('Trust Policy Type. Default: Notary') +param trustPolicyType string = 'Notary' + +@description('Trust Policy Status. This must be disabled when using Customer Managed Key. Default: disabled') +@allowed([ + 'disabled' +]) +param trustPolicyStatus string = 'disabled' + +@description('Retention Policy in days. Default: 30') +param retentionPolicyDays int = 30 + +@description('Retention Policy status. Default: enabled') +param retentionPolicyStatus string = 'enabled' + +// User Assigned Managed Identity +@description('User Assigned Managed Identity Resource Id.') +param userAssignedIdentityId string + +@description('User Assigned Managed Identity Principal Id.') +param userAssignedIdentityPrincipalId string + +@description('User Assigned Managed Identity Client Id.') +param userAssignedIdentityClientId string + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id.') +param privateZoneId string + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +@description('Deployment Script Identity Id. Required when useCMK=true.') +param deploymentScriptIdentityId string + +@description('Key Vault will be created with this name during the deployment, then deleted once ACR key is rotated.') +param tempKeyVaultName string = 'tmpkv${uniqueString(utcNow())}' + +/* + Create a temporary key vault and key to setup CMK. These will be deleted at the end of deployment using deployment script. + See: https://docs.microsoft.com/en-us/azure/container-registry/container-registry-customer-managed-keys#advanced-scenario-key-vault-firewall +*/ +module tempAkv '../../security/key-vault.bicep' = { + name: 'deploy-keyvault-temp' + params: { + name: tempKeyVaultName + softDeleteRetentionInDays: 7 + } +} + +module tempAkvKey '../../security/key-vault-key-rsa2048.bicep' = { + name: 'add-temp-cmk-akv-${name}' + params: { + akvName: tempAkv.outputs.akvName + keyName: 'cmk-acr' + } +} + +module tempAkvRoleAssignmentForCMK '../../iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-add-temp-${name}-${tempKeyVaultName}' + params: { + keyVaultName: tempAkv.outputs.akvName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + resourceSPObjectIds: array(userAssignedIdentityPrincipalId) + } +} + +/* Configure ACR & Use Temp AKV */ +resource acr 'Microsoft.ContainerRegistry/registries@2020-11-01-preview' = { + dependsOn: [ + tempAkvRoleAssignmentForCMK + ] + + name: name + tags: tags + location: resourceGroup().location + sku: { + name: 'Premium' + } + identity: { + type: 'SystemAssigned, UserAssigned' + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } + } + properties: { + adminUserEnabled: true + + networkRuleSet: { + defaultAction: 'Deny' + } + + policies: { + quarantinePolicy: { + status: quarantinePolicy + } + trustPolicy: { + type: trustPolicyType + status: trustPolicyStatus + } + retentionPolicy: { + days: retentionPolicyDays + status: retentionPolicyStatus + } + } + + encryption: { + status: 'enabled' + keyVaultProperties: { + identity: userAssignedIdentityClientId + keyIdentifier: tempAkvKey.outputs.keyUri + } + } + + dataEndpointEnabled: false + publicNetworkAccess: 'Disabled' + } +} + +resource acr_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = if (!empty(privateZoneId)) { + location: resourceGroup().location + name: '${acr.name}-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${acr.name}-endpoint' + properties: { + privateLinkServiceId: acr.id + groupIds: [ + 'registry' + ] + } + } + ] + } + + resource acr_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-azurecr-io' + properties: { + privateDnsZoneId: privateZoneId + } + } + ] + } + } +} + +// rotate from temporary key-vault to permanent key-vault & system-managed identity +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(akvResourceGroupName) + name: akvName +} + +module akvRoleAssignmentForCMK '../../iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-${acr.name}-key-vault' + scope: resourceGroup(akvResourceGroupName) + params: { + keyVaultName: akv.name + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + resourceSPObjectIds: array(acr.identity.principalId) + } +} + +module akvKey '../../security/key-vault-key-rsa2048.bicep' = { + name: 'add-cmk-${acr.name}' + scope: resourceGroup(akvResourceGroupName) + params: { + akvName: akvName + keyName: 'cmk-acr-${acr.name}' + } +} + +var cliCmkRotateCmkAndCleanUpCommand = ''' + az acr encryption rotate-key \ + -g {0} \ + -n {1} \ + --key-encryption-key {2} \ + --identity '[system]' + + az keyvault delete -g {0} -n {3} +''' + +module rotateCmkAndCleanUp '../../util/deployment-script.bicep' = { + dependsOn: [ + akvRoleAssignmentForCMK + ] + + name: 'rotate-cmk-and-clean-up-acr-${acr.name}' + params: { + deploymentScript: format(cliCmkRotateCmkAndCleanUpCommand, resourceGroup().name, name, akvKey.outputs.keyUri, tempAkv.outputs.akvName) + deploymentScriptName: 'rotate-cmk-and-clean-up-acr-${acr.name}-ds' + deploymentScriptIdentityId: deploymentScriptIdentityId + } +} + +output acrId string = acr.id diff --git a/azresources/containers/acr/acr-without-cmk.bicep b/azresources/containers/acr/acr-without-cmk.bicep new file mode 100644 index 00000000..97f55624 --- /dev/null +++ b/azresources/containers/acr/acr-without-cmk.bicep @@ -0,0 +1,117 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Container Registry Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Quarantine Policy. Default: disabled') +param quarantinePolicy string = 'disabled' + +@description('Trust Policy Type. Default: Notary') +param trustPolicyType string = 'Notary' + +@description('Trust Policy Status. Default: enabled') +param trustPolicyStatus string = 'enabled' + +@description('Retention Policy in days. Default: 30') +param retentionPolicyDays int = 30 + +@description('Retention Policy status. Default: enabled') +param retentionPolicyStatus string = 'enabled' + +// User Assigned Managed Identity +@description('User Assigned Managed Identity Resource Id.') +param userAssignedIdentityId string + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id.') +param privateZoneId string + +/* Configure ACR */ +resource acr 'Microsoft.ContainerRegistry/registries@2020-11-01-preview' = { + name: name + tags: tags + location: resourceGroup().location + sku: { + name: 'Premium' + } + identity: { + type: 'SystemAssigned, UserAssigned' + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } + } + properties: { + adminUserEnabled: true + + networkRuleSet: { + defaultAction: 'Deny' + } + + policies: { + quarantinePolicy: { + status: quarantinePolicy + } + trustPolicy: { + type: trustPolicyType + status: trustPolicyStatus + } + retentionPolicy: { + days: retentionPolicyDays + status: retentionPolicyStatus + } + } + + dataEndpointEnabled: false + publicNetworkAccess: 'Disabled' + } +} + +resource acr_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = if (!empty(privateZoneId)) { + location: resourceGroup().location + name: '${acr.name}-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${acr.name}-endpoint' + properties: { + privateLinkServiceId: acr.id + groupIds: [ + 'registry' + ] + } + } + ] + } + + resource acr_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-azurecr-io' + properties: { + privateDnsZoneId: privateZoneId + } + } + ] + } + } +} + +output acrId string = acr.id diff --git a/azresources/containers/acr/main.bicep b/azresources/containers/acr/main.bicep new file mode 100644 index 00000000..27f47a91 --- /dev/null +++ b/azresources/containers/acr/main.bicep @@ -0,0 +1,77 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Container Registry Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id.') +param privateZoneId string + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +@description('Deployment Script Identity Id. Required when useCMK=true.') +param deploymentScriptIdentityId string + +module acrIdentity '../../iam/user-assigned-identity.bicep' = { + name: '${name}-managed-identity' + params: { + name: '${name}-managed-identity' + } +} + +module acrWithCMK 'acr-with-cmk.bicep' = if (useCMK) { + name: 'deploy-acr-with-cmk' + params: { + name: name + tags: tags + + userAssignedIdentityId: acrIdentity.outputs.identityId + userAssignedIdentityPrincipalId: acrIdentity.outputs.identityPrincipalId + userAssignedIdentityClientId: acrIdentity.outputs.identityClientId + + privateEndpointSubnetId: privateEndpointSubnetId + privateZoneId: privateZoneId + + deploymentScriptIdentityId: deploymentScriptIdentityId + akvResourceGroupName: akvResourceGroupName + akvName: akvName + } +} + +module acrWithoutCMK 'acr-without-cmk.bicep' = if (!useCMK) { + name: 'deploy-acr-without-cmk' + params: { + name: name + tags: tags + + userAssignedIdentityId: acrIdentity.outputs.identityId + + privateEndpointSubnetId: privateEndpointSubnetId + privateZoneId: privateZoneId + } +} + +// Outputs +output acrId string = useCMK ? acrWithCMK.outputs.acrId : acrWithoutCMK.outputs.acrId diff --git a/azresources/containers/aks-kubenet/aks-kubenet-with-cmk.bicep b/azresources/containers/aks-kubenet/aks-kubenet-with-cmk.bicep new file mode 100644 index 00000000..b7cacfac --- /dev/null +++ b/azresources/containers/aks-kubenet/aks-kubenet-with-cmk.bicep @@ -0,0 +1,213 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Kubernetes Service Name.') +param name string + +@description('Azure Kubernetes Service Version.') +param version string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('AKS Managed Resource Group Name.') +param nodeResourceGroupName string + +@description('User Assigned Managed Identity Resource Id.') +param userAssignedIdentityId string + +// System Node Pool +@description('System Node Pool - Boolean to enable auto scaling.') +param systemNodePoolEnableAutoScaling bool + +@description('System Node Pool - Minimum Node Count.') +param systemNodePoolMinNodeCount int + +@description('System Node Pool - Maximum Node Count.') +param systemNodePoolMaxNodeCount int + +@description('System Node Pool - Node SKU.') +param systemNodePoolNodeSize string + +// User Node Pool +@description('User Node Pool - Boolean to enable auto scaling.') +param userNodePoolEnableAutoScaling bool + +@description('User Node Pool - Minimum Node Count.') +param userNodePoolMinNodeCount int + +@description('User Node Pool - Maximum Node Count.') +param userNodePoolMaxNodeCount int + +@description('User Node Pool - Node SKU.') +param userNodePoolNodeSize string + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +@description('DNS Prefix.') +param dnsPrefix string + +@description('Private DNS Zone Resource Id.') +param privateDNSZoneId string + +// Kubernetes Networking +@description('Pod CIDR. Default: 11.0.0.0/16') +param podCidr string = '11.0.0.0/16' + +@description('Service CIDR. Default: 20.0.0.0/16') +param serviceCidr string = '20.0.0.0/16' + +@description('DNS Service IP. Default: 20.0.0.10') +param dnsServiceIP string = '20.0.0.10' + +@description('Docker Bridge CIDR. Default: 30.0.0.1/16') +param dockerBridgeCidr string = '30.0.0.1/16' + +// Container Insights +@description('Log Analytics Workspace Resource Id. Default: blank') +param containerInsightsLogAnalyticsResourceId string = '' + +// Host Encryption +@description('Enable encryption at host (double encryption). Default: true') +param enableEncryptionAtHost bool = true + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(akvResourceGroupName) + name: akvName +} + +module akvKey '../../security/key-vault-key-rsa2048.bicep' = { + name: 'add-cmk-${name}' + scope: resourceGroup(akvResourceGroupName) + params: { + akvName: akvName + keyName: 'cmk-aks-${name}' + } +} + +resource diskEncryptionSet 'Microsoft.Compute/diskEncryptionSets@2020-12-01' = { + name: '${name}-disk-encryption-set' + location: resourceGroup().location + identity: { + type: 'SystemAssigned' + } + + properties: { + rotationToLatestKeyVersionEnabled: true + encryptionType: 'EncryptionAtRestWithPlatformAndCustomerKeys' + activeKey: { + keyUrl: akvKey.outputs.keyUriWithVersion + } + } +} + +module diskEncryptionSetRoleAssignmentForCMK '../../iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-${diskEncryptionSet.name}-key-vault' + scope: resourceGroup(akvResourceGroupName) + params: { + keyVaultName: akv.name + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + resourceSPObjectIds: array(diskEncryptionSet.identity.principalId) + } +} + +resource akskubenet 'Microsoft.ContainerService/managedClusters@2021-07-01' = { + dependsOn: [ + diskEncryptionSetRoleAssignmentForCMK + ] + + name: name + location: resourceGroup().location + tags: tags + properties: { + nodeResourceGroup: nodeResourceGroupName + kubernetesVersion: version + dnsPrefix: dnsPrefix + enableRBAC: true + networkProfile: { + networkPlugin: 'kubenet' + podCidr: podCidr + serviceCidr: serviceCidr + dnsServiceIP: dnsServiceIP + dockerBridgeCidr: dockerBridgeCidr + } + agentPoolProfiles: [ + { + count: systemNodePoolMinNodeCount + minCount: systemNodePoolMinNodeCount + maxCount: systemNodePoolMaxNodeCount + enableAutoScaling: systemNodePoolEnableAutoScaling + vmSize: systemNodePoolNodeSize + availabilityZones: [ + '1' + '2' + '3' + ] + type: 'VirtualMachineScaleSets' + osType: 'Linux' + vnetSubnetID: subnetId + name: 'systempool' + mode: 'System' + enableEncryptionAtHost: enableEncryptionAtHost + } + { + count: userNodePoolMinNodeCount + minCount: userNodePoolMinNodeCount + maxCount: userNodePoolMaxNodeCount + enableAutoScaling: userNodePoolEnableAutoScaling + vmSize: userNodePoolNodeSize + availabilityZones: [ + '1' + '2' + '3' + ] + type: 'VirtualMachineScaleSets' + osType: 'Linux' + vnetSubnetID: subnetId + name: 'agentpool' + mode: 'User' + enableEncryptionAtHost: enableEncryptionAtHost + } + ] + apiServerAccessProfile: { + enablePrivateCluster: true + enablePrivateClusterPublicFQDN: false + privateDNSZone: privateDNSZoneId + } + servicePrincipalProfile: { + clientId: 'msi' + } + addonProfiles: { + 'omsagent': (!empty(containerInsightsLogAnalyticsResourceId)) ? { + enabled: true + config: { + logAnalyticsWorkspaceResourceID: containerInsightsLogAnalyticsResourceId + } + } : { + enabled: false + } + } + diskEncryptionSetID: diskEncryptionSet.id + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } + } +} diff --git a/azresources/containers/aks-kubenet/aks-kubenet-without-cmk.bicep b/azresources/containers/aks-kubenet/aks-kubenet-without-cmk.bicep new file mode 100644 index 00000000..e173aa79 --- /dev/null +++ b/azresources/containers/aks-kubenet/aks-kubenet-without-cmk.bicep @@ -0,0 +1,161 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Kubernetes Service Name.') +param name string + +@description('Azure Kubernetes Service Version.') +param version string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('AKS Managed Resource Group Name.') +param nodeResourceGroupName string + +@description('User Assigned Managed Identity Resource Id.') +param userAssignedIdentityId string + +// System Node Pool +@description('System Node Pool - Boolean to enable auto scaling.') +param systemNodePoolEnableAutoScaling bool + +@description('System Node Pool - Minimum Node Count.') +param systemNodePoolMinNodeCount int + +@description('System Node Pool - Maximum Node Count.') +param systemNodePoolMaxNodeCount int + +@description('System Node Pool - Node SKU.') +param systemNodePoolNodeSize string + +// User Node Pool +@description('User Node Pool - Boolean to enable auto scaling.') +param userNodePoolEnableAutoScaling bool + +@description('User Node Pool - Minimum Node Count.') +param userNodePoolMinNodeCount int + +@description('User Node Pool - Maximum Node Count.') +param userNodePoolMaxNodeCount int + +@description('User Node Pool - Node SKU.') +param userNodePoolNodeSize string + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +@description('DNS Prefix.') +param dnsPrefix string + +@description('Private DNS Zone Resource Id.') +param privateDNSZoneId string + +// Kubernetes Networking +@description('Pod CIDR. Default: 11.0.0.0/16') +param podCidr string = '11.0.0.0/16' + +@description('Service CIDR. Default: 20.0.0.0/16') +param serviceCidr string = '20.0.0.0/16' + +@description('DNS Service IP. Default: 20.0.0.10') +param dnsServiceIP string = '20.0.0.10' + +@description('Docker Bridge CIDR. Default: 30.0.0.1/16') +param dockerBridgeCidr string = '30.0.0.1/16' + +// Container Insights +@description('Log Analytics Workspace Resource Id. Default: blank') +param containerInsightsLogAnalyticsResourceId string = '' + +// Host Encryption +@description('Enable encryption at host (double encryption). Default: true') +param enableEncryptionAtHost bool = true + +resource akskubenet 'Microsoft.ContainerService/managedClusters@2021-07-01' = { + name: name + location: resourceGroup().location + tags: tags + properties: { + nodeResourceGroup: nodeResourceGroupName + kubernetesVersion: version + dnsPrefix: dnsPrefix + enableRBAC: true + networkProfile: { + networkPlugin: 'kubenet' + podCidr: podCidr + serviceCidr: serviceCidr + dnsServiceIP: dnsServiceIP + dockerBridgeCidr: dockerBridgeCidr + } + agentPoolProfiles: [ + { + count: systemNodePoolMinNodeCount + minCount: systemNodePoolMinNodeCount + maxCount: systemNodePoolMaxNodeCount + enableAutoScaling: systemNodePoolEnableAutoScaling + vmSize: systemNodePoolNodeSize + availabilityZones: [ + '1' + '2' + '3' + ] + type: 'VirtualMachineScaleSets' + osType: 'Linux' + vnetSubnetID: subnetId + name: 'systempool' + mode: 'System' + enableEncryptionAtHost: enableEncryptionAtHost + } + { + count: userNodePoolMinNodeCount + minCount: userNodePoolMinNodeCount + maxCount: userNodePoolMaxNodeCount + enableAutoScaling: userNodePoolEnableAutoScaling + vmSize: userNodePoolNodeSize + availabilityZones: [ + '1' + '2' + '3' + ] + type: 'VirtualMachineScaleSets' + osType: 'Linux' + vnetSubnetID: subnetId + name: 'agentpool' + mode: 'User' + enableEncryptionAtHost: enableEncryptionAtHost + } + ] + apiServerAccessProfile: { + enablePrivateCluster: true + enablePrivateClusterPublicFQDN: false + privateDNSZone: privateDNSZoneId + } + servicePrincipalProfile: { + clientId: 'msi' + } + addonProfiles: { + 'omsagent': (!empty(containerInsightsLogAnalyticsResourceId)) ? { + enabled: true + config: { + logAnalyticsWorkspaceResourceID: containerInsightsLogAnalyticsResourceId + } + } : { + enabled: false + } + } + } + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${userAssignedIdentityId}': {} + } + } +} diff --git a/azresources/containers/aks-kubenet/main.bicep b/azresources/containers/aks-kubenet/main.bicep new file mode 100644 index 00000000..53838483 --- /dev/null +++ b/azresources/containers/aks-kubenet/main.bicep @@ -0,0 +1,218 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Kubernetes Service Name.') +param name string + +@description('Azure Kubernetes Service Version.') +param version string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('AKS Managed Resource Group Name.') +param nodeResourceGroupName string + +// System Node Pool +@description('System Node Pool - Boolean to enable auto scaling.') +param systemNodePoolEnableAutoScaling bool + +@description('System Node Pool - Minimum Node Count.') +param systemNodePoolMinNodeCount int + +@description('System Node Pool - Maximum Node Count.') +param systemNodePoolMaxNodeCount int + +@description('System Node Pool - Node SKU.') +param systemNodePoolNodeSize string + +// User Node Pool +@description('User Node Pool - Boolean to enable auto scaling.') +param userNodePoolEnableAutoScaling bool + +@description('User Node Pool - Minimum Node Count.') +param userNodePoolMinNodeCount int + +@description('User Node Pool - Maximum Node Count.') +param userNodePoolMaxNodeCount int + +@description('User Node Pool - Node SKU.') +param userNodePoolNodeSize string + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +@description('DNS Prefix.') +param dnsPrefix string + +@description('Private DNS Zone Resource Id.') +param privateDNSZoneId string + +// Kubernetes Networking +@description('Pod CIDR. Default: 11.0.0.0/16') +param podCidr string = '11.0.0.0/16' + +@description('Service CIDR. Default: 20.0.0.0/16') +param serviceCidr string = '20.0.0.0/16' + +@description('DNS Service IP. Default: 20.0.0.10') +param dnsServiceIP string = '20.0.0.10' + +@description('Docker Bridge CIDR. Default: 30.0.0.1/16') +param dockerBridgeCidr string = '30.0.0.1/16' + +// Container Insights +@description('Log Analytics Workspace Resource Id. Default: blank') +param containerInsightsLogAnalyticsResourceId string = '' + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// Host Encryption +@description('Enable encryption at host (double encryption). Default: true') +param enableEncryptionAtHost bool = true + +// Example: /subscriptions//resourceGroups//providers/Microsoft.Network/virtualNetworks//subnets/aks +var subnetIdSplit = split(subnetId, '/') +var virtualNetworkResourceGroup = subnetIdSplit[4] +var virtualNetworkName = subnetIdSplit[8] + +// Example: /subscriptions//resourceGroups//providers/Microsoft.Network/privateDnsZones/privatelink.canadacentral.azmk8s.io +var privateDnsZoneIdSplit = split(privateDNSZoneId, '/') +var privateDnsZoneSubscriptionId = privateDnsZoneIdSplit[2] +var privateZoneDnsResourceGroupName = privateDnsZoneIdSplit[4] +var privateZoneResourceName = last(privateDnsZoneIdSplit) + +module identity '../../iam/user-assigned-identity.bicep' = { + name: 'deploy-aks-identity' + params: { + name: '${name}-managed-identity' + } +} + +// assign permissions to identity per https://docs.microsoft.com/en-us/azure/aks/private-clusters#configure-private-dns-zone +module rbacPrivateDnsZoneContributor '../../iam/resource/private-dns-zone-role-assignment-to-sp.bicep' = { + name: 'rbac-private-dns-zone-contributor-${name}' + scope: resourceGroup(privateDnsZoneSubscriptionId, privateZoneDnsResourceGroupName) + params: { + zoneName: privateZoneResourceName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b12aa53e-6015-4669-85d0-8515ebb3ae7f') // Private DNS Zone Contributor + resourceSPObjectIds: array(identity.outputs.identityPrincipalId) + } +} + +module rbacNetworkContributor '../../iam/resource/virtual-network-role-assignment-to-sp.bicep' = { + name: 'rbac-network-contributor-${name}' + scope: resourceGroup(virtualNetworkResourceGroup) + params: { + vnetName: virtualNetworkName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7') // Network Contributor + resourceSPObjectIds: array(identity.outputs.identityPrincipalId) + } +} + +module aksWithoutCMK 'aks-kubenet-without-cmk.bicep' = if (!useCMK) { + dependsOn: [ + rbacPrivateDnsZoneContributor + rbacNetworkContributor + ] + + name: 'deploy-aks-without-cmk' + params: { + name: name + version: version + + userAssignedIdentityId: identity.outputs.identityId + + dnsPrefix: dnsPrefix + + nodeResourceGroupName: nodeResourceGroupName + + tags: tags + + subnetId: subnetId + + systemNodePoolMinNodeCount: systemNodePoolMinNodeCount + systemNodePoolMaxNodeCount: systemNodePoolMaxNodeCount + systemNodePoolEnableAutoScaling: systemNodePoolEnableAutoScaling + systemNodePoolNodeSize: systemNodePoolNodeSize + + userNodePoolMaxNodeCount: userNodePoolMaxNodeCount + userNodePoolEnableAutoScaling: userNodePoolEnableAutoScaling + userNodePoolMinNodeCount: userNodePoolMinNodeCount + userNodePoolNodeSize: userNodePoolNodeSize + + podCidr: podCidr + serviceCidr: serviceCidr + dnsServiceIP: dnsServiceIP + dockerBridgeCidr: dockerBridgeCidr + + privateDNSZoneId: privateDNSZoneId + + containerInsightsLogAnalyticsResourceId: containerInsightsLogAnalyticsResourceId + + enableEncryptionAtHost: enableEncryptionAtHost + } +} + +module aksWithCMK 'aks-kubenet-with-cmk.bicep' = if (useCMK) { + dependsOn: [ + rbacPrivateDnsZoneContributor + rbacNetworkContributor + ] + + name: 'deploy-aks-with-cmk' + params: { + name: name + version: version + + userAssignedIdentityId: identity.outputs.identityId + + dnsPrefix: dnsPrefix + + nodeResourceGroupName: nodeResourceGroupName + + tags: tags + + subnetId: subnetId + + systemNodePoolMinNodeCount: systemNodePoolMinNodeCount + systemNodePoolMaxNodeCount: systemNodePoolMaxNodeCount + systemNodePoolEnableAutoScaling: systemNodePoolEnableAutoScaling + systemNodePoolNodeSize: systemNodePoolNodeSize + + userNodePoolMaxNodeCount: userNodePoolMaxNodeCount + userNodePoolEnableAutoScaling: userNodePoolEnableAutoScaling + userNodePoolMinNodeCount: userNodePoolMinNodeCount + userNodePoolNodeSize: userNodePoolNodeSize + + podCidr: podCidr + serviceCidr: serviceCidr + dnsServiceIP: dnsServiceIP + dockerBridgeCidr: dockerBridgeCidr + + privateDNSZoneId: privateDNSZoneId + + containerInsightsLogAnalyticsResourceId: containerInsightsLogAnalyticsResourceId + + enableEncryptionAtHost: enableEncryptionAtHost + + akvResourceGroupName: akvResourceGroupName + akvName: akvName + } +} diff --git a/azresources/cost/budget-subscription.bicep b/azresources/cost/budget-subscription.bicep new file mode 100644 index 00000000..1a2c5366 --- /dev/null +++ b/azresources/cost/budget-subscription.bicep @@ -0,0 +1,62 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +@description('Subscription budget name.') +param budgetName string + +@description('Subscription budget amount.') +param budgetAmount int + +@description('Budget Time Window. Options are Monthly, Quarterly or Annually. Default: Monthly') +@allowed([ + 'Monthly' + 'Quarterly' + 'Annually' +]) +param timeGrain string = 'Monthly' + +@description('Subscription budget start date. New budget can not be created with the same name and different start date. You must delete the old budget before recreating or disable budget creation through createBudget flag. Default: 1st day of current month') +param startDate string = utcNow('yyyy-MM-01') + +@description('Subscription budget email notification addresses.') +param contactEmails array + +resource budget 'Microsoft.Consumption/budgets@2019-10-01' = { + name: budgetName + properties: { + category: 'Cost' + amount: budgetAmount + timeGrain: timeGrain + timePeriod: { + startDate: startDate + } + notifications: { + Notification1: { + enabled: true + operator: 'GreaterThan' + threshold: 25 + contactEmails: contactEmails + } + Notification2: { + enabled: true + operator: 'GreaterThan' + threshold: 50 + contactEmails: contactEmails + } + Notification3: { + enabled: true + operator: 'GreaterThan' + threshold: 75 + contactEmails: contactEmails + } + } + } +} diff --git a/azresources/data/sqldb/main.bicep b/azresources/data/sqldb/main.bicep new file mode 100644 index 00000000..11a71854 --- /dev/null +++ b/azresources/data/sqldb/main.bicep @@ -0,0 +1,99 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('SQL Database Logical Server Name.') +param sqlServerName string + +@description('Key/Value pair of tags.') +param tags object = {} + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id.') +param privateZoneId string + +// SQL Vulnerability Scanning +@description('SQL Vulnerability Scanning - Security Contact email address for alerts.') +param sqlVulnerabilitySecurityContactEmail string + +@description('SQL Vulnerability Scanning - Storage Account Name.') +param sqlVulnerabilityLoggingStorageAccountName string + +@description('SQL Vulnerability Scanning - Storage Account Path to store the vulnerability scan results.') +param sqlVulnerabilityLoggingStoragePath string + +// Credentials +@description('SQL Database Username.') +@secure() +param sqldbUsername string + +@description('SQL Database Password.') +@secure() +param sqldbPassword string + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// SQL Server without Customer Managed Key +module sqldbWithoutCMK 'sqldb-without-cmk.bicep' = if (!useCMK) { + name: 'deploy-sqldb-without-cmk' + params: { + sqlServerName: sqlServerName + + privateEndpointSubnetId: privateEndpointSubnetId + privateZoneId: privateZoneId + + sqlVulnerabilitySecurityContactEmail: sqlVulnerabilitySecurityContactEmail + + sqlVulnerabilityLoggingStorageAccountName: sqlVulnerabilityLoggingStorageAccountName + sqlVulnerabilityLoggingStoragePath: sqlVulnerabilityLoggingStoragePath + + sqldbUsername: sqldbUsername + sqldbPassword: sqldbPassword + + tags: tags + } +} + +// SQL Server with Customer Managed Key +module sqldbWithCMK 'sqldb-with-cmk.bicep' = if (useCMK) { + name: 'deploy-sqldb-with-cmk' + params: { + sqlServerName: sqlServerName + + privateEndpointSubnetId: privateEndpointSubnetId + privateZoneId: privateZoneId + + sqlVulnerabilitySecurityContactEmail: sqlVulnerabilitySecurityContactEmail + + sqlVulnerabilityLoggingStorageAccountName: sqlVulnerabilityLoggingStorageAccountName + sqlVulnerabilityLoggingStoragePath: sqlVulnerabilityLoggingStoragePath + + sqldbUsername: sqldbUsername + sqldbPassword: sqldbPassword + + tags: tags + + akvResourceGroupName: akvResourceGroupName + akvName: akvName + } +} + +// Outputs +output sqlDbFqdn string = useCMK ? sqldbWithCMK.outputs.sqlDbFqdn : sqldbWithoutCMK.outputs.sqlDbFqdn diff --git a/azresources/data/sqldb/sqldb-with-cmk-enable-tde.bicep b/azresources/data/sqldb/sqldb-with-cmk-enable-tde.bicep new file mode 100644 index 00000000..007b7fc6 --- /dev/null +++ b/azresources/data/sqldb/sqldb-with-cmk-enable-tde.bicep @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('SQL Database Logical Server Name.') +param sqlServerName string + +@description('Azure Key Vault Name.') +param akvName string + +@description('Azure Key Vault Key Name.') +param akvKeyName string + +@description('Azure Key Vault Key Version.') +param akvKeyVersion string + +@description('Azure Key Vault Key Uri with Version.') +param keyUriWithVersion string + +var tdeKeyName = '${akvName}_${akvKeyName}_${akvKeyVersion}' + +resource sqldbKey 'Microsoft.Sql/servers/keys@2021-02-01-preview' = { + name: '${sqlServerName}/${tdeKeyName}' + properties: { + serverKeyType: 'AzureKeyVault' + uri: keyUriWithVersion + } +} + +resource sqldbTDE 'Microsoft.Sql/servers/encryptionProtector@2021-02-01-preview' = { + dependsOn: [ + sqldbKey + ] + + name: '${sqlServerName}/current' + properties: { + serverKeyType: 'AzureKeyVault' + serverKeyName: tdeKeyName + autoRotationEnabled: true + } +} diff --git a/azresources/data/sqldb/sqldb-with-cmk.bicep b/azresources/data/sqldb/sqldb-with-cmk.bicep new file mode 100644 index 00000000..a4842105 --- /dev/null +++ b/azresources/data/sqldb/sqldb-with-cmk.bicep @@ -0,0 +1,191 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('SQL Database Logical Server Name.') +param sqlServerName string + +@description('Key/Value pair of tags.') +param tags object = {} + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id.') +param privateZoneId string + +// SQL Vulnerability Scanning +@description('SQL Vulnerability Scanning - Security Contact email address for alerts.') +param sqlVulnerabilitySecurityContactEmail string + +@description('SQL Vulnerability Scanning - Storage Account Name.') +param sqlVulnerabilityLoggingStorageAccountName string + +@description('SQL Vulnerability Scanning - Storage Account Path to store the vulnerability scan results.') +param sqlVulnerabilityLoggingStoragePath string + +// Credentials +@description('SQL Database Username.') +@secure() +param sqldbUsername string + +@description('SQL Database Password.') +@secure() +param sqldbPassword string + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(akvResourceGroupName) + name: akvName +} + +module akvKey '../../security/key-vault-key-rsa2048.bicep' = { + name: 'add-cmk-${sqlServerName}' + scope: resourceGroup(akvResourceGroupName) + params: { + akvName: akvName + keyName: 'cmk-sqldb-${sqlServerName}' + } +} + +resource sqlserver 'Microsoft.Sql/servers@2019-06-01-preview' = { + tags: tags + location: resourceGroup().location + name: sqlServerName + identity: { + type: 'SystemAssigned' + } + properties: { + administratorLogin: sqldbUsername + administratorLoginPassword: sqldbPassword + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Disabled' + } + + resource sqlserver_audit 'auditingSettings@2020-11-01-preview' = { + name: 'default' + properties: { + isAzureMonitorTargetEnabled: true + state: 'Enabled' + } + } + + resource sqlserver_devopsAudit 'devOpsAuditingSettings@2020-11-01-preview' = { + name: 'default' + properties: { + isAzureMonitorTargetEnabled: true + state: 'Enabled' + } + } + + resource sqlserver_securityAlertPolicies 'securityAlertPolicies@2020-11-01-preview' = { + name: 'Default' + properties: { + state: 'Enabled' + emailAccountAdmins: false + } + } +} + +resource sqlserver_va 'Microsoft.Sql/servers/vulnerabilityAssessments@2020-11-01-preview' = { + name: '${sqlServerName}/default' + dependsOn: [ + sqlserver + roleAssignSQLToSALogging + ] + properties: { + storageContainerPath: '${sqlVulnerabilityLoggingStoragePath}vulnerability-assessment' + recurringScans: { + isEnabled: true + emailSubscriptionAdmins: true + emails: [ + sqlVulnerabilitySecurityContactEmail + ] + } + } +} + +module akvRoleAssignmentForCMK '../../iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-${sqlServerName}-key-vault' + scope: resourceGroup(akvResourceGroupName) + params: { + keyVaultName: akv.name + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + resourceSPObjectIds: array(sqlserver.identity.principalId) + } +} + +module roleAssignSQLToSALogging '../../iam/resource/storage-role-assignment-to-sp.bicep' = { + name: 'rbac-${sqlServerName}-logging-storage-account' + params: { + storageAccountName: sqlVulnerabilityLoggingStorageAccountName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + resourceSPObjectIds: array(sqlserver.identity.principalId) + } +} + +module enableTDE 'sqldb-with-cmk-enable-tde.bicep' = { + dependsOn: [ + akvRoleAssignmentForCMK + ] + + name: 'deploy-tde-with-cmk' + params: { + sqlServerName: sqlserver.name + + akvName: akvName + akvKeyName: akvKey.outputs.keyName + akvKeyVersion: akvKey.outputs.keyVersion + keyUriWithVersion: akvKey.outputs.keyUriWithVersion + } +} + +resource sqlserver_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${sqlserver.name}-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${sqlserver.name}-endpoint' + properties: { + privateLinkServiceId: sqlserver.id + groupIds: [ + 'sqlServer' + ] + } + } + ] + } + + resource sqlserver_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink_database_windows_net' + properties: { + privateDnsZoneId: privateZoneId + } + } + ] + } + } +} + +// Outputs +output sqlDbFqdn string = sqlserver.properties.fullyQualifiedDomainName diff --git a/azresources/data/sqldb/sqldb-without-cmk.bicep b/azresources/data/sqldb/sqldb-without-cmk.bicep new file mode 100644 index 00000000..d8a2338e --- /dev/null +++ b/azresources/data/sqldb/sqldb-without-cmk.bicep @@ -0,0 +1,144 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('SQL Database Logical Server Name.') +param sqlServerName string + +@description('Key/Value pair of tags.') +param tags object = {} + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id.') +param privateZoneId string + +// SQL Vulnerability Scanning +@description('SQL Vulnerability Scanning - Security Contact email address for alerts.') +param sqlVulnerabilitySecurityContactEmail string + +@description('SQL Vulnerability Scanning - Storage Account Name.') +param sqlVulnerabilityLoggingStorageAccountName string + +@description('SQL Vulnerability Scanning - Storage Account Path to store the vulnerability scan results.') +param sqlVulnerabilityLoggingStoragePath string + +// Credentials +@description('SQL Database Username.') +@secure() +param sqldbUsername string + +@description('SQL Database Password.') +@secure() +param sqldbPassword string + +resource sqlserver 'Microsoft.Sql/servers@2019-06-01-preview' = { + tags: tags + location: resourceGroup().location + name: sqlServerName + identity: { + type: 'SystemAssigned' + } + properties: { + administratorLogin: sqldbUsername + administratorLoginPassword: sqldbPassword + minimalTlsVersion: '1.2' + publicNetworkAccess: 'Disabled' + } + + resource sqlserver_audit 'auditingSettings@2020-11-01-preview' = { + name: 'default' + properties: { + isAzureMonitorTargetEnabled: true + state: 'Enabled' + } + } + + resource sqlserver_devopsAudit 'devOpsAuditingSettings@2020-11-01-preview' = { + name: 'default' + properties: { + isAzureMonitorTargetEnabled: true + state: 'Enabled' + } + } + + resource sqlserver_securityAlertPolicies 'securityAlertPolicies@2020-11-01-preview' = { + name: 'Default' + properties: { + state: 'Enabled' + emailAccountAdmins: false + } + } +} + +resource sqlserver_va 'Microsoft.Sql/servers/vulnerabilityAssessments@2020-11-01-preview' = { + name: '${sqlServerName}/default' + dependsOn: [ + sqlserver + roleAssignSQLToSALogging + ] + properties: { + storageContainerPath: '${sqlVulnerabilityLoggingStoragePath}vulnerability-assessment' + recurringScans: { + isEnabled: true + emailSubscriptionAdmins: true + emails: [ + sqlVulnerabilitySecurityContactEmail + ] + } + } +} + +module roleAssignSQLToSALogging '../../iam/resource/storage-role-assignment-to-sp.bicep' = { + name: 'rbac-${sqlServerName}-logging-storage-account' + params: { + storageAccountName: sqlVulnerabilityLoggingStorageAccountName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + resourceSPObjectIds: array(sqlserver.identity.principalId) + } +} + +resource sqlserver_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${sqlserver.name}-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${sqlserver.name}-endpoint' + properties: { + privateLinkServiceId: sqlserver.id + groupIds: [ + 'sqlServer' + ] + } + } + ] + } + + resource sqlserver_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink_database_windows_net' + properties: { + privateDnsZoneId: privateZoneId + } + } + ] + } + } +} + +// Outputs +output sqlDbFqdn string = sqlserver.properties.fullyQualifiedDomainName diff --git a/azresources/data/sqlmi/main.bicep b/azresources/data/sqlmi/main.bicep new file mode 100644 index 00000000..6aecf874 --- /dev/null +++ b/azresources/data/sqlmi/main.bicep @@ -0,0 +1,109 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('SQL Managed Instance Name.') +param sqlServerName string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('SQL Managed Instance SKU. Default: GP_Gen5') +param skuName string = 'GP_Gen5' + +@description('Number of vCores. Defalut: 4') +param vCores int = 4 + +@description('Data Storage Size in GB. Default: 32') +param storageSizeInGB int = 32 + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +// SQL Vulnerability Scanning +@description('SQL Vulnerability Scanning - Security Contact email address for alerts.') +param sqlVulnerabilitySecurityContactEmail string + +@description('SQL Vulnerability Scanning - Storage Account Name.') +param sqlVulnerabilityLoggingStorageAccountName string + +@description('SQL Vulnerability Scanning - Storage Account Path to store the vulnerability scan results.') +param sqlVulnerabilityLoggingStoragePath string + +// Credentials +@description('SQL MI Username') +@secure() +param sqlmiUsername string + +@description('SQL MI Password') +@secure() +param sqlmiPassword string + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// SQL Managed Instance without Customer Managed Key +module sqlmiWithoutCMK 'sqlmi-without-cmk.bicep' = if (!useCMK) { + name: 'deploy-sqlmi-without-cmk' + params: { + name: sqlServerName + tags: tags + + skuName: skuName + + vCores: vCores + storageSizeInGB: storageSizeInGB + + subnetId: subnetId + + sqlVulnerabilityLoggingStorageAccountName: sqlVulnerabilityLoggingStorageAccountName + sqlVulnerabilityLoggingStoragePath: sqlVulnerabilityLoggingStoragePath + sqlVulnerabilitySecurityContactEmail: sqlVulnerabilitySecurityContactEmail + + sqlmiUsername: sqlmiUsername + sqlmiPassword: sqlmiPassword + } +} + +// SQL Managed Instance with Customer Managed Key +module sqlmiWithCMK 'sqlmi-with-cmk.bicep' = if (useCMK) { + name: 'deploy-sqlmi-with-cmk' + params: { + name: sqlServerName + tags: tags + + skuName: skuName + + vCores: vCores + storageSizeInGB: storageSizeInGB + + subnetId: subnetId + + sqlVulnerabilityLoggingStorageAccountName: sqlVulnerabilityLoggingStorageAccountName + sqlVulnerabilityLoggingStoragePath: sqlVulnerabilityLoggingStoragePath + sqlVulnerabilitySecurityContactEmail: sqlVulnerabilitySecurityContactEmail + + sqlmiUsername: sqlmiUsername + sqlmiPassword: sqlmiPassword + + akvResourceGroupName: akvResourceGroupName + akvName: akvName + } +} + +// Outputs +output sqlMiFqdn string = useCMK ? sqlmiWithCMK.outputs.sqlMiFqdn : sqlmiWithoutCMK.outputs.sqlMiFqdn diff --git a/azresources/data/sqlmi/sqlmi-with-cmk-enable-tde.bicep b/azresources/data/sqlmi/sqlmi-with-cmk-enable-tde.bicep new file mode 100644 index 00000000..825e8e26 --- /dev/null +++ b/azresources/data/sqlmi/sqlmi-with-cmk-enable-tde.bicep @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('SQL Managed Instance Name.') +param sqlServerName string + +@description('Azure Key Vault Name.') +param akvName string + +@description('Azure Key Vault Key Name.') +param akvKeyName string + +@description('Azure Key Vault Key Version.') +param akvKeyVersion string + +@description('Azure Key Vault Key Uri with Version.') +param keyUriWithVersion string + +var tdeKeyName = '${akvName}_${akvKeyName}_${akvKeyVersion}' + +resource sqlmiKey 'Microsoft.Sql/managedInstances/keys@2021-02-01-preview' = { + name: '${sqlServerName}/${tdeKeyName}' + properties: { + serverKeyType: 'AzureKeyVault' + uri: keyUriWithVersion + } +} + +resource sqlmiTDE 'Microsoft.Sql/managedInstances/encryptionProtector@2021-02-01-preview' = { + dependsOn: [ + sqlmiKey + ] + + name: '${sqlServerName}/current' + properties: { + serverKeyType: 'AzureKeyVault' + serverKeyName: tdeKeyName + autoRotationEnabled: true + } +} diff --git a/azresources/data/sqlmi/sqlmi-with-cmk.bicep b/azresources/data/sqlmi/sqlmi-with-cmk.bicep new file mode 100644 index 00000000..0c0a1c90 --- /dev/null +++ b/azresources/data/sqlmi/sqlmi-with-cmk.bicep @@ -0,0 +1,151 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('SQL Managed Instance Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('SQL Managed Instance SKU. Default: GP_Gen5') +param skuName string = 'GP_Gen5' + +@description('Number of vCores. Defalut: 4') +param vCores int = 4 + +@description('Data Storage Size in GB. Default: 32') +param storageSizeInGB int = 32 + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +// SQL Vulnerability Scanning +@description('SQL Vulnerability Scanning - Security Contact email address for alerts.') +param sqlVulnerabilitySecurityContactEmail string + +@description('SQL Vulnerability Scanning - Storage Account Name.') +param sqlVulnerabilityLoggingStorageAccountName string + +@description('SQL Vulnerability Scanning - Storage Account Path to store the vulnerability scan results.') +param sqlVulnerabilityLoggingStoragePath string + +// Credentials +@description('SQL MI Username') +@secure() +param sqlmiUsername string + +@description('SQL MI Password') +@secure() +param sqlmiPassword string + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(akvResourceGroupName) + name: akvName +} + +module akvKey '../../security/key-vault-key-rsa2048.bicep' = { + name: 'add-cmk-${name}' + scope: resourceGroup(akvResourceGroupName) + params: { + akvName: akvName + keyName: 'cmk-sqlmi-${name}' + } +} + +resource sqlmi 'Microsoft.Sql/managedInstances@2020-11-01-preview' = { + name: name + location: resourceGroup().location + tags: tags + identity: { + type: 'SystemAssigned' + } + sku: { + name: skuName + } + properties: { + administratorLogin: sqlmiUsername + administratorLoginPassword: sqlmiPassword + subnetId: subnetId + licenseType: 'LicenseIncluded' + vCores: vCores + storageSizeInGB: storageSizeInGB + } + + resource sqlmi_securityAlertPolicies 'securityAlertPolicies@2020-11-01-preview' = { + name: 'Default' + properties: { + state: 'Enabled' + emailAccountAdmins: false + } + } +} + +resource sqlmi_va 'Microsoft.Sql/managedInstances/vulnerabilityAssessments@2020-11-01-preview' = { + name: '${name}/default' + dependsOn: [ + sqlmi + roleAssignSQLMIToSALogging + ] + properties: { + storageContainerPath: '${sqlVulnerabilityLoggingStoragePath}vulnerability-assessment' + recurringScans: { + isEnabled: true + emailSubscriptionAdmins: true + emails: [ + sqlVulnerabilitySecurityContactEmail + ] + } + } +} + +module akvRoleAssignmentForCMK '../../iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-${name}-key-vault' + scope: resourceGroup(akvResourceGroupName) + params: { + keyVaultName: akv.name + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + resourceSPObjectIds: array(sqlmi.identity.principalId) + } +} + +module roleAssignSQLMIToSALogging '../../iam/resource/storage-role-assignment-to-sp.bicep' = { + name: 'rbac-${name}-logging-storage-account' + params: { + storageAccountName: sqlVulnerabilityLoggingStorageAccountName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + resourceSPObjectIds: array(sqlmi.identity.principalId) + } +} + +module enableTDE 'sqlmi-with-cmk-enable-tde.bicep' = { + dependsOn: [ + akvRoleAssignmentForCMK + ] + + name: 'deploy-tde-with-cmk' + params: { + sqlServerName: sqlmi.name + + akvName: akvName + akvKeyName: akvKey.outputs.keyName + akvKeyVersion: akvKey.outputs.keyVersion + keyUriWithVersion: akvKey.outputs.keyUriWithVersion + } +} + +// Outputs +output sqlMiFqdn string = sqlmi.properties.fullyQualifiedDomainName diff --git a/azresources/data/sqlmi/sqlmi-without-cmk.bicep b/azresources/data/sqlmi/sqlmi-without-cmk.bicep new file mode 100644 index 00000000..2031bbdf --- /dev/null +++ b/azresources/data/sqlmi/sqlmi-without-cmk.bicep @@ -0,0 +1,107 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('SQL Managed Instance Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('SQL Managed Instance SKU. Default: GP_Gen5') +param skuName string = 'GP_Gen5' + +@description('Number of vCores. Defalut: 4') +param vCores int = 4 + +@description('Data Storage Size in GB. Default: 32') +param storageSizeInGB int = 32 + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +// SQL Vulnerability Scanning +@description('SQL Vulnerability Scanning - Security Contact email address for alerts.') +param sqlVulnerabilitySecurityContactEmail string + +@description('SQL Vulnerability Scanning - Storage Account Name.') +param sqlVulnerabilityLoggingStorageAccountName string + +@description('SQL Vulnerability Scanning - Storage Account Path to store the vulnerability scan results.') +param sqlVulnerabilityLoggingStoragePath string + +// Credentials +@description('SQL MI Username') +@secure() +param sqlmiUsername string + +@description('SQL MI Password') +@secure() +param sqlmiPassword string + +resource sqlmi 'Microsoft.Sql/managedInstances@2020-11-01-preview' = { + name: name + location: resourceGroup().location + tags: tags + identity: { + type: 'SystemAssigned' + } + sku: { + name: skuName + } + properties: { + administratorLogin: sqlmiUsername + administratorLoginPassword: sqlmiPassword + subnetId: subnetId + licenseType: 'LicenseIncluded' + vCores: vCores + storageSizeInGB: storageSizeInGB + } + + resource sqlmi_securityAlertPolicies 'securityAlertPolicies@2020-11-01-preview' = { + name: 'Default' + dependsOn: [ + sqlmi + ] + properties: { + state: 'Enabled' + emailAccountAdmins: false + } + } +} + +resource sqlmi_va 'Microsoft.Sql/managedInstances/vulnerabilityAssessments@2020-11-01-preview' = { + name: '${name}/default' + dependsOn: [ + sqlmi + roleAssignSQLMIToSALogging + ] + properties: { + storageContainerPath: '${sqlVulnerabilityLoggingStoragePath}vulnerability-assessment' + recurringScans: { + isEnabled: true + emailSubscriptionAdmins: true + emails: [ + sqlVulnerabilitySecurityContactEmail + ] + } + } +} + +module roleAssignSQLMIToSALogging '../../iam/resource/storage-role-assignment-to-sp.bicep' = { + name: 'rbac-${name}-logging-storage-account' + params: { + storageAccountName: sqlVulnerabilityLoggingStorageAccountName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'ba92f5b4-2d11-453d-a403-e96b0029c9fe') + resourceSPObjectIds: array(sqlmi.identity.principalId) + } +} + +// Outputs +output sqlMiFqdn string = sqlmi.properties.fullyQualifiedDomainName diff --git a/azresources/iam/resource/key-vault-role-assignment-to-sp.bicep b/azresources/iam/resource/key-vault-role-assignment-to-sp.bicep new file mode 100644 index 00000000..5e605095 --- /dev/null +++ b/azresources/iam/resource/key-vault-role-assignment-to-sp.bicep @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Key Vault Name.') +param keyVaultName string + +@description('Role Definition Id.') +param roleDefinitionId string + +@description('Array of Service Principal Object Ids.') +param resourceSPObjectIds array = [] + +resource scopeOfRoleAssignment 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + name: keyVaultName +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for spId in resourceSPObjectIds: { + name: guid(scopeOfRoleAssignment.id, spId, roleDefinitionId) + scope: scopeOfRoleAssignment + properties: { + roleDefinitionId: roleDefinitionId + principalId: spId + principalType: 'ServicePrincipal' + } +}] + +module roleAssignmentWait '../../util/wait.bicep' = [for (spId, idx) in resourceSPObjectIds: { + name: '${roleAssignment[idx].name}-wait' + scope: resourceGroup() + params: { + waitNamePrefix: roleAssignment[idx].name + loopCounter: 10 + } +}] diff --git a/azresources/iam/resource/private-dns-zone-role-assignment-to-sp.bicep b/azresources/iam/resource/private-dns-zone-role-assignment-to-sp.bicep new file mode 100644 index 00000000..6c8a27b2 --- /dev/null +++ b/azresources/iam/resource/private-dns-zone-role-assignment-to-sp.bicep @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Private DNS Zone Name.') +param zoneName string + +@description('Role Definition Id.') +param roleDefinitionId string + +@description('Array of Service Principal Object Ids.') +param resourceSPObjectIds array = [] + +resource scopeOfRoleAssignment 'Microsoft.Network/privateDnsZones@2020-06-01' existing = { + name: zoneName +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for spId in resourceSPObjectIds: { + name: guid(scopeOfRoleAssignment.id, spId, roleDefinitionId) + scope: scopeOfRoleAssignment + properties: { + roleDefinitionId: roleDefinitionId + principalId: spId + principalType: 'ServicePrincipal' + } +}] + +module roleAssignmentWait '../../util/wait.bicep' = [for (spId, idx) in resourceSPObjectIds: { + name: '${roleAssignment[idx].name}-wait' + scope: resourceGroup() + params: { + waitNamePrefix: roleAssignment[idx].name + loopCounter: 10 + } +}] diff --git a/azresources/iam/resource/storage-role-assignment-to-sp.bicep b/azresources/iam/resource/storage-role-assignment-to-sp.bicep new file mode 100644 index 00000000..875992c3 --- /dev/null +++ b/azresources/iam/resource/storage-role-assignment-to-sp.bicep @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Storage Account Name.') +param storageAccountName string + +@description('Role Definition Id.') +param roleDefinitionId string + +@description('Array of Service Principal Object Ids.') +param resourceSPObjectIds array = [] + +resource scopeOfRoleAssignment 'Microsoft.Storage/storageAccounts@2021-04-01' existing = { + name: storageAccountName +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for spId in resourceSPObjectIds: { + name: guid(scopeOfRoleAssignment.id, storageAccountName, spId, roleDefinitionId) + scope: scopeOfRoleAssignment + properties: { + roleDefinitionId: roleDefinitionId + principalId: spId + principalType: 'ServicePrincipal' + } +}] + +module roleAssignmentWait '../../util/wait.bicep' = [for (spId, idx) in resourceSPObjectIds: { + name: '${roleAssignment[idx].name}-wait' + scope: resourceGroup() + params: { + waitNamePrefix: roleAssignment[idx].name + loopCounter: 10 + } +}] diff --git a/azresources/iam/resource/virtual-network-role-assignment-to-sp.bicep b/azresources/iam/resource/virtual-network-role-assignment-to-sp.bicep new file mode 100644 index 00000000..0570a040 --- /dev/null +++ b/azresources/iam/resource/virtual-network-role-assignment-to-sp.bicep @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Virtual Network Name.') +param vnetName string + +@description('Role Definition Id.') +param roleDefinitionId string + +@description('Array of Service Principal Object Ids.') +param resourceSPObjectIds array = [] + +resource scopeOfRoleAssignment 'Microsoft.Network/virtualNetworks@2021-02-01' existing = { + name: vnetName +} + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for spId in resourceSPObjectIds: { + name: guid(scopeOfRoleAssignment.id, spId, roleDefinitionId) + scope: scopeOfRoleAssignment + properties: { + roleDefinitionId: roleDefinitionId + principalId: spId + principalType: 'ServicePrincipal' + } +}] + +module roleAssignmentWait '../../util/wait.bicep' = [for (spId, idx) in resourceSPObjectIds: { + name: '${roleAssignment[idx].name}-wait' + scope: resourceGroup() + params: { + waitNamePrefix: roleAssignment[idx].name + loopCounter: 10 + } +}] diff --git a/azresources/iam/resourceGroup/role-assignment-to-sp.bicep b/azresources/iam/resourceGroup/role-assignment-to-sp.bicep new file mode 100644 index 00000000..c7dc3c5b --- /dev/null +++ b/azresources/iam/resourceGroup/role-assignment-to-sp.bicep @@ -0,0 +1,32 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Role Definition Id.') +param roleDefinitionId string + +@description('Array of Service Principal Object Ids.') +param resourceSPObjectIds array = [] + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for spId in resourceSPObjectIds: { + name: guid(resourceGroup().id, spId, roleDefinitionId) + properties: { + roleDefinitionId: roleDefinitionId + principalId: spId + principalType: 'ServicePrincipal' + } +}] + +module roleAssignmentWait '../../util/wait.bicep' = [for (spId, idx) in resourceSPObjectIds: { + name: '${roleAssignment[idx].name}-wait' + scope: resourceGroup() + params: { + waitNamePrefix: roleAssignment[idx].name + loopCounter: 10 + } +}] diff --git a/azresources/iam/subscription/role-assignment-to-group.bicep b/azresources/iam/subscription/role-assignment-to-group.bicep new file mode 100644 index 00000000..6017ec88 --- /dev/null +++ b/azresources/iam/subscription/role-assignment-to-group.bicep @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +@description('Role Definition Id.') +param roleDefinitionId string + +@description('Array of Security Group Object Ids.') +param groupObjectIds array = [] + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for groupId in groupObjectIds: { + name: guid(subscription().id, groupId, roleDefinitionId) + scope: subscription() + properties: { + roleDefinitionId: roleDefinitionId + principalId: groupId + principalType: 'Group' + } +}] diff --git a/azresources/iam/subscription/role-assignment-to-sp.bicep b/azresources/iam/subscription/role-assignment-to-sp.bicep new file mode 100644 index 00000000..707b91ea --- /dev/null +++ b/azresources/iam/subscription/role-assignment-to-sp.bicep @@ -0,0 +1,35 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +@description('Role Definition Id.') +param roleDefinitionId string + +@description('Array of Service Principal Object Ids.') +param resourceSPObjectIds array = [] + +resource roleAssignment 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = [for spId in resourceSPObjectIds: { + name: guid(subscription().id, spId, roleDefinitionId) + scope: subscription() + properties: { + roleDefinitionId: roleDefinitionId + principalId: spId + principalType: 'ServicePrincipal' + } +}] + +module roleAssignmentWait '../../util/wait-subscription.bicep' = [for (spId, idx) in resourceSPObjectIds: { + name: '${roleAssignment[idx].name}-wait' + scope: subscription() + params: { + waitNamePrefix: roleAssignment[idx].name + loopCounter: 10 + } +}] diff --git a/azresources/iam/user-assigned-identity.bicep b/azresources/iam/user-assigned-identity.bicep new file mode 100644 index 00000000..8b455523 --- /dev/null +++ b/azresources/iam/user-assigned-identity.bicep @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('User Assigned Managed Identity Name.') +param name string + +resource identity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: name + location: resourceGroup().location +} + +// Outputs +output identityId string = identity.id +output identityPrincipalId string = identity.properties.principalId +output identityClientId string = identity.properties.clientId diff --git a/azresources/integration/eventhub.bicep b/azresources/integration/eventhub.bicep new file mode 100644 index 00000000..ae16b3be --- /dev/null +++ b/azresources/integration/eventhub.bicep @@ -0,0 +1,77 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Event Hub Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Capacity Unit. Default: 1') +param capacity int = 1 + +// Networking +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id.') +param privateZoneEventHubId string + +resource eventhub 'Microsoft.EventHub/namespaces@2021-01-01-preview' = { + name: name + location: resourceGroup().location + tags: tags + sku: { + name: 'Standard' + tier: 'Standard' + capacity: capacity + } + identity: { + type: 'SystemAssigned' + } + properties: { + isAutoInflateEnabled: false + zoneRedundant: true + } +} + +resource eventhub_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = { + location: resourceGroup().location + name: '${eventhub.name}-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${eventhub.name}-endpoint' + properties: { + privateLinkServiceId: eventhub.id + groupIds: [ + 'namespace' + ] + } + } + ] + } + + resource eventhub_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink-eventhub-ms' + properties: { + privateDnsZoneId: privateZoneEventHubId + } + } + ] + } + } +} diff --git a/azresources/monitor/ai-web.bicep b/azresources/monitor/ai-web.bicep new file mode 100644 index 00000000..ff78135c --- /dev/null +++ b/azresources/monitor/ai-web.bicep @@ -0,0 +1,28 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Application Insights Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +resource ai 'Microsoft.Insights/components@2020-02-02-preview' = { + name: name + tags: tags + location: resourceGroup().location + kind: 'web' + properties: { + Application_Type: 'web' + } +} + +// Outputs +output aiId string = ai.id +output aiIKey string = ai.properties.InstrumentationKey diff --git a/azresources/monitor/log-analytics.bicep b/azresources/monitor/log-analytics.bicep new file mode 100644 index 00000000..9f48dcc7 --- /dev/null +++ b/azresources/monitor/log-analytics.bicep @@ -0,0 +1,81 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Log Analytics Workspace Name.') +param workspaceName string + +@description('Automation Account Name.') +param automationAccountName string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Log Analytics Workspace Data Retention in Days. Default: 730 days') +param workspaceRetentionInDays int = 730 + +// Log Analytics Workspace Solutions +var solutions = [ + 'AgentHealthAssessment' + 'AntiMalware' + 'AzureActivity' + 'ChangeTracking' + 'Security' + 'SecurityInsights' + 'ServiceMap' + 'SQLAssessment' + 'Updates' + 'VMInsights' +] + +// Create Automation Account +module automationAccount '../automation/automation-account.bicep' = { + name: 'automation-account' + params: { + automationAccountName: automationAccountName + tags: tags + } +} + +// Create Log Analytics Workspace +resource workspace 'Microsoft.OperationalInsights/workspaces@2020-08-01' = { + name: workspaceName + tags: tags + location: resourceGroup().location + properties: { + sku: { + name: 'PerNode' + } + retentionInDays: workspaceRetentionInDays + } +} + +// Link Log Analytics Workspace to Automation Account +resource automationAccountLinkedToWorkspace 'Microsoft.OperationalInsights/workspaces/linkedServices@2020-08-01' = { + name: '${workspace.name}/Automation' + properties: { + resourceId: automationAccount.outputs.automationAccountId + } +} + +// Add Log Analytics Workspace Solutions +resource workspaceSolutions 'Microsoft.OperationsManagement/solutions@2015-11-01-preview' = [for solution in solutions: { + name: '${solution}(${workspace.name})' + location: resourceGroup().location + properties: { + workspaceResourceId: workspace.id + } + plan: { + name: '${solution}(${workspace.name})' + product: 'OMSGallery/${solution}' + publisher: 'Microsoft' + promotionCode: '' + } +}] + +output workspaceResourceId string = workspace.id diff --git a/azresources/network/app-gateway-v2-waf.bicep b/azresources/network/app-gateway-v2-waf.bicep new file mode 100644 index 00000000..a8841e9f --- /dev/null +++ b/azresources/network/app-gateway-v2-waf.bicep @@ -0,0 +1,166 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Application Gateway v2 Name.') +param name string + +@description('Subnet Resource Id.') +param subnetId string + +@description('Firewall Mode. Default: Prevention') +@allowed([ + 'Detection' + 'Prevention' +]) +param firewallMode string = 'Prevention' + +@description('OWASP Rule Set Version. Default: 3.0') +@allowed([ + '3.2' + '3.1' + '3.0' + '2.2.9' +]) +param owaspRuleSetVersion string = '3.0' + +@description('SSL Predefined Policy. Default: AppGwSslPolicy20170401') +@allowed([ + 'AppGwSslPolicy20150501' + 'AppGwSslPolicy20170401' + 'AppGwSslPolicy20170401S' +]) +param sslPredefinedPolicyName string = 'AppGwSslPolicy20170401' + +@description('Autoscale Minimum Capacity. Default: 1') +param autoScaleMinCapacity int = 1 + +@description('Autoscale Maximum Capacity. Default: 2') +param autoScaleMaxCapacity int = 2 + +@description('Enable HTTP 2. Default: true') +param enableHttp2 bool = true + +resource appgwPublicIp 'Microsoft.Network/publicIPAddresses@2020-06-01' = { + name: '${name}PublicIp' + location: resourceGroup().location + sku: { + name: 'Standard' + } + properties: { + publicIPAllocationMethod: 'Static' + } +} + +resource appgw 'Microsoft.Network/applicationGateways@2020-07-01' = { + name: name + location: resourceGroup().location + properties: { + sku: { + name: 'WAF_v2' + tier: 'WAF_v2' + } + sslPolicy: { + policyType: 'Predefined' + policyName: sslPredefinedPolicyName + } + gatewayIPConfigurations: [ + { + properties: { + subnet: { + id: subnetId + } + } + name: 'gatewayIpConfig' + } + ] + webApplicationFirewallConfiguration: { + enabled: true + firewallMode: firewallMode + ruleSetType: 'OWASP' + ruleSetVersion: owaspRuleSetVersion + requestBodyCheck: true + } + enableHttp2: enableHttp2 + autoscaleConfiguration: { + minCapacity: autoScaleMinCapacity + maxCapacity: autoScaleMaxCapacity + } + frontendIPConfigurations: [ + { + name: 'frontendPublicIpConfig' + properties: { + publicIPAddress: { + id: appgwPublicIp.id + } + } + } + ] + frontendPorts: [ + { + name: 'placeholder_frontendport_80' + properties: { + port: 80 + } + } + ] + backendAddressPools: [ + { + name: 'placeholder_backendpool' + properties: { + backendAddresses: [ + ] + } + } + ] + backendHttpSettingsCollection: [ + { + name: 'placeholder_backendhttpSettings' + properties: { + port: 80 + } + } + ] + httpListeners: [ + { + name: 'placeholder_httplistener' + properties: { + protocol: 'Http' + frontendPort: { + id: resourceId('Microsoft.Network/applicationGateways/frontendPorts', name, 'placeholder_frontendport_80') + } + frontendIPConfiguration: { + id: resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations', name, 'frontendPublicIpConfig') + } + } + } + ] + requestRoutingRules: [ + { + name: 'placeholder_routingrule' + properties: { + ruleType: 'Basic' + httpListener: { + id: resourceId('Microsoft.Network/applicationGateways/httpListeners', name, 'placeholder_httplistener') + } + backendAddressPool: { + id: resourceId('Microsoft.Network/applicationGateways/backendAddressPools', name, 'placeholder_backendpool') + } + backendHttpSettings: { + id: resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', name, 'placeholder_backendhttpSettings') + } + } + } + ] + } + zones: [ + '1' + '2' + '3' + ] +} diff --git a/azresources/network/bastion.bicep b/azresources/network/bastion.bicep new file mode 100644 index 00000000..cdb5baec --- /dev/null +++ b/azresources/network/bastion.bicep @@ -0,0 +1,53 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Bastion Name') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +// Networking +@description('Subnet Resource Id.') +param subnetId string + +resource bastionPublicIP 'Microsoft.Network/publicIPAddresses@2020-06-01' = { + location: resourceGroup().location + name: '${name}PublicIp' + tags: tags + sku: { + name: 'Standard' + } + properties: { + publicIPAddressVersion: 'IPv4' + publicIPAllocationMethod: 'Static' + } +} + +resource bastion 'Microsoft.Network/bastionHosts@2020-06-01' = { + location: resourceGroup().location + name: name + tags: tags + properties: { + dnsName: uniqueString(resourceGroup().id) + ipConfigurations: [ + { + name: 'IpConf' + properties: { + subnet: { + id: subnetId + } + publicIPAddress: { + id: bastionPublicIP.id + } + } + } + ] + } +} diff --git a/azresources/network/ddos-standard.bicep b/azresources/network/ddos-standard.bicep new file mode 100644 index 00000000..4fa235d3 --- /dev/null +++ b/azresources/network/ddos-standard.bicep @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure DDOS Standard Plan Name.') +param name string + +resource ddosPlan 'Microsoft.Network/ddosProtectionPlans@2020-07-01' = { + name: name + location: resourceGroup().location + properties: {} +} + +// Outputs +output ddosPlanId string = ddosPlan.id diff --git a/azresources/network/firewall.bicep b/azresources/network/firewall.bicep new file mode 100644 index 00000000..856c1469 --- /dev/null +++ b/azresources/network/firewall.bicep @@ -0,0 +1,92 @@ +// ---------------------------------------------------------------------------------- +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +@description('Azure Firewall Name') +param name string + +@description('Availability Zones to deploy Azure Firewall.') +param zones array + +@description('Subnet Id for AzureFirewallSubnet.') +param firewallSubnetId string + +@description('Subnet Id for AzureFirewallManagementSubnet.') +param firewallManagementSubnetId string + +@description('Whether to deploy Azure Firewall with Forced Tunneling mode or not.') +param forcedTunnelingEnabled bool + +@description('Existing Firewall Policy Resource Id') +param existingFirewallPolicyId string + +resource firewallPublicIp 'Microsoft.Network/publicIPAddresses@2021-02-01' = if (!forcedTunnelingEnabled) { + name: '${name}PublicIp' + location: resourceGroup().location + sku: { + name: 'Standard' + tier: 'Regional' + } + zones: !empty(zones) ? zones : null + properties: { + publicIPAllocationMethod: 'Static' + } +} + +resource firewallManagementPublicIp 'Microsoft.Network/publicIPAddresses@2021-02-01' = if (forcedTunnelingEnabled) { + name: '${name}MangementPublicIp' + location: resourceGroup().location + sku: { + name: 'Standard' + tier: 'Regional' + } + zones: !empty(zones) ? zones : null + properties: { + publicIPAllocationMethod: 'Static' + } +} + +resource firewall 'Microsoft.Network/azureFirewalls@2021-02-01' = { + name: name + location: resourceGroup().location + zones: !empty(zones) ? zones : null + properties: { + sku: { + name: 'AZFW_VNet' + tier: 'Premium' + } + firewallPolicy: { + id: existingFirewallPolicyId + } + ipConfigurations: [ + { + name: 'ipConfig' + properties: { + subnet: { + id: firewallSubnetId + } + publicIPAddress: !forcedTunnelingEnabled ? { + id: firewallPublicIp.id + } : null + } + } + ] + managementIpConfiguration: forcedTunnelingEnabled ? { + name: 'managementIpConfig' + properties: { + subnet: { + id: firewallManagementSubnetId + } + publicIPAddress: { + id: firewallManagementPublicIp.id + } + } + } : null + } +} + + +// Outputs +output firewallId string = firewall.id +output firewallPrivateIp string = firewall.properties.ipConfigurations[0].properties.privateIPAddress diff --git a/azresources/network/lb-egress.bicep b/azresources/network/lb-egress.bicep new file mode 100644 index 00000000..653c8f85 --- /dev/null +++ b/azresources/network/lb-egress.bicep @@ -0,0 +1,82 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Egress Azure Load Balancer Name') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +var loadBalancerBackendPoolName = 'lbBackendPool' +var loadBalancerBackendPoolId = resourceId('Microsoft.Network/loadBalancers/backendAddressPools', name, loadBalancerBackendPoolName) + +var loadBalancerFrontendConfigName = 'frontendConfig' +var loadBalancerFrontendConfigId = resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', name, loadBalancerFrontendConfigName) + +resource lbPublicIp 'Microsoft.Network/publicIPAddresses@2020-06-01' = { + name: '${name}PublicIp' + tags: tags + location: resourceGroup().location + sku: { + name: 'Standard' + } + properties: { + publicIPAllocationMethod: 'Static' + } +} + +resource lb 'Microsoft.Network/loadBalancers@2020-06-01' = { + name: name + tags: tags + location: resourceGroup().location + sku: { + name: 'Standard' + } + properties: { + frontendIPConfigurations: [ + { + name: loadBalancerFrontendConfigName + properties: { + publicIPAddress: { + id: lbPublicIp.id + } + } + } + ] + backendAddressPools: [ + { + name: loadBalancerBackendPoolName + } + ] + outboundRules: [ + { + name: 'outbound-rule' + properties: { + allocatedOutboundPorts: 0 + protocol: 'All' + enableTcpReset: true + idleTimeoutInMinutes: 4 + backendAddressPool: { + id: loadBalancerBackendPoolId + } + frontendIPConfigurations: [ + { + id: loadBalancerFrontendConfigId + } + ] + } + } + ] + } +} + +// Outputs +output lbId string = lb.id +output lbBackendPoolName string = loadBalancerBackendPoolName +output lbBackendPoolFullName string = '${lb.name}/${loadBalancerBackendPoolName}' diff --git a/azresources/network/nsg/nsg-allowall.bicep b/azresources/network/nsg/nsg-allowall.bicep new file mode 100644 index 00000000..1951e59f --- /dev/null +++ b/azresources/network/nsg/nsg-allowall.bicep @@ -0,0 +1,59 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Network Security Group Name.') +param name string + +resource nsg 'Microsoft.Network/networkSecurityGroups@2020-11-01' = { + name: name + location: resourceGroup().location + properties: { + securityRules: [ + { + name: 'AllowAllInbound' + properties: { + description: 'Allow all in' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 1000 + direction: 'Inbound' + sourcePortRanges: [] + destinationPortRanges: [] + sourceAddressPrefixes: [] + destinationAddressPrefixes: [] + } + } + { + name: 'AllowAllOutbound' + properties: { + description: 'Allow all out' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 1000 + direction: 'Outbound' + sourcePortRanges: [] + destinationPortRanges: [] + sourceAddressPrefixes: [] + destinationAddressPrefixes: [] + } + } + ] + } +} + +// Outputs +output nsgId string = nsg.id diff --git a/azresources/network/nsg/nsg-appgwv2.bicep b/azresources/network/nsg/nsg-appgwv2.bicep new file mode 100644 index 00000000..e33a3868 --- /dev/null +++ b/azresources/network/nsg/nsg-appgwv2.bicep @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Network Security Group Name.') +param name string + +resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: name + location: resourceGroup().location + properties: { + securityRules: [ + { + name: 'AllowGatewayManagerInbound' + properties: { + priority: 100 + direction: 'Inbound' + access: 'Allow' + protocol: 'Tcp' + sourceAddressPrefix: 'GatewayManager' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '65200-65535' + } + } + ] + } +} + +// Outputs +output nsgId string = nsg.id diff --git a/azresources/network/nsg/nsg-bastion.bicep b/azresources/network/nsg/nsg-bastion.bicep new file mode 100644 index 00000000..d09427a3 --- /dev/null +++ b/azresources/network/nsg/nsg-bastion.bicep @@ -0,0 +1,78 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Network Security Group Name.') +param name string + +resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: name + location: resourceGroup().location + properties: { + securityRules: [ + { + name: 'AllowHttpsInbound' + properties: { + priority: 120 + direction: 'Inbound' + access: 'Allow' + protocol: 'Tcp' + sourceAddressPrefix: 'Internet' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '443' + } + } + { + name: 'AllowGatewayManagerInbound' + properties: { + priority: 130 + direction: 'Inbound' + access: 'Allow' + protocol: 'Tcp' + sourceAddressPrefix: 'GatewayManager' + sourcePortRange: '*' + destinationAddressPrefix: '*' + destinationPortRange: '443' + } + } + { + name: 'AllowSshRdpOutbound' + properties: { + priority: 100 + direction: 'Outbound' + access: 'Allow' + protocol: '*' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: 'VirtualNetwork' + destinationPortRanges: [ + '22' + '3389' + ] + } + } + { + name: 'AllowAzureCloudOutbound' + properties: { + priority: 110 + direction: 'Outbound' + access: 'Allow' + protocol: 'Tcp' + sourceAddressPrefix: '*' + sourcePortRange: '*' + destinationAddressPrefix: 'AzureCloud' + destinationPortRange: '443' + } + } + ] + } +} + +// Outputs +output nsgId string = nsg.id diff --git a/azresources/network/nsg/nsg-databricks.bicep b/azresources/network/nsg/nsg-databricks.bicep new file mode 100644 index 00000000..657e5b70 --- /dev/null +++ b/azresources/network/nsg/nsg-databricks.bicep @@ -0,0 +1,204 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Network Security Group Name for Public Subnet.') +param namePublic string + +@description('Network Security Group Name for Private Subnet.') +param namePrivate string + +resource nsgPublic 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: namePublic + location: resourceGroup().location + properties: { + securityRules: [ + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-worker-inbound' + properties: { + description: 'Required for worker nodes communication within a cluster.' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 100 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-databricks-webapp' + properties: { + description: 'Required for workers communication with Databricks Webapp.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'AzureDatabricks' + access: 'Allow' + priority: 100 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-sql' + properties: { + description: 'Required for workers communication with Azure SQL services.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '3306' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'Sql' + access: 'Allow' + priority: 101 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-storage' + properties: { + description: 'Required for workers communication with Azure Storage services.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'Storage' + access: 'Allow' + priority: 102 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-worker-outbound' + properties: { + description: 'Required for worker nodes communication within a cluster.' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 103 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-eventhub' + properties: { + description: 'Required for worker communication with Azure Eventhub services.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '9093' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'EventHub' + access: 'Allow' + priority: 104 + direction: 'Outbound' + } + } + ] + } +} + +resource nsgPrivate 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: namePrivate + location: resourceGroup().location + properties: { + securityRules: [ + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-worker-inbound' + properties: { + description: 'Required for worker nodes communication within a cluster.' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 100 + direction: 'Inbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-databricks-webapp' + properties: { + description: 'Required for workers communication with Databricks Webapp.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'AzureDatabricks' + access: 'Allow' + priority: 100 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-sql' + properties: { + description: 'Required for workers communication with Azure SQL services.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '3306' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'Sql' + access: 'Allow' + priority: 101 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-storage' + properties: { + description: 'Required for workers communication with Azure Storage services.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '443' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'Storage' + access: 'Allow' + priority: 102 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-worker-outbound' + properties: { + description: 'Required for worker nodes communication within a cluster.' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'VirtualNetwork' + access: 'Allow' + priority: 103 + direction: 'Outbound' + } + } + { + name: 'Microsoft.Databricks-workspaces_UseOnly_databricks-worker-to-eventhub' + properties: { + description: 'Required for worker communication with Azure Eventhub services.' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '9093' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: 'EventHub' + access: 'Allow' + priority: 104 + direction: 'Outbound' + } + } + ] + } +} + +// Outputs +output publicNsgId string = nsgPublic.id +output privateNsgId string = nsgPrivate.id diff --git a/azresources/network/nsg/nsg-empty.bicep b/azresources/network/nsg/nsg-empty.bicep new file mode 100644 index 00000000..18c9a5e9 --- /dev/null +++ b/azresources/network/nsg/nsg-empty.bicep @@ -0,0 +1,22 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Network Security Group Name.') +param name string + +resource nsg 'Microsoft.Network/networkSecurityGroups@2020-11-01' = { + name: name + location: resourceGroup().location + properties: { + securityRules: [ ] + } +} + +// Outputs +output nsgId string = nsg.id diff --git a/azresources/network/nsg/nsg-sqlmi.bicep b/azresources/network/nsg/nsg-sqlmi.bicep new file mode 100644 index 00000000..bfa92b57 --- /dev/null +++ b/azresources/network/nsg/nsg-sqlmi.bicep @@ -0,0 +1,79 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Network Security Group Name.') +param name string + +resource nsg 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: name + location: resourceGroup().location + properties: { + securityRules: [ + { + name: 'allow_tds_inbound' + properties: { + description: 'Allow access to data' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '1433' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 1000 + direction: 'Inbound' + } + } + { + name: 'allow_redirect_inbound' + properties: { + description: 'Allow inbound redirect traffic to Managed Instance inside the virtual network' + protocol: 'Tcp' + sourcePortRange: '*' + destinationPortRange: '11000-11999' + sourceAddressPrefix: 'VirtualNetwork' + destinationAddressPrefix: '*' + access: 'Allow' + priority: 1100 + direction: 'Inbound' + } + } + { + name: 'deny_all_inbound' + properties: { + description: 'Deny all other inbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + access: 'Deny' + priority: 4096 + direction: 'Inbound' + } + } + { + name: 'deny_all_outbound' + properties: { + description: 'Deny all other outbound traffic' + protocol: '*' + sourcePortRange: '*' + destinationPortRange: '*' + sourceAddressPrefix: '*' + destinationAddressPrefix: '*' + access: 'Deny' + priority: 4096 + direction: 'Outbound' + } + } + ] + } +} + +// Outputs +output nsgId string = nsg.id diff --git a/azresources/network/private-dns-zone-privatelinks.bicep b/azresources/network/private-dns-zone-privatelinks.bicep new file mode 100644 index 00000000..150937c0 --- /dev/null +++ b/azresources/network/private-dns-zone-privatelinks.bicep @@ -0,0 +1,89 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Virtual Network Resource Id') +param vnetId string + +@description('Boolean flag to determine whether to create new Private DNS Zones or to reference existing ones.') +param dnsCreateNewZone bool + +@description('Boolean flag to determine whether to link the DNS zone to the virtual network.') +param dnsLinkToVirtualNetwork bool + +@description('Private DNS Zones Subscription Id. Required when dnsCreateNewZone=false') +param dnsExistingZoneSubscriptionId string + +@description('Private DNS Zones Resource Group. Required when dnsCreateNewZone=false') +param dnsExistingZoneResourceGroupName string + +@description('Array of Private DNS Zones to provision.') +param privateDnsZones array = [ + 'privatelink.azure-automation.net' + 'privatelink${environment().suffixes.sqlServerHostname}' + 'privatelink.sql.azuresynapse.net' + 'privatelink.dev.azuresynapse.net' + 'privatelink.azuresynapse.net' + 'privatelink.blob.${environment().suffixes.storage}' + 'privatelink.table.${environment().suffixes.storage}' + 'privatelink.queue.${environment().suffixes.storage}' + 'privatelink.file.${environment().suffixes.storage}' + 'privatelink.web.${environment().suffixes.storage}' + 'privatelink.dfs.${environment().suffixes.storage}' + 'privatelink.documents.azure.com' + 'privatelink.mongo.cosmos.azure.com' + 'privatelink.cassandra.cosmos.azure.com' + 'privatelink.gremlin.cosmos.azure.com' + 'privatelink.table.cosmos.azure.com' + 'privatelink.postgres.database.azure.com' + 'privatelink.mysql.database.azure.com' + 'privatelink.mariadb.database.azure.com' + 'privatelink.vaultcore.azure.net' + 'privatelink.canadacentral.azmk8s.io' + 'privatelink.canadaeast.azmk8s.io' + 'privatelink.search.windows.net' + 'privatelink.azurecr.io' + 'privatelink.azconfig.io' + 'privatelink.cnc.backup.windowsazure.com' + 'privatelink.cne.backup.windowsazure.com' + 'privatelink.siterecovery.windowsazure.com' + 'privatelink.servicebus.windows.net' + 'privatelink.azure-devices.net' + 'privatelink.eventgrid.azure.net' + 'privatelink.azurewebsites.net' + 'privatelink.api.azureml.ms' + 'privatelink.notebooks.azure.net' + 'privatelink.service.signalr.net' + 'privatelink.monitor.azure.com' + 'privatelink.oms.opinsights.azure.com' + 'privatelink.ods.opinsights.azure.com' + 'privatelink.agentsvc.azure-automation.net' + 'privatelink.cognitiveservices.azure.com' + 'privatelink.afs.azure.net' + 'privatelink.datafactory.azure.net' + 'privatelink.redis.cache.windows.net' + 'privatelink.redisenterprise.cache.azure.net' + 'privatelink.purview.azure.com' + 'privatelink.azurehealthcareapis.com' +] + +module dnsZone 'private-dns-zone.bicep' = [for zone in privateDnsZones: { + name: replace(zone, '.', '_') + scope: resourceGroup() + params: { + zone: zone + vnetId: vnetId + + registrationEnabled: false + + dnsCreateNewZone: dnsCreateNewZone + dnsLinkToVirtualNetwork: dnsLinkToVirtualNetwork + dnsExistingZoneSubscriptionId: dnsExistingZoneSubscriptionId + dnsExistingZoneResourceGroupName: dnsExistingZoneResourceGroupName + } +}] diff --git a/azresources/network/private-dns-zone-virtual-network-link.bicep b/azresources/network/private-dns-zone-virtual-network-link.bicep new file mode 100644 index 00000000..92d8f2a5 --- /dev/null +++ b/azresources/network/private-dns-zone-virtual-network-link.bicep @@ -0,0 +1,31 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Private DNS Zone Virtual Network Link Name.') +param name string + +@description('Private DNS Zone Name.') +param zone string + +@description('Virtual Network Resource Id.') +param vnetId string + +@description('Boolean flag to enable automatic DNS registration for VMs.') +param registrationEnabled bool + +resource vnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = { + name: '${zone}/${name}' + location: 'global' + properties: { + registrationEnabled: registrationEnabled + virtualNetwork: { + id: vnetId + } + } +} diff --git a/azresources/network/private-dns-zone.bicep b/azresources/network/private-dns-zone.bicep new file mode 100644 index 00000000..31ade2d5 --- /dev/null +++ b/azresources/network/private-dns-zone.bicep @@ -0,0 +1,65 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Private DNS Zone Name.') +param zone string + +@description('Virtual Network Resource Id.') +param vnetId string + +@description('Boolean flag to enable automatic DNS registration for VMs.') +param registrationEnabled bool + +@description('Boolean flag to determine whether to create new Private DNS Zones or to reference existing ones.') +param dnsCreateNewZone bool + +@description('Boolean flag to determine whether to link the DNS zone to the virtual network.') +param dnsLinkToVirtualNetwork bool + +@description('Private DNS Zones Subscription Id. Required when dnsCreateNewZone=false') +param dnsExistingZoneSubscriptionId string + +@description('Private DNS Zones Resource Group. Required when dnsCreateNewZone=false') +param dnsExistingZoneResourceGroupName string + +// When DNS Zone is managed in the Spoke +resource privateDnsZoneNew 'Microsoft.Network/privateDnsZones@2018-09-01' = if (dnsCreateNewZone) { + name: zone + location: 'global' +} + +module privateDnsZoneVirtualNetworkLinkNew 'private-dns-zone-virtual-network-link.bicep' = if (dnsCreateNewZone && dnsLinkToVirtualNetwork) { + name: 'configure-vnetlink-use-new-${uniqueString(zone, vnetId)}' + params: { + name: uniqueString(vnetId) + vnetId: vnetId + zone: privateDnsZoneNew.name + registrationEnabled: registrationEnabled + } +} + +// When DNS Zone is managed in the Hub +resource privateDnsZoneExisting 'Microsoft.Network/privateDnsZones@2018-09-01' existing = if (!dnsCreateNewZone) { + scope: resourceGroup(dnsExistingZoneSubscriptionId, dnsExistingZoneResourceGroupName) + name: zone +} + +module privateDnsZoneVirtualNetworkLinkExisting 'private-dns-zone-virtual-network-link.bicep' = if (!dnsCreateNewZone && dnsLinkToVirtualNetwork) { + name: 'configure-vnetlink-use-existing-${uniqueString(zone, vnetId)}' + scope: resourceGroup(dnsExistingZoneSubscriptionId, dnsExistingZoneResourceGroupName) + params: { + name: uniqueString(vnetId) + vnetId: vnetId + zone: privateDnsZoneExisting.name + registrationEnabled: registrationEnabled + } +} + +// Outputs +output privateDnsZoneId string = dnsCreateNewZone ? privateDnsZoneNew.id : privateDnsZoneExisting.id diff --git a/azresources/network/udr/udr-custom.bicep b/azresources/network/udr/udr-custom.bicep new file mode 100644 index 00000000..042d3c54 --- /dev/null +++ b/azresources/network/udr/udr-custom.bicep @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('User Defined Route Name.') +param name string + +/* +Example for routes array: + + { + name: 'table1' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: '10.0.0.4' + } + } + { + name: 'table2' + properties: { + addressPrefix: '10.1.2.0/24' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: '10.0.0.5' + } + } +*/ +@description('Array of routes') +param routes array = [] + +resource udr 'Microsoft.Network/routeTables@2020-11-01' = { + name: name + location: resourceGroup().location + properties: { + disableBgpRoutePropagation: false + routes: routes + } +} + +// Outputs +output udrName string = udr.name +output udrId string = udr.id diff --git a/azresources/network/udr/udr-databricks-private.bicep b/azresources/network/udr/udr-databricks-private.bicep new file mode 100644 index 00000000..76e368f7 --- /dev/null +++ b/azresources/network/udr/udr-databricks-private.bicep @@ -0,0 +1,22 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('User Defined Route Name.') +param name string + +resource udr 'Microsoft.Network/routeTables@2020-06-01' = { + location: resourceGroup().location + name: name + properties: { + disableBgpRoutePropagation: false + } +} + +// Outputs +output udrId string = udr.id diff --git a/azresources/network/udr/udr-databricks-public.bicep b/azresources/network/udr/udr-databricks-public.bicep new file mode 100644 index 00000000..76e368f7 --- /dev/null +++ b/azresources/network/udr/udr-databricks-public.bicep @@ -0,0 +1,22 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('User Defined Route Name.') +param name string + +resource udr 'Microsoft.Network/routeTables@2020-06-01' = { + location: resourceGroup().location + name: name + properties: { + disableBgpRoutePropagation: false + } +} + +// Outputs +output udrId string = udr.id diff --git a/azresources/network/udr/udr-sqlmi.bicep b/azresources/network/udr/udr-sqlmi.bicep new file mode 100644 index 00000000..76e368f7 --- /dev/null +++ b/azresources/network/udr/udr-sqlmi.bicep @@ -0,0 +1,22 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('User Defined Route Name.') +param name string + +resource udr 'Microsoft.Network/routeTables@2020-06-01' = { + location: resourceGroup().location + name: name + properties: { + disableBgpRoutePropagation: false + } +} + +// Outputs +output udrId string = udr.id diff --git a/azresources/network/vnet-peering.bicep b/azresources/network/vnet-peering.bicep new file mode 100644 index 00000000..4df38555 --- /dev/null +++ b/azresources/network/vnet-peering.bicep @@ -0,0 +1,38 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Source Virtual Network Name.') +param sourceVnetName string + +@description('Target Virtual Network Resource Id.') +param targetVnetId string + +@description('Virtual Network Peering Name.') +param peeringName string + +@description('Boolean flag to determine whether remote gateways are used. Default: false') +param useRemoteGateways bool = false + +@description('Boolean flag to determine virtual network access through the peer. Default: true') +param allowVirtualNetworkAccess bool = true + +@description('Boolean flag to determine traffic forwarding. Default: true') +param allowForwardedTraffic bool = true + +resource vnetPeering 'Microsoft.Network/virtualNetworks/virtualNetworkPeerings@2020-06-01' = { + name: '${sourceVnetName}/${peeringName}' + properties: { + useRemoteGateways: useRemoteGateways + allowVirtualNetworkAccess: allowVirtualNetworkAccess + allowForwardedTraffic: allowForwardedTraffic + remoteVirtualNetwork: { + id: targetVnetId + } + } +} diff --git a/azresources/security-center/asc.bicep b/azresources/security-center/asc.bicep new file mode 100644 index 00000000..a31c1172 --- /dev/null +++ b/azresources/security-center/asc.bicep @@ -0,0 +1,69 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +@description('Log Analytics Workspace Resource Id.') +param logAnalyticsWorkspaceResourceId string + +@description('Security Contact Email Address.') +param securityContactEmail string + +@description('Security Contact Phone.') +param securityContactPhone string + +// Enable Security Contacts +resource ascSecurityContacts 'Microsoft.Security/securityContacts@2017-08-01-preview' = { + name: 'default1' + properties: { + email: securityContactEmail + phone: securityContactPhone + alertNotifications: 'On' + alertsToAdmins: 'On' + } +} + +// Enable Log Analytics Workspace +resource ascWorkspaceSettings 'Microsoft.Security/workspaceSettings@2017-08-01-preview' = { + name: 'default' + properties: { + scope: subscription().id + workspaceId: logAnalyticsWorkspaceResourceId + } +} + +resource ascAutoProvision 'Microsoft.Security/autoProvisioningSettings@2017-08-01-preview' = { + name: 'default' + properties: { + autoProvision: 'On' + } +} + +// Enable Azure Defender +var azureDefenderServices = [ + 'Arm' + 'AppServices' + 'ContainerRegistry' + 'Dns' + 'KeyVaults' + 'KubernetesService' + 'OpenSourceRelationalDatabases' + 'SqlServers' + 'SqlServerVirtualMachines' + 'StorageAccounts' + 'VirtualMachines' +] + +resource ascDefender 'Microsoft.Security/pricings@2018-06-01' = [for service in azureDefenderServices: { + name: service + properties: { + pricingTier: 'Standard' + } +}] + diff --git a/azresources/security/key-vault-key-rsa2048.bicep b/azresources/security/key-vault-key-rsa2048.bicep new file mode 100644 index 00000000..26b02986 --- /dev/null +++ b/azresources/security/key-vault-key-rsa2048.bicep @@ -0,0 +1,32 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Key Vault Name.') +param akvName string + +@description('RSA Key Name.') +param keyName string + +resource akvKey 'Microsoft.KeyVault/vaults/keys@2020-04-01-preview' = { + name: '${akvName}/${keyName}' + properties: { + kty: 'RSA' + keySize: 2048 + attributes: { + enabled: true + } + } +} + +// Outputs +output keyName string = keyName +output keyId string = akvKey.id +output keyVersion string = last(split(akvKey.properties.keyUriWithVersion, '/')) +output keyUri string = akvKey.properties.keyUri +output keyUriWithVersion string = akvKey.properties.keyUriWithVersion diff --git a/azresources/security/key-vault-secret.bicep b/azresources/security/key-vault-secret.bicep new file mode 100644 index 00000000..f915f907 --- /dev/null +++ b/azresources/security/key-vault-secret.bicep @@ -0,0 +1,43 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Key Vault Name.') +param akvName string + +@description('Secret Name.') +param secretName string + +@description('Secret Value') +@secure() +param secretValue string + +@description('Secret Expiry in days.') +param secretExpiryInDays int + +@description('Expiry Year.') +param yearNow int = int(trim(utcNow(' yyyy '))) + +@description('Expiry Month.') +param monthNow int = int(trim(utcNow(' M '))) + +@description('Expiry Day.') +param dayNow int = int(trim(utcNow(' d '))) + +var expSeconds = (yearNow - 1970) * 31536000 + monthNow * 2628000 + dayNow * 86400 + secretExpiryInDays * 86400 + +resource akvSecret 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = { + name: '${akvName}/${secretName}' + properties: { + attributes: { + enabled: true + exp: expSeconds + } + value: secretValue + } +} diff --git a/azresources/security/key-vault.bicep b/azresources/security/key-vault.bicep new file mode 100644 index 00000000..d9c5dd38 --- /dev/null +++ b/azresources/security/key-vault.bicep @@ -0,0 +1,99 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Azure Key Vault Name.') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Boolean flag to enable Azure Key Vault for Deployment. Default: false') +param enabledForDeployment bool = false + +@description('Boolean flag to enable Azure Key Vault for Disk Encryption. Default: false') +param enabledForDiskEncryption bool = false + +@description('Boolean flag to enable Azure Key Vault for Template Deployment. Default: false') +param enabledForTemplateDeployment bool = false + +@description('Soft Delete Retention in Days. Default: 90') +@minValue(7) +param softDeleteRetentionInDays int = 90 + +@description('Private Endpoint Subnet Resource Id.') +param privateEndpointSubnetId string = '' + +@description('Private DNS Zone Resource Id.') +param privateZoneId string = '' + +resource akv 'Microsoft.KeyVault/vaults@2019-09-01' = { + location: resourceGroup().location + name: name + tags: tags + properties: { + sku: { + name: 'standard' + family: 'A' + } + tenantId: subscription().tenantId + + enableSoftDelete: true + enablePurgeProtection: true + softDeleteRetentionInDays: softDeleteRetentionInDays + + enabledForDeployment: enabledForDeployment + enabledForDiskEncryption: enabledForDiskEncryption + enabledForTemplateDeployment: enabledForTemplateDeployment + + networkAcls: { + bypass: 'AzureServices' + defaultAction: !(empty(privateZoneId)) ? 'Deny' : 'Allow' + } + enableRbacAuthorization: true + } +} + +resource akv_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = if (!(empty(privateZoneId))) { + location: resourceGroup().location + name: '${akv.name}-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${akv.name}-endpoint' + properties: { + privateLinkServiceId: akv.id + groupIds: [ + 'vault' + ] + } + } + ] + } + + resource akv_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink_vaultcore_azure_net' + properties: { + privateDnsZoneId: privateZoneId + } + } + ] + } + } +} + +// Outputs +output akvName string = akv.name +output akvId string = akv.id diff --git a/azresources/service-health/service-health-caller-params.json b/azresources/service-health/service-health-caller-params.json new file mode 100644 index 00000000..b8b40f9f --- /dev/null +++ b/azresources/service-health/service-health-caller-params.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + + "parameters": { + "serviceHealthAlerts": { + "value": { + "incidentTypes": [ "Incident", "Security", "Maintenance", "Information", "ActionRequired" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "email-1@company.com", "email-2@company.com" ], + "email": [ "email-1@company.com", "email-3@company.com", "email-4@company.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "1234567890" }, { "countryCode": "1", "phoneNumber": "0987654321" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "1234567890" } ] + } + } + } + + } + } \ No newline at end of file diff --git a/azresources/service-health/service-health-params.json b/azresources/service-health/service-health-params.json new file mode 100644 index 00000000..0cfe17a8 --- /dev/null +++ b/azresources/service-health/service-health-params.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "incidentTypes": { + "value": [ "Incident", "Security", "Maintenance", "Information", "ActionRequired" ] + }, + "regions": { + "value": [ "Global", "Canada East", "Canada Central" ] + }, + "receivers": { + "value": { + "app": [ + "email-1@company.com", + "email-2@company.com" + ], + "email": [ + "email-1@company.com", + // "email-2@company.com", + "email-3@company.com", + "email-4@company.com" + ], + "sms": [ + { + "countryCode": "1", + "phoneNumber": "1234567890" + }, + { + "countryCode": "1", + "phoneNumber": "0987654321" + } + ], + "voice": [ + { + "countryCode": "1", + "phoneNumber": "1234567890" + } + ] + } + } + } + } \ No newline at end of file diff --git a/azresources/service-health/service-health.bicep b/azresources/service-health/service-health.bicep new file mode 100644 index 00000000..b7467cec --- /dev/null +++ b/azresources/service-health/service-health.bicep @@ -0,0 +1,140 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// PARAMETERS + +@description('An array of zero or more Incident Type elements') +@allowed([ + 'Maintenance' + 'Informational' + 'ActionRequired' + 'Incident' + 'Security' +]) +param incidentTypes array = [ + 'Incident' + 'Security' +] + +@description('An array of zero or more Region Names') +param regions array = [ + 'Global' + 'Canada East' + 'Canada Central' +] + +@description('An object containing arrays for \'app\', \'email\', \'sms\', and \'voice\' receivers. \'app\' and \'email\' are arrays of string values representing email addresses. \'sms\' and \'voice\' are arrays of objects with JSON structure: { "countryCode": "", "phoneNumber": "" }.') +param receivers object = { + app: [] + email: [] + sms: [] + voice: [] +} + +@description('Action group name.') +param actionGroupName string = 'ALZ action group' + +@description('Action group short name. Maximum 12 character length.') +@maxLength(12) +param actionGroupShortName string = substring(actionGroupName, 0, max(length(actionGroupName),12)) + +@description('Alert rule name.') +param alertRuleName string = 'ALZ alert rule' + +@description('Alert rule description.') +param alertRuleDescription string = 'Alert rule for Azure Landing Zone' + + +// VARIABLES + +var incidentTypesProperty = [for incidentType in incidentTypes: { + field: 'properties.incidentType' + equals: incidentType +}] + +var regionsProperty = regions + +var appReceiversProperty = [for app in receivers.app: { + name: 'app-receivers-${uniqueString(resourceGroup().id, app)}' + emailAddress: app +}] + +var emailReceiversProperty = [for email in receivers.email: { + name: 'email-receivers-${uniqueString(resourceGroup().id, email)}' + emailAddress: email + useCommonAlertSchema: true +}] + +var smsReceiversProperty = [for sms in receivers.sms: { + name: 'sms-receivers-${uniqueString(resourceGroup().id, concat(sms.countryCode, sms.phoneNumber))}' + countryCode: sms.countryCode + phoneNumber: sms.phoneNumber +}] + +var voiceReceiversProperty = [for voice in receivers.voice: { + name: 'voice-receivers-${uniqueString(resourceGroup().id, concat(voice.countryCode, voice.phoneNumber))}' + countryCode: voice.countryCode + phoneNumber: voice.phoneNumber +}] + +// RESOURCES + +resource actionGroup 'microsoft.insights/actionGroups@2019-06-01' = { + name: actionGroupName + location: 'Global' + properties: { + groupShortName: actionGroupShortName + enabled: true + emailReceivers: emailReceiversProperty + smsReceivers: smsReceiversProperty + webhookReceivers: [] + itsmReceivers: [] + azureAppPushReceivers: appReceiversProperty + automationRunbookReceivers: [] + voiceReceivers: voiceReceiversProperty + logicAppReceivers: [] + azureFunctionReceivers: [] + armRoleReceivers: [] + } +} + +resource alertRule 'microsoft.insights/activityLogAlerts@2020-10-01' = { + name: alertRuleName + location: 'Global' + properties: { + description: alertRuleDescription + enabled: true + scopes: [ + subscription().id + ] + condition: { + allOf: [ + { + field: 'category' + equals: 'ServiceHealth' + } + { + anyOf: incidentTypesProperty + } + { + field: 'properties.impactedServices[*].ImpactedRegions[*].RegionName' + containsAny: regionsProperty + } + ] + } + actions: { + actionGroups: [ + { + actionGroupId: actionGroup.id + webhookProperties: {} + } + ] + } + } +} diff --git a/azresources/storage/storage-adlsgen2-fs.bicep b/azresources/storage/storage-adlsgen2-fs.bicep new file mode 100644 index 00000000..e49f3d4e --- /dev/null +++ b/azresources/storage/storage-adlsgen2-fs.bicep @@ -0,0 +1,18 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('ADLS Gen2 Storage Account Name') +param adlsName string + +@description('Filesystem name') +param fsName string + +resource fs 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-02-01' = { + name: '${adlsName}/default/${fsName}' +} diff --git a/azresources/storage/storage-adlsgen2.bicep b/azresources/storage/storage-adlsgen2.bicep new file mode 100644 index 00000000..7de5760f --- /dev/null +++ b/azresources/storage/storage-adlsgen2.bicep @@ -0,0 +1,196 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('ADLS Gen2 Storage Account Name') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Private Endpoint Subnet Id') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id for blob.') +param blobPrivateZoneId string + +@description('Private DNS Zone Resource Id for dfs.') +param dfsPrivateZoneId string + +@description('Default Network Acls. Default: deny') +param defaultNetworkAcls string = 'deny' + +@description('Bypass Network Acls. Default: AzureServices,Logging,Metrics') +param bypassNetworkAcls string = 'AzureServices,Logging,Metrics' + +@description('Array of Subnet Resource Ids for Virtual Network Access') +param subnetIdForVnetAccess array = [] + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// Deployment Script Identity +@description('Deployment Script Identity Resource Id. This identity is used to execute Azure CLI as part of the deployment.') +param deploymentScriptIdentityId string + +/* Storage Account */ +resource storage 'Microsoft.Storage/storageAccounts@2019-06-01' = { + tags: tags + location: resourceGroup().location + name: name + identity: { + type: 'SystemAssigned' + } + kind: 'StorageV2' + sku: { + name: 'Standard_GRS' + } + properties: { + accessTier: 'Hot' + isHnsEnabled: true + minimumTlsVersion: 'TLS1_2' + supportsHttpsTrafficOnly: true + allowBlobPublicAccess: false + encryption: { + requireInfrastructureEncryption: true + keySource: 'Microsoft.Storage' + services: { + blob: { + enabled: true + keyType: 'Account' + } + file: { + enabled: true + keyType: 'Account' + } + queue: { + enabled: true + keyType: 'Account' + } + table: { + enabled: true + keyType: 'Account' + } + } + } + networkAcls: { + defaultAction: defaultNetworkAcls + bypass: bypassNetworkAcls + virtualNetworkRules: [for subnetId in subnetIdForVnetAccess: { + id: subnetId + action: 'Allow' + }] + } + } +} + +resource threatProtection 'Microsoft.Security/advancedThreatProtectionSettings@2019-01-01' = { + name: 'current' + scope: storage + properties: { + isEnabled: true + } +} + +/* Customer Managed Keys - configured after the storage account is created with managed key */ +module enableCMK 'storage-enable-cmk.bicep' = if (useCMK) { + name: 'deploy-cmk-${name}' + params: { + storageAccountName: storage.name + storageResourceGroupName: resourceGroup().name + + keyVaultResourceGroupName: akvResourceGroupName + keyVaultName: akvName + + deploymentScriptIdentityId: deploymentScriptIdentityId + } +} + +/* Private Endpoints */ +resource datalake_blob_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = if (!empty(blobPrivateZoneId)) { + location: resourceGroup().location + name: '${storage.name}-blob-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${storage.name}-blob-endpoint' + properties: { + privateLinkServiceId: storage.id + groupIds: [ + 'blob' + ] + } + } + ] + } + + resource datalake_blob_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink_blob_core_windows_net' + properties: { + privateDnsZoneId: blobPrivateZoneId + } + } + ] + } + } +} + +resource datalake_dfs_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = if (!empty(dfsPrivateZoneId)) { + location: resourceGroup().location + name: '${storage.name}-dfs-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${storage.name}-dfs-endpoint' + properties: { + privateLinkServiceId: storage.id + groupIds: [ + 'dfs' + ] + } + } + ] + } + + resource datalake_dfs_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink_dfs_core_windows_net' + properties: { + privateDnsZoneId: dfsPrivateZoneId + } + } + ] + } + } +} + +// Outputs +output storageName string = storage.name +output storageId string = storage.id +output primaryDfsEndpoint string = storage.properties.primaryEndpoints.dfs diff --git a/azresources/storage/storage-enable-cmk.bicep b/azresources/storage/storage-enable-cmk.bicep new file mode 100644 index 00000000..e91d6ad9 --- /dev/null +++ b/azresources/storage/storage-enable-cmk.bicep @@ -0,0 +1,74 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Storage Account Resource Group Name.') +param storageResourceGroupName string + +@description('Storage Account Name.') +param storageAccountName string + +@description('Azure Key Vault Resource Group Name.') +param keyVaultResourceGroupName string + +@description('Azure Key Vault Name.') +param keyVaultName string + +@description('Deployment Script Identity Resource Id. This identity is used to execute Azure CLI as part of the deployment.') +param deploymentScriptIdentityId string + +resource storage 'Microsoft.Storage/storageAccounts@2021-04-01' existing = { + scope: resourceGroup(storageResourceGroupName) + name: storageAccountName +} + +resource akv 'Microsoft.KeyVault/vaults@2021-04-01-preview' existing = { + scope: resourceGroup(keyVaultResourceGroupName) + name: keyVaultName +} + +module roleAssignForAKV '../iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-${storage.name}-key-vault' + scope: resourceGroup(keyVaultResourceGroupName) + params: { + keyVaultName: keyVaultName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'e147488a-f6f5-4113-8e2d-b22465e65bf6') // Key Vault Crypto Service Encryption User + resourceSPObjectIds: array(storage.identity.principalId) + } +} + +module akvKey '../security/key-vault-key-rsa2048.bicep' = { + scope: resourceGroup(keyVaultResourceGroupName) + name: 'add-cmk-storage-${storage.name}' + params: { + akvName: keyVaultName + keyName: 'cmk-storage-${storage.name}' + } +} + +var cliCommand = ''' + az storage account update \ + --resource-group {0} \ + --name {1} \ + --encryption-key-vault {2} \ + --encryption-key-name {3} \ + --encryption-key-source Microsoft.Keyvault +''' + +module enableCmk '../util/deployment-script.bicep' = { + dependsOn: [ + roleAssignForAKV + ] + + name: 'enable-cmk-${storage.name}' + params: { + deploymentScript: format(cliCommand, resourceGroup().name, storage.name, akv.properties.vaultUri, akvKey.outputs.keyName) + deploymentScriptName: 'enable-cmk-${storage.name}-ds' + deploymentScriptIdentityId: deploymentScriptIdentityId + } +} diff --git a/azresources/storage/storage-generalpurpose.bicep b/azresources/storage/storage-generalpurpose.bicep new file mode 100644 index 00000000..11c91f2b --- /dev/null +++ b/azresources/storage/storage-generalpurpose.bicep @@ -0,0 +1,196 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('General Purpose Storage Account Name') +param name string + +@description('Key/Value pair of tags.') +param tags object = {} + +@description('Private Endpoint Subnet Id') +param privateEndpointSubnetId string + +@description('Private DNS Zone Resource Id for blob.') +param blobPrivateZoneId string + +@description('Private DNS Zone Resource Id for file.') +param filePrivateZoneId string + +@description('Default Network Acls. Default: deny') +param defaultNetworkAcls string = 'deny' + +@description('Bypass Network Acls. Default: AzureServices,Logging,Metrics') +param bypassNetworkAcls string = 'AzureServices,Logging,Metrics' + +@description('Array of Subnet Resource Ids for Virtual Network Access') +param subnetIdForVnetAccess array = [] + +// Customer Managed Key +@description('Boolean flag that determines whether to enable Customer Managed Key.') +param useCMK bool + +// Azure Key Vault +@description('Azure Key Vault Resource Group Name. Required when useCMK=true.') +param akvResourceGroupName string + +@description('Azure Key Vault Name. Required when useCMK=true.') +param akvName string + +// Deployment Script Identity +@description('Deployment Script Identity Resource Id. This identity is used to execute Azure CLI as part of the deployment.') +param deploymentScriptIdentityId string + +/* Storage Account */ +resource storage 'Microsoft.Storage/storageAccounts@2019-06-01' = { + tags: tags + location: resourceGroup().location + name: name + identity: { + type: 'SystemAssigned' + } + kind: 'StorageV2' + sku: { + name: 'Standard_GRS' + } + properties: { + accessTier: 'Hot' + isHnsEnabled: false + minimumTlsVersion: 'TLS1_2' + supportsHttpsTrafficOnly: true + allowBlobPublicAccess: false + encryption: { + requireInfrastructureEncryption: true + keySource: 'Microsoft.Storage' + services: { + blob: { + enabled: true + keyType: 'Account' + } + file: { + enabled: true + keyType: 'Account' + } + queue: { + enabled: true + keyType: 'Account' + } + table: { + enabled: true + keyType: 'Account' + } + } + } + networkAcls: { + defaultAction: defaultNetworkAcls + bypass: bypassNetworkAcls + virtualNetworkRules: [for subnetId in subnetIdForVnetAccess: { + id: subnetId + action: 'Allow' + }] + } + } +} + +resource threatProtection 'Microsoft.Security/advancedThreatProtectionSettings@2019-01-01' = { + name: 'current' + scope: storage + properties: { + isEnabled: true + } +} + +/* Customer Managed Keys - configured after the storage account is created with managed key */ +module enableCMK 'storage-enable-cmk.bicep' = if (useCMK) { + name: 'deploy-cmk-${name}' + params: { + storageAccountName: storage.name + storageResourceGroupName: resourceGroup().name + + keyVaultName: akvName + keyVaultResourceGroupName: akvResourceGroupName + + deploymentScriptIdentityId: deploymentScriptIdentityId + } +} + +/* Private Endpoints */ +resource storage_blob_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = if (!empty(blobPrivateZoneId)) { + location: resourceGroup().location + name: '${storage.name}-blob-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${storage.name}-blob-endpoint' + properties: { + privateLinkServiceId: storage.id + groupIds: [ + 'blob' + ] + } + } + ] + } + + resource storage_blob_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink_blob_core_windows_net' + properties: { + privateDnsZoneId: blobPrivateZoneId + } + } + ] + } + } +} + +resource storage_file_pe 'Microsoft.Network/privateEndpoints@2020-06-01' = if (!empty(filePrivateZoneId)) { + location: resourceGroup().location + name: '${storage.name}-file-endpoint' + properties: { + subnet: { + id: privateEndpointSubnetId + } + privateLinkServiceConnections: [ + { + name: '${storage.name}-file-endpoint' + properties: { + privateLinkServiceId: storage.id + groupIds: [ + 'file' + ] + } + } + ] + } + + resource storage_file_pe_dns_reg 'privateDnsZoneGroups@2020-06-01' = { + name: 'default' + properties: { + privateDnsZoneConfigs: [ + { + name: 'privatelink_file_core_windows_net' + properties: { + privateDnsZoneId: filePrivateZoneId + } + } + ] + } + } +} + +// Outputs +output storageName string = storage.name +output storageId string = storage.id +output storagePath string = storage.properties.primaryEndpoints.blob diff --git a/azresources/util/delete-lock.bicep b/azresources/util/delete-lock.bicep new file mode 100644 index 00000000..da433967 --- /dev/null +++ b/azresources/util/delete-lock.bicep @@ -0,0 +1,15 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +resource lock 'Microsoft.Authorization/locks@2016-09-01' = { + name: 'DeleteLock' + properties: { + level: 'CanNotDelete' + } +} diff --git a/azresources/util/deployment-script.bicep b/azresources/util/deployment-script.bicep new file mode 100644 index 00000000..b1577069 --- /dev/null +++ b/azresources/util/deployment-script.bicep @@ -0,0 +1,49 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Deployment Script Name.') +param deploymentScriptName string + +@description('Deployment Script') +param deploymentScript string + +@description('Identity for the deployment script to execute in Azure Container Instance.') +param deploymentScriptIdentityId string + +@description('Azure CLI Version. Default: 2.26.0') +param azCliVersion string = '2.26.0' + +@description('Force Update Tag. Default: utcNow()') +param forceUpdateTag string = utcNow() + +@description('Script timeout in ISO 8601 format. Default is 1 hour.') +param timeout string = 'PT1H' + +@description('Script retention in ISO 8601 format. Default is 1 hour.') +param retentionInterval string = 'PT1H' + +resource ds 'Microsoft.Resources/deploymentScripts@2020-10-01' = { + name: deploymentScriptName + location: resourceGroup().location + kind: 'AzureCLI' + identity: { + type: 'UserAssigned' + userAssignedIdentities: { + '${deploymentScriptIdentityId}': {} + } + } + properties: { + forceUpdateTag: forceUpdateTag + azCliVersion: azCliVersion + retentionInterval: retentionInterval + timeout: timeout + cleanupPreference: 'OnExpiration' + scriptContent: deploymentScript + } +} diff --git a/azresources/util/wait-on-arm-subscription.bicep b/azresources/util/wait-on-arm-subscription.bicep new file mode 100644 index 00000000..4ebd1f06 --- /dev/null +++ b/azresources/util/wait-on-arm-subscription.bicep @@ -0,0 +1,15 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +@description('Dummy input to simulate waiting.') +param input string + +output output string = input diff --git a/azresources/util/wait-on-arm.bicep b/azresources/util/wait-on-arm.bicep new file mode 100644 index 00000000..f6535c69 --- /dev/null +++ b/azresources/util/wait-on-arm.bicep @@ -0,0 +1,13 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Dummy input to simulate waiting.') +param input string + +output output string = input diff --git a/azresources/util/wait-subscription.bicep b/azresources/util/wait-subscription.bicep new file mode 100644 index 00000000..11abc809 --- /dev/null +++ b/azresources/util/wait-subscription.bicep @@ -0,0 +1,28 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +@description('Loop Counter.') +@minValue(1) +param loopCounter int + +@description('Prefix used for loop.') +@minLength(2) +@maxLength(50) +param waitNamePrefix string + +@batchSize(1) +module wait 'wait-on-arm-subscription.bicep' = [for i in range(1, loopCounter): { + scope: subscription() + name: '${waitNamePrefix}-${i}' + params: { + input: 'waitOnArm-${i}' + } +}] diff --git a/azresources/util/wait.bicep b/azresources/util/wait.bicep new file mode 100644 index 00000000..e64f4e63 --- /dev/null +++ b/azresources/util/wait.bicep @@ -0,0 +1,25 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Loop Counter.') +@minValue(1) +param loopCounter int + +@description('Prefix used for loop.') +@minLength(2) +@maxLength(50) +param waitNamePrefix string + +@batchSize(1) +module wait 'wait-on-arm.bicep' = [for i in range(1, loopCounter): { + name: '${waitNamePrefix}-${i}' + params: { + input: 'waitOnArm-${i}' + } +}] diff --git a/bicepconfig.json b/bicepconfig.json new file mode 100644 index 00000000..65bc07d9 --- /dev/null +++ b/bicepconfig.json @@ -0,0 +1,28 @@ +{ + "analyzers": { + "core": { + "verbose": false, + "enabled": true, + "rules": { + "no-unused-params": { + "level": "error" + }, + "no-unused-vars": { + "level": "error" + }, + "simplify-interpolation": { + "level": "error" + }, + "prefer-interpolation": { + "level": "error" + }, + "secure-parameter-default": { + "level": "error" + }, + "no-hardcoded-env-urls": { + "level": "error" + } + } + } + } +} \ No newline at end of file diff --git a/config/linters/.ansible-lint.yml b/config/linters/.ansible-lint.yml new file mode 100644 index 00000000..13395192 --- /dev/null +++ b/config/linters/.ansible-lint.yml @@ -0,0 +1,52 @@ +--- +########################## +########################## +## Ansible Linter rules ## +########################## +########################## + +############################# +# Exclude paths from linter # +############################# +#exclude_paths: + +######################## +# Make output parsable # +######################## +parseable: true + +####################### +# Set output to quiet # +####################### +quiet: true + +##################### +# Path to rules dir # +##################### +#rulesdir: + +################ +# Tags to skip # +################ +skip_list: + - 'empty-string-compare' # Allow compare to empty string + - '204' # Allow string length greater than 160 chars + - 'no-changed-when' # False positives for running command shells + - 'command-instead-of-module' # Allow git commands for push, add, etc... + - 'command-instead-of-shell' # Allow use of shell when you want + - 'no-handler' # Allow step to run like handler + +################## +# Tags to follow # +################## +#tags: + +############# +# Use rules # +############# +use_default_rules: true + +################# +# Set verbosity # +################# +verbosity: 1 diff --git a/config/linters/.cfnlintrc.yml b/config/linters/.cfnlintrc.yml new file mode 100644 index 00000000..16e8c976 --- /dev/null +++ b/config/linters/.cfnlintrc.yml @@ -0,0 +1,2 @@ +include_checks: + - I diff --git a/config/linters/.chktexrc b/config/linters/.chktexrc new file mode 100644 index 00000000..c2a96fb1 --- /dev/null +++ b/config/linters/.chktexrc @@ -0,0 +1,802 @@ +## +## ChkTeX, example resource file for ChkTeX. +## Copyright (C) 1995-96 Jens T. Berger Thielemann +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +## +## Contact the author at: +## Jens Berger +## Spektrumvn. 4 +## N-0666 Oslo +## Norway +## E-mail: +## + +##################################################################### +# +# Note: The format has changed slightly (again). The { ... } +# syntax does now mean case-sensitive comparing, while [ ... ] means +# case-insensitive comparing of the keywords. Case-insensitive +# comparing of the keywords is only supported on a few of the +# keywords (it's not meaningful in all contexts, and it slows ChkTeX +# down). Keywords supporting this are marked throughout the file. +# +# You may also reset a list by saying "KEYWORD = { ... }"; it will +# then be set equal to the contents of the list you specify. +# +# Comments begin with `#', and continues for the rest of the line. +# Blank lines plus leading and trailing spaces are of course ignored. +# +# The general format of this file is the following: +# +# KEYWORD { item item ...} [ item item ... ] /* Adds items */ +# +# KEYWORD [ item item ...] { item item ... } /* Adds items */ +# +# KEYWORD = item +# +# KEYWORD = { item item ... } /* Clears list before adding */ +# +# KEYWORD = [ item item ... ] /* Clears list before adding */ +# +# This does _not_ mean that you may alternate the forms; certain +# keywords demands a list, other a single value. You thus have to +# look at the examples of their use. +# +# Please also note that if you specify a list-keyword twice, we'll +# concatenate the lists. If you specify a item-keyword twice, we'll +# kill the previous value. +# +# We are slightly context-sensitive when detecting tokens like "}" +# and "]"; they have to be preceded by a space. This generally makes +# life easier. +# +# Items are separated by spaces. Newlines are considered as spaces, +# but can't be escaped. You may surround items with quotes (`"') to +# easily put spaces into them. +# +# Escape sequences available: +# +# Sequence Resulting character +# ! A space (type `! ', not just a exclamation mark) +# !" " +# !# # +# !! ! +# !{ { +# !} } +# ![ [ +# !] ] +# != = +# !b Backspace +# !n New line +# !r Carriage return +# !t Tab +# !f Form feed +# !xNN NN must be a hexadecimal number (00 - ff), +# _both_ characters must be included. +# !dNNN DDD must be a decimal number (000 - 255), all +# three characters must be included. Unspecified +# results if DDD > 255. +# !NNN DDD must be a octal number (000 - 377), all +# three characters must be included. Unspecified +# results if DDD > 377. +# +# Minor note: As you can see, most of these escape sequences are +# equal to those in C (with some extensions); however, we use ! +# instead of \ as escape character for obvious reasons. +# +# +# Quick summary of keywords follows. Keywords marked with a * accept +# keywords accepting case-insensitive lists. +# +# Abbrev* - A list of abbreviations not automatically caught. +# CenterDots - Commands/characters which should have \cdots in +# between. +# CmdLine - Default commandline options. These will be +# processed before the ones you give on the command +# line. +# HyphDash \ +# NumDash - Number of dashes allowed in different contexts. +# WordDash / +# IJAccent - Commands which puts an accent _over_ their +# argument. +# Italic - Commands immediately turning on italic mode. +# ItalCmd - Commands putting their argument into italic. +# Linker - Commands which should have a non-breaking space in +# front. +# LowDots - Commands/characters which should have \ldots in +# between. +# MathEnvir - Environments which turn on math mode. +# MathCmd - Commands which turn on math mode. +# TextCmd - Commands which turn off math mode. +# MathRoman - Mathematical operators with LaTeX replacement +# defined. +# NoCharNext - Insists on that certain commands aren't followed by +# certain characters. +# NonItalic - Commands immediately turning off italic mode. +# NotPreSpaced- Commands which should not have a space in front of +# them. +# Primitives - Primitive TeX commands. +# PostLink - Commands which generates a page reference. +# OutFormat - Formats to use for output. See the -f & -v switch +# in the main doc. +# QuoteStyle - Either "Traditional" or "Logical". See main doc, +# warning 38. +# Silent - These commands do not produce any textual output; +# and are thus allowed to have a space after them. +# TabSize - Tab size you are using. +# TeXInputs - Paths to search \input and \include files for. +# UserWarn* - These strings will be searched for throughout the +# text. +# VerbEnvir - Environments which contents should be ignored. +# VerbClear - String we will overwrite unwanted data with. +# WipeArg - Commands (with arguments) which should be ignored +# in the checking. +# + + +##################################################################### +# +# Enter which type of quote-style you are using here. Currently, we +# support the following styles: +# +# Style Example of use +# Traditional "An example," he said, "would be great." +# Logical "An example", he said, "would be great". +# + +QuoteStyle = Logical + +##################################################################### +# +# Enter here what interval you have between your tabs. Only regular +# intervals are supported. +# + +TabSize = 8 + +##################################################################### +# +# Here, you can put default commandline options; most users would for +# instance like to put -v2 here. +# + +CmdLine +{ + -v2 +} + +##################################################################### +# +# These patterns will be searched for through the text; no matter +# whether they appear as normal text, commands or whatever. +# Currently case-sensitive. They are not found in comments. +# +# I usually define a special command like this: +# +# \def\unknown{\large\bf??} +# +# which I use whenever there is some information I don't have at the +# moment of writing. Thus, it makes sense to search for it. +# +# You should be able to develop your own uses for this. +# + +UserWarn +{ + + \unknown +### +# +# Another example; one should write \chktex or Chk\TeX - never ChkTeX. +# +### + + ChkTeX +} +[ + +### +# +# You may put case-insensitive patterns here. +# +### + +] + + +### +# +# These patterns will be searched for, no matter whether they appear +# as normal text, commands or arguments. However, they will _not_ +# match in verbatim environments. +# +# Remember that you have to escape (with a !) the following +# characters: "#!= as well as spaces and {}[] if they are proceeded by +# a space. +# +# Since these are PCRE regular expressions, you can use (?i) to make +# the expression case insensitive. See the man pages (man pcresyntax) +# or the nicely formatted http://perldoc.perl.org/perlre.html for +# documentation on the regular expression syntax. Note however that +# some the features of perl regular expression are not available such +# as running code (callouts), and replacing. +# +# An initial PCRE comment (?# ... ) can be used change what is +# displayed, thereby reminding yourself how to fix the problem. +# +### +UserWarnRegex +{ + + (?!#Always! use! \nmid)\\not! *(\||\\mid) + + # capitalize section when saying Section 6. + (?!#-1:Capitalize! before! references)PCRE:\b(chapter|(sub)?section|theorem|lemma|proposition|corollary|appendix)~\\ref + (?!#1:Capitalize! before! references)POSIX:([^[:alnum:]]|^)(chapter|(sub)?section|theorem|lemma|proposition|corollary|appendix)~\\ref + + # spell it out. + # PCRE:(?i)\bintro\b(?!#Spell! it! out.! This! comment! is! not! used.) + # POSIX:([^[:alnum:]]|^)intro([^[:alnum:]]|$) + + # Pretty tables--see http://texdoc.net/texmf-dist/doc/latex/booktabs/booktabs.pdf + (?!#-2:Use! \toprule,! midrule,! or! \bottomrule! from! booktabs)\\hline + # This relies on it being on a single line, and not having anything + # else on that line. With PCRE we could match balanced [] and {}, + # but I wonder if it's worth the complexity... + (?!#-2:Vertical! rules! in! tables! are! ugly)\\begin\{(array|tabularx?\*?)\}(\[.*\])?\{.*\|.*\} + +} + + +##################################################################### +# +# Here you can list the path of where ChkTeX should look for files it +# \inputs. The // postfix is now supported; if you append a double +# path-separator we'll recursively search that directory directories. +# MS-DOS users must append \\ instead, e.g. "C:\EMTEX\\". +# +# If you under either MS-DOS or UNIX wish to search an entire +# partition or the complete directory tree, you must use *three* +# slashes, e.g. "c:\\\" or "///". This may be considered to be a bug. +# +# By default, we'll search the current directory (not recursively, +# put "//" in the list for this); any paths specified below will be +# searched in addition to this. +# + +TeXInputs +{ + +} + +##################################################################### +# +# Here you may specify more output formats for use with the -v option, +# it simply indexes into this list. Remember to use ! instead of \, +# though. +# +# For explanation of how % fields expand; look at ChkTeX.{dvi,ps,pdf}. +# +# We will by default select entry number _two_ in this list (we count +# from 0), and -v without any parameter selects entry number _three_. +# + +OutFormat +{ + +# -v0; silent mode +%f%b%l%b%c%b%n%b%m!n + +# -v1; normal mode +"%k %n in %f line %l: %m!n%r%s%t!n%u!n" + +# -v2; fancy mode +"%k %n in %f line %l: %m!n%r%i%s%I%t!n!n" + +# -v3; lacheck mode +"!"%f!", line %l: %m!n" + +# -v4; verbose lacheck mode +"!"%f!", line %l: %m!n%r%s%t!n%u!n" + +# -v5; no line number, ease auto-test +"%k %n in %f: %m!n%r%s%t!n%u!n" + +# -v6; emacs compilation mode +"!"%f!", line %l.%c:(#%n) %m!n" + +} + + + +##################################################################### +# +# These commands should be ignored when detecting whether a command +# is ended by a space. You can specify regular expressions in the [] +# section in case you have many custom macros that can be safely +# terminated with a space. +# + +Silent +{ + \rm \em \bf \it \sl \sf \sc \tt \selectfont + \rmfamily \sffamily \ttfamily \mdseries \bfseries + \slshape \scshape \relax + \vskip \pagebreak \nopagebreak + + \textrm \textem \textbf \textit \textsl \textsf \textsc \texttt + + \clearpage \ddots \dotfill \flushbottom \fussy \indent \linebreak + \onecolumn \pagebreak \pushtabs \poptabs \scriptsize \sloppy + \twocolumn \vdots + \today \kill \newline \thicklines \thinlines + + \columnsep \space \item \tiny \footnotesize \small \normalsize + \normal \large \Large \LARGE \huge \Huge \printindex + + \newpage \listoffigures \listoftables \tableofcontents + \maketitle \makeindex + + \hline \hrule \vrule + + \centering + + \bigskip \medskip \smallskip + + \noindent \expandafter + + \makeatletter \makeatother + + \columnseprule + + \textwidth \textheight \hsize \vsize + + \if \fi \else + + \csname \endcsname + + \z@ \p@ \@warning \typeout + + \dots \ldots \input \endinput \nextline \leavevmode \cdots + \appendix \listfiles \and \quad + \hskip \vfill \vfil \hfill \hfil \topmargin \oddsidemargin + \frenchspacing \nonfrenchspacing + \begingroup \endgroup \par + + \vrefwarning \upshape \headheight \headsep \hoffset \voffset + \cdot \qquad + \left \right + \qedhere + + \xspace + + \addlinespace \cr \fill \frontmatter + \toprule \midrule \bottomrule + +}[ +# Here you can put regular expressions to match Silent macros. It was +# designed for the case where you have many custom macros sharing a +# common prefix, but can of course be used for other things. + +# Support ConTeXt to at least some extent +\\start.* \\stop.* + +] + +##################################################################### +# +# Here, you can specify the length of various dashes. We sort the +# dash according to which type of characters that are on the left and +# right of it. We are only conclusive if they are the same. +# +# We associate as follows: +# +# Name Type of character on each side +# HyphDash Alphabetic (foo-bar) +# NumDash Numeric (2--3) +# WordDash Space (like this --- see?) +# +# Below you specify how many dashes which are legal in each case. We +# define 0 as a magic constant which always generates an error. You +# may specify more than one legal dash-length. +# +# Let's look at an example. You use the following dash-syntax: +# +# foo-bar +# 2--3 +# like this---see? +# +# +# HYPHDASH { 1 3 } # Either a hyphen, or inter-word +# NUMDASH { 2 } # Between words +# WORDDASH { 0 } # We never use this +# + +HyphDash +{ + 1 3 +} + +NumDash +{ + 2 +} + +WordDash +{ + 3 +} + +##################################################################### +# +# Here are exceptions to the dash rules above. For example, an +# n-dash -- between words is usually wrong, but in some cases it is +# correct, such as when naming a theorem. The Birch--Swinnerton-Dyer +# conjecture is one example where the difference matters. You can +# tell that Birch is one person and Swinnerton-Dyer is another. +# +# Adding line suppressions for these is possible, but can quickly +# become tedious if a certain theorem is referenced often. For this +# reason exceptions can be specified here. They are case-sensitive. +# + +DashExcpt +{ + Birch--Swinnerton-Dyer +} + +##################################################################### +# +# This keyword indicates commands whose argument isn't LaTeX code, +# and thus should be ignored. +# +# After the command, you may place arguments that you wish that +# should be wiped in the process; use [] for optional arguments, {} +# for required ones and * if the command supports an alternative +# variant. These should be separated from the command with a colon. +# Some commands (e.g. \cmidrule) use () to delimit and optional +# argument and so this syntax is supported as well. +# +# For instance, if you would like to wipe the \newcommand command, +# you would declare it as \newcommand:*[][]{} +# +# These commands may be "executed" before they're wiped, so you will +# typically also wish to list filehandling commands and similar here. +# + +WipeArg +{ + \label:{} \ref:{} \eqref:{} \vref:{} \pageref:{} \index:[]{} + \cite:[][]{} \nocite:{} + \input:{} \verbatiminput:[]{} \listinginput:[]{}{} + \verbatimtabinput:[]{} \include:{} \includeonly:{} + \bibitem:[]{} + \cline:{} \cmidrule:[](){} + \href:{}{} + # Cleveref -- there are many others that could be here as well... + \cref:*{} \cpageref:*{} \crefrange:*{}{} \cpagerefrange:*{}{} + \Cref:*{} \Cpageref:*{} \Crefrange:*{}{} \Cpagerefrange:*{}{} + # natbib + \citet:*[][]{} \citep:*[][]{} \citealt:*{} \citealp:*[]{} \citeauthor:*{} + \Citet:*[][]{} \Citep:*[][]{} \Citealt:*{} \Citealp:*[]{} \Citeauthor:{} + \citetext:{} \citeyear:*{} \citeyearpar:{} + # tipa which uses " + \textipa:{} +} + +##################################################################### +# +# These environments contain material which will be typeset as +# mathematics by LaTeX. This turns on/off some warnings. +# +# We will automagically append a * to each keyword. +# + +MathEnvir +{ + displaymath math eqnarray array equation + align alignat gather flalign multline +} + +##################################################################### +# +# These commands contain material which will be typeset as mathematics +# by LaTeX. The commands are assumed to have one mandatory argument +# which is in math mode. This turns on/off some warnings. +# + +MathCmd +{ + \ensuremath +} + +##################################################################### +# +# These commands contain material which will _not_ be typeset as +# mathematics by LaTeX even if it would otherwise be in mathmode. The +# commands are assumed to have one mandatory argument which is in text +# mode. This turns on/off some warnings. +# + +TextCmd +{ + \text \intertext \shortintertext \mbox +} + +##################################################################### +# +# These environments contains material which contents should be +# ignored. +# +# We will automagically append a * to each keyword. +# + +VerbEnvir +{ + verbatim comment listing verbatimtab rawhtml errexam picture texdraw + filecontents pgfpicture tikzpicture minted lstlisting IPA ignore +} + +##################################################################### +# +# ChkTeX does automagically catch most abbreviations; the ones we +# need to list here, are those which are most likely to be followed +# by a word with an upper-case letter (that is not the beginning of a +# new sentence). +# +# The case-insensitive abbreviations are not really case-insensitive, +# it seems to be more practical to only let the first character be +# case-insensitive, while the remaining are case-sensitive. +# +# To speed up the searching process somewhat, we require that these +# end in a `.', this should not be a problem. +# +# Much of this work (both the abbreviations below, and the regexps +# necessary to catch the remaining automatically) have been provided +# by Russ Bubley, . +# + +Abbrev +{ +# Ordinals +1st. 2nd. 3rd. 4th. +# Titles +Mr. Mrs. Miss. Ms. Dr. Prof. St. + +# +# Days +# Mon. Tue. Wed. Thu. Fri. Sat. Sun. +# +# Months +# Jan. Feb. Mar. Apr. May. Jun. Jul. Aug. Sep. Oct. Nov. Dec. +# +# Letters +# Kt. Jr. +# +# Corporate +# Co. Ltd. +# +# Addresses +# Rd. Dr. St. Ave. Cres. Gdns. Sq. Circ. Terr. Pl. Arc. La. Clo. Ho. Est. Gn. +# +# Misc. +# oe. pbab. ps. rsvp. Tx. +} +[ +### +# +# The first letter is case-insensitive in the abbrevs in this +# list. Due to the nature of the checking algorithm used for +# this, entries consisting of only one character will be +# silently ignored. +# +## + +# Latin +# cf. "et al." etc. qed. qv. viz. +# +# Corporate +# inc. plc. +# +# Misc +# fax. pcs. qty. tel. misc. +] + +##################################################################### +# +# Commands which accent characters, meaning that \i or \j (\imath and +# \jmath in mathmode) should be used instead of `i' and `j' +# + +IJAccent +{ + \hat \check \breve \acute \grave \tilde \bar \vec \dot \ddot + + \' \` \^ \" \~ \= \. \u \v \H \t + +### +# +# The remaining accent commands (\c,\d,\b) put their accent _under_ +# the character, not above, and should thus be used with normal i's +# and j's. +# +### + +} + +##################################################################### +# +# Commands which, when the group is terminated, needs italic +# correction. +# + +Italic +{ + \it \em \sl + \itshape \slshape +} + +##################################################################### +# +# Commands which makes the font non-italic. +# + +NonItalic +{ + \bf \rm \sf \tt \sc + \upshape +} + +##################################################################### +# +# Commands which put their argument into italic (and thus possibly +# needs italic correction in the end). +# +# This is currently empty, since \textit, \textsl and \emph do that +# automatically. +# + +ItalCmd +{ +} + +##################################################################### +# +# These commands all have in common that a pagebreak right in front +# of them is highly undesirable; thus there should be no space in +# front of them. +# + +PostLink +{ + \index \label +} + +##################################################################### +# +# These commands should not have a space in front of them for various +# reasons. I.e. much the same as POSTLINK, but produces another +# warning. +# + +NotPreSpaced +{ + \footnote \footnotemark \/ +} + +##################################################################### +# +# The commands listed here, should be prepended with a `~', as in +# "look in table~\ref{foo}", to avoid the references being split +# across lines. +# + +Linker +{ + \ref \vref \pageref \eqref \cite +} + +##################################################################### +# +# Commands/characters which should have \cdots in between, e.g. +# $1+2+3+\cdots+n$. +# + +CenterDots +{ + = + - \cdot \div & \times \geq \leq < > +} + +##################################################################### +# +# Commands/characters which should have \ldots in between, e.g. +# $1,2,3,\ldots,n$. +# + +LowDots +{ + . , ; +} + +##################################################################### +# +# In maths mode, there are certain aliases for mathematical operators +# like sin, cos, etc. Ignore the leading backslash in the commands, +# and so forth. You should list these below. +# + +MathRoman +{ + log lg ln lim limsup liminf sin arcsin sinh cos arccos cosh tan + arctan tanh cot coth sec csc max min sup inf arg ker dim hom det + exp Pr gcd deg bmod pmod mod +} + +##################################################################### +# +# These TeX commands have become unnecessary, as there are LaTeX +# commands that does the same. Purists should thus avoid these in +# their code. +# +# (These are a spell-corrected version of those lacheck uses). +# + +Primitives +{ + \above \advance \catcode \chardef \closein \closeout \copy \count + \countdef \cr \crcr \csname \delcode \dimendef \dimen \divide + \expandafter \font \hskip \vskip \openout +} + +##################################################################### +# +# Format: \command:characters +# +# We'll emit a warning if any of characters are found after the +# command. +# + +NoCharNext +{ + \left:{}$ \right:{}$ +} + +##################################################################### +# +# We're killing \verb@...@ commands and the arguments of the commands +# listed above in WipeArg by overwriting them with a string or a +# single character. +# +# This should not contain an alphabetic character (in case the user +# writes (\foo\verb@bar@), neither should it contain be one of +# LaTeX's reserved characters (`#$%&~_^\{}'), or any parenthesis +# character ('()[]{}'). If possible, don't use a punctuation +# character, either, or any spacing character. +# +# The asterisk is also unsuitable, as some commands behave in another +# way if they are appended with an asterisk. Which more or less +# leaves us with the pipe. +# +# Please note that this may also be a _string_, which will be +# repeated until the proper length is reached. +# + +VerbClear = "|" + +# +# All for now - have fun. +# +##################################################################### diff --git a/config/linters/.clj-kondo/config.edn b/config/linters/.clj-kondo/config.edn new file mode 100644 index 00000000..8a2999a1 --- /dev/null +++ b/config/linters/.clj-kondo/config.edn @@ -0,0 +1,7 @@ +{:linters + {:unresolved-symbol + {:exclude [(compojure.api.sweet/defroutes)]} + :refer-all + {:exclude [clj-time.jdbc]} + } +} diff --git a/config/linters/.coffee-lint.json b/config/linters/.coffee-lint.json new file mode 100644 index 00000000..053b20dc --- /dev/null +++ b/config/linters/.coffee-lint.json @@ -0,0 +1,135 @@ +{ + "arrow_spacing": { + "level": "ignore" + }, + "braces_spacing": { + "level": "ignore", + "spaces": 0, + "empty_object_spaces": 0 + }, + "camel_case_classes": { + "level": "error" + }, + "coffeescript_error": { + "level": "error" + }, + "colon_assignment_spacing": { + "level": "ignore", + "spacing": { + "left": 0, + "right": 0 + } + }, + "cyclomatic_complexity": { + "level": "ignore", + "value": 10 + }, + "duplicate_key": { + "level": "error" + }, + "empty_constructor_needs_parens": { + "level": "ignore" + }, + "ensure_comprehensions": { + "level": "warn" + }, + "eol_last": { + "level": "ignore" + }, + "indentation": { + "value": 2, + "level": "warn" + }, + "line_endings": { + "level": "ignore", + "value": "unix" + }, + "max_line_length": { + "value": 80, + "level": "ignore", + "limitComments": true + }, + "missing_fat_arrows": { + "level": "ignore", + "is_strict": false + }, + "newlines_after_classes": { + "value": 3, + "level": "ignore" + }, + "no_backticks": { + "level": "error" + }, + "no_debugger": { + "level": "warn", + "console": false + }, + "no_empty_functions": { + "level": "ignore" + }, + "no_empty_param_list": { + "level": "ignore" + }, + "no_implicit_braces": { + "level": "ignore", + "strict": true + }, + "no_implicit_parens": { + "level": "ignore", + "strict": true + }, + "no_interpolation_in_single_quotes": { + "level": "ignore" + }, + "no_nested_string_interpolation": { + "level": "warn" + }, + "no_plusplus": { + "level": "ignore" + }, + "no_private_function_fat_arrows": { + "level": "warn" + }, + "no_stand_alone_at": { + "level": "ignore" + }, + "no_tabs": { + "level": "error" + }, + "no_this": { + "level": "ignore" + }, + "no_throwing_strings": { + "level": "error" + }, + "no_trailing_semicolons": { + "level": "error" + }, + "no_trailing_whitespace": { + "level": "ignore", + "allowed_in_comments": false, + "allowed_in_empty_lines": true + }, + "no_unnecessary_double_quotes": { + "level": "ignore" + }, + "no_unnecessary_fat_arrows": { + "level": "warn" + }, + "non_empty_constructor_needs_parens": { + "level": "ignore" + }, + "prefer_english_operator": { + "level": "ignore", + "doubleNotLevel": "ignore" + }, + "space_operators": { + "level": "ignore" + }, + "spacing_after_comma": { + "level": "ignore" + }, + "transform_messes_up_line_numbers": { + "level": "warn" + } +} diff --git a/config/linters/.ecrc b/config/linters/.ecrc new file mode 100644 index 00000000..e052a07b --- /dev/null +++ b/config/linters/.ecrc @@ -0,0 +1,18 @@ +{ + "Verbose": false, + "Debug": false, + "IgnoreDefaults": false, + "SpacesAftertabs": false, + "NoColor": false, + "Exclude": [], + "AllowedContentTypes": [], + "PassedFiles": [], + "Disable": { + "EndOfLine": false, + "Indentation": false, + "InsertFinalNewline": false, + "TrimTrailingWhitespace": false, + "IndentSize": false, + "MaxLineLength": false + } +} diff --git a/config/linters/.eslintrc.yml b/config/linters/.eslintrc.yml new file mode 100644 index 00000000..d8d60ec1 --- /dev/null +++ b/config/linters/.eslintrc.yml @@ -0,0 +1,41 @@ +--- + +############################# +############################# +## JavaScript Linter rules ## +############################# +############################# + +############ +# Env Vars # +############ +env: + browser: true + es6: true + jest: true + +############### +# Global Vars # +############### +globals: + Atomics: readonly + SharedArrayBuffer: readonly + +############### +# Parser vars # +############### +parser: '@typescript-eslint/parser' +parserOptions: + ecmaVersion: 2018 + sourceType: module + +########### +# Plugins # +########### +plugins: + - '@typescript-eslint' + +######### +# Rules # +######### +rules: {} diff --git a/config/linters/.flake8 b/config/linters/.flake8 new file mode 100644 index 00000000..6deafc26 --- /dev/null +++ b/config/linters/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 diff --git a/config/linters/.golangci.yml b/config/linters/.golangci.yml new file mode 100644 index 00000000..c3e3c91a --- /dev/null +++ b/config/linters/.golangci.yml @@ -0,0 +1,40 @@ +--- +######################### +######################### +## Golang Linter rules ## +######################### +######################### + +# configure golangci-lint +# see https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml +issues: + exclude-rules: + - path: _test\.go + linters: + - dupl + - gosec + - goconst +linters: + enable: + - golint + - gosec + - unconvert + - gocyclo + - goconst + - goimports + - maligned + - gocritic +linters-settings: + errcheck: + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. + check-blank: true + govet: + # report about shadowed variables + check-shadowing: true + gocyclo: + # minimal code complexity to report, 30 by default + min-complexity: 15 + maligned: + # print struct with more effective memory layout or not, false by default + suggest-new: true diff --git a/config/linters/.groovylintrc.json b/config/linters/.groovylintrc.json new file mode 100644 index 00000000..474f8151 --- /dev/null +++ b/config/linters/.groovylintrc.json @@ -0,0 +1,65 @@ +{ + "extends": "recommended", + "rules": { + "CatchException": { + "enabled": false + }, + "CatchThrowable": { + "enabled": false + }, + "ClassJavadoc": { + "enabled": false + }, + "ClosureAsLastMethodParameter": { + "enabled": false + }, + "DuplicateNumberLiteral": { + "enabled": false + }, + "DuplicateStringLiteral": { + "enabled": false + }, + "FieldTypeRequired": { + "enabled": false + }, + "JavaIoPackageAccess": { + "enabled": false + }, + "MethodParameterTypeRequired": { + "enabled": false + }, + "MethodSize": { + "enabled": false + }, + "NoDef": { + "enabled": false + }, + "PrintStackTrace": { + "enabled": false + }, + "PropertyName": { + "enabled": false + }, + "SpaceAroundMapEntryColon": { + "enabled": false + }, + "SystemExit": { + "enabled": false + }, + "UnnecessaryGetter": { + "enabled": false + }, + "UnnecessaryObjectReferences": { + "enabled": false + }, + "UnnecessarySetter": { + "enabled": false + }, + "VariableName": { + "enabled": false + }, + "VariableTypeRequired": { + "enabled": false + } + } +} diff --git a/config/linters/.hadolint.yaml b/config/linters/.hadolint.yaml new file mode 100644 index 00000000..ec230881 --- /dev/null +++ b/config/linters/.hadolint.yaml @@ -0,0 +1,12 @@ +--- +########################## +## Hadolint config file ## +########################## +ignored: + - DL4001 # Ignore wget and curl in same file + - DL4006 # ignore pipefail as we dont want to add layers + - DL3018 # We do pin version in pipfile.lock + - DL3013 # We do pin version in pipfile.lock + - DL3003 # Ignore workdir so we dont add layers + - SC2016 # ignore as its intepreted later + - DL3044 # Ignore using env in env diff --git a/config/linters/.htmlhintrc b/config/linters/.htmlhintrc new file mode 100644 index 00000000..5daf1dd9 --- /dev/null +++ b/config/linters/.htmlhintrc @@ -0,0 +1,25 @@ +{ + "tagname-lowercase": true, + "attr-lowercase": true, + "attr-value-double-quotes": true, + "attr-value-not-empty": false, + "attr-no-duplication": true, + "doctype-first": true, + "tag-pair": true, + "tag-self-close": false, + "spec-char-escape": true, + "id-unique": true, + "src-not-empty": true, + "title-require": true, + "alt-require": true, + "doctype-html5": true, + "id-class-value": "dash", + "style-disabled": false, + "inline-style-disabled": false, + "inline-script-disabled": false, + "space-tab-mixed-disabled": "space", + "id-class-ad-disabled": false, + "href-abs-or-rel": false, + "attr-unsafe-chars": true, + "head-script-disabled": true +} diff --git a/config/linters/.lintr b/config/linters/.lintr new file mode 100644 index 00000000..aac8e22e --- /dev/null +++ b/config/linters/.lintr @@ -0,0 +1 @@ +linters: with_defaults(object_usage_linter = NULL) diff --git a/config/linters/.luacheckrc b/config/linters/.luacheckrc new file mode 100644 index 00000000..00aa308f --- /dev/null +++ b/config/linters/.luacheckrc @@ -0,0 +1 @@ +--std max diff --git a/config/linters/.markdown-lint.yml b/config/linters/.markdown-lint.yml new file mode 100644 index 00000000..f2dec62f --- /dev/null +++ b/config/linters/.markdown-lint.yml @@ -0,0 +1,35 @@ +--- +########################### +########################### +## Markdown Linter rules ## +########################### +########################### + +# Linter rules doc: +# - https://github.com/DavidAnson/markdownlint +# +# Note: +# To comment out a single error: +# +# any violations you want +# +# + +############### +# Rules by id # +############### +MD004: false # Unordered list style +MD007: + indent: 2 # Unordered list indentation +MD013: + line_length: 808 # Line length +MD026: + punctuation: ".,;:!。,;:" # List of not allowed +MD029: false # Ordered list item prefix +MD033: false # Allow inline HTML +MD036: false # Emphasis used instead of a heading + +################# +# Rules by tags # +################# +blank_lines: false # Error on blank lines diff --git a/config/linters/.openapirc.yml b/config/linters/.openapirc.yml new file mode 100644 index 00000000..fdf641e1 --- /dev/null +++ b/config/linters/.openapirc.yml @@ -0,0 +1,9 @@ +--- + +########################## +########################## +## OpenAPI Linter rules ## +########################## +########################## + +extends: spectral:oas diff --git a/config/linters/.perlcriticrc b/config/linters/.perlcriticrc new file mode 100644 index 00000000..eb920334 --- /dev/null +++ b/config/linters/.perlcriticrc @@ -0,0 +1,2 @@ +severity = 1 +verbose = %f:%l:%c [%s %p] %m near '%r'\n diff --git a/config/linters/.protolintrc.yml b/config/linters/.protolintrc.yml new file mode 100644 index 00000000..7bd3e0ce --- /dev/null +++ b/config/linters/.protolintrc.yml @@ -0,0 +1,7 @@ +# Lint directives. +lint: + # Linter rules. + # Run `protolint list` to see all available rules. + rules: + # Set the default to all linters. + all_default: false diff --git a/config/linters/.python-black b/config/linters/.python-black new file mode 100644 index 00000000..e69de29b diff --git a/config/linters/.python-lint b/config/linters/.python-lint new file mode 100644 index 00000000..ae374f57 --- /dev/null +++ b/config/linters/.python-lint @@ -0,0 +1,542 @@ +[MASTER] +errors-only= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + locally-enabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=no + +# Activate the evaluation score. +score=no + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=optparse.Values,sys.exit + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[BASIC] + +# Naming style matching correct argument names +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style +#argument-rgx= + +# Naming style matching correct attribute names +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style +#class-attribute-rgx= + +# Naming style matching correct class names +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming-style +#class-rgx= + +# Naming style matching correct constant names +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# Naming style matching correct inline iteration names +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style +#inlinevar-rgx= + +# Naming style matching correct method names +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style +#method-rgx= + +# Naming style matching correct module names +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style +#variable-rgx= + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub, + TERMIOS, + Bastion, + rexec + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/config/linters/.ruby-lint.yml b/config/linters/.ruby-lint.yml new file mode 100644 index 00000000..b9c4d880 --- /dev/null +++ b/config/linters/.ruby-lint.yml @@ -0,0 +1,8 @@ +--- +####################### +# Rubocop Config file # +####################### + +inherit_gem: + rubocop-github: + - config/default.yml diff --git a/config/linters/.snakefmt.toml b/config/linters/.snakefmt.toml new file mode 100644 index 00000000..e69de29b diff --git a/config/linters/.sql-config.json b/config/linters/.sql-config.json new file mode 100644 index 00000000..db91b910 --- /dev/null +++ b/config/linters/.sql-config.json @@ -0,0 +1,3 @@ +{ + "_comment": "details can be found at: https://sql-lint.readthedocs.io/en/latest/files/configuration.html" +} diff --git a/config/linters/.stylelintrc.json b/config/linters/.stylelintrc.json new file mode 100644 index 00000000..40db42c6 --- /dev/null +++ b/config/linters/.stylelintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "stylelint-config-standard" +} diff --git a/config/linters/.yaml-lint.yml b/config/linters/.yaml-lint.yml new file mode 100644 index 00000000..e9ec8bef --- /dev/null +++ b/config/linters/.yaml-lint.yml @@ -0,0 +1,59 @@ +--- +########################################### +# These are the rules used for # +# linting all the yaml files in the stack # +# NOTE: # +# You can disable line with: # +# # yamllint disable-line # +########################################### +rules: + braces: + level: warning + min-spaces-inside: 0 + max-spaces-inside: 0 + min-spaces-inside-empty: 1 + max-spaces-inside-empty: 5 + brackets: + level: warning + min-spaces-inside: 0 + max-spaces-inside: 0 + min-spaces-inside-empty: 1 + max-spaces-inside-empty: 5 + colons: + level: warning + max-spaces-before: 0 + max-spaces-after: 1 + commas: + level: warning + max-spaces-before: 0 + min-spaces-after: 1 + max-spaces-after: 1 + comments: disable + comments-indentation: disable + document-end: disable + document-start: + level: warning + present: true + empty-lines: + level: warning + max: 2 + max-start: 0 + max-end: 0 + hyphens: + level: warning + max-spaces-after: 1 + indentation: + level: warning + spaces: consistent + indent-sequences: true + check-multi-line-strings: false + key-duplicates: enable + line-length: + level: warning + max: 120 + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: true + new-line-at-end-of-file: disable + new-lines: + type: unix + trailing-spaces: disable diff --git a/config/linters/analysis_options.yml b/config/linters/analysis_options.yml new file mode 100644 index 00000000..0c444f44 --- /dev/null +++ b/config/linters/analysis_options.yml @@ -0,0 +1,57 @@ +--- +########################## +########################## +## Dart Linter rules ## +########################## +########################## + +# Pedantic Rules +# https://github.com/dart-lang/pedantic + +linter: + rules: + - always_declare_return_types + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_empty_else + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + - avoid_relative_lib_imports + - avoid_return_types_on_setters + - avoid_shadowing_type_parameters + - avoid_types_as_parameter_names + - camel_case_extensions + - curly_braces_in_flow_control_structures + - empty_catches + - empty_constructor_bodies + - library_names + - library_prefixes + - no_duplicate_case_values + - null_closures + - omit_local_variable_types + - prefer_adjacent_string_concatenation + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_contains + - prefer_equal_for_default_values + - prefer_final_fields + - prefer_for_elements_to_map_fromIterable + - prefer_generic_function_type_aliases + - prefer_if_null_operators + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType + - prefer_single_quotes + - prefer_spread_collections + - recursive_getters + - slash_for_doc_comments + - type_init_formals + - unawaited_futures + - unnecessary_const + - unnecessary_new + - unnecessary_null_in_if_null_operators + - unnecessary_this + - unrelated_type_equality_checks + - use_function_type_syntax_for_parameters + - use_rethrow_when_possible + - valid_regexps diff --git a/config/linters/sun_checks.xml b/config/linters/sun_checks.xml new file mode 100644 index 00000000..dd88521d --- /dev/null +++ b/config/linters/sun_checks.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/4f9f8765-911a-4a6d-af60-4bc0473268c0_generic-subscription_canadacentral.json b/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/4f9f8765-911a-4a6d-af60-4bc0473268c0_generic-subscription_canadacentral.json new file mode 100644 index 00000000..e06aac6d --- /dev/null +++ b/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/4f9f8765-911a-4a6d-af60-4bc0473268c0_generic-subscription_canadacentral.json @@ -0,0 +1,167 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + }, + "actionGroupName": "Sub1 ALZ action group", + "actionGroupShortName": "sub1-alert", + "alertRuleName": "Sub1 ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Role: Contributor", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "alzcanadapubsec@microsoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "rgAutomation102021W1", + "networking": "rgVnet102021W1", + "networkWatcher": "NetworkWatcherRG" + } + }, + "automationAccountName": { + "value": "automation" + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4" + } + }, + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.2.0.0/16" + ], + "subnets": { + "oz": { + "comments": "Foundational Elements Zone (OZ)", + "name": "oz", + "addressPrefix": "10.2.1.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "paz": { + "comments": "Presentation Zone (PAZ)", + "name": "paz", + "addressPrefix": "10.2.2.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "rz": { + "comments": "Application Zone (RZ)", + "name": "rz", + "addressPrefix": "10.2.3.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "hrz": { + "comments": "Data Zone (HRZ)", + "name": "hrz", + "addressPrefix": "10.2.4.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "optional": [ + { + "comments": "App Service", + "name": "appservice", + "addressPrefix": "10.2.5.0/25", + "nsg": { + "enabled": false + }, + "udr": { + "enabled": false + }, + "delegations": { + "serviceName": "Microsoft.Web/serverFarms" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/82f7705e-3386-427b-95b7-cbed91ab29a7_healthcare_canadacentral.json b/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/82f7705e-3386-427b-95b7-cbed91ab29a7_healthcare_canadacentral.json new file mode 100644 index 00000000..09108c38 --- /dev/null +++ b/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/82f7705e-3386-427b-95b7-cbed91ab29a7_healthcare_canadacentral.json @@ -0,0 +1,174 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + }, + "actionGroupName": "Sub2 ALZ action group", + "actionGroupShortName": "sub2-alert", + "alertRuleName": "Sub2 ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Role: Contributor", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "alzcanadapubsec@microsoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "health102021W1Automation", + "compute": "health102021W1Compute", + "monitor": "health102021W1Monitor", + "networking": "health102021W1Network", + "networkWatcher": "NetworkWatcherRG", + "security": "health102021W1Security", + "storage": "health102021W1Storage" + } + }, + "useCMK": { + "value": true + }, + "keyVault": { + "value": { + "secretExpiryInDays": 3650 + } + }, + "automation": { + "value": { + "name": "health102021W1automation" + } + }, + "sqldb": { + "value": { + "enabled": true, + "username": "azadmin" + } + }, + "synapse": { + "value": { + "username": "azadmin" + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4", + "privateDnsManagedByHub": true, + "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", + "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" + } + }, + "network": { + "value": { + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "health102021W1vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.5.0.0/16" + ], + "subnets": { + "oz": { + "comments": "Foundational Elements Zone (OZ)", + "name": "oz", + "addressPrefix": "10.5.1.0/25" + }, + "paz": { + "comments": "Presentation Zone (PAZ)", + "name": "paz", + "addressPrefix": "10.5.2.0/25" + }, + "rz": { + "comments": "Application Zone (RZ)", + "name": "rz", + "addressPrefix": "10.5.3.0/25" + }, + "hrz": { + "comments": "Data Zone (HRZ)", + "name": "hrz", + "addressPrefix": "10.5.4.0/25" + }, + "databricksPublic": { + "comments": "Databricks Public Delegated Subnet", + "name": "databrickspublic", + "addressPrefix": "10.5.5.0/25" + }, + "databricksPrivate": { + "comments": "Databricks Private Delegated Subnet", + "name": "databricksprivate", + "addressPrefix": "10.5.6.0/25" + }, + "privateEndpoints": { + "comments": "Private Endpoints Subnet", + "name": "privateendpoints", + "addressPrefix": "10.5.7.0/25" + }, + "web": { + "comments": "Azure Web App Delegated Subnet", + "name": "webapp", + "addressPrefix": "10.5.8.0/25" + } + } + } + } + } +} \ No newline at end of file diff --git a/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/ec6c5689-db04-4f1e-b76d-834a51dd0e27_machinelearning_canadacentral.json b/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/ec6c5689-db04-4f1e-b76d-834a51dd0e27_machinelearning_canadacentral.json new file mode 100644 index 00000000..a5fec9cf --- /dev/null +++ b/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/ec6c5689-db04-4f1e-b76d-834a51dd0e27_machinelearning_canadacentral.json @@ -0,0 +1,190 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + }, + "actionGroupName": "Sub3 ALZ action group", + "actionGroupShortName": "sub3-alert", + "alertRuleName": "Sub3 ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Role: Contributor", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "alzcanadapubsec@microsoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "azmlcmk102021W1Automation", + "compute": "azmlcmk102021W1Compute", + "monitor": "azmlcmk102021W1Monitor", + "networking": "azmlcmk102021W1Network", + "networkWatcher": "NetworkWatcherRG", + "security": "azmlcmk102021W1Security", + "storage": "azmlcmk102021W1Storage" + } + }, + "useCMK": { + "value": true + }, + "automation": { + "value": { + "name": "azmlcmk102021W1automation" + } + }, + "keyVault": { + "value": { + "secretExpiryInDays": 3650 + } + }, + "aks": { + "value": { + "version": "1.21.2" + } + }, + "sqldb": { + "value": { + "enabled": true, + "username": "azadmin" + } + }, + "sqlmi": { + "value": { + "enabled": false, + "username": "azadmin" + } + }, + "aml": { + "value": { + "enableHbiWorkspace": false + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4", + "privateDnsManagedByHub": true, + "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", + "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" + } + }, + "network": { + "value": { + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "azmlcmk102021W1vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.1.0.0/16" + ], + "subnets": { + "oz": { + "comments": "Foundational Elements Zone (OZ)", + "name": "oz", + "addressPrefix": "10.1.1.0/25" + }, + "paz": { + "comments": "Presentation Zone (PAZ)", + "name": "paz", + "addressPrefix": "10.1.2.0/25" + }, + "rz": { + "comments": "Application Zone (RZ)", + "name": "rz", + "addressPrefix": "10.1.3.0/25" + }, + "hrz": { + "comments": "Data Zone (HRZ)", + "name": "hrz", + "addressPrefix": "10.1.4.0/25" + }, + "sqlmi": { + "comments": "SQL Managed Instances Delegated Subnet", + "name": "sqlmi", + "addressPrefix": "10.1.5.0/25" + }, + "databricksPublic": { + "comments": "Databricks Public Delegated Subnet", + "name": "databrickspublic", + "addressPrefix": "10.1.6.0/25" + }, + "databricksPrivate": { + "comments": "Databricks Private Delegated Subnet", + "name": "databricksprivate", + "addressPrefix": "10.1.7.0/25" + }, + "privateEndpoints": { + "comments": "Private Endpoints Subnet", + "name": "privateendpoints", + "addressPrefix": "10.1.8.0/25" + }, + "aks": { + "comments": "AKS Subnet", + "name": "aks", + "addressPrefix": "10.1.9.0/25" + } + } + } + } + } +} \ No newline at end of file diff --git a/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/f08c3057-1713-4a6f-b7e6-0df355b60c30_machinelearning_canadacentral.json b/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/f08c3057-1713-4a6f-b7e6-0df355b60c30_machinelearning_canadacentral.json new file mode 100644 index 00000000..3857f2f6 --- /dev/null +++ b/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/f08c3057-1713-4a6f-b7e6-0df355b60c30_machinelearning_canadacentral.json @@ -0,0 +1,190 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + }, + "actionGroupName": "Sub4 ALZ action group", + "actionGroupShortName": "sub4-alert", + "alertRuleName": "Sub4 ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Role: Contributor", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "alzcanadapubsec@microsoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "azmlnocmk102021W1Automation", + "compute": "azmlnocmk102021W1Compute", + "monitor": "azmlnocmk102021W1Monitor", + "networking": "azmlnocmk102021W1Network", + "networkWatcher": "NetworkWatcherRG", + "security": "azmlnocmk102021W1Security", + "storage": "azmlnocmk102021W1Storage" + } + }, + "useCMK": { + "value": false + }, + "automation": { + "value": { + "name": "azmlnocmk102021W1automation" + } + }, + "keyVault": { + "value": { + "secretExpiryInDays": 3650 + } + }, + "aks": { + "value": { + "version": "1.21.2" + } + }, + "sqldb": { + "value": { + "enabled": true, + "username": "azadmin" + } + }, + "sqlmi": { + "value": { + "enabled": false, + "username": "azadmin" + } + }, + "aml": { + "value": { + "enableHbiWorkspace": false + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4", + "privateDnsManagedByHub": true, + "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", + "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" + } + }, + "network": { + "value": { + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "azmlnocmk102021W1vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.3.0.0/16" + ], + "subnets": { + "oz": { + "comments": "Foundational Elements Zone (OZ)", + "name": "oz", + "addressPrefix": "10.3.1.0/25" + }, + "paz": { + "comments": "Presentation Zone (PAZ)", + "name": "paz", + "addressPrefix": "10.3.2.0/25" + }, + "rz": { + "comments": "Application Zone (RZ)", + "name": "rz", + "addressPrefix": "10.3.3.0/25" + }, + "hrz": { + "comments": "Data Zone (HRZ)", + "name": "hrz", + "addressPrefix": "10.3.4.0/25" + }, + "sqlmi": { + "comments": "SQL Managed Instances Delegated Subnet", + "name": "sqlmi", + "addressPrefix": "10.3.5.0/25" + }, + "databricksPublic": { + "comments": "Databricks Public Delegated Subnet", + "name": "databrickspublic", + "addressPrefix": "10.3.6.0/25" + }, + "databricksPrivate": { + "comments": "Databricks Private Delegated Subnet", + "name": "databricksprivate", + "addressPrefix": "10.3.7.0/25" + }, + "privateEndpoints": { + "comments": "Private Endpoints Subnet", + "name": "privateendpoints", + "addressPrefix": "10.3.8.0/25" + }, + "aks": { + "comments": "AKS Subnet", + "name": "aks", + "addressPrefix": "10.3.9.0/25" + } + } + } + } + } +} \ No newline at end of file diff --git a/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/f459218a-e8bb-49c9-b768-ee6828a144aa_machinelearning_canadacentral.json b/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/f459218a-e8bb-49c9-b768-ee6828a144aa_machinelearning_canadacentral.json new file mode 100644 index 00000000..730e675b --- /dev/null +++ b/config/subscriptions/CanadaESLZ-main/pubsec/LandingZones/DevTest/f459218a-e8bb-49c9-b768-ee6828a144aa_machinelearning_canadacentral.json @@ -0,0 +1,190 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + }, + "actionGroupName": "Sub5 ALZ action group", + "actionGroupShortName": "sub5-alert", + "alertRuleName": "Sub5 ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Role: Contributor", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "alzcanadapubsec@microsoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "azmlcmksqlmi102021W1Automation", + "compute": "azmlcmksqlmi102021W1Compute", + "monitor": "azmlcmksqlmi102021W1Monitor", + "networking": "azmlcmksqlmi102021W1Network", + "networkWatcher": "NetworkWatcherRG", + "security": "azmlcmksqlmi102021W1Security", + "storage": "azmlcmksqlmi102021W1Storage" + } + }, + "useCMK": { + "value": true + }, + "automation": { + "value": { + "name": "azmlcmksqlmi102021W1automation" + } + }, + "keyVault": { + "value": { + "secretExpiryInDays": 3650 + } + }, + "aks": { + "value": { + "version": "1.21.2" + } + }, + "sqldb": { + "value": { + "enabled": true, + "username": "azadmin" + } + }, + "sqlmi": { + "value": { + "enabled": true, + "username": "azadmin" + } + }, + "aml": { + "value": { + "enableHbiWorkspace": false + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4", + "privateDnsManagedByHub": true, + "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", + "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" + } + }, + "network": { + "value": { + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "azmlcmksqlmi102021W1vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.4.0.0/16" + ], + "subnets": { + "oz": { + "comments": "Foundational Elements Zone (OZ)", + "name": "oz", + "addressPrefix": "10.4.1.0/25" + }, + "paz": { + "comments": "Presentation Zone (PAZ)", + "name": "paz", + "addressPrefix": "10.4.2.0/25" + }, + "rz": { + "comments": "Application Zone (RZ)", + "name": "rz", + "addressPrefix": "10.4.3.0/25" + }, + "hrz": { + "comments": "Data Zone (HRZ)", + "name": "hrz", + "addressPrefix": "10.4.4.0/25" + }, + "sqlmi": { + "comments": "SQL Managed Instances Delegated Subnet", + "name": "sqlmi", + "addressPrefix": "10.4.5.0/25" + }, + "databricksPublic": { + "comments": "Databricks Public Delegated Subnet", + "name": "databrickspublic", + "addressPrefix": "10.4.6.0/25" + }, + "databricksPrivate": { + "comments": "Databricks Private Delegated Subnet", + "name": "databricksprivate", + "addressPrefix": "10.4.7.0/25" + }, + "privateEndpoints": { + "comments": "Private Endpoints Subnet", + "name": "privateendpoints", + "addressPrefix": "10.4.8.0/25" + }, + "aks": { + "comments": "AKS Subnet", + "name": "aks", + "addressPrefix": "10.4.9.0/25" + } + } + } + } + } +} \ No newline at end of file diff --git a/config/subscriptions/README.md b/config/subscriptions/README.md new file mode 100644 index 00000000..b99dc231 --- /dev/null +++ b/config/subscriptions/README.md @@ -0,0 +1,41 @@ +# Subscription configuration files + +## Disclaimer + +Copyright (c) Microsoft Corporation. + +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + +## Overview + +Create and maintain your subscription configuration (parameter) JSON files in this directory. + +The directory hierarchy is comprised of the following elements, from this directory downward: + +1. A environment directory named for the Azure DevOps Org and Git Repo branch name, e.g. 'CanadaESLZ-main'. +2. The management group hierarchy defined for your environment, e.g. pubsec/Platform/LandingZone/Prod. The location of the config file represents which Management Group the subscription is a member of. + +For example, if your Azure DevOps organization name is 'CanadaESLZ', you have two Git Repo branches named 'main' and 'dev', and you have top level management group named 'pubsec' with the standard structure, then your path structure would look like this: + +``` +/config/subscriptions + /CanadaESLZ-main <- Your environment, e.g. CanadaESLZ-main, CanadaESLZ-dev, etc. + /pubsec <- Your top level management root group name + /LandingZones + /Prod + /xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_generic-subscription.json +``` + +The JSON config file name is in one of the following two formats: + +- [AzureSubscriptionGUID]\_[TemplateName].json +- [AzureSubscriptionGUID]\_[TemplateName]\_[DeploymentLocation].json + + +The subscription GUID is needed by the pipeline; since it's not available in the file contents it is specified in the config file name. + +The template name/type is a text fragment corresponding to a path name (or part of a path name) under the '/landingzones' top level path. It indicates which Bicep templates to run on the subscription. For example, the generic subscription path is `/landingzones/lz-generic-subscription`, so we remove the `lz-` prefix and use `generic-subscription` to specify this type of landing zone. + +The deployment location is the short name of an Azure deployment location, which may be used to override the `deploymentRegion` YAML variable. The allowable values for this value can be determined by looking at the `Name` column output of the command: `az account list-locations -o table`. diff --git a/config/subscriptions/devopsincanada-skeeler/pubsec/LandingZones/DevTest/248e2ceb-23b4-41e7-bd3f-3d28594ca842_generic-subscription_canadacentral.json b/config/subscriptions/devopsincanada-skeeler/pubsec/LandingZones/DevTest/248e2ceb-23b4-41e7-bd3f-3d28594ca842_generic-subscription_canadacentral.json new file mode 100644 index 00000000..93fc846d --- /dev/null +++ b/config/subscriptions/devopsincanada-skeeler/pubsec/LandingZones/DevTest/248e2ceb-23b4-41e7-bd3f-3d28594ca842_generic-subscription_canadacentral.json @@ -0,0 +1,167 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "skeeler@m365incanada.onmicrosoft.com" ], + "email": [ "skeeler@m365incanada.onmicrosoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + }, + "actionGroupName": "Sub2 ALZ action group", + "actionGroupShortName": "sub2-alert", + "alertRuleName": "Sub2 ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + }, + "securityCenter": { + "value": { + "email": "skeeler@m365incanada.onmicrosoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Role: Contributor", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "ae712826-a993-4819-a167-4045d965b5a5" + ] + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc", + "securityGroupObjectIds": [ + "ae712826-a993-4819-a167-4045d965b5a5" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "skeeler@m365incanada.onmicrosoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "rgAutomation", + "networking": "rgVnet", + "networkWatcher": "NetworkWatcherRG" + } + }, + "automationAccountName": { + "value": "automation" + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/1a7025c7-f492-4eb9-9cf6-12c7889e4dfd/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.0.36" + } + }, + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.11.0.0/16" + ], + "subnets": { + "oz": { + "comments": "Foundational Elements Zone (OZ)", + "name": "oz", + "addressPrefix": "10.11.1.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "paz": { + "comments": "Presentation Zone (PAZ)", + "name": "paz", + "addressPrefix": "10.11.2.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "rz": { + "comments": "Application Zone (RZ)", + "name": "rz", + "addressPrefix": "10.11.3.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "hrz": { + "comments": "Data Zone (HRZ)", + "name": "hrz", + "addressPrefix": "10.11.4.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "optional": [ + { + "comments": "App Service", + "name": "appservice", + "addressPrefix": "10.11.5.0/25", + "nsg": { + "enabled": false + }, + "udr": { + "enabled": false + }, + "delegations": { + "serviceName": "Microsoft.Web/serverFarms" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/config/subscriptions/devopsincanada-skeeler/pubsec/LandingZones/DevTest/802e608f-f31b-4a10-86da-7fbb8f660a10_generic-subscription_canadacentral.json b/config/subscriptions/devopsincanada-skeeler/pubsec/LandingZones/DevTest/802e608f-f31b-4a10-86da-7fbb8f660a10_generic-subscription_canadacentral.json new file mode 100644 index 00000000..84729585 --- /dev/null +++ b/config/subscriptions/devopsincanada-skeeler/pubsec/LandingZones/DevTest/802e608f-f31b-4a10-86da-7fbb8f660a10_generic-subscription_canadacentral.json @@ -0,0 +1,167 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "skeeler@m365incanada.onmicrosoft.com" ], + "email": [ "skeeler@m365incanada.onmicrosoft.com" ], + "sms": [ { "countryCode": "1", "phoneNumber": "5555555555" } ], + "voice": [ { "countryCode": "1", "phoneNumber": "5555555555" } ] + }, + "actionGroupName": "Sub1 ALZ action group", + "actionGroupShortName": "sub1-alert", + "alertRuleName": "Sub1 ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + }, + "securityCenter": { + "value": { + "email": "skeeler@m365incanada.onmicrosoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [ + { + "comments": "Built-in Role: Contributor", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "ae712826-a993-4819-a167-4045d965b5a5" + ] + }, + { + "comments": "Custom Role: Landing Zone Application Owner", + "roleDefinitionId": "b4c87314-c1a1-5320-9c43-779585186bcc", + "securityGroupObjectIds": [ + "ae712826-a993-4819-a167-4045d965b5a5" + ] + } + ] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "skeeler@m365incanada.onmicrosoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "rgAutomation", + "networking": "rgVnet", + "networkWatcher": "NetworkWatcherRG" + } + }, + "automationAccountName": { + "value": "automation" + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/1a7025c7-f492-4eb9-9cf6-12c7889e4dfd/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.0.36" + } + }, + "network": { + "value": { + "deployVnet": true, + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.10.0.0/16" + ], + "subnets": { + "oz": { + "comments": "Foundational Elements Zone (OZ)", + "name": "oz", + "addressPrefix": "10.10.1.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "paz": { + "comments": "Presentation Zone (PAZ)", + "name": "paz", + "addressPrefix": "10.10.2.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "rz": { + "comments": "Application Zone (RZ)", + "name": "rz", + "addressPrefix": "10.10.3.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "hrz": { + "comments": "Data Zone (HRZ)", + "name": "hrz", + "addressPrefix": "10.10.4.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "optional": [ + { + "comments": "App Service", + "name": "appservice", + "addressPrefix": "10.10.5.0/25", + "nsg": { + "enabled": false + }, + "udr": { + "enabled": false + }, + "delegations": { + "serviceName": "Microsoft.Web/serverFarms" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/config/variables/CanadaESLZ-main.yml b/config/variables/CanadaESLZ-main.yml new file mode 100644 index 00000000..c5b3f9f3 --- /dev/null +++ b/config/variables/CanadaESLZ-main.yml @@ -0,0 +1,281 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +# Environment YAML files can be used to supplement +# the variables specified in 'config/variables/common.yml'. You can: +# * Override existing common-vars.yml variable value settings, and +# * Create new variable values not present in common-vars.yml +# +# The naming convention for these YAML files is: +# {organization}-{branch}.yml +# +# where {organization} is the organization variable from the +# common.yml file +# and {branch} is the Azure Repos branch name used by the +# currently executing pipeline. + +variables: + # Management Groups + var-parentManagementGroupId: 343ddfdb-bef5-46d9-99cf-ed67d5948783 + var-topLevelManagementGroupName: pubsec + + # Logging + var-logging-managementGroupId: pubsecPlatform + var-logging-subscriptionId: bc0a4f9f-07fa-4284-b1bd-fbad38578d3a + var-logging-logAnalyticsResourceGroupName: pubsec-central-logging-rg + var-logging-logAnalyticsWorkspaceName: log-analytics-workspace + var-logging-logAnalyticsAutomationAccountName: automation-account + var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix: pubsecnsg + var-logging-serviceHealthAlerts: > + { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + }, + "actionGroupName": "ALZ action group", + "actionGroupShortName": "alz-alert", + "alertRuleName": "ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + var-logging-securityCenter: > + { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + var-logging-subscriptionRoleAssignments: > + [ + { + "comments": "Built-in Contributor Role", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + } + ] + var-logging-subscriptionBudget: > + { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ "alzcanadapubsec@microsoft.com" ] + } + var-logging-subscriptionTags: > + { + "ISSO": "isso-tbd" + } + var-logging-resourceTags: > + { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + + # Hub Networking + var-hubnetwork-managementGroupId: pubsecPlatform + var-hubnetwork-subscriptionId: ed7f4eed-9010-4227-b115-2a5e37728f27 + var-hubnetwork-serviceHealthAlerts: > + { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + }, + "actionGroupName": "ALZ action group", + "actionGroupShortName": "alz-alert", + "alertRuleName": "ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + var-hubnetwork-securityCenter: > + { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + var-hubnetwork-subscriptionRoleAssignments: > + [ + { + "comments": "Built-in Contributor Role", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "38f33f7e-a471-4630-8ce9-c6653495a2ee" + ] + } + ] + var-hubnetwork-subscriptionBudget: > + { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ "alzcanadapubsec@microsoft.com" ] + } + var-hubnetwork-subscriptionTags: > + { + "ISSO": "isso-tbd" + } + var-hubnetwork-resourceTags: > + { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + + ## Hub Networking - Private Dns Zones + var-hubnetwork-deployPrivateDnsZones: true + var-hubnetwork-rgPrivateDnsZonesName: pubsec-dns-rg + + ## Hub Networking - DDOS + var-hubnetwork-deployDdosStandard: false + var-hubnetwork-rgDdosName: pubsec-ddos-rg + var-hubnetwork-ddosPlanName: ddos-plan + + ## Hub Networking - Public Zone + var-hubnetwork-rgPazName: pubsec-public-access-zone-rg + + ## Hub Networking - Management Restricted Zone Virtual Network + var-hubnetwork-rgMrzName: pubsec-management-restricted-zone-rg + var-hubnetwork-mrzVnetName: management-restricted-vnet + var-hubnetwork-mrzVnetAddressPrefixRFC1918: 10.18.4.0/22 + + var-hubnetwork-mrzMazSubnetName: MazSubnet + var-hubnetwork-mrzMazSubnetAddressPrefix: 10.18.4.0/25 + + var-hubnetwork-mrzInfSubnetName: InfSubnet + var-hubnetwork-mrzInfSubnetAddressPrefix: 10.18.4.128/25 + + var-hubnetwork-mrzSecSubnetName: SecSubnet + var-hubnetwork-mrzSecSubnetAddressPrefix: 10.18.5.0/26 + + var-hubnetwork-mrzLogSubnetName: LogSubnet + var-hubnetwork-mrzLogSubnetAddressPrefix: 10.18.5.64/26 + + var-hubnetwork-mrzMgmtSubnetName: MgmtSubnet + var-hubnetwork-mrzMgmtSubnetAddressPrefix: 10.18.5.128/26 + + var-hubnetwork-bastionName: bastion + + #################################################################################### + ### Hub Networking with Azure Firewall ### + #################################################################################### + var-hubnetwork-azfw-rgPolicyName: pubsec-azure-firewall-policy-rg + var-hubnetwork-azfw-policyName: pubsecAzureFirewallPolicy + + var-hubnetwork-azfw-rgHubName: pubsec-hub-networking-rg + var-hubnetwork-azfw-hubVnetName: hub-vnet + var-hubnetwork-azfw-hubVnetAddressPrefixRFC1918: 10.18.0.0/22 + var-hubnetwork-azfw-hubVnetAddressPrefixRFC6598: 100.60.0.0/16 + var-hubnetwork-azfw-hubVnetAddressPrefixBastion: 192.168.0.0/16 + + var-hubnetwork-azfw-hubPazSubnetName: PAZSubnet + var-hubnetwork-azfw-hubPazSubnetAddressPrefix: 100.60.1.0/24 + + var-hubnetwork-azfw-hubGatewaySubnetPrefix: 10.18.0.0/27 + var-hubnetwork-azfw-hubAzureFirewallSubnetAddressPrefix: 10.18.1.0/24 + var-hubnetwork-azfw-hubAzureFirewallManagementSubnetAddressPrefix: 10.18.2.0/26 + var-hubnetwork-azfw-hubBastionSubnetAddressPrefix: 192.168.0.0/24 + + var-hubnetwork-azfw-azureFirewallName: pubsecAzureFirewall + var-hubnetwork-azfw-azureFirewallZones: '["1", "2", "3"]' + var-hubnetwork-azfw-azureFirewallForcedTunnelingEnabled: false + var-hubnetwork-azfw-azureFirewallForcedTunnelingNextHop: 10.17.1.4 + + #################################################################################### + ### Hub Networking with Fortinet Firewalls ### + #################################################################################### + + ## Hub Networking - Core Virtual Network + var-hubnetwork-nva-rgHubName: pubsec-hub-networking-rg + var-hubnetwork-nva-hubVnetName: hub-vnet + var-hubnetwork-nva-hubVnetAddressPrefixRFC1918: 10.18.0.0/22 + var-hubnetwork-nva-hubVnetAddressPrefixRFC6598: 100.60.0.0/16 + var-hubnetwork-nva-hubVnetAddressPrefixBastion: 192.168.0.0/16 + + var-hubnetwork-nva-hubEanSubnetName: EanSubnet + var-hubnetwork-nva-hubEanSubnetAddressPrefix: 10.18.0.0/27 + + var-hubnetwork-nva-hubPublicSubnetName: PublicSubnet + var-hubnetwork-nva-hubPublicSubnetAddressPrefix: 100.60.0.0/24 + + var-hubnetwork-nva-hubPazSubnetName: PAZSubnet + var-hubnetwork-nva-hubPazSubnetAddressPrefix: 100.60.1.0/24 + + var-hubnetwork-nva-hubDevIntSubnetName: DevIntSubnet + var-hubnetwork-nva-hubDevIntSubnetAddressPrefix: 10.18.0.64/27 + + var-hubnetwork-nva-hubProdIntSubnetName: PrdIntSubnet + var-hubnetwork-nva-hubProdIntSubnetAddressPrefix: 10.18.0.32/27 + + var-hubnetwork-nva-hubMrzIntSubnetName: MrzSubnet + var-hubnetwork-nva-hubMrzIntSubnetAddressPrefix: 10.18.0.96/27 + + var-hubnetwork-nva-hubHASubnetName: HASubnet + var-hubnetwork-nva-hubHASubnetAddressPrefix: 10.18.0.128/28 + + var-hubnetwork-nva-hubGatewaySubnetPrefix: 10.18.1.0/27 + + var-hubnetwork-nva-hubBastionSubnetAddressPrefix: 192.168.0.0/24 + + ## Hub Networking - Firewall Virtual Appliances + var-hubnetwork-nva-deployFirewallVMs: false + var-hubnetwork-nva-useFortigateFW: false + + ### Hub Networking - Firewall Virtual Appliances - For Non-production Traffic + var-hubnetwork-nva-fwDevILBName: pubsecDevFWILB + var-hubnetwork-nva-fwDevVMSku: Standard_D8s_v4 + var-hubnetwork-nva-fwDevVM1Name: pubsecDevFW1 + var-hubnetwork-nva-fwDevVM2Name: pubsecDevFW2 + var-hubnetwork-nva-fwDevILBExternalFacingIP: 100.60.0.7 + var-hubnetwork-nva-fwDevVM1ExternalFacingIP: 100.60.0.8 + var-hubnetwork-nva-fwDevVM2ExternalFacingIP: 100.60.0.9 + var-hubnetwork-nva-fwDevVM1MrzIntIP: 10.18.0.104 + var-hubnetwork-nva-fwDevVM2MrzIntIP: 10.18.0.105 + var-hubnetwork-nva-fwDevILBDevIntIP: 10.18.0.68 + var-hubnetwork-nva-fwDevVM1DevIntIP: 10.18.0.69 + var-hubnetwork-nva-fwDevVM2DevIntIP: 10.18.0.70 + var-hubnetwork-nva-fwDevVM1HAIP: 10.18.0.134 + var-hubnetwork-nva-fwDevVM2HAIP: 10.18.0.135 + + ### Hub Networking - Firewall Virtual Appliances - For Production Traffic + var-hubnetwork-nva-fwProdILBName: pubsecProdFWILB + var-hubnetwork-nva-fwProdVMSku: Standard_F8s_v2 + var-hubnetwork-nva-fwProdVM1Name: pubsecProdFW1 + var-hubnetwork-nva-fwProdVM2Name: pubsecProdFW2 + var-hubnetwork-nva-fwProdILBExternalFacingIP: 100.60.0.4 + var-hubnetwork-nva-fwProdVM1ExternalFacingIP: 100.60.0.5 + var-hubnetwork-nva-fwProdVM2ExternalFacingIP: 100.60.0.6 + var-hubnetwork-nva-fwProdVM1MrzIntIP: 10.18.0.101 + var-hubnetwork-nva-fwProdVM2MrzIntIP: 10.18.0.102 + var-hubnetwork-nva-fwProdILBPrdIntIP: 10.18.0.36 + var-hubnetwork-nva-fwProdVM1PrdIntIP: 10.18.0.37 + var-hubnetwork-nva-fwProdVM2PrdIntIP: 10.18.0.38 + var-hubnetwork-nva-fwProdVM1HAIP: 10.18.0.132 + var-hubnetwork-nva-fwProdVM2HAIP: 10.18.0.133 \ No newline at end of file diff --git a/config/variables/README.md b/config/variables/README.md new file mode 100644 index 00000000..e6f97d8c --- /dev/null +++ b/config/variables/README.md @@ -0,0 +1,19 @@ +# Environment configuration files + +## Disclaimer + +Copyright (c) Microsoft Corporation. + +Licensed under the MIT license. + +THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. + +## Overview + +Create and maintain your environment configuration (variables) files in this directory. + +During execution, each top-level pipeline definition loads two variable files using the `import` directive in the `variables:` section of the pipeline definition. + +The first variable file loaded is `common.yml`. This file contains variable settings that are common through all environments (org-branch combinations). + +The second variable file loaded is determined using a combination of the Azure DevOps organization name and the repository branch the pipeline is operating in. For example, if your Azure DevOps organization name is `contoso`, then pipelines running on the `main` branch would load the variable file named `contoso-main` while pipelines running on the `user-feature` branch would load the variable file named `contoso-user-feature`. This allows you to separate configuration values by environment. Note that any variables re-declared in the environment variable file will override the original definition from the `common.yml` file based on their loading order. diff --git a/config/variables/common.yml b/config/variables/common.yml new file mode 100644 index 00000000..7859ad5c --- /dev/null +++ b/config/variables/common.yml @@ -0,0 +1,19 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +variables: + + deploymentRegion: canadacentral + serviceConnection: spn-azure-platform-ops + vmImage: ubuntu-latest + deployOperation: create # valid options: 'create', 'what-if' + subscriptionsPathFromRoot: 'config/subscriptions' + var-bashPreInjectScript: 'set -E; function catch { echo "##vso[task.logissue type=error]Caller: $(caller), LineNo: $LINENO, Command: $BASH_COMMAND" ; exit 1 ; } ; echo ; echo "Current working directory: $(pwd)" ; echo ; trap catch ERR' + var-bashPostInjectScript: ':' + var-TriggerSubscriptionDeployOn: 'A' # A=added, M=modified, D=deleted diff --git a/config/variables/devopsincanada-skeeler.yml b/config/variables/devopsincanada-skeeler.yml new file mode 100644 index 00000000..81c1a593 --- /dev/null +++ b/config/variables/devopsincanada-skeeler.yml @@ -0,0 +1,281 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +# Environment YAML files can be used to supplement +# the variables specified in 'config/variables/common.yml'. You can: +# * Override existing common-vars.yml variable value settings, and +# * Create new variable values not present in common-vars.yml +# +# The naming convention for these YAML files is: +# {organization}-{branch}.yml +# +# where {organization} is the organization variable from the +# common.yml file +# and {branch} is the Azure Repos branch name used by the +# currently executing pipeline. + +variables: + # Management Groups + var-parentManagementGroupId: 80fabd0e-d9f9-4261-97f0-37b9244e12c1 + var-topLevelManagementGroupName: pubsec + + # Logging + var-logging-managementGroupId: pubsecPlatform + var-logging-subscriptionId: 1a7025c7-f492-4eb9-9cf6-12c7889e4dfd + var-logging-logAnalyticsResourceGroupName: pubsec-central-logging-rg + var-logging-logAnalyticsWorkspaceName: log-analytics-workspace + var-logging-logAnalyticsAutomationAccountName: automation-account + var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix: pubsecnsg + var-logging-serviceHealthAlerts: > + { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "skeeler@m365incanada.onmicrosoft.com" ], + "email": [ "skeeler@m365incanada.onmicrosoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + }, + "actionGroupName": "ALZ action group", + "actionGroupShortName": "alz-alert", + "alertRuleName": "ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + var-logging-securityCenter: > + { + "email": "skeeler@m365incanada.onmicrosoft.com", + "phone": "5555555555" + } + var-logging-subscriptionRoleAssignments: > + [ + { + "comments": "Built-in Contributor Role", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "ae712826-a993-4819-a167-4045d965b5a5" + ] + } + ] + var-logging-subscriptionBudget: > + { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ "skeeler@m365incanada.onmicrosoft.com" ] + } + var-logging-subscriptionTags: > + { + "ISSO": "isso-tbd" + } + var-logging-resourceTags: > + { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + + # Hub Networking + var-hubnetwork-managementGroupId: pubsecPlatform + var-hubnetwork-subscriptionId: 1a7025c7-f492-4eb9-9cf6-12c7889e4dfd + var-hubnetwork-serviceHealthAlerts: > + { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "skeeler@m365incanada.onmicrosoft.com" ], + "email": [ "skeeler@m365incanada.onmicrosoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + }, + "actionGroupName": "ALZ action group", + "actionGroupShortName": "alz-alert", + "alertRuleName": "ALZ alert rule", + "alertRuleDescription": "Alert rule for Azure Landing Zone" + } + var-hubnetwork-securityCenter: > + { + "email": "skeeler@m365incanada.onmicrosoft.com", + "phone": "5555555555" + } + var-hubnetwork-subscriptionRoleAssignments: > + [ + { + "comments": "Built-in Contributor Role", + "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", + "securityGroupObjectIds": [ + "ae712826-a993-4819-a167-4045d965b5a5" + ] + } + ] + var-hubnetwork-subscriptionBudget: > + { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ "skeeler@m365incanada.onmicrosoft.com" ] + } + var-hubnetwork-subscriptionTags: > + { + "ISSO": "isso-tbd" + } + var-hubnetwork-resourceTags: > + { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + + ## Hub Networking - Private Dns Zones + var-hubnetwork-deployPrivateDnsZones: false + var-hubnetwork-rgPrivateDnsZonesName: pubsec-dns-rg + + ## Hub Networking - DDOS + var-hubnetwork-deployDdosStandard: false + var-hubnetwork-rgDdosName: pubsec-ddos-rg + var-hubnetwork-ddosPlanName: ddos-plan + + ## Hub Networking - Public Zone + var-hubnetwork-rgPazName: pubsec-public-access-zone-rg + + ## Hub Networking - Management Restricted Zone Virtual Network + var-hubnetwork-rgMrzName: pubsec-management-restricted-zone-rg + var-hubnetwork-mrzVnetName: management-restricted-vnet + var-hubnetwork-mrzVnetAddressPrefixRFC1918: 10.18.4.0/22 + + var-hubnetwork-mrzMazSubnetName: MazSubnet + var-hubnetwork-mrzMazSubnetAddressPrefix: 10.18.4.0/25 + + var-hubnetwork-mrzInfSubnetName: InfSubnet + var-hubnetwork-mrzInfSubnetAddressPrefix: 10.18.4.128/25 + + var-hubnetwork-mrzSecSubnetName: SecSubnet + var-hubnetwork-mrzSecSubnetAddressPrefix: 10.18.5.0/26 + + var-hubnetwork-mrzLogSubnetName: LogSubnet + var-hubnetwork-mrzLogSubnetAddressPrefix: 10.18.5.64/26 + + var-hubnetwork-mrzMgmtSubnetName: MgmtSubnet + var-hubnetwork-mrzMgmtSubnetAddressPrefix: 10.18.5.128/26 + + var-hubnetwork-bastionName: bastion + + #################################################################################### + ### Hub Networking with Azure Firewall ### + #################################################################################### + var-hubnetwork-azfw-rgPolicyName: pubsec-azure-firewall-policy-rg + var-hubnetwork-azfw-policyName: pubsecAzureFirewallPolicy + + var-hubnetwork-azfw-rgHubName: pubsec-hub-networking-rg + var-hubnetwork-azfw-hubVnetName: hub-vnet + var-hubnetwork-azfw-hubVnetAddressPrefixRFC1918: 10.18.0.0/22 + var-hubnetwork-azfw-hubVnetAddressPrefixRFC6598: 100.60.0.0/16 + var-hubnetwork-azfw-hubVnetAddressPrefixBastion: 192.168.0.0/16 + + var-hubnetwork-azfw-hubPazSubnetName: PAZSubnet + var-hubnetwork-azfw-hubPazSubnetAddressPrefix: 100.60.1.0/24 + + var-hubnetwork-azfw-hubGatewaySubnetPrefix: 10.18.0.0/27 + var-hubnetwork-azfw-hubAzureFirewallSubnetAddressPrefix: 10.18.1.0/24 + var-hubnetwork-azfw-hubAzureFirewallManagementSubnetAddressPrefix: 10.18.2.0/26 + var-hubnetwork-azfw-hubBastionSubnetAddressPrefix: 192.168.0.0/24 + + var-hubnetwork-azfw-azureFirewallName: pubsecAzureFirewall + var-hubnetwork-azfw-azureFirewallZones: '["1", "2", "3"]' + var-hubnetwork-azfw-azureFirewallForcedTunnelingEnabled: false + var-hubnetwork-azfw-azureFirewallForcedTunnelingNextHop: 10.17.1.4 + + #################################################################################### + ### Hub Networking with Fortinet Firewalls ### + #################################################################################### + + ## Hub Networking - Core Virtual Network + var-hubnetwork-nva-rgHubName: pubsec-hub-networking-rg + var-hubnetwork-nva-hubVnetName: hub-vnet + var-hubnetwork-nva-hubVnetAddressPrefixRFC1918: 10.18.0.0/22 + var-hubnetwork-nva-hubVnetAddressPrefixRFC6598: 100.60.0.0/16 + var-hubnetwork-nva-hubVnetAddressPrefixBastion: 192.168.0.0/16 + + var-hubnetwork-nva-hubEanSubnetName: EanSubnet + var-hubnetwork-nva-hubEanSubnetAddressPrefix: 10.18.0.0/27 + + var-hubnetwork-nva-hubPublicSubnetName: PublicSubnet + var-hubnetwork-nva-hubPublicSubnetAddressPrefix: 100.60.0.0/24 + + var-hubnetwork-nva-hubPazSubnetName: PAZSubnet + var-hubnetwork-nva-hubPazSubnetAddressPrefix: 100.60.1.0/24 + + var-hubnetwork-nva-hubDevIntSubnetName: DevIntSubnet + var-hubnetwork-nva-hubDevIntSubnetAddressPrefix: 10.18.0.64/27 + + var-hubnetwork-nva-hubProdIntSubnetName: PrdIntSubnet + var-hubnetwork-nva-hubProdIntSubnetAddressPrefix: 10.18.0.32/27 + + var-hubnetwork-nva-hubMrzIntSubnetName: MrzSubnet + var-hubnetwork-nva-hubMrzIntSubnetAddressPrefix: 10.18.0.96/27 + + var-hubnetwork-nva-hubHASubnetName: HASubnet + var-hubnetwork-nva-hubHASubnetAddressPrefix: 10.18.0.128/28 + + var-hubnetwork-nva-hubGatewaySubnetPrefix: 10.18.1.0/27 + + var-hubnetwork-nva-hubBastionSubnetAddressPrefix: 192.168.0.0/24 + + ## Hub Networking - Firewall Virtual Appliances + var-hubnetwork-nva-deployFirewallVMs: false + var-hubnetwork-nva-useFortigateFW: false + + ### Hub Networking - Firewall Virtual Appliances - For Non-production Traffic + var-hubnetwork-nva-fwDevILBName: pubsecDevFWILB + var-hubnetwork-nva-fwDevVMSku: Standard_D8s_v4 + var-hubnetwork-nva-fwDevVM1Name: pubsecDevFW1 + var-hubnetwork-nva-fwDevVM2Name: pubsecDevFW2 + var-hubnetwork-nva-fwDevILBExternalFacingIP: 100.60.0.7 + var-hubnetwork-nva-fwDevVM1ExternalFacingIP: 100.60.0.8 + var-hubnetwork-nva-fwDevVM2ExternalFacingIP: 100.60.0.9 + var-hubnetwork-nva-fwDevVM1MrzIntIP: 10.18.0.104 + var-hubnetwork-nva-fwDevVM2MrzIntIP: 10.18.0.105 + var-hubnetwork-nva-fwDevILBDevIntIP: 10.18.0.68 + var-hubnetwork-nva-fwDevVM1DevIntIP: 10.18.0.69 + var-hubnetwork-nva-fwDevVM2DevIntIP: 10.18.0.70 + var-hubnetwork-nva-fwDevVM1HAIP: 10.18.0.134 + var-hubnetwork-nva-fwDevVM2HAIP: 10.18.0.135 + + ### Hub Networking - Firewall Virtual Appliances - For Production Traffic + var-hubnetwork-nva-fwProdILBName: pubsecProdFWILB + var-hubnetwork-nva-fwProdVMSku: Standard_F8s_v2 + var-hubnetwork-nva-fwProdVM1Name: pubsecProdFW1 + var-hubnetwork-nva-fwProdVM2Name: pubsecProdFW2 + var-hubnetwork-nva-fwProdILBExternalFacingIP: 100.60.0.4 + var-hubnetwork-nva-fwProdVM1ExternalFacingIP: 100.60.0.5 + var-hubnetwork-nva-fwProdVM2ExternalFacingIP: 100.60.0.6 + var-hubnetwork-nva-fwProdVM1MrzIntIP: 10.18.0.101 + var-hubnetwork-nva-fwProdVM2MrzIntIP: 10.18.0.102 + var-hubnetwork-nva-fwProdILBPrdIntIP: 10.18.0.36 + var-hubnetwork-nva-fwProdVM1PrdIntIP: 10.18.0.37 + var-hubnetwork-nva-fwProdVM2PrdIntIP: 10.18.0.38 + var-hubnetwork-nva-fwProdVM1HAIP: 10.18.0.132 + var-hubnetwork-nva-fwProdVM2HAIP: 10.18.0.133 \ No newline at end of file diff --git a/isv/SampleGenericSubscription/main.bicep b/isv/SampleGenericSubscription/main.bicep new file mode 100644 index 00000000..d4453553 --- /dev/null +++ b/isv/SampleGenericSubscription/main.bicep @@ -0,0 +1,64 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +// Resource Groups +param rgVnetName string = 'networkingRG' + +// VNET +param vnetName string = 'vnet' +param vnetAddressSpace string = '10.0.0.0/16' + +// Presentation Zone (PAZ) Subnet +param subnetPresentationName string = 'paz' +param subnetPresentationPrefix string = '10.0.1.0/24' + +// Internal Foundational Elements (OZ) Subnet +param subnetFoundationalElementsName string = 'oz' +param subnetFoundationalElementsPrefix string = '10.0.2.0/24' + +// Application zone (RZ) Subnet +param subnetApplicationName string = 'rz' +param subnetApplicationPrefix string = '10.0.3.0/24' + +// Data Zone (HRZ) Subnet +param subnetDataName string = 'hrz' +param subnetDataPrefix string = '10.0.4.0/24' + +// Resource Groups +resource rgVnet 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: rgVnetName + location: deployment().location +} + +// Virtual Network +module vnet 'networking.bicep' = { + name: 'vnet' + scope: resourceGroup(rgVnet.name) + params: { + vnetName: vnetName + vnetAddressSpace: vnetAddressSpace + subnetFoundationalElementsName: subnetFoundationalElementsName + subnetFoundationalElementsPrefix: subnetFoundationalElementsPrefix + subnetPresentationName: subnetPresentationName + subnetPresentationPrefix: subnetPresentationPrefix + subnetApplicationName: subnetApplicationName + subnetApplicationPrefix: subnetApplicationPrefix + subnetDataName: subnetDataName + subnetDataPrefix: subnetDataPrefix + } +} + +// Outputs +output vnetId string = vnet.outputs.vnetId +output foundationalElementSubnetId string = vnet.outputs.foundationalElementSubnetId +output presentationSubnetId string = vnet.outputs.presentationSubnetId +output applicationSubnetId string = vnet.outputs.applicationSubnetId +output dataSubnetId string = vnet.outputs.dataSubnetId diff --git a/isv/SampleGenericSubscription/networking.bicep b/isv/SampleGenericSubscription/networking.bicep new file mode 100644 index 00000000..123ba507 --- /dev/null +++ b/isv/SampleGenericSubscription/networking.bicep @@ -0,0 +1,167 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// VNET +param vnetName string +param vnetAddressSpace string + +// Presentation Zone (PAZ) Subnet +param subnetPresentationName string +param subnetPresentationPrefix string + +// Internal Foundational Elements (OZ) Subnet +param subnetFoundationalElementsName string +param subnetFoundationalElementsPrefix string + +// Application zone (RZ) Subnet +param subnetApplicationName string +param subnetApplicationPrefix string + +// Data Zone (HRZ) Subnet +param subnetDataName string +param subnetDataPrefix string + +// Network Security Groups +resource nsgFoundationalElements 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: '${subnetFoundationalElementsName}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +resource nsgPresentation 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: '${subnetPresentationName}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +resource nsgApplication 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: '${subnetApplicationName}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +resource nsgData 'Microsoft.Network/networkSecurityGroups@2020-06-01' = { + name: '${subnetDataName}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +// Route Tables +resource udrFoundationalElements 'Microsoft.Network/routeTables@2020-06-01' = { + name: '${subnetFoundationalElementsName}Udr' + location: resourceGroup().location + properties: { + routes: [ + ] + } +} + +resource udrPresentation 'Microsoft.Network/routeTables@2020-06-01' = { + name: '${subnetPresentationName}Udr' + location: resourceGroup().location + properties: { + routes: [ + ] + } +} + +resource udrApplication 'Microsoft.Network/routeTables@2020-06-01' = { + name: '${subnetApplicationName}Udr' + location: resourceGroup().location + properties: { + routes: [ + ] + } +} + +resource udrData 'Microsoft.Network/routeTables@2020-06-01' = { + name: '${subnetDataName}Udr' + location: resourceGroup().location + properties: { + routes: [ + ] + } +} + +// Virtual Network +resource vnetNew 'Microsoft.Network/virtualNetworks@2020-06-01' = { + name: vnetName + location: resourceGroup().location + properties: { + addressSpace: { + addressPrefixes: [ + vnetAddressSpace + ] + } + subnets: [ + { + name: subnetFoundationalElementsName + properties: { + addressPrefix: subnetFoundationalElementsPrefix + routeTable: { + id: udrFoundationalElements.id + } + networkSecurityGroup: { + id: nsgFoundationalElements.id + } + } + } + { + name: subnetPresentationName + properties: { + addressPrefix: subnetPresentationPrefix + routeTable: { + id: udrPresentation.id + } + networkSecurityGroup: { + id: nsgPresentation.id + } + } + } + { + name: subnetApplicationName + properties: { + addressPrefix: subnetApplicationPrefix + routeTable: { + id: udrApplication.id + } + networkSecurityGroup: { + id: nsgApplication.id + } + } + } + { + name: subnetDataName + properties: { + addressPrefix: subnetDataPrefix + routeTable: { + id: udrData.id + } + networkSecurityGroup: { + id: nsgData.id + } + } + } + ] + } +} + +output vnetId string = vnetNew.id +output foundationalElementSubnetId string = '${vnetName}/subnets/${subnetFoundationalElementsName}' +output presentationSubnetId string = '${vnetName}/subnets/${subnetPresentationName}' +output applicationSubnetId string = '${vnetName}/subnets/${subnetApplicationName}' +output dataSubnetId string = '${vnetName}/subnets/${subnetDataName}' diff --git a/isv/SampleGenericSubscription/readme.md b/isv/SampleGenericSubscription/readme.md new file mode 100644 index 00000000..2b1a857a --- /dev/null +++ b/isv/SampleGenericSubscription/readme.md @@ -0,0 +1,9 @@ +## Deployment + +Use Azure CLI to deploy the generic subscription landing zone. + +Replace ___SUBSCRIPTION_ID___ with the Subscription ID Guid. + +```bash +az deployment sub create --subscription ___SUBSCRIPTION_ID___ --template-file main.bicep -l canadacentral +``` \ No newline at end of file diff --git a/landingzones/lz-generic-subscription/main.bicep b/landingzones/lz-generic-subscription/main.bicep new file mode 100644 index 00000000..764bd7a6 --- /dev/null +++ b/landingzones/lz-generic-subscription/main.bicep @@ -0,0 +1,451 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +/* + +Generic Subscription Landing Zone archetype provides the basic Azure subscription configuration that includes: + +* Service Health alerts (optional) +* Azure Automation Account +* Azure Virtual Network +* Role-based access control for Owner, Contributor, Reader & Application Owner (custom role) +* Integration with Azure Cost Management for Subscription-scoped budget +* Integration with Azure Security Center +* Integration to Hub Virtual Network (optional) +* Support for Network Virtual Appliance (i.e. Fortinet, Azure Firewall) in the Hub Network (if integrated to hub network) +* Support for Azure Bastion in the Hub (if integrated to hub network) + +This landing is typically used for: + +* Lift & Shift Azure Migrations +* COTS (Commercial off-the-shelf) products +* General deployment where Application teams own and operate the application stack +* Evaluating/prototying new application designs + +*/ + +targetScope = 'subscription' + +// Service Health +// Example (JSON) +// ----------------------------- +// "serviceHealthAlerts": { +// "value": { +// "incidentTypes": [ "Incident", "Security", "Maintenance", "Information", "ActionRequired" ], +// "regions": [ "Global", "Canada East", "Canada Central" ], +// "receivers": { +// "app": [ "email-1@company.com", "email-2@company.com" ], +// "email": [ "email-1@company.com", "email-3@company.com", "email-4@company.com" ], +// "sms": [ { "countryCode": "1", "phoneNumber": "1234567890" }, { "countryCode": "1", "phoneNumber": "0987654321" } ], +// "voice": [ { "countryCode": "1", "phoneNumber": "1234567890" } ] +// }, +// "actionGroupName": "ALZ action group", +// "actionGroupShortName": "alz-alert", +// "alertRuleName": "ALZ alert rule", +// "alertRuleDescription": "Alert rule for Azure Landing Zone" +// } +// } +@description('Service Health alerts') +param serviceHealthAlerts object = {} + +// Log Analytics +@description('Log Analytics Resource Id to integrate Azure Security Center.') +param logAnalyticsWorkspaceResourceId string + +// Azure Security Center +// Example (JSON) +// ----------------------------- +// "securityCenter": { +// "value": { +// "email": "alzcanadapubsec@microsoft.com", +// "phone": "5555555555" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// email: 'alzcanadapubsec@microsoft.com' +// phone: '5555555555' +// } +@description('Security Center configuration. It includes email and phone.') +param securityCenter object + +// Subscription Role Assignments +// Example (JSON) +// ----------------------------- +// [ +// { +// "comments": "Built-in Contributor Role", +// "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", +// "securityGroupObjectIds": [ +// "38f33f7e-a471-4630-8ce9-c6653495a2ee" +// ] +// } +// ] + +// Example (Bicep) +// ----------------------------- +// [ +// { +// comments: 'Built-In Contributor Role' +// roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' +// securityGroupObjectIds: [ +// '38f33f7e-a471-4630-8ce9-c6653495a2ee' +// ] +// } +// ] +@description('Array of role assignments at subscription scope. The array will contain an object with comments, roleDefinitionId and array of securityGroupObjectIds.') +param subscriptionRoleAssignments array = [] + +// Subscription Budget +// Example (JSON) +// --------------------------- +// "subscriptionBudget": { +// "value": { +// "createBudget": false, +// "name": "MonthlySubscriptionBudget", +// "amount": 1000, +// "timeGrain": "Monthly", +// "contactEmails": [ "alzcanadapubsec@microsoft.com" ] +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// createBudget: true +// name: 'MonthlySubscriptionBudget' +// amount: 1000 +// timeGrain: 'Monthly' +// contactEmails: [ +// 'alzcanadapubsec@microsoft.com' +// ] +// } +@description('Subscription budget configuration containing createBudget flag, name, amount, timeGrain and array of contactEmails') +param subscriptionBudget object + +// Tags +// Example (JSON) +// ----------------------------- +// "subscriptionTags": { +// "value": { +// "ISSO": "isso-tag" +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// ISSO: 'isso-tag' +// } +@description('A set of key/value pairs of tags assigned to the subscription.') +param subscriptionTags object + +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact: 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Resource Groups +// Example (JSON) +// ----------------------------- +// "resourceGroups": { +// "value": { +// "automation": "rgAutomation", +// "networking": "rgVnet", +// "networkWatcher": "NetworkWatcherRG" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// automation: 'rgAutomation092021W3' +// networking: 'rgVnet092021W3' +// networkWatcher: 'NetworkWatcherRG' +// } +@description('Resource groups required for the achetype. It includes automation, networking and networkWatcher.') +param resourceGroups object + +// Networking +// Example (JSON) +// ----------------------------- +// "hubNetwork": { +// "value": { +// "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", +// "rfc1918IPRange": "10.18.0.0/22", +// "rfc6598IPRange": "100.60.0.0/16", +// "egressVirtualApplianceIp": "10.18.0.36" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// virtualNetworkId: '/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet' +// rfc1918IPRange: '10.18.0.0/22' +// rfc6598IPRange: '100.60.0.0/16' +// egressVirtualApplianceIp: '10.18.0.36' +// } +@description('Hub Network configuration that includes virtualNetworkId, rfc1918IPRange, rfc6598IPRange and egressVirtualApplianceIp.') +param hubNetwork object + +// Example (JSON) +// ----------------------------- +// "network": { +// "value": { +// "deployVnet": true, +// +// "peerToHubVirtualNetwork": true, +// "useRemoteGateway": false, +// +// "name": "vnet", +// "dnsServers": [ +// "10.18.1.4" +// ], +// "addressPrefixes": [ +// "10.2.0.0/16" +// ], +// "subnets": { +// "oz": { +// "comments": "Foundational Elements Zone (OZ)", +// "name": "oz", +// "addressPrefix": "10.2.1.0/25", +// "nsg": { +// "enabled": true +// }, +// "udr": { +// "enabled": true +// } +// }, +// "paz": { +// "comments": "Presentation Zone (PAZ)", +// "name": "paz", +// "addressPrefix": "10.2.2.0/25", +// "nsg": { +// "enabled": true +// }, +// "udr": { +// "enabled": true +// } +// }, +// "rz": { +// "comments": "Application Zone (RZ)", +// "name": "rz", +// "addressPrefix": "10.2.3.0/25", +// "nsg": { +// "enabled": true +// }, +// "udr": { +// "enabled": true +// } +// }, +// "hrz": { +// "comments": "Data Zone (HRZ)", +// "name": "hrz", +// "addressPrefix": "10.2.4.0/25", +// "nsg": { +// "enabled": true +// }, +// "udr": { +// "enabled": true +// } +// }, +// "optional": [ +// { +// "comments": "App Service", +// "name": "appservice", +// "addressPrefix": "10.2.5.0/25", +// "nsg": { +// "enabled": false +// }, +// "udr": { +// "enabled": false +// }, +// "delegations": { +// "serviceName": "Microsoft.Web/serverFarms" +// } +// } +// ] +// } +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// deployVnet: true +// +// peerToHubVirtualNetwork: true +// useRemoteGateway: false +// +// name: 'vnet' +// dnsServers: [ +// '10.18.1.4' +// ] +// addressPrefixes: [ +// '10.2.0.0/16' +// ] +// subnets: { +// oz: { +// comments: 'Foundational Elements Zone (OZ)' +// name: 'oz' +// addressPrefix: '10.2.1.0/25' +// nsg: { +// enabled: true +// } +// udr: { +// enabled: true +// } +// } +// paz: { +// comments: 'Presentation Zone (PAZ)' +// name: 'paz' +// addressPrefix: '10.2.2.0/25' +// nsg: { +// enabled: true +// } +// udr: { +// enabled: true +// } +// } +// rz: { +// comments: 'Application Zone (RZ)' +// name: 'rz' +// addressPrefix: '10.2.3.0/25' +// nsg: { +// enabled: true +// } +// udr: { +// enabled: true +// } +// } +// hrz: { +// comments: 'Data Zone (HRZ)' +// name: 'hrz' +// addressPrefix: '10.2.4.0/25' +// nsg: { +// enabled: true +// } +// udr: { +// enabled: true +// } +// } +// optional: [ +// { +// comments: 'App Service' +// name: 'appservice' +// addressPrefix: '10.2.5.0/25' +// nsg: { +// enabled: false +// } +// udr: { +// enabled: false +// } +// delegations: { +// 'serviceName: 'Microsoft.Web/serverFarms' +// } +// } +// ] +// } +// } +@description('Network configuration for the spoke virtual network. It includes name, dnsServers, address spaces, vnet peering and subnets.') +param network object + +// Automation +@description('Azure Automation Account name.') +param automationAccountName string + +/* + Scaffold the subscription which includes: + * Azure Security Center - Enable Azure Defender (all available options) + * Azure Security Center - Configure Log Analytics Workspace + * Azure Security Center - Configure Security Alert Contact + * Service Health Alerts + * Role Assignments to Security Groups + * Subscription Budget + * Subscription Tags +*/ +module subScaffold '../scaffold-subscription.bicep' = { + name: 'configure-subscription' + scope: subscription() + params: { + serviceHealthAlerts: serviceHealthAlerts + subscriptionRoleAssignments: subscriptionRoleAssignments + subscriptionBudget: subscriptionBudget + subscriptionTags: subscriptionTags + resourceTags: resourceTags + + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + securityCenter: securityCenter + } +} + +// Create Network Watcher Resource Group +resource rgNetworkWatcher 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.networkWatcher + location: deployment().location + tags: resourceTags +} + +// Create Virtual Network Resource Group - only if Virtual Network is being deployed +resource rgVnet 'Microsoft.Resources/resourceGroups@2020-06-01' = if (network.deployVnet) { + name: network.deployVnet ? resourceGroups.networking : 'placeholder' + location: deployment().location + tags: resourceTags +} + +// Create Azure Automation Resource Group +resource rgAutomation 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.automation + location: deployment().location + tags: resourceTags +} + +// Create automation account +module automationAccount '../../azresources/automation/automation-account.bicep' = { + name: 'deploy-automation-account' + scope: rgAutomation + params: { + automationAccountName: automationAccountName + tags: resourceTags + } +} + +// Create & configure virtaual network - only if Virtual Network is being deployed +module vnet 'networking.bicep' = if (network.deployVnet) { + name: 'deploy-networking' + scope: resourceGroup(rgVnet.name) + params: { + hubNetwork: hubNetwork + network: network + } +} diff --git a/landingzones/lz-generic-subscription/main.parameters-sample.json b/landingzones/lz-generic-subscription/main.parameters-sample.json new file mode 100644 index 00000000..225db3a6 --- /dev/null +++ b/landingzones/lz-generic-subscription/main.parameters-sample.json @@ -0,0 +1,152 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + } + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments":{ + "value": [] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ "alzcanadapubsec@microsoft.com" ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "rgAutomation092021W3", + "networking": "rgVnet092021W3", + "networkWatcher": "NetworkWatcherRG" + } + }, + "automationAccountName": { + "value": "automation" + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.1.4" + } + }, + "network": { + "value": { + "deployVnet": true, + + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + + "name": "vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.2.0.0/16" + ], + "subnets": { + "oz": { + "comments": "Foundational Elements Zone (OZ)", + "name": "oz", + "addressPrefix": "10.2.1.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "paz": { + "comments": "Presentation Zone (PAZ)", + "name": "paz", + "addressPrefix": "10.2.2.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "rz": { + "comments": "Application Zone (RZ)", + "name": "rz", + "addressPrefix": "10.2.3.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "hrz": { + "comments": "Data Zone (HRZ)", + "name": "hrz", + "addressPrefix": "10.2.4.0/25", + "nsg": { + "enabled": true + }, + "udr": { + "enabled": true + } + }, + "optional": [ + { + "comments": "App Service", + "name": "appservice", + "addressPrefix": "10.2.5.0/25", + "nsg": { + "enabled": false + }, + "udr": { + "enabled": false + }, + "delegations": { + "serviceName": "Microsoft.Web/serverFarms" + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/landingzones/lz-generic-subscription/networking.bicep b/landingzones/lz-generic-subscription/networking.bicep new file mode 100644 index 00000000..c15d02ec --- /dev/null +++ b/landingzones/lz-generic-subscription/networking.bicep @@ -0,0 +1,330 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// Networking +// Example (JSON) +// ----------------------------- +// "hubNetwork": { +// "value": { +// "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", +// "rfc1918IPRange": "10.18.0.0/22", +// "rfc6598IPRange": "100.60.0.0/16", +// "egressVirtualApplianceIp": "10.18.0.36" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// virtualNetworkId: '/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet' +// rfc1918IPRange: '10.18.0.0/22' +// rfc6598IPRange: '100.60.0.0/16' +// egressVirtualApplianceIp: '10.18.0.36' +// } +@description('Hub Network configuration that includes virtualNetworkId, rfc1918IPRange, rfc6598IPRange and egressVirtualApplianceIp.') +param hubNetwork object + +// Example (JSON) +// ----------------------------- +// "network": { +// "value": { +// "deployVnet": true, +// +// "peerToHubVirtualNetwork": true, +// "useRemoteGateway": false, +// +// "name": "vnet", +// "dnsServers": [ +// "10.18.1.4" +// ], +// "addressPrefixes": [ +// "10.2.0.0/16" +// ], +// "subnets": { +// "oz": { +// "comments": "Foundational Elements Zone (OZ)", +// "name": "oz", +// "addressPrefix": "10.2.1.0/25", +// "nsg": { +// "enabled": true +// }, +// "udr": { +// "enabled": true +// } +// }, +// "paz": { +// "comments": "Presentation Zone (PAZ)", +// "name": "paz", +// "addressPrefix": "10.2.2.0/25", +// "nsg": { +// "enabled": true +// }, +// "udr": { +// "enabled": true +// } +// }, +// "rz": { +// "comments": "Application Zone (RZ)", +// "name": "rz", +// "addressPrefix": "10.2.3.0/25", +// "nsg": { +// "enabled": true +// }, +// "udr": { +// "enabled": true +// } +// }, +// "hrz": { +// "comments": "Data Zone (HRZ)", +// "name": "hrz", +// "addressPrefix": "10.2.4.0/25", +// "nsg": { +// "enabled": true +// }, +// "udr": { +// "enabled": true +// } +// }, +// "optional": [ +// { +// "comments": "App Service", +// "name": "appservice", +// "addressPrefix": "10.2.5.0/25", +// "nsg": { +// "enabled": false +// }, +// "udr": { +// "enabled": false +// }, +// "delegations": { +// "serviceName": "Microsoft.Web/serverFarms" +// } +// } +// ] +// } +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// deployVnet: true +// +// peerToHubVirtualNetwork: true +// useRemoteGateway: false +// +// name: 'vnet' +// dnsServers: [ +// '10.18.1.4' +// ] +// addressPrefixes: [ +// '10.2.0.0/16' +// ] +// subnets: { +// oz: { +// comments: 'Foundational Elements Zone (OZ)' +// name: 'oz' +// addressPrefix: '10.2.1.0/25' +// nsg: { +// enabled: true +// } +// udr: { +// enabled: true +// } +// } +// paz: { +// comments: 'Presentation Zone (PAZ)' +// name: 'paz' +// addressPrefix: '10.2.2.0/25' +// nsg: { +// enabled: true +// } +// udr: { +// enabled: true +// } +// } +// rz: { +// comments: 'Application Zone (RZ)' +// name: 'rz' +// addressPrefix: '10.2.3.0/25' +// nsg: { +// enabled: true +// } +// udr: { +// enabled: true +// } +// } +// hrz: { +// comments: 'Data Zone (HRZ)' +// name: 'hrz' +// addressPrefix: '10.2.4.0/25' +// nsg: { +// enabled: true +// } +// udr: { +// enabled: true +// } +// } +// optional: [ +// { +// comments: 'App Service' +// name: 'appservice' +// addressPrefix: '10.2.5.0/25' +// nsg: { +// enabled: false +// } +// udr: { +// enabled: false +// } +// delegations: { +// 'serviceName: 'Microsoft.Web/serverFarms' +// } +// } +// ] +// } +// } +@description('Network configuration for the spoke virtual network. It includes name, dnsServers, address spaces, vnet peering and subnets.') +param network object + +var hubVnetIdSplit = split(hubNetwork.virtualNetworkId, '/') + +var routesToHub = [ + // Force Routes to Hub IPs (RFC1918 range) via FW despite knowing that route via peering + { + name: 'PrdSpokesUdrHubRFC1918FWRoute' + properties: { + addressPrefix: hubNetwork.rfc1918IPRange + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } + // Force Routes to Hub IPs (CGNAT range) via FW despite knowing that route via peering + { + name: 'PrdSpokesUdrHubRFC6598FWRoute' + properties: { + addressPrefix: hubNetwork.rfc6598IPRange + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } + { + name: 'RouteToEgressFirewall' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } +] + +// Merge the required and optional subnets into a single array and use this array to create the resources +var requiredSubnets = [ + network.subnets.oz + network.subnets.paz + network.subnets.rz + network.subnets.hrz +] + +var allSubnets = union(requiredSubnets, network.subnets.optional) + +// Network Security Groups +resource nsg 'Microsoft.Network/networkSecurityGroups@2021-02-01' = [for subnet in allSubnets: if (subnet.nsg.enabled) { + name: '${subnet.name}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +}] + +// Route Tables +resource udr 'Microsoft.Network/routeTables@2021-02-01' = [for subnet in allSubnets: if (subnet.udr.enabled) { + name: '${subnet.name}Udr' + location: resourceGroup().location + properties: { + routes: network.peerToHubVirtualNetwork ? routesToHub : null + } +}] + +// Virtual Network +resource vnet 'Microsoft.Network/virtualNetworks@2021-02-01' = { + name: network.name + location: resourceGroup().location + properties: { + dhcpOptions: { + dnsServers: network.dnsServers + } + addressSpace: { + addressPrefixes: network.addressPrefixes + } + subnets: [for (subnet, i) in allSubnets: { + name: subnet.name + properties: { + addressPrefix: subnet.addressPrefix + networkSecurityGroup: (subnet.nsg.enabled) ? { + id: nsg[i].id + } : null + routeTable: (subnet.udr.enabled) ? { + id: udr[i].id + } : null + delegations: contains(subnet, 'delegations') ? [ + { + name: replace(subnet.delegations.serviceName, '/', '.') + properties: { + serviceName: subnet.delegations.serviceName + } + } + ] : null + } + }] + } +} + +// Virtual Network Peering - Spoke to Hub +module vnetPeeringSpokeToHub '../../azresources/network/vnet-peering.bicep' = if (network.peerToHubVirtualNetwork) { + name: 'deploy-vnet-peering-spoke-to-hub' + scope: resourceGroup() + params: { + peeringName: 'Hub-${vnet.name}-to-${last(hubVnetIdSplit)}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: vnet.name + targetVnetId: hubNetwork.virtualNetworkId + useRemoteGateways: network.useRemoteGateway + } +} + +// Virtual Network Peering - Hub to Spoke +// We must rescope the deployment to the subscription id & resource group of where the Hub VNET is located. +module vnetPeeringHubToSpoke '../../azresources/network/vnet-peering.bicep' = if (network.peerToHubVirtualNetwork) { + name: 'deploy-vnet-peering-${subscription().subscriptionId}' + // vnet id = /subscriptions/<>/resourceGroups/<>/providers/Microsoft.Network/virtualNetworks/<> + scope: resourceGroup(network.peerToHubVirtualNetwork ? hubVnetIdSplit[2] : '', network.peerToHubVirtualNetwork ? hubVnetIdSplit[4] : '') + params: { + peeringName: 'Spoke-${last(hubVnetIdSplit)}-to-${vnet.name}-${uniqueString(vnet.id)}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: last(hubVnetIdSplit) + targetVnetId: vnet.id + useRemoteGateways: false + } +} + +// Outputs +output vnetId string = vnet.id +output vnetName string = vnet.name +output vnetPeered bool = network.peerToHubVirtualNetwork + +output ozSubnetId string = '${vnet.id}/subnets/${network.subnets.oz.name}' +output pazSubnetId string = '${vnet.id}/subnets/${network.subnets.paz.name}' +output rzSubnetId string = '${vnet.id}/subnets/${network.subnets.rz.name}' +output hrzSubnetId string = '${vnet.id}/subnets/${network.subnets.hrz.name}' + +output optionalSubnets array = [for subnet in network.subnets.optional: { + 'id': '${vnet.id}/subnets/${subnet.name}' +}] diff --git a/landingzones/lz-healthcare/lz.bicep b/landingzones/lz-healthcare/lz.bicep new file mode 100644 index 00000000..35ab1920 --- /dev/null +++ b/landingzones/lz-healthcare/lz.bicep @@ -0,0 +1,821 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +// Security Contact Email Address +@description('Contact email address for security alerts.') +param securityContactEmail string + +// Tags +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact: 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Resource Groups +// Example (JSON) +// ----------------------------- +// "resourceGroups": { +// "value": { +// "automation": "healthAutomation", +// "compute": "healthCompute", +// "monitor": "healthMonitor", +// "networking": "healthNetworking", +// "networkWatcher": "NetworkWatcherRG", +// "security": "healthSecurity", +// "storage": "healthStorage" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// automation: 'healthAutomation' +// compute: 'healthCompute' +// monitor: 'healthMonitor' +// networking: 'healthNetworking' +// networkWatcher: 'NetworkWatcherRG' +// security: 'healthSecurity' +// storage: 'healthStorage' +// } +@description('Resource groups required for the achetype. It includes automation, compute, monitor, networking, networkWatcher, security and storage.') +param resourceGroups object + +@description('Boolean flag to determine whether customer managed keys are used. Default: false') +param useCMK bool = false + +// Azure Automation Account +// Example (JSON) +// ----------------------------- +// "automation": { +// "value": { +// "name": "healthAutomation" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// name: 'healthAutomation' +// } +@description('Azure Automation Account configuration. Includes name.') +param automation object + +// Azure Key Vault +// Example (JSON) +//----------------------------- +// "keyVault": { +// "value": { +// "secretExpiryInDays": 3650 +// } +// } + +// Example (Bicep) +//----------------------------- +// { +// secretExpiryInDays: 3650 +// } +@description('Azure Key Vault configuraiton. Includes secretExpiryInDays.') +param keyVault object + +// SQL Database +// ----------------------------- +// Example (JSON) +// "sqldb": { +// "value": { +// "enabled": true, +// "username": "azadmin" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// enabled: true +// username: 'azadmin' +// } +@description('SQL Database configuration. Includes enabled flag and username.') +param sqldb object + +// Synapse +// ----------------------------- +// Example (JSON) +// "synapse": { +// "value": { +// "username": "azadmin" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// username: 'azadmin' +// } +@description('Synapse Analytics configuration. Includes username.') +param synapse object + +// Networking +// Example (JSON) +// ----------------------------- +// "hubNetwork": { +// "value": { +// "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", +// "rfc1918IPRange": "10.18.0.0/22", +// "rfc6598IPRange": "100.60.0.0/16", +// "egressVirtualApplianceIp": "10.18.0.36", +// "privateDnsManagedByHub": true, +// "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", +// "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// virtualNetworkId: '/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet' +// rfc1918IPRange: '10.18.0.0/22' +// rfc6598IPRange: '100.60.0.0/16' +// egressVirtualApplianceIp: '10.18.0.36' +// privateDnsManagedByHub: true, +// privateDnsManagedByHubSubscriptionId: 'ed7f4eed-9010-4227-b115-2a5e37728f27', +// privateDnsManagedByHubResourceGroupName: 'pubsec-dns-rg' +// } +@description('Hub Network configuration that includes virtualNetworkId, rfc1918IPRange, rfc6598IPRange, egressVirtualApplianceIp, privateDnsManagedByHub flag, privateDnsManagedByHubSubscriptionId and privateDnsManagedByHubResourceGroupName.') +param hubNetwork object + +// Example (JSON) +// ----------------------------- +// "network": { +// "value": { +// "peerToHubVirtualNetwork": true, +// "useRemoteGateway": false, +// "name": "vnet", +// "dnsServers": [ +// "10.18.1.4" +// ], +// "addressPrefixes": [ +// "10.2.0.0/16" +// ], +// "subnets": { +// "oz": { +// "comments": "Foundational Elements Zone (OZ)", +// "name": "oz", +// "addressPrefix": "10.2.1.0/25" +// }, +// "paz": { +// "comments": "Presentation Zone (PAZ)", +// "name": "paz", +// "addressPrefix": "10.2.2.0/25" +// }, +// "rz": { +// "comments": "Application Zone (RZ)", +// "name": "rz", +// "addressPrefix": "10.2.3.0/25" +// }, +// "hrz": { +// "comments": "Data Zone (HRZ)", +// "name": "hrz", +// "addressPrefix": "10.2.4.0/25" +// }, +// "privateEndpoints": { +// "comments": "Private Endpoints Subnet", +// "name": "privateendpoints", +// "addressPrefix": "10.2.5.0/25" +// }, +// "databricksPublic": { +// "comments": "Databricks Public Delegated Subnet", +// "name": "databrickspublic", +// "addressPrefix": "10.2.6.0/25" +// }, +// "databricksPrivate": { +// "comments": "Databricks Private Delegated Subnet", +// "name": "databricksprivate", +// "addressPrefix": "10.2.7.0/25" +// }, +// "web": { +// "comments": "Azure Web App Delegated Subnet", +// "name": "webapp", +// "addressPrefix": "10.2.8.0/25" +// } +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// peerToHubVirtualNetwork: true +// useRemoteGateway: false +// name: 'vnet' +// dnsServers: [ +// '10.18.1.4' +// ] +// addressPrefixes: [ +// '10.2.0.0/16' +// ] +// subnets: { +// oz: { +// comments: 'Foundational Elements Zone (OZ)' +// name: 'oz' +// addressPrefix: '10.21.0/25' +// } +// paz: { +// comments: 'Presentation Zone (PAZ)' +// name: 'paz' +// addressPrefix: '10.22.0/25' +// } +// rz: { +// comments: 'Application Zone (RZ)' +// name: 'rz' +// addressPrefix: '10.2.3.0/25' +// } +// hrz: { +// comments: 'Data Zone (HRZ)' +// name: 'hrz' +// addressPrefix: '10.2.4.0/25' +// } +// databricksPublic: { +// comments: 'Databricks Public Delegated Subnet' +// name: 'databrickspublic' +// addressPrefix: '10.2.5.0/25' +// } +// databricksPrivate: { +// comments: 'Databricks Private Delegated Subnet' +// name: 'databricksprivate' +// addressPrefix: '10.2.6.0/25' +// } +// privateEndpoints: { +// comments: 'Private Endpoints Subnet' +// name: 'privateendpoints' +// addressPrefix: '10.2.7.0/25' +// } +// web: { +// comments: 'Azure Web App Delegated Subnet' +// name: 'webapp' +// addressPrefix: '10.2.8.0/25' +// } +// } +// } +@description('Network configuration. Includes peerToHubVirtualNetwork flag, useRemoteGateway flag, name, dnsServers, addressPrefixes and subnets (oz, paz, rz, hrz, privateEndpoints, databricksPublic, databricksPrivate, web) ') +param network object + +var sqldbPassword = '${uniqueString(rgStorage.id)}*${toUpper(uniqueString(sqldb.username))}' +var synapsePassword = '${uniqueString(rgCompute.id)}*${toUpper(uniqueString(synapse.username))}' + +var databricksName = 'databricks' +var databricksEgressLbName = 'egressLb' +var datalakeStorageName = 'datalake${uniqueString(rgStorage.id)}' +var amlMetaStorageName = 'amlmeta${uniqueString(rgCompute.id)}' +var akvName = 'akv${uniqueString(rgSecurity.id)}' +var sqlServerName = 'sqlserver${uniqueString(rgStorage.id)}' +var adfName = 'adf${uniqueString(rgCompute.id)}' +var amlName = 'aml${uniqueString(rgCompute.id)}' +var acrName = 'acr${uniqueString(rgStorage.id)}' +var aiName = 'ai${uniqueString(rgMonitor.id)}' +var storageLoggingName = 'salogging${uniqueString(rgStorage.id)}' +var synapseName = 'syn${uniqueString(rgMonitor.id)}' +var fhirName = 'fhir${uniqueString(rgCompute.id)}' +var azfuncStorageName = 'azfuncstg${uniqueString(rgCompute.id)}' +var azfuncName = 'azfunc${uniqueString(rgCompute.id)}' +var azfunhpName = 'azfunchp${uniqueString(rgCompute.id)}' +var stranalyticsName = 'strana${uniqueString(rgCompute.id)}' +var eventhubName = 'eventhub${uniqueString(rgCompute.id)}' + +//resource group deployments +resource rgNetworkWatcher 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.networkWatcher + location: deployment().location + tags: resourceTags +} + +resource rgAutomation 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.automation + location: deployment().location + tags: resourceTags +} + +resource rgVnet 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroups.networking + location: deployment().location + tags: resourceTags +} + +resource rgStorage 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.storage + location: deployment().location + tags: resourceTags +} + +resource rgCompute 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.compute + location: deployment().location + tags: resourceTags +} + +resource rgSecurity 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.security + location: deployment().location + tags: resourceTags +} + +resource rgMonitor 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.monitor + location: deployment().location + tags: resourceTags +} + +// Automation +module automationAccount '../../azresources/automation/automation-account.bicep' = { + name: 'deploy-automation-account' + scope: rgAutomation + params: { + automationAccountName: automation.name + tags: resourceTags + } +} + +// Prepare for CMK deployments +module deploymentScriptIdentity '../../azresources/iam/user-assigned-identity.bicep' = { + name: 'deploy-ds-managed-identity' + scope: rgAutomation + params: { + name: 'deployment-scripts' + } +} + +module rgStorageDeploymentScriptRBAC '../../azresources/iam/resourceGroup/role-assignment-to-sp.bicep' = { + scope: rgStorage + name: 'rbac-ds-${resourceGroups.storage}' + params: { + // Owner - this role is cleaned up as part of this deployment + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + resourceSPObjectIds: array(deploymentScriptIdentity.outputs.identityPrincipalId) + } +} + +module rgComputeDeploymentScriptRBAC '../../azresources/iam/resourceGroup/role-assignment-to-sp.bicep' = { + scope: rgCompute + name: 'rbac-ds-${resourceGroups.compute}' + params: { + // Owner - this role is cleaned up as part of this deployment + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + resourceSPObjectIds: array(deploymentScriptIdentity.outputs.identityPrincipalId) + } +} + +// Clean up the role assignments +var azCliCommandDeploymentScriptPermissionCleanup = ''' + az role assignment delete --assignee {0} --scope {1} +''' + +module rgStorageDeploymentScriptPermissionCleanup '../../azresources/util/deployment-script.bicep' = { + dependsOn: [ + acr + dataLake + storageLogging + synapseAnalytics + ] + + scope: rgAutomation + name: 'ds-rbac-${resourceGroups.storage}-cleanup' + params: { + deploymentScript: format(azCliCommandDeploymentScriptPermissionCleanup, deploymentScriptIdentity.outputs.identityPrincipalId, rgStorage.id) + deploymentScriptIdentityId: deploymentScriptIdentity.outputs.identityId + deploymentScriptName: 'ds-rbac-${resourceGroups.storage}-cleanup' + } +} + +module rgComputeDeploymentScriptPermissionCleanup '../../azresources/util/deployment-script.bicep' = { + dependsOn: [ + dataLakeMetaData + ] + + scope: rgAutomation + name: 'ds-rbac-${resourceGroups.compute}-cleanup' + params: { + deploymentScript: format(azCliCommandDeploymentScriptPermissionCleanup, deploymentScriptIdentity.outputs.identityPrincipalId, rgCompute.id) + deploymentScriptIdentityId: deploymentScriptIdentity.outputs.identityId + deploymentScriptName: 'ds-rbac-${resourceGroups.compute}-cleanup' + } +} + +//virtual network deployment +module networking 'networking.bicep' = { + name: 'deploy-networking' + scope: rgVnet + params: { + hubNetwork: hubNetwork + network: network + } +} + +// Data and AI & related services deployment +module akv '../../azresources/security/key-vault.bicep' = { + name: 'deploy-akv' + scope: rgSecurity + params: { + name: akvName + tags: resourceTags + + enabledForDiskEncryption: true + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + privateZoneId: networking.outputs.keyVaultPrivateDnsZoneId + } +} + +module storageLogging '../../azresources/storage/storage-generalpurpose.bicep' = { + name: 'deploy-storage-for-logging' + scope: rgStorage + params: { + tags: resourceTags + name: storageLoggingName + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + blobPrivateZoneId: networking.outputs.dataLakeBlobPrivateDnsZoneId + filePrivateZoneId: networking.outputs.dataLakeFilePrivateDnsZoneId + + defaultNetworkAcls: 'Deny' + subnetIdForVnetAccess: [] + + useCMK: useCMK + deploymentScriptIdentityId: useCMK ? deploymentScriptIdentity.outputs.identityId : '' + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module sqlDb '../../azresources/data/sqldb/main.bicep' = if (sqldb.enabled) { + name: 'deploy-sqldb' + scope: rgStorage + params: { + tags: resourceTags + sqlServerName: sqlServerName + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + privateZoneId: networking.outputs.sqlDBPrivateDnsZoneId + sqldbUsername: sqldb.username + sqldbPassword: sqldbPassword + sqlVulnerabilityLoggingStorageAccountName: storageLogging.outputs.storageName + sqlVulnerabilityLoggingStoragePath: storageLogging.outputs.storagePath + sqlVulnerabilitySecurityContactEmail: securityContactEmail + + useCMK: useCMK + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module dataLake '../../azresources/storage/storage-adlsgen2.bicep' = { + name: 'deploy-datalake' + scope: rgStorage + params: { + tags: resourceTags + name: datalakeStorageName + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + + blobPrivateZoneId: networking.outputs.dataLakeBlobPrivateDnsZoneId + dfsPrivateZoneId: networking.outputs.dataLakeDfsPrivateDnsZoneId + + defaultNetworkAcls: 'Deny' + subnetIdForVnetAccess: [] + + useCMK: useCMK + deploymentScriptIdentityId: useCMK ? deploymentScriptIdentity.outputs.identityId : '' + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module egressLb '../../azresources/network/lb-egress.bicep' = { + name: 'deploy-databricks-egressLb' + scope: rgCompute + params: { + name: databricksEgressLbName + tags: resourceTags + } +} + +module databricks '../../azresources/analytics/databricks/main.bicep' = { + name: 'deploy-databricks' + scope: rgCompute + params: { + name: databricksName + tags: resourceTags + vnetId: networking.outputs.vnetId + pricingTier: 'premium' + managedResourceGroupId: '${subscription().id}/resourceGroups/${rgCompute.name}-${databricksName}-${uniqueString(rgCompute.id)}' + publicSubnetName: networking.outputs.databricksPublicSubnetName + privateSubnetName: networking.outputs.databricksPrivateSubnetName + loadbalancerId: egressLb.outputs.lbId + loadBalancerBackendPoolName: egressLb.outputs.lbBackendPoolName + } +} + +module adf '../../azresources/analytics/adf/main.bicep' = { + name: 'deploy-adf' + scope: rgCompute + params: { + name: adfName + tags: resourceTags + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + datafactoryPrivateZoneId: networking.outputs.adfDataFactoryPrivateDnsZoneId + + useCMK: useCMK + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module acr '../../azresources/containers/acr/main.bicep' = { + name: 'deploy-acr' + scope: rgStorage + params: { + name: acrName + tags: resourceTags + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + privateZoneId: networking.outputs.acrPrivateDnsZoneId + + useCMK: useCMK + deploymentScriptIdentityId: useCMK ? deploymentScriptIdentity.outputs.identityId : '' + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module appInsights '../../azresources/monitor/ai-web.bicep' = { + name: 'deploy-appinsights-web' + scope: rgMonitor + params: { + tags: resourceTags + name: aiName + } +} + +// azure machine learning uses a metadata data lake storage account + +module dataLakeMetaData '../../azresources/storage/storage-generalpurpose.bicep' = { + name: 'deploy-aml-metadata-storage' + scope: rgCompute + params: { + tags: resourceTags + name: amlMetaStorageName + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + blobPrivateZoneId: networking.outputs.dataLakeBlobPrivateDnsZoneId + filePrivateZoneId: networking.outputs.dataLakeFilePrivateDnsZoneId + + useCMK: useCMK + deploymentScriptIdentityId: useCMK ? deploymentScriptIdentity.outputs.identityId : '' + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module aml '../../azresources/analytics/aml/main.bicep' = { + name: 'deploy-aml' + scope: rgCompute + params: { + name: amlName + tags: resourceTags + containerRegistryId: acr.outputs.acrId + storageAccountId: dataLakeMetaData.outputs.storageId + appInsightsId: appInsights.outputs.aiId + + privateZoneAzureMLApiId: networking.outputs.amlApiPrivateDnsZoneId + privateZoneAzureMLNotebooksId: networking.outputs.amlNotebooksPrivateDnsZoneId + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + + useCMK: useCMK + akvResourceGroupName: rgSecurity.name + akvName: akv.outputs.akvName + } +} + +module synapseAnalytics '../../azresources/analytics/synapse/main.bicep' = { + name: 'deploy-synapse' + scope: rgCompute + params: { + name: synapseName + tags: resourceTags + + managedResourceGroupName: '${rgCompute.name}-${synapseName}-${uniqueString(rgCompute.id)}' + + adlsResourceGroupName: rgStorage.name + adlsName: dataLake.outputs.storageName + adlsFSName: 'synapsecontainer' + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + synapsePrivateZoneId: networking.outputs.synapsePrivateDnsZoneId + synapseDevPrivateZoneId: networking.outputs.synapseDevPrivateDnsZoneId + synapseSqlPrivateZoneId: networking.outputs.synapseSqlPrivateDnsZoneId + + synapseUsername: synapse.username + synapsePassword: synapsePassword + + sqlVulnerabilityLoggingStorageAccounResourceGroupName: rgStorage.name + sqlVulnerabilityLoggingStorageAccountName: storageLogging.outputs.storageName + sqlVulnerabilityLoggingStoragePath: storageLogging.outputs.storagePath + sqlVulnerabilitySecurityContactEmail: securityContactEmail + + deploymentScriptIdentityId: deploymentScriptIdentity.outputs.identityId + + useCMK: useCMK + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module akvsynapseUsername '../../azresources/security/key-vault-secret.bicep' = { + dependsOn: [ + akv + ] + name: 'add-akv-secret-synapseUsername' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'synapseUsername' + secretValue: synapse.username + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +module akvSqlDbUsername '../../azresources/security/key-vault-secret.bicep' = if (sqldb.enabled) { + dependsOn: [ + akv + ] + name: 'add-akv-secret-sqldbUsername' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'sqldbUsername' + secretValue: sqldb.username + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +module akvSqlDbPassword '../../azresources/security/key-vault-secret.bicep' = if (sqldb.enabled) { + dependsOn: [ + akv + ] + name: 'add-akv-secret-sqldbPassword' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'sqldbPassword' + secretValue: sqldbPassword + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +module akvSqlDbConnection '../../azresources/security/key-vault-secret.bicep' = if (sqldb.enabled) { + dependsOn: [ + akv + ] + name: 'add-akv-secret-SqlDbConnectionString' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'SqlDbConnectionString' + secretValue: 'Server=tcp:${sqldb.enabled ? sqlDb.outputs.sqlDbFqdn : ''},1433;Initial Catalog=${sqlServerName};Persist Security Info=False;User ID=${sqldb.username};Password=${sqldbPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;' + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +module akvsynapsePassword '../../azresources/security/key-vault-secret.bicep' = { + dependsOn: [ + akv + ] + name: 'add-akv-secret-synapsePassword' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'synapsePassword' + secretValue: synapsePassword + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +// Key Vault Secrets User - used for accessing secrets in ADF pipelines +module roleAssignADFToAKV '../../azresources/iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-${adfName}-${akvName}' + scope: rgSecurity + params: { + keyVaultName: akv.outputs.akvName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') // Key Vault Secrets User + resourceSPObjectIds: array(adf.outputs.identityPrincipalId) + } +} + +// FHIR +module fhir '../../azresources/compute/fhir.bicep' = { + name: 'deploy-fhir' + scope: rgCompute + params: { + name: fhirName + tags: resourceTags + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + privateZoneId: networking.outputs.fhirPrivateDnsZoneId + } +} + + +// AzFunc +module functionStorage '../../azresources/storage/storage-generalpurpose.bicep' = { + name: 'deploy-function-storage' + scope: rgCompute + params: { + tags: resourceTags + name: azfuncStorageName + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + blobPrivateZoneId: networking.outputs.dataLakeBlobPrivateDnsZoneId + filePrivateZoneId: networking.outputs.dataLakeFilePrivateDnsZoneId + + useCMK: useCMK + deploymentScriptIdentityId: useCMK ? deploymentScriptIdentity.outputs.identityId : '' + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module functionAppServicePlan '../../azresources/compute/web/app-service-plan-linux.bicep' = { + name: 'deploy-functions-plan' + scope: rgCompute + params: { + name: azfunhpName + skuName: 'S1' + skuTier: 'Standard' + + tags: resourceTags + } +} + +module functionApp '../../azresources/compute/web/functions-python-linux.bicep' = { + name: 'deploy-azure-function' + scope: rgCompute + params: { + name: azfuncName + appServicePlanId: functionAppServicePlan.outputs.planId + + aiIKey: appInsights.outputs.aiIKey + + storageName: functionStorage.outputs.storageName + storageId: functionStorage.outputs.storageId + + vnetIntegrationSubnetId: networking.outputs.webAppSubnetId + + tags: resourceTags + } +} + +// Streaming Analytics +module streamanalytics '../../azresources/analytics/stream-analytics/main.bicep' = { + name: 'deploy-stream-analytics' + scope: rgCompute + params: { + name: stranalyticsName + tags: resourceTags + } +} + +// Event Hub +module eventhub '../../azresources/integration/eventhub.bicep' = { + name: 'deploy-eventhub' + scope: rgCompute + params: { + name: eventhubName + tags: resourceTags + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + privateZoneEventHubId : networking.outputs.eventhubPrivateDnsZoneId + } +} diff --git a/landingzones/lz-healthcare/main.bicep b/landingzones/lz-healthcare/main.bicep new file mode 100644 index 00000000..dc86dbf3 --- /dev/null +++ b/landingzones/lz-healthcare/main.bicep @@ -0,0 +1,446 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +// Service Health +// Example (JSON) +// ----------------------------- +// "serviceHealthAlerts": { +// "value": { +// "incidentTypes": [ "Incident", "Security", "Maintenance", "Information", "ActionRequired" ], +// "regions": [ "Global", "Canada East", "Canada Central" ], +// "receivers": { +// "app": [ "email-1@company.com", "email-2@company.com" ], +// "email": [ "email-1@company.com", "email-3@company.com", "email-4@company.com" ], +// "sms": [ { "countryCode": "1", "phoneNumber": "1234567890" }, { "countryCode": "1", "phoneNumber": "0987654321" } ], +// "voice": [ { "countryCode": "1", "phoneNumber": "1234567890" } ] +// }, +// "actionGroupName": "ALZ action group", +// "actionGroupShortName": "alz-alert", +// "alertRuleName": "ALZ alert rule", +// "alertRuleDescription": "Alert rule for Azure Landing Zone" +// } +// } +@description('Service Health alerts') +param serviceHealthAlerts object = {} + +// Log Analytics +@description('Log Analytics Resource Id to integrate Azure Security Center.') +param logAnalyticsWorkspaceResourceId string + +// Azure Security Center +// Example (JSON) +// ----------------------------- +// "securityCenter": { +// "value": { +// "email": "alzcanadapubsec@microsoft.com", +// "phone": "5555555555" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// email: 'alzcanadapubsec@microsoft.com' +// phone: '5555555555' +// } +@description('Security Center configuration. It includes email and phone.') +param securityCenter object + +// Subscription Role Assignments +// Example (JSON) +// ----------------------------- +// [ +// { +// "comments": "Built-in Contributor Role", +// "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", +// "securityGroupObjectIds": [ +// "38f33f7e-a471-4630-8ce9-c6653495a2ee" +// ] +// } +// ] + +// Example (Bicep) +// ----------------------------- +// [ +// { +// comments: 'Built-In Contributor Role' +// roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' +// securityGroupObjectIds: [ +// '38f33f7e-a471-4630-8ce9-c6653495a2ee' +// ] +// } +// ] +@description('Array of role assignments at subscription scope. The array will contain an object with comments, roleDefinitionId and array of securityGroupObjectIds.') +param subscriptionRoleAssignments array = [] + +// Subscription Budget +// Example (JSON) +// --------------------------- +// "subscriptionBudget": { +// "value": { +// "createBudget": false, +// "name": "MonthlySubscriptionBudget", +// "amount": 1000, +// "timeGrain": "Monthly", +// "contactEmails": [ "alzcanadapubsec@microsoft.com" ] +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// createBudget: true +// name: 'MonthlySubscriptionBudget' +// amount: 1000 +// timeGrain: 'Monthly' +// contactEmails: [ +// 'alzcanadapubsec@microsoft.com' +// ] +// } +@description('Subscription budget configuration containing createBudget flag, name, amount, timeGrain and array of contactEmails') +param subscriptionBudget object + +// Tags +// Example (JSON) +// ----------------------------- +// "subscriptionTags": { +// "value": { +// "ISSO": "isso-tag" +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// ISSO: 'isso-tag' +// } +@description('A set of key/value pairs of tags assigned to the subscription.') +param subscriptionTags object + +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact': 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Resource Groups +// Example (JSON) +// ----------------------------- +// "resourceGroups": { +// "value": { +// "automation": "healthAutomation", +// "compute": "healthCompute", +// "monitor": "healthMonitor", +// "networking": "healthNetworking", +// "networkWatcher": "NetworkWatcherRG", +// "security": "healthSecurity", +// "storage": "healthStorage" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// automation: 'healthAutomation' +// compute: 'healthCompute' +// monitor: 'healthMonitor' +// networking: 'healthNetworking' +// networkWatcher: 'NetworkWatcherRG' +// security: 'healthSecurity' +// storage: 'healthStorage' +// } +@description('Resource groups required for the achetype. It includes automation, compute, monitor, networking, networkWatcher, security and storage.') +param resourceGroups object + +@description('Boolean flag to determine whether customer managed keys are used. Default: false') +param useCMK bool = false + +// Azure Automation Account +// Example (JSON) +// ----------------------------- +// "automation": { +// "value": { +// "name": "healthAutomation" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// name: 'healthAutomation' +// } +@description('Azure Automation Account configuration. Includes name.') +param automation object + +// Azure Key Vault +// Example (JSON) +//----------------------------- +// "keyVault": { +// "value": { +// "secretExpiryInDays": 3650 +// } +// } + +// Example (Bicep) +//----------------------------- +// { +// secretExpiryInDays: 3650 +// } +@description('Azure Key Vault configuraiton. Includes secretExpiryInDays.') +param keyVault object + +// SQL Database +// ----------------------------- +// Example (JSON) +// "sqldb": { +// "value": { +// "enabled": true, +// "username": "azadmin" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// enabled: true +// username: 'azadmin' +// } +@description('SQL Database configuration. Includes enabled flag and username.') +param sqldb object + +// Synapse +// ----------------------------- +// Example (JSON) +// "synapse": { +// "value": { +// "username": "azadmin" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// username: 'azadmin' +// } +@description('Synapse Analytics configuration. Includes username.') +param synapse object + +// Networking +// Example (JSON) +// ----------------------------- +// "hubNetwork": { +// "value": { +// "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", +// "rfc1918IPRange": "10.18.0.0/22", +// "rfc6598IPRange": "100.60.0.0/16", +// "egressVirtualApplianceIp": "10.18.0.36", +// "privateDnsManagedByHub": true, +// "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", +// "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// virtualNetworkId: '/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet' +// rfc1918IPRange: '10.18.0.0/22' +// rfc6598IPRange: '100.60.0.0/16' +// egressVirtualApplianceIp: '10.18.0.36' +// privateDnsManagedByHub: true, +// privateDnsManagedByHubSubscriptionId: 'ed7f4eed-9010-4227-b115-2a5e37728f27', +// privateDnsManagedByHubResourceGroupName: 'pubsec-dns-rg' +// } +@description('Hub Network configuration that includes virtualNetworkId, rfc1918IPRange, rfc6598IPRange, egressVirtualApplianceIp, privateDnsManagedByHub flag, privateDnsManagedByHubSubscriptionId and privateDnsManagedByHubResourceGroupName.') +param hubNetwork object + +// Example (JSON) +// ----------------------------- +// "network": { +// "value": { +// "peerToHubVirtualNetwork": true, +// "useRemoteGateway": false, +// "name": "vnet", +// "dnsServers": [ +// "10.18.1.4" +// ], +// "addressPrefixes": [ +// "10.2.0.0/16" +// ], +// "subnets": { +// "oz": { +// "comments": "Foundational Elements Zone (OZ)", +// "name": "oz", +// "addressPrefix": "10.2.1.0/25" +// }, +// "paz": { +// "comments": "Presentation Zone (PAZ)", +// "name": "paz", +// "addressPrefix": "10.2.2.0/25" +// }, +// "rz": { +// "comments": "Application Zone (RZ)", +// "name": "rz", +// "addressPrefix": "10.2.3.0/25" +// }, +// "hrz": { +// "comments": "Data Zone (HRZ)", +// "name": "hrz", +// "addressPrefix": "10.2.4.0/25" +// }, +// "privateEndpoints": { +// "comments": "Private Endpoints Subnet", +// "name": "privateendpoints", +// "addressPrefix": "10.2.5.0/25" +// }, +// "databricksPublic": { +// "comments": "Databricks Public Delegated Subnet", +// "name": "databrickspublic", +// "addressPrefix": "10.2.6.0/25" +// }, +// "databricksPrivate": { +// "comments": "Databricks Private Delegated Subnet", +// "name": "databricksprivate", +// "addressPrefix": "10.2.7.0/25" +// }, +// "web": { +// "comments": "Azure Web App Delegated Subnet", +// "name": "webapp", +// "addressPrefix": "10.2.8.0/25" +// } +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// peerToHubVirtualNetwork: true +// useRemoteGateway: false +// name: 'vnet' +// dnsServers: [ +// '10.18.1.4' +// ] +// addressPrefixes: [ +// '10.2.0.0/16' +// ] +// subnets: { +// oz: { +// comments: 'Foundational Elements Zone (OZ)' +// name: 'oz' +// addressPrefix: '10.21.0/25' +// } +// paz: { +// comments: 'Presentation Zone (PAZ)' +// name: 'paz' +// addressPrefix: '10.22.0/25' +// } +// rz: { +// comments: 'Application Zone (RZ)' +// name: 'rz' +// addressPrefix: '10.2.3.0/25' +// } +// hrz: { +// comments: 'Data Zone (HRZ)' +// name: 'hrz' +// addressPrefix: '10.2.4.0/25' +// } +// databricksPublic: { +// comments: 'Databricks Public Delegated Subnet' +// name: 'databrickspublic' +// addressPrefix: '10.2.5.0/25' +// } +// databricksPrivate: { +// comments: 'Databricks Private Delegated Subnet' +// name: 'databricksprivate' +// addressPrefix: '10.2.6.0/25' +// } +// privateEndpoints: { +// comments: 'Private Endpoints Subnet' +// name: 'privateendpoints' +// addressPrefix: '10.2.7.0/25' +// } +// web: { +// comments: 'Azure Web App Delegated Subnet' +// name: 'webapp' +// addressPrefix: '10.2.8.0/25' +// } +// } +// } +@description('Network configuration. Includes peerToHubVirtualNetwork flag, useRemoteGateway flag, name, dnsServers, addressPrefixes and subnets (oz, paz, rz, hrz, privateEndpoints, databricksPublic, databricksPrivate, web) ') +param network object + +/* + Scaffold the subscription which includes: + * Azure Security Center - Enable Azure Defender (all available options) + * Azure Security Center - Configure Log Analytics Workspace + * Azure Security Center - Configure Security Alert Contact + * Service Health Alerts + * Role Assignments to Security Groups + * Subscription Budget + * Subscription Tags +*/ +module subScaffold '../scaffold-subscription.bicep' = { + name: 'configure-subscription' + scope: subscription() + params: { + serviceHealthAlerts: serviceHealthAlerts + subscriptionRoleAssignments: subscriptionRoleAssignments + subscriptionBudget: subscriptionBudget + subscriptionTags: subscriptionTags + resourceTags: resourceTags + + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + + securityCenter: securityCenter + } +} + +// Deploy Landing Zone +module landingZone 'lz.bicep' = { + name: 'deploy-healthcare-archetype' + scope: subscription() + params: { + securityContactEmail: securityCenter.email + + resourceTags: resourceTags + resourceGroups: resourceGroups + + useCMK: useCMK + + automation: automation + keyVault: keyVault + sqldb: sqldb + synapse: synapse + + hubNetwork: hubNetwork + network: network + } +} diff --git a/landingzones/lz-healthcare/main.parameters-sample.json b/landingzones/lz-healthcare/main.parameters-sample.json new file mode 100644 index 00000000..30d47210 --- /dev/null +++ b/landingzones/lz-healthcare/main.parameters-sample.json @@ -0,0 +1,159 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + } + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "alzcanadapubsec@microsoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "healthAutomation", + "compute": "healthCompute", + "monitor": "healthMonitor", + "networking": "healthNetworking", + "networkWatcher": "NetworkWatcherRG", + "security": "healthSecurity", + "storage": "healthStorage" + } + }, + "useCMK": { + "value": false + }, + "keyVault": { + "value": { + "secretExpiryInDays": 3650 + } + }, + "automation": { + "value": { + "name": "healthAutomation" + } + }, + "sqldb": { + "value": { + "enabled": true, + "username": "azadmin" + } + }, + "synapse": { + "value": { + "username": "azadmin" + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.0.36", + "privateDnsManagedByHub": true, + "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", + "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" + } + }, + "network": { + "value": { + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.2.0.0/16" + ], + "subnets": { + "oz": { + "comments": "Foundational Elements Zone (OZ)", + "name": "oz", + "addressPrefix": "10.2.1.0/25" + }, + "paz": { + "comments": "Presentation Zone (PAZ)", + "name": "paz", + "addressPrefix": "10.2.2.0/25" + }, + "rz": { + "comments": "Application Zone (RZ)", + "name": "rz", + "addressPrefix": "10.2.3.0/25" + }, + "hrz": { + "comments": "Data Zone (HRZ)", + "name": "hrz", + "addressPrefix": "10.2.4.0/25" + }, + "privateEndpoints": { + "comments": "Private Endpoints Subnet", + "name": "privateendpoints", + "addressPrefix": "10.2.5.0/25" + }, + "databricksPublic": { + "comments": "Databricks Public Delegated Subnet", + "name": "databrickspublic", + "addressPrefix": "10.2.6.0/25" + }, + "databricksPrivate": { + "comments": "Databricks Private Delegated Subnet", + "name": "databricksprivate", + "addressPrefix": "10.2.7.0/25" + }, + "web": { + "comments": "Azure Web App Delegated Subnet", + "name": "webapp", + "addressPrefix": "10.2.8.0/25" + } + } + } + } + } +} \ No newline at end of file diff --git a/landingzones/lz-healthcare/networking.bicep b/landingzones/lz-healthcare/networking.bicep new file mode 100644 index 00000000..c06263fd --- /dev/null +++ b/landingzones/lz-healthcare/networking.bicep @@ -0,0 +1,696 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// Networking +// Example (JSON) +// ----------------------------- +// "hubNetwork": { +// "value": { +// "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", +// "rfc1918IPRange": "10.18.0.0/22", +// "rfc6598IPRange": "100.60.0.0/16", +// "egressVirtualApplianceIp": "10.18.0.36", +// "privateDnsManagedByHub": true, +// "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", +// "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// virtualNetworkId: '/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet' +// rfc1918IPRange: '10.18.0.0/22' +// rfc6598IPRange: '100.60.0.0/16' +// egressVirtualApplianceIp: '10.18.0.36' +// privateDnsManagedByHub: true, +// privateDnsManagedByHubSubscriptionId: 'ed7f4eed-9010-4227-b115-2a5e37728f27', +// privateDnsManagedByHubResourceGroupName: 'pubsec-dns-rg' +// } +@description('Hub Network configuration that includes virtualNetworkId, rfc1918IPRange, rfc6598IPRange, egressVirtualApplianceIp, privateDnsManagedByHub flag, privateDnsManagedByHubSubscriptionId and privateDnsManagedByHubResourceGroupName.') +param hubNetwork object + +// Example (JSON) +// ----------------------------- +// "network": { +// "value": { +// "peerToHubVirtualNetwork": true, +// "useRemoteGateway": false, +// "name": "vnet", +// "dnsServers": [ +// "10.18.1.4" +// ], +// "addressPrefixes": [ +// "10.2.0.0/16" +// ], +// "subnets": { +// "oz": { +// "comments": "Foundational Elements Zone (OZ)", +// "name": "oz", +// "addressPrefix": "10.2.1.0/25" +// }, +// "paz": { +// "comments": "Presentation Zone (PAZ)", +// "name": "paz", +// "addressPrefix": "10.2.2.0/25" +// }, +// "rz": { +// "comments": "Application Zone (RZ)", +// "name": "rz", +// "addressPrefix": "10.2.3.0/25" +// }, +// "hrz": { +// "comments": "Data Zone (HRZ)", +// "name": "hrz", +// "addressPrefix": "10.2.4.0/25" +// }, +// "privateEndpoints": { +// "comments": "Private Endpoints Subnet", +// "name": "privateendpoints", +// "addressPrefix": "10.2.5.0/25" +// }, +// "databricksPublic": { +// "comments": "Databricks Public Delegated Subnet", +// "name": "databrickspublic", +// "addressPrefix": "10.2.6.0/25" +// }, +// "databricksPrivate": { +// "comments": "Databricks Private Delegated Subnet", +// "name": "databricksprivate", +// "addressPrefix": "10.2.7.0/25" +// }, +// "web": { +// "comments": "Azure Web App Delegated Subnet", +// "name": "webapp", +// "addressPrefix": "10.2.8.0/25" +// } +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// peerToHubVirtualNetwork: true +// useRemoteGateway: false +// name: 'vnet' +// dnsServers: [ +// '10.18.1.4' +// ] +// addressPrefixes: [ +// '10.2.0.0/16' +// ] +// subnets: { +// oz: { +// comments: 'Foundational Elements Zone (OZ)' +// name: 'oz' +// addressPrefix: '10.21.0/25' +// } +// paz: { +// comments: 'Presentation Zone (PAZ)' +// name: 'paz' +// addressPrefix: '10.22.0/25' +// } +// rz: { +// comments: 'Application Zone (RZ)' +// name: 'rz' +// addressPrefix: '10.2.3.0/25' +// } +// hrz: { +// comments: 'Data Zone (HRZ)' +// name: 'hrz' +// addressPrefix: '10.2.4.0/25' +// } +// databricksPublic: { +// comments: 'Databricks Public Delegated Subnet' +// name: 'databrickspublic' +// addressPrefix: '10.2.5.0/25' +// } +// databricksPrivate: { +// comments: 'Databricks Private Delegated Subnet' +// name: 'databricksprivate' +// addressPrefix: '10.2.6.0/25' +// } +// privateEndpoints: { +// comments: 'Private Endpoints Subnet' +// name: 'privateendpoints' +// addressPrefix: '10.2.7.0/25' +// } +// web: { +// comments: 'Azure Web App Delegated Subnet' +// name: 'webapp' +// addressPrefix: '10.2.8.0/25' +// } +// } +// } +@description('Network configuration. Includes peerToHubVirtualNetwork flag, useRemoteGateway flag, name, dnsServers, addressPrefixes and subnets (oz, paz, rz, hrz, privateEndpoints, databricksPublic, databricksPrivate, web) ') +param network object + +var hubVnetIdSplit = split(hubNetwork.virtualNetworkId, '/') +var usingCustomDNSServers = length(network.dnsServers) > 0 + +var routesToHub = [ + // Force Routes to Hub IPs (RFC1918 range) via FW despite knowing that route via peering + { + name: 'PrdSpokesUdrHubRFC1918FWRoute' + properties: { + addressPrefix: hubNetwork.rfc1918IPRange + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } + // Force Routes to Hub IPs (CGNAT range) via FW despite knowing that route via peering + { + name: 'PrdSpokesUdrHubRFC6598FWRoute' + properties: { + addressPrefix: hubNetwork.rfc6598IPRange + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } + { + name: 'RouteToEgressFirewall' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } +] + +// Network Security Groups +resource nsgOZ 'Microsoft.Network/networkSecurityGroups@2021-02-01' = { + name: '${network.subnets.oz.name}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +resource nsgPAZ 'Microsoft.Network/networkSecurityGroups@2021-02-01' = { + name: '${network.subnets.paz.name}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +resource nsgRZ 'Microsoft.Network/networkSecurityGroups@2021-02-01' = { + name: '${network.subnets.rz.name}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +resource nsgHRZ 'Microsoft.Network/networkSecurityGroups@2021-02-01' = { + name: '${network.subnets.hrz.name}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +module nsgDatabricks '../../azresources/network/nsg/nsg-databricks.bicep' = { + name: 'deploy-nsg-databricks' + params: { + namePublic: '${network.subnets.databricksPublic.name}Nsg' + namePrivate: '${network.subnets.databricksPrivate.name}Nsg' + } +} + +// Network security groups (NSGs): You can block outbound traffic with an NSG that's placed on your integration subnet. +// The inbound rules don't apply because you can't use VNet Integration to provide inbound access to your app. +// At the moment, there are no outbound rules to block outbound traffic +// See https://docs.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#regional-vnet-integration +module nsgWebApp '../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-webapp' + params: { + name: '${network.subnets.web.name}Nsg' + } +} + +// Route Tables +resource udrOZ 'Microsoft.Network/routeTables@2021-02-01' = { + name: '${network.subnets.oz.name}Udr' + location: resourceGroup().location + properties: { + routes: network.peerToHubVirtualNetwork ? routesToHub : null + } +} + +resource udrPAZ 'Microsoft.Network/routeTables@2021-02-01' = { + name: '${network.subnets.paz.name}Udr' + location: resourceGroup().location + properties: { + routes: network.peerToHubVirtualNetwork ? routesToHub : null + } +} + +resource udrRZ 'Microsoft.Network/routeTables@2021-02-01' = { + name: '${network.subnets.rz.name}Udr' + location: resourceGroup().location + properties: { + routes: network.peerToHubVirtualNetwork ? routesToHub : null + } +} + +resource udrHRZ 'Microsoft.Network/routeTables@2021-02-01' = { + name: '${network.subnets.hrz.name}Udr' + location: resourceGroup().location + properties: { + routes: network.peerToHubVirtualNetwork ? routesToHub : null + } +} + +module udrDatabricksPublic '../../azresources/network/udr/udr-databricks-public.bicep' = { + name: 'deploy-route-table-databricks-public' + params: { + name: '${network.subnets.databricksPublic.name}Udr' + } +} + +module udrDatabricksPrivate '../../azresources/network/udr/udr-databricks-private.bicep' = { + name: 'deploy-route-table-databricks-private' + params: { + name: '${network.subnets.databricksPrivate.name}Udr' + } +} + +// Route tables (UDRs): You can place a route table on the integration subnet to send outbound traffic where you want. +// At the moment, the route table is empty but rules can be added to force tunnel. +// See https://docs.microsoft.com/azure/app-service/web-sites-integrate-with-vnet#regional-vnet-integration +module udrWebApp '../../azresources/network/udr/udr-custom.bicep' = { + name: 'deploy-route-table-web-app' + params: { + name: '${network.subnets.web.name}Udr' + routes: [] + } +} + +// Virtual Network +resource vnet 'Microsoft.Network/virtualNetworks@2021-02-01' = { + name: network.name + location: resourceGroup().location + properties: { + dhcpOptions: { + dnsServers: network.dnsServers + } + addressSpace: { + addressPrefixes: network.addressPrefixes + } + subnets: [ + { + name: network.subnets.oz.name + properties: { + addressPrefix: network.subnets.oz.addressPrefix + routeTable: { + id: udrOZ.id + } + networkSecurityGroup: { + id: nsgOZ.id + } + } + } + { + name: network.subnets.paz.name + properties: { + addressPrefix: network.subnets.paz.addressPrefix + routeTable: { + id: udrPAZ.id + } + networkSecurityGroup: { + id: nsgPAZ.id + } + } + } + { + name: network.subnets.rz.name + properties: { + addressPrefix: network.subnets.rz.addressPrefix + routeTable: { + id: udrRZ.id + } + networkSecurityGroup: { + id: nsgRZ.id + } + } + } + { + name: network.subnets.hrz.name + properties: { + addressPrefix: network.subnets.hrz.addressPrefix + routeTable: { + id: udrHRZ.id + } + networkSecurityGroup: { + id: nsgHRZ.id + } + } + } + { + name: network.subnets.privateEndpoints.name + properties: { + addressPrefix: network.subnets.privateEndpoints.addressPrefix + privateEndpointNetworkPolicies: 'Disabled' + serviceEndpoints: [ + { + service: 'Microsoft.Storage' + } + ] + } + } + { + name: network.subnets.web.name + properties: { + addressPrefix: network.subnets.web.addressPrefix + networkSecurityGroup: { + id: nsgWebApp.outputs.nsgId + } + routeTable: { + id: udrWebApp.outputs.udrId + } + delegations: [ + { + name: 'webapp' + properties: { + serviceName: 'Microsoft.Web/serverFarms' + } + } + ] + } + } + { + name: network.subnets.databricksPublic.name + properties: { + addressPrefix: network.subnets.databricksPublic.addressPrefix + networkSecurityGroup: { + id: nsgDatabricks.outputs.publicNsgId + } + routeTable: { + id: udrDatabricksPublic.outputs.udrId + } + delegations: [ + { + name: 'databricks-delegation-public' + properties: { + serviceName: 'Microsoft.Databricks/workspaces' + } + } + ] + } + } + { + name: network.subnets.databricksPrivate.name + properties: { + addressPrefix: network.subnets.databricksPrivate.addressPrefix + networkSecurityGroup: { + id: nsgDatabricks.outputs.privateNsgId + } + routeTable: { + id: udrDatabricksPrivate.outputs.udrId + } + delegations: [ + { + name: 'databricks-delegation-private' + properties: { + serviceName: 'Microsoft.Databricks/workspaces' + } + } + ] + } + } + ] + } +} + +module vnetPeeringSpokeToHub '../../azresources/network/vnet-peering.bicep' = if (network.peerToHubVirtualNetwork) { + name: 'deploy-vnet-peering-spoke-to-hub' + scope: resourceGroup() + params: { + peeringName: 'Hub-${vnet.name}-to-${last(hubVnetIdSplit)}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: vnet.name + targetVnetId: hubNetwork.virtualNetworkId + useRemoteGateways: network.useRemoteGateway + } +} + +// For Hub to Spoke vnet peering, we must rescope the deployment to the subscription id & resource group of where the Hub VNET is located. +module vnetPeeringHubToSpoke '../../azresources/network/vnet-peering.bicep' = if (network.peerToHubVirtualNetwork) { + name: 'deploy-vnet-peering-${subscription().subscriptionId}' + // vnet id = /subscriptions/<>/resourceGroups/<>/providers/Microsoft.Network/virtualNetworks/<> + scope: resourceGroup(network.peerToHubVirtualNetwork ? hubVnetIdSplit[2] : '', network.peerToHubVirtualNetwork ? hubVnetIdSplit[4] : '') + params: { + peeringName: 'Spoke-${last(hubVnetIdSplit)}-to-${vnet.name}-${uniqueString(vnet.id)}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: last(hubVnetIdSplit) + targetVnetId: vnet.id + useRemoteGateways: false + } +} + +// Private DNS Zones +module privatezone_sqldb '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-sqldb' + scope: resourceGroup() + params: { + zone: 'privatelink${environment().suffixes.sqlServerHostname}' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_adf_datafactory '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-adf-datafactory' + scope: resourceGroup() + params: { + zone: 'privatelink.datafactory.azure.net' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_keyvault '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-keyvault' + scope: resourceGroup() + params: { + zone: 'privatelink.vaultcore.azure.net' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_acr '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-acr' + scope: resourceGroup() + params: { + zone: 'privatelink.azurecr.io' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_datalake_blob '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-blob' + scope: resourceGroup() + params: { + zone: 'privatelink.blob.${environment().suffixes.storage}' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_datalake_dfs '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-dfs' + scope: resourceGroup() + params: { + zone: 'privatelink.dfs.${environment().suffixes.storage}' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_datalake_file '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-file' + scope: resourceGroup() + params: { + zone: 'privatelink.file.${environment().suffixes.storage}' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_azureml_api '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-azureml-api' + scope: resourceGroup() + params: { + zone: 'privatelink.api.azureml.ms' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_azureml_notebook '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-azureml-notebook' + scope: resourceGroup() + params: { + zone: 'privatelink.notebooks.azure.net' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_fhir '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-fhir' + scope: resourceGroup() + params: { + zone: 'privatelink.azurehealthcareapis.com' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_eventhub '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-eventhub' + scope: resourceGroup() + params: { + zone: 'privatelink.servicebus.windows.net' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_synapse '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-synapse' + scope: resourceGroup() + params: { + zone: 'privatelink.azuresynapse.net' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_synapse_dev '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-synapse-dev' + scope: resourceGroup() + params: { + zone: 'privatelink.dev.azuresynapse.net' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_synapse_sql '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-synapse-sql' + scope: resourceGroup() + params: { + zone: 'privatelink.sql.azuresynapse.net' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +output vnetId string = vnet.id + +output ozSubnetId string = '${vnet.id}/subnets/${network.subnets.oz.name}' +output pazSubnetId string = '${vnet.id}/subnets/${network.subnets.paz.name}' +output rzSubnetId string = '${vnet.id}/subnets/${network.subnets.rz.name}' +output hrzId string = '${vnet.id}/subnets/${network.subnets.hrz.name}' +output privateEndpointSubnetId string = '${vnet.id}/subnets/${network.subnets.privateEndpoints.name}' +output webAppSubnetId string = '${vnet.id}/subnets/${network.subnets.web.name}' + +output databricksPublicSubnetName string = network.subnets.databricksPublic.name +output databricksPrivateSubnetName string = network.subnets.databricksPrivate.name + +output dataLakeDfsPrivateDnsZoneId string = privatezone_datalake_dfs.outputs.privateDnsZoneId +output dataLakeBlobPrivateDnsZoneId string = privatezone_datalake_blob.outputs.privateDnsZoneId +output dataLakeFilePrivateDnsZoneId string = privatezone_datalake_file.outputs.privateDnsZoneId +output adfDataFactoryPrivateDnsZoneId string = privatezone_adf_datafactory.outputs.privateDnsZoneId +output keyVaultPrivateDnsZoneId string = privatezone_keyvault.outputs.privateDnsZoneId +output acrPrivateDnsZoneId string = privatezone_acr.outputs.privateDnsZoneId +output sqlDBPrivateDnsZoneId string = privatezone_sqldb.outputs.privateDnsZoneId +output amlApiPrivateDnsZoneId string = privatezone_azureml_api.outputs.privateDnsZoneId +output amlNotebooksPrivateDnsZoneId string = privatezone_azureml_notebook.outputs.privateDnsZoneId +output fhirPrivateDnsZoneId string = privatezone_fhir.outputs.privateDnsZoneId +output eventhubPrivateDnsZoneId string = privatezone_eventhub.outputs.privateDnsZoneId +output synapsePrivateDnsZoneId string = privatezone_synapse.outputs.privateDnsZoneId +output synapseDevPrivateDnsZoneId string = privatezone_synapse_dev.outputs.privateDnsZoneId +output synapseSqlPrivateDnsZoneId string = privatezone_synapse_sql.outputs.privateDnsZoneId diff --git a/landingzones/lz-machinelearning/lz.bicep b/landingzones/lz-machinelearning/lz.bicep new file mode 100644 index 00000000..047f130d --- /dev/null +++ b/landingzones/lz-machinelearning/lz.bicep @@ -0,0 +1,817 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +// Log Analytics +@description('Log Analytics Resource Id to integrate Azure Security Center.') +param logAnalyticsWorkspaceResourceId string + +// Security Contact Email Address +@description('Contact email address for security alerts.') +param securityContactEmail string + +// Tags +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact: 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Resource Groups +// Example (JSON) +// ----------------------------- +// "resourceGroups": { +// "value": { +// "automation": "azmlAutomation", +// "compute": "azmlCompute", +// "monitor": "azmlMonitor", +// "networking": "azmlNetworking", +// "networkWatcher": "NetworkWatcherRG", +// "security": "azmlSecurity", +// "storage": "azmlStorage" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// automation: 'azmlAutomation' +// compute: 'azmlCompute' +// monitor: 'azmlMonitor' +// networking: 'azmlNetworking' +// networkWatcher: 'NetworkWatcherRG' +// security: 'azmlSecurity' +// storage: 'azmlStorage' +// } +@description('Resource groups required for the achetype. It includes automation, compute, monitor, networking, networkWatcher, security and storage.') +param resourceGroups object + +@description('Boolean flag to determine whether customer managed keys are used. Default: false') +param useCMK bool = false + +// Azure Automation Account +// Example (JSON) +// ----------------------------- +// "automation": { +// "value": { +// "name": "azmlautomation" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// name: 'azmlautomation' +// } +@description('Azure Automation Account configuration. Includes name.') +param automation object + +// Azure Key Vault +// Example (JSON) +//----------------------------- +// "keyVault": { +// "value": { +// "secretExpiryInDays": 3650 +// } +// } + +// Example (Bicep) +//----------------------------- +// { +// secretExpiryInDays: 3650 +// } +@description('Azure Key Vault configuraiton. Includes secretExpiryInDays.') +param keyVault object + +// Azure Kubernetes Service +// Example (JSON) +//----------------------------- +// "aks": { +// "value": { +// "version": "1.21.2" +// } +// } + +// Example (Bicep) +//----------------------------- +// { +// version: '1.21.2' +// } +@description('Azure Kubernetes Service configuration. Includes version.') +param aks object + +// SQL Database +// ----------------------------- +// Example (JSON) +// "sqldb": { +// "value": { +// "enabled": true, +// "username": "azadmin" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// enabled: true +// username: 'azadmin' +// } +@description('SQL Database configuration. Includes enabled flag and username.') +param sqldb object + +// SQL Managed Instance +// ----------------------------- +// Example (JSON) +// "sqlmi": { +// "value": { +// "enabled": true, +// "username": "azadmin" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// enabled: true +// username: 'azadmin' +// } +@description('SQL Managed Instance configuration. Includes enabled flag and username.') +param sqlmi object + +// Example (JSON) +// ----------------------------- +// "aml": { +// "value": { +// "enableHbiWorkspace": false +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// enableHbiWorkspace: false +// } +@description('Azure Machine Learning configuration. Includes enableHbiWorkspace.') +param aml object + +// Networking +// Example (JSON) +// ----------------------------- +// "hubNetwork": { +// "value": { +// "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", +// "rfc1918IPRange": "10.18.0.0/22", +// "rfc6598IPRange": "100.60.0.0/16", +// "egressVirtualApplianceIp": "10.18.0.36", +// "privateDnsManagedByHub": true, +// "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", +// "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// virtualNetworkId: '/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet' +// rfc1918IPRange: '10.18.0.0/22' +// rfc6598IPRange: '100.60.0.0/16' +// egressVirtualApplianceIp: '10.18.0.36' +// privateDnsManagedByHub: true, +// privateDnsManagedByHubSubscriptionId: 'ed7f4eed-9010-4227-b115-2a5e37728f27', +// privateDnsManagedByHubResourceGroupName: 'pubsec-dns-rg' +// } +@description('Hub Network configuration that includes virtualNetworkId, rfc1918IPRange, rfc6598IPRange, egressVirtualApplianceIp, privateDnsManagedByHub flag, privateDnsManagedByHubSubscriptionId and privateDnsManagedByHubResourceGroupName.') +param hubNetwork object + +// Example (JSON) +// ----------------------------- +// "network": { +// "value": { +// "peerToHubVirtualNetwork": true, +// "useRemoteGateway": false, +// "name": "vnet", +// "dnsServers": [ +// "10.18.1.4" +// ], +// "addressPrefixes": [ +// "10.2.0.0/16" +// ], +// "subnets": { +// "oz": { +// "comments": "Foundational Elements Zone (OZ)", +// "name": "oz", +// "addressPrefix": "10.2.1.0/25" +// }, +// "paz": { +// "comments": "Presentation Zone (PAZ)", +// "name": "paz", +// "addressPrefix": "10.2.2.0/25" +// }, +// "rz": { +// "comments": "Application Zone (RZ)", +// "name": "rz", +// "addressPrefix": "10.2.3.0/25" +// }, +// "hrz": { +// "comments": "Data Zone (HRZ)", +// "name": "hrz", +// "addressPrefix": "10.2.4.0/25" +// }, +// "privateEndpoints": { +// "comments": "Private Endpoints Subnet", +// "name": "privateendpoints", +// "addressPrefix": "10.2.5.0/25" +// }, +// "sqlmi": { +// "comments": "SQL Managed Instances Delegated Subnet", +// "name": "sqlmi", +// "addressPrefix": "10.2.6.0/25" +// }, +// "databricksPublic": { +// "comments": "Databricks Public Delegated Subnet", +// "name": "databrickspublic", +// "addressPrefix": "10.2.7.0/25" +// }, +// "databricksPrivate": { +// "comments": "Databricks Private Delegated Subnet", +// "name": "databricksprivate", +// "addressPrefix": "10.2.8.0/25" +// }, +// "aks": { +// "comments": "AKS Subnet", +// "name": "aks", +// "addressPrefix": "10.2.9.0/25" +// } +// } +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// peerToHubVirtualNetwork: true +// useRemoteGateway: false +// name: 'vnet' +// dnsServers: [ +// '10.18.1.4' +// ] +// addressPrefixes: [ +// '10.2.0.0/16' +// ] +// subnets: { +// oz: { +// comments: 'Foundational Elements Zone (OZ)' +// name: 'oz' +// addressPrefix: '10.2.1.0/25' +// } +// paz: { +// comments: 'Presentation Zone (PAZ)' +// name: 'paz' +// addressPrefix: '10.2.2.0/25' +// } +// rz: { +// comments: 'Application Zone (RZ)' +// name: 'rz' +// addressPrefix: '10.2.3.0/25' +// } +// hrz: { +// comments: 'Data Zone (HRZ)' +// name: 'hrz' +// addressPrefix: '10.2.4.0/25' +// } +// privateEndpoints: { +// comments: 'Private Endpoints Subnet' +// name: 'privateendpoints' +// addressPrefix: '10.2.5.0/25' +// } +// sqlmi: { +// comments: 'SQL Managed Instances Delegated Subnet' +// name: 'sqlmi' +// addressPrefix: '10.2.6.0/25' +// } +// databricksPublic: { +// comments: 'Databricks Public Delegated Subnet' +// name: 'databrickspublic' +// addressPrefix: '10.2.7.0/25' +// } +// databricksPrivate: { +// comments: 'Databricks Private Delegated Subnet' +// name: 'databricksprivate' +// addressPrefix: '10.2.8.0/25' +// } +// aks: { +// comments: 'AKS Subnet' +// name: 'aks' +// addressPrefix: '10.2.9.0/25' +// } +// } +// } +@description('Network configuration. Includes peerToHubVirtualNetwork flag, useRemoteGateway flag, name, dnsServers, addressPrefixes and subnets (oz, paz, rz, hrz, privateEndpoints, sqlmi, databricksPublic, databricksPrivate, aks) ') +param network object + +var sqldbPassword = '${uniqueString(rgStorage.id)}*${toUpper(uniqueString(sqldb.username))}' +var sqlmiPassword = '${uniqueString(rgStorage.id)}*${toUpper(uniqueString(sqlmi.username))}' + +var databricksName = 'databricks' +var databricksEgressLbName = 'egressLb' +var datalakeStorageName = 'datalake${uniqueString(rgStorage.id)}' +var amlMetaStorageName = 'amlmeta${uniqueString(rgCompute.id)}' +var akvName = 'akv${uniqueString(rgSecurity.id)}' +var sqlServerName = 'sqlserver${uniqueString(rgStorage.id)}' +var adfName = 'adf${uniqueString(rgCompute.id)}' +var aksName = 'aks${uniqueString(rgCompute.id)}' +var sqlMiName = 'sqlmi${uniqueString(rgStorage.id)}' +var amlName = 'aml${uniqueString(rgCompute.id)}' +var acrName = 'acr${uniqueString(rgStorage.id)}' +var aiName = 'ai${uniqueString(rgMonitor.id)}' +var storageLoggingName = 'salogging${uniqueString(rgStorage.id)}' + +var useDeploymentScripts = useCMK + +//resource group deployments +resource rgNetworkWatcher 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.networkWatcher + location: deployment().location + tags: resourceTags +} + +resource rgAutomation 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.automation + location: deployment().location + tags: resourceTags +} + +resource rgVnet 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroups.networking + location: deployment().location + tags: resourceTags +} + +resource rgStorage 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.storage + location: deployment().location + tags: resourceTags +} + +resource rgCompute 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.compute + location: deployment().location + tags: resourceTags +} + +resource rgSecurity 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.security + location: deployment().location + tags: resourceTags +} + +resource rgMonitor 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroups.monitor + location: deployment().location + tags: resourceTags +} + +// Automation +module automationAccount '../../azresources/automation/automation-account.bicep' = { + name: 'deploy-automation-account' + scope: rgAutomation + params: { + automationAccountName: automation.name + tags: resourceTags + } +} + +// Prepare for CMK deployments +module deploymentScriptIdentity '../../azresources/iam/user-assigned-identity.bicep' = if (useDeploymentScripts) { + name: 'deploy-ds-managed-identity' + scope: rgAutomation + params: { + name: 'deployment-scripts' + } +} + +module rgStorageDeploymentScriptRBAC '../../azresources/iam/resourceGroup/role-assignment-to-sp.bicep' = if (useDeploymentScripts) { + scope: rgStorage + name: 'rbac-ds-${resourceGroups.storage}' + params: { + // Owner - this role is cleaned up as part of this deployment + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + resourceSPObjectIds: useDeploymentScripts ? array(deploymentScriptIdentity.outputs.identityPrincipalId) : [] + } +} + +module rgComputeDeploymentScriptRBAC '../../azresources/iam/resourceGroup/role-assignment-to-sp.bicep' = if (useDeploymentScripts) { + scope: rgCompute + name: 'rbac-ds-${resourceGroups.compute}' + params: { + // Owner - this role is cleaned up as part of this deployment + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + resourceSPObjectIds: useDeploymentScripts ? array(deploymentScriptIdentity.outputs.identityPrincipalId) : [] + } +} + +// Clean up the role assignments +var azCliCommandDeploymentScriptPermissionCleanup = ''' + az role assignment delete --assignee {0} --scope {1} +''' + +module rgStorageDeploymentScriptPermissionCleanup '../../azresources/util/deployment-script.bicep' = if (useDeploymentScripts) { + dependsOn: [ + acr + dataLake + storageLogging + ] + + scope: rgAutomation + name: 'ds-rbac-${resourceGroups.storage}-cleanup' + params: { + deploymentScript: format(azCliCommandDeploymentScriptPermissionCleanup, useDeploymentScripts ? deploymentScriptIdentity.outputs.identityPrincipalId : '', rgStorage.id) + deploymentScriptIdentityId: useDeploymentScripts ? deploymentScriptIdentity.outputs.identityId : '' + deploymentScriptName: 'ds-rbac-${resourceGroups.storage}-cleanup' + } +} + +module rgComputeDeploymentScriptPermissionCleanup '../../azresources/util/deployment-script.bicep' = if (useDeploymentScripts) { + dependsOn: [ + dataLakeMetaData + ] + + scope: rgAutomation + name: 'ds-rbac-${resourceGroups.compute}-cleanup' + params: { + deploymentScript: format(azCliCommandDeploymentScriptPermissionCleanup, useDeploymentScripts ? deploymentScriptIdentity.outputs.identityPrincipalId : '', rgCompute.id) + deploymentScriptIdentityId: useDeploymentScripts ? deploymentScriptIdentity.outputs.identityId : '' + deploymentScriptName: 'ds-rbac-${resourceGroups.compute}-cleanup' + } +} + +//virtual network deployment +module networking 'networking.bicep' = { + name: 'deploy-networking' + scope: rgVnet + params: { + hubNetwork: hubNetwork + network: network + } +} + +// Data and AI & related services deployment +module akv '../../azresources/security/key-vault.bicep' = { + name: 'deploy-akv' + scope: rgSecurity + params: { + name: akvName + tags: resourceTags + + enabledForDiskEncryption: true + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + privateZoneId: networking.outputs.keyVaultPrivateDnsZoneId + } +} + +module sqlMi '../../azresources/data/sqlmi/main.bicep' = if (sqlmi.enabled) { + name: 'deploy-sqlmi' + scope: rgStorage + params: { + tags: resourceTags + + sqlServerName: sqlMiName + + subnetId: networking.outputs.sqlMiSubnetId + + sqlmiUsername: sqlmi.username + sqlmiPassword: sqlmiPassword + + sqlVulnerabilityLoggingStorageAccountName: storageLogging.outputs.storageName + sqlVulnerabilityLoggingStoragePath: storageLogging.outputs.storagePath + sqlVulnerabilitySecurityContactEmail: securityContactEmail + + useCMK: useCMK + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module storageLogging '../../azresources/storage/storage-generalpurpose.bicep' = { + name: 'deploy-storage-for-logging' + scope: rgStorage + params: { + tags: resourceTags + name: storageLoggingName + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + blobPrivateZoneId: networking.outputs.dataLakeBlobPrivateDnsZoneId + filePrivateZoneId: networking.outputs.dataLakeFilePrivateDnsZoneId + + defaultNetworkAcls: 'Deny' + subnetIdForVnetAccess: array(networking.outputs.sqlMiSubnetId) + + useCMK: useCMK + deploymentScriptIdentityId: useCMK ? deploymentScriptIdentity.outputs.identityId : '' + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module sqlDb '../../azresources/data/sqldb/main.bicep' = if (sqldb.enabled) { + name: 'deploy-sqldb' + scope: rgStorage + params: { + tags: resourceTags + sqlServerName: sqlServerName + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + privateZoneId: networking.outputs.sqlDBPrivateDnsZoneId + sqldbUsername: sqldb.username + sqldbPassword: sqldbPassword + sqlVulnerabilityLoggingStorageAccountName: storageLogging.outputs.storageName + sqlVulnerabilityLoggingStoragePath: storageLogging.outputs.storagePath + sqlVulnerabilitySecurityContactEmail: securityContactEmail + + useCMK: useCMK + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module dataLake '../../azresources/storage/storage-adlsgen2.bicep' = { + name: 'deploy-datalake' + scope: rgStorage + params: { + tags: resourceTags + name: datalakeStorageName + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + + blobPrivateZoneId: networking.outputs.dataLakeBlobPrivateDnsZoneId + dfsPrivateZoneId: networking.outputs.dataLakeDfsPrivateDnsZoneId + + useCMK: useCMK + deploymentScriptIdentityId: useCMK ? deploymentScriptIdentity.outputs.identityId : '' + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module egressLb '../../azresources/network/lb-egress.bicep' = { + name: 'deploy-databricks-egressLb' + scope: rgCompute + params: { + name: databricksEgressLbName + tags: resourceTags + } +} + +module databricks '../../azresources/analytics/databricks/main.bicep' = { + name: 'deploy-databricks' + scope: rgCompute + params: { + name: databricksName + tags: resourceTags + vnetId: networking.outputs.vnetId + pricingTier: 'premium' + managedResourceGroupId: '${subscription().id}/resourceGroups/${rgCompute.name}-${databricksName}-${uniqueString(rgCompute.id)}' + publicSubnetName: networking.outputs.databricksPublicSubnetName + privateSubnetName: networking.outputs.databricksPrivateSubnetName + loadbalancerId: egressLb.outputs.lbId + loadBalancerBackendPoolName: egressLb.outputs.lbBackendPoolName + } +} + +module aksKubnet '../../azresources/containers/aks-kubenet/main.bicep' = { + name: 'deploy-aksKubnet' + scope: rgCompute + params: { + tags: resourceTags + + name: aksName + version: aks.version + + systemNodePoolEnableAutoScaling: true + systemNodePoolMinNodeCount: 1 + systemNodePoolMaxNodeCount: 3 + systemNodePoolNodeSize: 'Standard_DS2_v2' + + userNodePoolEnableAutoScaling: true + userNodePoolMinNodeCount: 1 + userNodePoolMaxNodeCount: 3 + userNodePoolNodeSize: 'Standard_DS2_v2' + + dnsPrefix: toLower(aksName) + subnetId: networking.outputs.aksSubnetId + nodeResourceGroupName: '${rgCompute.name}-${aksName}-${uniqueString(rgCompute.id)}' + + privateDNSZoneId: networking.outputs.aksPrivateDnsZoneId + + containerInsightsLogAnalyticsResourceId: logAnalyticsWorkspaceResourceId + + useCMK: useCMK + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module adf '../../azresources/analytics/adf/main.bicep' = { + name: 'deploy-adf' + scope: rgCompute + params: { + name: adfName + tags: resourceTags + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + datafactoryPrivateZoneId: networking.outputs.adfDataFactoryPrivateDnsZoneId + + useCMK: useCMK + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module acr '../../azresources/containers/acr/main.bicep' = { + name: 'deploy-acr' + scope: rgStorage + params: { + name: acrName + tags: resourceTags + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + privateZoneId: networking.outputs.acrPrivateDnsZoneId + + useCMK: useCMK + deploymentScriptIdentityId: useCMK ? deploymentScriptIdentity.outputs.identityId : '' + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module appInsights '../../azresources/monitor/ai-web.bicep' = { + name: 'deploy-appinsights-web' + scope: rgMonitor + params: { + tags: resourceTags + name: aiName + } +} + +// azure machine learning uses a metadata data lake storage account + +module dataLakeMetaData '../../azresources/storage/storage-generalpurpose.bicep' = { + name: 'deploy-aml-metadata-storage' + scope: rgCompute + params: { + tags: resourceTags + name: amlMetaStorageName + + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + blobPrivateZoneId: networking.outputs.dataLakeBlobPrivateDnsZoneId + filePrivateZoneId: networking.outputs.dataLakeFilePrivateDnsZoneId + + useCMK: useCMK + deploymentScriptIdentityId: useCMK ? deploymentScriptIdentity.outputs.identityId : '' + akvResourceGroupName: useCMK ? rgSecurity.name : '' + akvName: useCMK ? akv.outputs.akvName : '' + } +} + +module machineLearning '../../azresources/analytics/aml/main.bicep' = { + name: 'deploy-aml' + scope: rgCompute + params: { + name: amlName + tags: resourceTags + containerRegistryId: acr.outputs.acrId + storageAccountId: dataLakeMetaData.outputs.storageId + appInsightsId: appInsights.outputs.aiId + privateZoneAzureMLApiId: networking.outputs.amlApiPrivateDnsZoneId + privateZoneAzureMLNotebooksId: networking.outputs.amlNotebooksPrivateDnsZoneId + privateEndpointSubnetId: networking.outputs.privateEndpointSubnetId + enableHbiWorkspace: aml.enableHbiWorkspace + + useCMK: useCMK + akvResourceGroupName: rgSecurity.name + akvName: akv.outputs.akvName + } +} + +// Adding secrets to key vault +module akvSqlDbUsername '../../azresources/security/key-vault-secret.bicep' = if (sqldb.enabled) { + dependsOn: [ + akv + ] + name: 'add-akv-secret-sqldbUsername' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'sqldbUsername' + secretValue: sqldb.username + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +module akvSqlDbPassword '../../azresources/security/key-vault-secret.bicep' = if (sqldb.enabled) { + dependsOn: [ + akv + ] + name: 'add-akv-secret-sqldbPassword' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'sqldbPassword' + secretValue: sqldbPassword + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +module akvSqlDbConnection '../../azresources/security/key-vault-secret.bicep' = if (sqldb.enabled) { + dependsOn: [ + akv + ] + name: 'add-akv-secret-SqlDbConnectionString' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'SqlDbConnectionString' + secretValue: 'Server=tcp:${sqldb.enabled ? sqlDb.outputs.sqlDbFqdn : ''},1433;Initial Catalog=${sqlServerName};Persist Security Info=False;User ID=${sqldb.username};Password=${sqldbPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;' + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +module akvSqlmiUsername '../../azresources/security/key-vault-secret.bicep' = if (sqlmi.enabled) { + dependsOn: [ + akv + ] + name: 'add-akv-secret-sqlmiUsername' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'sqlmiUsername' + secretValue: sqlmi.username + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +module akvSqlmiPassword '../../azresources/security/key-vault-secret.bicep' = if (sqlmi.enabled) { + dependsOn: [ + akv + ] + name: 'add-akv-secret-sqlmiPassword' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'sqlmiPassword' + secretValue: sqlmiPassword + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +module akvSqlMiConnection '../../azresources/security/key-vault-secret.bicep' = if (sqlmi.enabled) { + dependsOn: [ + akv + ] + name: 'add-akv-secret-SqlMiConnectionString' + scope: rgSecurity + params: { + akvName: akvName + secretName: 'SqlMiConnectionString' + secretValue: 'Server=tcp:${sqlmi.enabled ? sqlMi.outputs.sqlMiFqdn : ''},1433;Initial Catalog=${sqlMiName};Persist Security Info=False;User ID=${sqlmi.username};Password=${sqlmiPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;' + secretExpiryInDays: keyVault.secretExpiryInDays + } +} + +// Key Vault Secrets User - used for accessing secrets in ADF pipelines +module roleAssignADFToAKV '../../azresources/iam/resource/key-vault-role-assignment-to-sp.bicep' = { + name: 'rbac-${adfName}-${akvName}' + scope: rgSecurity + params: { + keyVaultName: akv.outputs.akvName + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') // Key Vault Secrets User + resourceSPObjectIds: array(adf.outputs.identityPrincipalId) + } +} diff --git a/landingzones/lz-machinelearning/main.bicep b/landingzones/lz-machinelearning/main.bicep new file mode 100644 index 00000000..1b1f3e8b --- /dev/null +++ b/landingzones/lz-machinelearning/main.bicep @@ -0,0 +1,496 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +// Service Health +// Example (JSON) +// ----------------------------- +// "serviceHealthAlerts": { +// "value": { +// "incidentTypes": [ "Incident", "Security", "Maintenance", "Information", "ActionRequired" ], +// "regions": [ "Global", "Canada East", "Canada Central" ], +// "receivers": { +// "app": [ "email-1@company.com", "email-2@company.com" ], +// "email": [ "email-1@company.com", "email-3@company.com", "email-4@company.com" ], +// "sms": [ { "countryCode": "1", "phoneNumber": "1234567890" }, { "countryCode": "1", "phoneNumber": "0987654321" } ], +// "voice": [ { "countryCode": "1", "phoneNumber": "1234567890" } ] +// }, +// "actionGroupName": "ALZ action group", +// "actionGroupShortName": "alz-alert", +// "alertRuleName": "ALZ alert rule", +// "alertRuleDescription": "Alert rule for Azure Landing Zone" +// } +// } +@description('Service Health alerts') +param serviceHealthAlerts object = {} + +// Log Analytics +@description('Log Analytics Resource Id to integrate Azure Security Center.') +param logAnalyticsWorkspaceResourceId string + +// Azure Security Center +// Example (JSON) +// ----------------------------- +// "securityCenter": { +// "value": { +// "email": "alzcanadapubsec@microsoft.com", +// "phone": "5555555555" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// email: 'alzcanadapubsec@microsoft.com' +// phone: '5555555555' +// } +@description('Security Center configuration. It includes email and phone.') +param securityCenter object + +// Subscription Role Assignments +// Example (JSON) +// ----------------------------- +// [ +// { +// "comments": "Built-in Contributor Role", +// "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", +// "securityGroupObjectIds": [ +// "38f33f7e-a471-4630-8ce9-c6653495a2ee" +// ] +// } +// ] + +// Example (Bicep) +// ----------------------------- +// [ +// { +// comments: 'Built-In Contributor Role' +// roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' +// securityGroupObjectIds: [ +// '38f33f7e-a471-4630-8ce9-c6653495a2ee' +// ] +// } +// ] +@description('Array of role assignments at subscription scope. The array will contain an object with comments, roleDefinitionId and array of securityGroupObjectIds.') +param subscriptionRoleAssignments array = [] + +// Subscription Budget +// Example (JSON) +// --------------------------- +// "subscriptionBudget": { +// "value": { +// "createBudget": false, +// "name": "MonthlySubscriptionBudget", +// "amount": 1000, +// "timeGrain": "Monthly", +// "contactEmails": [ "alzcanadapubsec@microsoft.com" ] +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// createBudget: true +// name: 'MonthlySubscriptionBudget' +// amount: 1000 +// timeGrain: 'Monthly' +// contactEmails: [ +// 'alzcanadapubsec@microsoft.com' +// ] +// } +@description('Subscription budget configuration containing createBudget flag, name, amount, timeGrain and array of contactEmails') +param subscriptionBudget object + +// Tags +// Example (JSON) +// ----------------------------- +// "subscriptionTags": { +// "value": { +// "ISSO": "isso-tag" +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// ISSO: 'isso-tag' +// } +@description('A set of key/value pairs of tags assigned to the subscription.') +param subscriptionTags object + +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact: 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Resource Groups +// Example (JSON) +// ----------------------------- +// "resourceGroups": { +// "value": { +// "automation": "azmlAutomation", +// "compute": "azmlCompute", +// "monitor": "azmlMonitor", +// "networking": "azmlNetworking", +// "networkWatcher": "NetworkWatcherRG", +// "security": "azmlSecurity", +// "storage": "azmlStorage" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// automation: 'azmlAutomation' +// compute: 'azmlCompute' +// monitor: 'azmlMonitor' +// networking: 'azmlNetworking' +// networkWatcher: 'NetworkWatcherRG' +// security: 'azmlSecurity' +// storage: 'azmlStorage' +// } +@description('Resource groups required for the achetype. It includes automation, compute, monitor, networking, networkWatcher, security and storage.') +param resourceGroups object + +@description('Boolean flag to determine whether customer managed keys are used. Default: false') +param useCMK bool = false + +// Azure Automation Account +// Example (JSON) +// ----------------------------- +// "automation": { +// "value": { +// "name": "azmlautomation" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// name: 'azmlautomation' +// } +@description('Azure Automation Account configuration. Includes name.') +param automation object + +// Azure Key Vault +// Example (JSON) +//----------------------------- +// "keyVault": { +// "value": { +// "secretExpiryInDays": 3650 +// } +// } + +// Example (Bicep) +//----------------------------- +// { +// secretExpiryInDays: 3650 +// } +@description('Azure Key Vault configuraiton. Includes secretExpiryInDays.') +param keyVault object + +// Azure Kubernetes Service +// Example (JSON) +//----------------------------- +// "aks": { +// "value": { +// "version": "1.21.2" +// } +// } + +// Example (Bicep) +//----------------------------- +// { +// version: '1.21.2' +// } +@description('Azure Kubernetes Service configuration. Includes version.') +param aks object + +// SQL Database +// ----------------------------- +// Example (JSON) +// "sqldb": { +// "value": { +// "enabled": true, +// "username": "azadmin" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// enabled: true +// username: 'azadmin' +// } +@description('SQL Database configuration. Includes enabled flag and username.') +param sqldb object + +// SQL Managed Instance +// ----------------------------- +// Example (JSON) +// "sqlmi": { +// "value": { +// "enabled": true, +// "username": "azadmin" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// enabled: true +// username: 'azadmin' +// } +@description('SQL Managed Instance configuration. Includes enabled flag and username.') +param sqlmi object + +// Example (JSON) +// ----------------------------- +// "aml": { +// "value": { +// "enableHbiWorkspace": false +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// enableHbiWorkspace: false +// } +@description('Azure Machine Learning configuration. Includes enableHbiWorkspace.') +param aml object + +// Networking +// Example (JSON) +// ----------------------------- +// "hubNetwork": { +// "value": { +// "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", +// "rfc1918IPRange": "10.18.0.0/22", +// "rfc6598IPRange": "100.60.0.0/16", +// "egressVirtualApplianceIp": "10.18.0.36", +// "privateDnsManagedByHub": true, +// "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", +// "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// virtualNetworkId: '/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet' +// rfc1918IPRange: '10.18.0.0/22' +// rfc6598IPRange: '100.60.0.0/16' +// egressVirtualApplianceIp: '10.18.0.36' +// privateDnsManagedByHub: true, +// privateDnsManagedByHubSubscriptionId: 'ed7f4eed-9010-4227-b115-2a5e37728f27', +// privateDnsManagedByHubResourceGroupName: 'pubsec-dns-rg' +// } +@description('Hub Network configuration that includes virtualNetworkId, rfc1918IPRange, rfc6598IPRange, egressVirtualApplianceIp, privateDnsManagedByHub flag, privateDnsManagedByHubSubscriptionId and privateDnsManagedByHubResourceGroupName.') +param hubNetwork object + +// Example (JSON) +// ----------------------------- +// "network": { +// "value": { +// "peerToHubVirtualNetwork": true, +// "useRemoteGateway": false, +// "name": "vnet", +// "dnsServers": [ +// "10.18.1.4" +// ], +// "addressPrefixes": [ +// "10.2.0.0/16" +// ], +// "subnets": { +// "oz": { +// "comments": "Foundational Elements Zone (OZ)", +// "name": "oz", +// "addressPrefix": "10.2.1.0/25" +// }, +// "paz": { +// "comments": "Presentation Zone (PAZ)", +// "name": "paz", +// "addressPrefix": "10.2.2.0/25" +// }, +// "rz": { +// "comments": "Application Zone (RZ)", +// "name": "rz", +// "addressPrefix": "10.2.3.0/25" +// }, +// "hrz": { +// "comments": "Data Zone (HRZ)", +// "name": "hrz", +// "addressPrefix": "10.2.4.0/25" +// }, +// "privateEndpoints": { +// "comments": "Private Endpoints Subnet", +// "name": "privateendpoints", +// "addressPrefix": "10.2.5.0/25" +// }, +// "sqlmi": { +// "comments": "SQL Managed Instances Delegated Subnet", +// "name": "sqlmi", +// "addressPrefix": "10.2.6.0/25" +// }, +// "databricksPublic": { +// "comments": "Databricks Public Delegated Subnet", +// "name": "databrickspublic", +// "addressPrefix": "10.2.7.0/25" +// }, +// "databricksPrivate": { +// "comments": "Databricks Private Delegated Subnet", +// "name": "databricksprivate", +// "addressPrefix": "10.2.8.0/25" +// }, +// "aks": { +// "comments": "AKS Subnet", +// "name": "aks", +// "addressPrefix": "10.2.9.0/25" +// } +// } +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// peerToHubVirtualNetwork: true +// useRemoteGateway: false +// name: 'vnet' +// dnsServers: [ +// '10.18.1.4' +// ] +// addressPrefixes: [ +// '10.2.0.0/16' +// ] +// subnets: { +// oz: { +// comments: 'Foundational Elements Zone (OZ)' +// name: 'oz' +// addressPrefix: '10.2.1.0/25' +// } +// paz: { +// comments: 'Presentation Zone (PAZ)' +// name: 'paz' +// addressPrefix: '10.2.2.0/25' +// } +// rz: { +// comments: 'Application Zone (RZ)' +// name: 'rz' +// addressPrefix: '10.2.3.0/25' +// } +// hrz: { +// comments: 'Data Zone (HRZ)' +// name: 'hrz' +// addressPrefix: '10.2.4.0/25' +// } +// privateEndpoints: { +// comments: 'Private Endpoints Subnet' +// name: 'privateendpoints' +// addressPrefix: '10.2.5.0/25' +// } +// sqlmi: { +// comments: 'SQL Managed Instances Delegated Subnet' +// name: 'sqlmi' +// addressPrefix: '10.2.6.0/25' +// } +// databricksPublic: { +// comments: 'Databricks Public Delegated Subnet' +// name: 'databrickspublic' +// addressPrefix: '10.2.7.0/25' +// } +// databricksPrivate: { +// comments: 'Databricks Private Delegated Subnet' +// name: 'databricksprivate' +// addressPrefix: '10.2.8.0/25' +// } +// aks: { +// comments: 'AKS Subnet' +// name: 'aks' +// addressPrefix: '10.2.9.0/25' +// } +// } +// } +@description('Network configuration. Includes peerToHubVirtualNetwork flag, useRemoteGateway flag, name, dnsServers, addressPrefixes and subnets (oz, paz, rz, hrz, privateEndpoints, sqlmi, databricksPublic, databricksPrivate, aks) ') +param network object + +/* + Scaffold the subscription which includes: + * Azure Security Center - Enable Azure Defender (all available options) + * Azure Security Center - Configure Log Analytics Workspace + * Azure Security Center - Configure Security Alert Contact + * Role Assignments to Security Groups + * Service Health Alerts + * Subscription Budget + * Subscription Tags +*/ +module subScaffold '../scaffold-subscription.bicep' = { + name: 'configure-subscription' + scope: subscription() + params: { + serviceHealthAlerts: serviceHealthAlerts + subscriptionRoleAssignments: subscriptionRoleAssignments + subscriptionBudget: subscriptionBudget + subscriptionTags: subscriptionTags + resourceTags: resourceTags + + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + + securityCenter: securityCenter + } +} + +// Deploy Landing Zone +module landingZone 'lz.bicep' = { + name: 'deploy-machinelearning-archetype' + scope: subscription() + params: { + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + + securityContactEmail: securityCenter.email + + resourceTags: resourceTags + resourceGroups: resourceGroups + + useCMK: useCMK + + automation: automation + keyVault: keyVault + aks: aks + sqldb: sqldb + sqlmi: sqlmi + aml: aml + + hubNetwork: hubNetwork + network: network + } +} diff --git a/landingzones/lz-machinelearning/main.parameters-sample.json b/landingzones/lz-machinelearning/main.parameters-sample.json new file mode 100644 index 00000000..610a4465 --- /dev/null +++ b/landingzones/lz-machinelearning/main.parameters-sample.json @@ -0,0 +1,175 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + } + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "alzcanadapubsec@microsoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroups": { + "value": { + "automation": "azmlAutomation", + "compute": "azmlCompute", + "monitor": "azmlMonitor", + "networking": "azmlNetworking", + "networkWatcher": "NetworkWatcherRG", + "security": "azmlSecurity", + "storage": "azmlStorage" + } + }, + "useCMK": { + "value": true + }, + "keyVault": { + "value": { + "secretExpiryInDays": 3650 + } + }, + "automation": { + "value": { + "name": "azmlautomation" + } + }, + "aks": { + "value": { + "version": "1.21.2" + } + }, + "sqldb": { + "value": { + "enabled": true, + "username": "azadmin" + } + }, + "sqlmi": { + "value": { + "enabled": true, + "username": "azadmin" + } + }, + "aml": { + "value": { + "enableHbiWorkspace": false + } + }, + "hubNetwork": { + "value": { + "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", + "rfc1918IPRange": "10.18.0.0/22", + "rfc6598IPRange": "100.60.0.0/16", + "egressVirtualApplianceIp": "10.18.0.36", + "privateDnsManagedByHub": true, + "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", + "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" + } + }, + "network": { + "value": { + "peerToHubVirtualNetwork": true, + "useRemoteGateway": false, + "name": "vnet", + "dnsServers": [ + "10.18.1.4" + ], + "addressPrefixes": [ + "10.2.0.0/16" + ], + "subnets": { + "oz": { + "comments": "Foundational Elements Zone (OZ)", + "name": "oz", + "addressPrefix": "10.2.1.0/25" + }, + "paz": { + "comments": "Presentation Zone (PAZ)", + "name": "paz", + "addressPrefix": "10.2.2.0/25" + }, + "rz": { + "comments": "Application Zone (RZ)", + "name": "rz", + "addressPrefix": "10.2.3.0/25" + }, + "hrz": { + "comments": "Data Zone (HRZ)", + "name": "hrz", + "addressPrefix": "10.2.4.0/25" + }, + "privateEndpoints": { + "comments": "Private Endpoints Subnet", + "name": "privateendpoints", + "addressPrefix": "10.2.5.0/25" + }, + "sqlmi": { + "comments": "SQL Managed Instances Delegated Subnet", + "name": "sqlmi", + "addressPrefix": "10.2.6.0/25" + }, + "databricksPublic": { + "comments": "Databricks Public Delegated Subnet", + "name": "databrickspublic", + "addressPrefix": "10.2.7.0/25" + }, + "databricksPrivate": { + "comments": "Databricks Private Delegated Subnet", + "name": "databricksprivate", + "addressPrefix": "10.2.8.0/25" + }, + "aks": { + "comments": "AKS Subnet", + "name": "aks", + "addressPrefix": "10.2.9.0/25" + } + } + } + } + } +} \ No newline at end of file diff --git a/landingzones/lz-machinelearning/networking.bicep b/landingzones/lz-machinelearning/networking.bicep new file mode 100644 index 00000000..4010bddf --- /dev/null +++ b/landingzones/lz-machinelearning/networking.bicep @@ -0,0 +1,643 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// Networking +// Example (JSON) +// ----------------------------- +// "hubNetwork": { +// "value": { +// "virtualNetworkId": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet", +// "rfc1918IPRange": "10.18.0.0/22", +// "rfc6598IPRange": "100.60.0.0/16", +// "egressVirtualApplianceIp": "10.18.0.36", +// "privateDnsManagedByHub": true, +// "privateDnsManagedByHubSubscriptionId": "ed7f4eed-9010-4227-b115-2a5e37728f27", +// "privateDnsManagedByHubResourceGroupName": "pubsec-dns-rg" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// virtualNetworkId: '/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourceGroups/pubsec-hub-networking-rg/providers/Microsoft.Network/virtualNetworks/hub-vnet' +// rfc1918IPRange: '10.18.0.0/22' +// rfc6598IPRange: '100.60.0.0/16' +// egressVirtualApplianceIp: '10.18.0.36' +// privateDnsManagedByHub: true, +// privateDnsManagedByHubSubscriptionId: 'ed7f4eed-9010-4227-b115-2a5e37728f27', +// privateDnsManagedByHubResourceGroupName: 'pubsec-dns-rg' +// } +@description('Hub Network configuration that includes virtualNetworkId, rfc1918IPRange, rfc6598IPRange, egressVirtualApplianceIp, privateDnsManagedByHub flag, privateDnsManagedByHubSubscriptionId and privateDnsManagedByHubResourceGroupName.') +param hubNetwork object + +// Example (JSON) +// ----------------------------- +// "network": { +// "value": { +// "peerToHubVirtualNetwork": true, +// "useRemoteGateway": false, +// "name": "vnet", +// "dnsServers": [ +// "10.18.1.4" +// ], +// "addressPrefixes": [ +// "10.2.0.0/16" +// ], +// "subnets": { +// "oz": { +// "comments": "Foundational Elements Zone (OZ)", +// "name": "oz", +// "addressPrefix": "10.2.1.0/25" +// }, +// "paz": { +// "comments": "Presentation Zone (PAZ)", +// "name": "paz", +// "addressPrefix": "10.2.2.0/25" +// }, +// "rz": { +// "comments": "Application Zone (RZ)", +// "name": "rz", +// "addressPrefix": "10.2.3.0/25" +// }, +// "hrz": { +// "comments": "Data Zone (HRZ)", +// "name": "hrz", +// "addressPrefix": "10.2.4.0/25" +// }, +// "privateEndpoints": { +// "comments": "Private Endpoints Subnet", +// "name": "privateendpoints", +// "addressPrefix": "10.2.5.0/25" +// }, +// "sqlmi": { +// "comments": "SQL Managed Instances Delegated Subnet", +// "name": "sqlmi", +// "addressPrefix": "10.2.6.0/25" +// }, +// "databricksPublic": { +// "comments": "Databricks Public Delegated Subnet", +// "name": "databrickspublic", +// "addressPrefix": "10.2.7.0/25" +// }, +// "databricksPrivate": { +// "comments": "Databricks Private Delegated Subnet", +// "name": "databricksprivate", +// "addressPrefix": "10.2.8.0/25" +// }, +// "aks": { +// "comments": "AKS Subnet", +// "name": "aks", +// "addressPrefix": "10.2.9.0/25" +// } +// } +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// peerToHubVirtualNetwork: true +// useRemoteGateway: false +// name: 'vnet' +// dnsServers: [ +// '10.18.1.4' +// ] +// addressPrefixes: [ +// '10.2.0.0/16' +// ] +// subnets: { +// oz: { +// comments: 'Foundational Elements Zone (OZ)' +// name: 'oz' +// addressPrefix: '10.2.1.0/25' +// } +// paz: { +// comments: 'Presentation Zone (PAZ)' +// name: 'paz' +// addressPrefix: '10.2.2.0/25' +// } +// rz: { +// comments: 'Application Zone (RZ)' +// name: 'rz' +// addressPrefix: '10.2.3.0/25' +// } +// hrz: { +// comments: 'Data Zone (HRZ)' +// name: 'hrz' +// addressPrefix: '10.2.4.0/25' +// } +// privateEndpoints: { +// comments: 'Private Endpoints Subnet' +// name: 'privateendpoints' +// addressPrefix: '10.2.5.0/25' +// } +// sqlmi: { +// comments: 'SQL Managed Instances Delegated Subnet' +// name: 'sqlmi' +// addressPrefix: '10.2.6.0/25' +// } +// databricksPublic: { +// comments: 'Databricks Public Delegated Subnet' +// name: 'databrickspublic' +// addressPrefix: '10.2.7.0/25' +// } +// databricksPrivate: { +// comments: 'Databricks Private Delegated Subnet' +// name: 'databricksprivate' +// addressPrefix: '10.2.8.0/25' +// } +// aks: { +// comments: 'AKS Subnet' +// name: 'aks' +// addressPrefix: '10.2.9.0/25' +// } +// } +// } +@description('Network configuration. Includes peerToHubVirtualNetwork flag, useRemoteGateway flag, name, dnsServers, addressPrefixes and subnets (oz, paz, rz, hrz, privateEndpoints, sqlmi, databricksPublic, databricksPrivate, aks) ') +param network object + +var hubVnetIdSplit = split(hubNetwork.virtualNetworkId, '/') +var usingCustomDNSServers = length(network.dnsServers) > 0 + +var routesToHub = [ + // Force Routes to Hub IPs (RFC1918 range) via FW despite knowing that route via peering + { + name: 'PrdSpokesUdrHubRFC1918FWRoute' + properties: { + addressPrefix: hubNetwork.rfc1918IPRange + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } + // Force Routes to Hub IPs (CGNAT range) via FW despite knowing that route via peering + { + name: 'PrdSpokesUdrHubRFC6598FWRoute' + properties: { + addressPrefix: hubNetwork.rfc6598IPRange + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } + { + name: 'RouteToEgressFirewall' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: hubNetwork.egressVirtualApplianceIp + } + } +] + +// Network Security Groups +resource nsgOZ 'Microsoft.Network/networkSecurityGroups@2021-02-01' = { + name: '${network.subnets.oz.name}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +resource nsgPAZ 'Microsoft.Network/networkSecurityGroups@2021-02-01' = { + name: '${network.subnets.paz.name}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +resource nsgRZ 'Microsoft.Network/networkSecurityGroups@2021-02-01' = { + name: '${network.subnets.rz.name}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +resource nsgHRZ 'Microsoft.Network/networkSecurityGroups@2021-02-01' = { + name: '${network.subnets.hrz.name}Nsg' + location: resourceGroup().location + properties: { + securityRules: [] + } +} + +module nsgDatabricks '../../azresources/network/nsg/nsg-databricks.bicep' = { + name: 'deploy-nsg-databricks' + params: { + namePublic: '${network.subnets.databricksPublic.name}Nsg' + namePrivate: '${network.subnets.databricksPrivate.name}Nsg' + } +} + +module nsgSqlMi '../../azresources/network/nsg/nsg-sqlmi.bicep' = { + name: 'deploy-nsg-sqlmi' + params: { + name: '${network.subnets.sqlmi.name}Nsg' + } +} + +// Route Tables +resource udrOZ 'Microsoft.Network/routeTables@2021-02-01' = { + name: '${network.subnets.oz.name}Udr' + location: resourceGroup().location + properties: { + routes: network.peerToHubVirtualNetwork ? routesToHub : null + } +} + +resource udrPAZ 'Microsoft.Network/routeTables@2021-02-01' = { + name: '${network.subnets.paz.name}Udr' + location: resourceGroup().location + properties: { + routes: network.peerToHubVirtualNetwork ? routesToHub : null + } +} + +resource udrRZ 'Microsoft.Network/routeTables@2021-02-01' = { + name: '${network.subnets.rz.name}Udr' + location: resourceGroup().location + properties: { + routes: network.peerToHubVirtualNetwork ? routesToHub : null + } +} + +resource udrHRZ 'Microsoft.Network/routeTables@2021-02-01' = { + name: '${network.subnets.hrz.name}Udr' + location: resourceGroup().location + properties: { + routes: network.peerToHubVirtualNetwork ? routesToHub : null + } +} + +module udrSqlMi '../../azresources/network/udr/udr-sqlmi.bicep' = { + name: 'deploy-route-table-sqlmi' + params: { + name: '${network.subnets.sqlmi.name}Udr' + } +} + +module udrDatabricksPublic '../../azresources/network/udr/udr-databricks-public.bicep' = { + name: 'deploy-route-table-databricks-public' + params: { + name: '${network.subnets.databricksPublic.name}Udr' + } +} + +module udrDatabricksPrivate '../../azresources/network/udr/udr-databricks-private.bicep' = { + name: 'deploy-route-table-databricks-private' + params: { + name: '${network.subnets.databricksPrivate.name}Udr' + } +} + +// Virtual Network +resource vnet 'Microsoft.Network/virtualNetworks@2021-02-01' = { + name: network.name + location: resourceGroup().location + properties: { + dhcpOptions: { + dnsServers: network.dnsServers + } + addressSpace: { + addressPrefixes: network.addressPrefixes + } + subnets: [ + { + name: network.subnets.oz.name + properties: { + addressPrefix: network.subnets.oz.addressPrefix + routeTable: { + id: udrOZ.id + } + networkSecurityGroup: { + id: nsgOZ.id + } + } + } + { + name: network.subnets.paz.name + properties: { + addressPrefix: network.subnets.paz.addressPrefix + routeTable: { + id: udrPAZ.id + } + networkSecurityGroup: { + id: nsgPAZ.id + } + } + } + { + name: network.subnets.rz.name + properties: { + addressPrefix: network.subnets.rz.addressPrefix + routeTable: { + id: udrRZ.id + } + networkSecurityGroup: { + id: nsgRZ.id + } + } + } + { + name: network.subnets.hrz.name + properties: { + addressPrefix: network.subnets.hrz.addressPrefix + routeTable: { + id: udrHRZ.id + } + networkSecurityGroup: { + id: nsgHRZ.id + } + } + } + { + name: network.subnets.privateEndpoints.name + properties: { + addressPrefix: network.subnets.privateEndpoints.addressPrefix + privateEndpointNetworkPolicies: 'Disabled' + } + } + { + name: network.subnets.aks.name + properties: { + addressPrefix: network.subnets.aks.addressPrefix + privateEndpointNetworkPolicies: 'Disabled' + } + } + { + name: network.subnets.databricksPublic.name + properties: { + addressPrefix: network.subnets.databricksPublic.addressPrefix + networkSecurityGroup: { + id: nsgDatabricks.outputs.publicNsgId + } + routeTable: { + id: udrDatabricksPublic.outputs.udrId + } + delegations: [ + { + name: 'databricks-delegation-public' + properties: { + serviceName: 'Microsoft.Databricks/workspaces' + } + } + ] + } + } + { + name: network.subnets.databricksPrivate.name + properties: { + addressPrefix: network.subnets.databricksPrivate.addressPrefix + networkSecurityGroup: { + id: nsgDatabricks.outputs.privateNsgId + } + routeTable: { + id: udrDatabricksPrivate.outputs.udrId + } + delegations: [ + { + name: 'databricks-delegation-private' + properties: { + serviceName: 'Microsoft.Databricks/workspaces' + } + } + ] + } + } + { + name: network.subnets.sqlmi.name + properties: { + addressPrefix: network.subnets.sqlmi.addressPrefix + routeTable: { + id: udrSqlMi.outputs.udrId + } + networkSecurityGroup: { + id: nsgSqlMi.outputs.nsgId + } + serviceEndpoints: [ + { + service: 'Microsoft.Storage' + } + ] + delegations: [ + { + name: 'sqlmi-delegation' + properties: { + serviceName: 'Microsoft.Sql/managedInstances' + } + } + ] + } + } + ] + } +} + +module vnetPeeringSpokeToHub '../../azresources/network/vnet-peering.bicep' = if (network.peerToHubVirtualNetwork) { + name: 'deploy-vnet-peering-spoke-to-hub' + scope: resourceGroup() + params: { + peeringName: 'Hub-${vnet.name}-to-${last(hubVnetIdSplit)}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: vnet.name + targetVnetId: hubNetwork.virtualNetworkId + useRemoteGateways: network.useRemoteGateway + } +} + +// For Hub to Spoke vnet peering, we must rescope the deployment to the subscription id & resource group of where the Hub VNET is located. +module vnetPeeringHubToSpoke '../../azresources/network/vnet-peering.bicep' = if (network.peerToHubVirtualNetwork) { + name: 'deploy-vnet-peering-${subscription().subscriptionId}' + // vnet id = /subscriptions/<>/resourceGroups/<>/providers/Microsoft.Network/virtualNetworks/<> + scope: resourceGroup(network.peerToHubVirtualNetwork ? hubVnetIdSplit[2] : '', network.peerToHubVirtualNetwork ? hubVnetIdSplit[4] : '') + params: { + peeringName: 'Spoke-${last(hubVnetIdSplit)}-to-${vnet.name}-${uniqueString(vnet.id)}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: last(hubVnetIdSplit) + targetVnetId: vnet.id + useRemoteGateways: false + } +} + +// Private DNS Zones +module privatezone_sqldb '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-sqldb' + scope: resourceGroup() + params: { + zone: 'privatelink${environment().suffixes.sqlServerHostname}' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_adf_datafactory '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-adf-datafactory' + scope: resourceGroup() + params: { + zone: 'privatelink.datafactory.azure.net' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_keyvault '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-keyvault' + scope: resourceGroup() + params: { + zone: 'privatelink.vaultcore.azure.net' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_acr '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-acr' + scope: resourceGroup() + params: { + zone: 'privatelink.azurecr.io' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_datalake_blob '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-blob' + scope: resourceGroup() + params: { + zone: 'privatelink.blob.${environment().suffixes.storage}' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_datalake_dfs '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-dfs' + scope: resourceGroup() + params: { + zone: 'privatelink.dfs.${environment().suffixes.storage}' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_datalake_file '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-file' + scope: resourceGroup() + params: { + zone: 'privatelink.file.${environment().suffixes.storage}' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_azureml_api '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-azureml-api' + scope: resourceGroup() + params: { + zone: 'privatelink.api.azureml.ms' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_azureml_notebook '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-azureml-notebook' + scope: resourceGroup() + params: { + zone: 'privatelink.notebooks.azure.net' + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +module privatezone_aks '../../azresources/network/private-dns-zone.bicep' = { + name: 'deploy-privatezone-aks' + scope: resourceGroup() + params: { + zone: toLower('privatelink.${resourceGroup().location}.azmk8s.io') + vnetId: vnet.id + + dnsCreateNewZone: !hubNetwork.privateDnsManagedByHub + dnsLinkToVirtualNetwork: !hubNetwork.privateDnsManagedByHub || (hubNetwork.privateDnsManagedByHub && !usingCustomDNSServers) + dnsExistingZoneSubscriptionId: hubNetwork.privateDnsManagedByHubSubscriptionId + dnsExistingZoneResourceGroupName: hubNetwork.privateDnsManagedByHubResourceGroupName + registrationEnabled: false + } +} + +output vnetId string = vnet.id + +output ozSubnetId string = '${vnet.id}/subnets/${network.subnets.oz.name}' +output pazSubnetId string = '${vnet.id}/subnets/${network.subnets.paz.name}' +output rzSubnetId string = '${vnet.id}/subnets/${network.subnets.rz.name}' +output hrzId string = '${vnet.id}/subnets/${network.subnets.hrz.name}' +output privateEndpointSubnetId string = '${vnet.id}/subnets/${network.subnets.privateEndpoints.name}' +output sqlMiSubnetId string = '${vnet.id}/subnets/${network.subnets.sqlmi.name}' +output aksSubnetId string = '${vnet.id}/subnets/${network.subnets.aks.name}' + +output databricksPublicSubnetName string = network.subnets.databricksPublic.name +output databricksPrivateSubnetName string = network.subnets.databricksPrivate.name + +output dataLakeDfsPrivateDnsZoneId string = privatezone_datalake_dfs.outputs.privateDnsZoneId +output dataLakeBlobPrivateDnsZoneId string = privatezone_datalake_blob.outputs.privateDnsZoneId +output dataLakeFilePrivateDnsZoneId string = privatezone_datalake_file.outputs.privateDnsZoneId +output adfDataFactoryPrivateDnsZoneId string = privatezone_adf_datafactory.outputs.privateDnsZoneId +output keyVaultPrivateDnsZoneId string = privatezone_keyvault.outputs.privateDnsZoneId +output acrPrivateDnsZoneId string = privatezone_acr.outputs.privateDnsZoneId +output sqlDBPrivateDnsZoneId string = privatezone_sqldb.outputs.privateDnsZoneId +output amlApiPrivateDnsZoneId string = privatezone_azureml_api.outputs.privateDnsZoneId +output amlNotebooksPrivateDnsZoneId string = privatezone_azureml_notebook.outputs.privateDnsZoneId +output aksPrivateDnsZoneId string = privatezone_aks.outputs.privateDnsZoneId diff --git a/landingzones/lz-platform-connectivity-hub-azfw/azfw-policy/azure-firewall-policy.bicep b/landingzones/lz-platform-connectivity-hub-azfw/azfw-policy/azure-firewall-policy.bicep new file mode 100644 index 00000000..17d92e5e --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-azfw/azfw-policy/azure-firewall-policy.bicep @@ -0,0 +1,844 @@ +// ---------------------------------------------------------------------------------- +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +@description('Azure Firewall Policy Name') +param name string + +resource policy 'Microsoft.Network/firewallPolicies@2021-02-01' = { + name: name + location: resourceGroup().location + properties: { + sku: { + tier: 'Premium' + } + dnsSettings: { + enableProxy: true + } + intrusionDetection: { + mode: 'Alert' + } + threatIntelMode: 'Alert' + } + + // Azure / Priority: 200 + resource azureCollectionGroup 'ruleCollectionGroups@2021-02-01' = { + name: 'Azure' + properties: { + priority: 200 + ruleCollections: [ + // Azure AD + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure AD' + priority: 100 + action: { + type: 'Allow' + } + rules: [ + { + ruleType: 'NetworkRule' + name: 'Azure AD Tag' + destinationAddresses: [ + 'AzureActiveDirectory' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + { + ruleType: 'NetworkRule' + name: 'Azure AD FQDNs' + destinationFqdns: [ + 'aadcdn.msauth.net' + 'aadcdn.msftauth.net' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + ] + } + // Azure Resource Manager + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure Resource Manager' + priority: 105 + action: { + type: 'Allow' + } + rules: [ + { + ruleType: 'NetworkRule' + name: 'Azure Resource Manager Tag' + destinationAddresses: [ + 'AzureResourceManager' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + ] + } + // Azure Portal + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure Portal' + priority: 110 + action: { + type: 'Allow' + } + rules: [ + { + ruleType: 'NetworkRule' + name: 'Azure Portal Tag' + destinationAddresses: [ + 'AzurePortal' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + { + ruleType: 'NetworkRule' + name: 'Azure Portal FQDNs' + destinationFqdns: [ + 'afd.hosting.portal.azure.net' + 'bmxservice.trafficmanager.net' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + ] + } + // Azure Monitor + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure Monitor' + priority: 120 + action: { + type: 'Allow' + } + rules: [ + { + ruleType: 'NetworkRule' + name: 'Azure Monitor Tag' + destinationAddresses: [ + 'AzureMonitor' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + ] + } + // Azure Automation & Guest Configuration + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure Automation and Guest Configuration' + action: { + type: 'Allow' + } + priority: 130 + rules: [ + { + ruleType: 'NetworkRule' + name: 'GuestAndHybridManagement Tag' + destinationAddresses: [ + 'GuestAndHybridManagement' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + { + ruleType: 'NetworkRule' + name: 'Guest Configuration - FQDNs' + destinationFqdns: [ + 'agentserviceapi.guestconfiguration.azure.com' + 'oaasguestconfigwcuss1.blob.core.windows.net' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + ] + } + // Data Factory + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure Data Factory' + action: { + type: 'Allow' + } + priority: 139 + rules: [ + { + ruleType: 'NetworkRule' + name: 'Data Factory Tag' + destinationAddresses: [ + 'DataFactory' + 'DataFactoryManagement' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + ] + } + // Azure Databricks + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure Databricks' + action: { + type: 'Allow' + } + priority: 140 + rules: [ + { + ruleType: 'NetworkRule' + name: 'Azure Databricks Tag' + destinationAddresses: [ + 'AzureDatabricks' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + { + ruleType: 'NetworkRule' + name: 'databricks.com' + destinationFqdns: [ + 'sourcemaps.dev.databricks.com' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + ] + } + // Azure Machine Learning + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure ML' + action: { + type: 'Allow' + } + priority: 150 + rules: [ + { + ruleType: 'NetworkRule' + name: 'Azure ML Tag' + destinationAddresses: [ + 'AzureMachineLearning' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + ] + } + // Signal R + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'SignalR' + action: { + type: 'Allow' + } + priority: 160 + rules: [ + { + ruleType: 'NetworkRule' + name: 'SignalR Tag' + destinationAddresses: [ + 'AzureSignalR' + ] + destinationPorts: [ + '443' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + ] + } + // Azure Synapse Analytics Application Rules + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Synapse Analytics FQDNs' + action: { + type: 'Allow' + } + priority: 997 + rules: [ + { + ruleType: 'ApplicationRule' + name: 'Synapse Analytics FQDNs' + targetFqdns: [ + 'web.azuresynapse.net' + '*.dev.azuresynapse.net' + '*.sql.azuresynapse.net' + ] + sourceAddresses: [ + '*' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + } + ] + } + // Azure Data Factory Application Rules + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure Data Factory FQDNs' + action: { + type: 'Allow' + } + priority: 998 + rules: [ + { + ruleType: 'ApplicationRule' + name: 'Azure Data Factory FQDNs' + targetFqdns: [ + 'adf.azure.com' + + // https://docs.microsoft.com/en-us/azure/data-factory/data-factory-ux-troubleshoot-guide + 'dpcanadacentral.svc.datafactory.azure.com' + 'dpcanadaeast.svc.datafactory.azure.com' + ] + sourceAddresses: [ + '*' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + } + ] + } + // Azure Machine Learning Application Rules + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure ML FQDNs' + action: { + type: 'Allow' + } + priority: 999 + rules: [ + { + ruleType: 'ApplicationRule' + name: 'AzureML Notebooks FQDNs' + targetFqdns: [ + '*.notebooks.azure.net' + ] + sourceAddresses: [ + '*' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + } + { + ruleType: 'ApplicationRule' + name: 'AzureML API FQDNs' + targetFqdns: [ + '*.api.azureml.ms' + ] + sourceAddresses: [ + '*' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + } + { + ruleType: 'ApplicationRule' + name: 'AzureML Samples FQDNs' + targetFqdns: [ + 'notebieastus.blob.core.windows.net' + ] + sourceAddresses: [ + '*' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + } + ] + } + // Qualys Rule Collection + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + priority: 1000 + name: 'Azure Security Center - Qualys' + action: { + type: 'Allow' + } + rules: [ + // Reference: https://docs.microsoft.com/azure/security-center/deploy-vulnerability-assessment-vm#deploy-the-integrated-scanner-to-your-azure-and-hybrid-machines + { + ruleType: 'ApplicationRule' + name: 'US Data Center' + targetFqdns: [ + 'qagpublic.qg3.apps.qualys.com' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + sourceAddresses: [ + '*' + ] + } + { + ruleType: 'ApplicationRule' + name: 'European Data Center' + targetFqdns: [ + 'qagpublic.qg2.apps.qualys.eu' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + sourceAddresses: [ + '*' + ] + } + ] + } + // Azure Backup + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Azure Backup' + action: { + type: 'Allow' + } + priority: 1100 + rules: [ + { + ruleType: 'ApplicationRule' + name: 'Azure Backup' + fqdnTags: [ + 'AzureBackup' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + sourceAddresses: [ + '*' + ] + } + ] + } + ] + } + } + + // Windows // Priority 1000 + resource windowsCollectionGroup 'ruleCollectionGroups@2021-02-01' = { + dependsOn: [ + azureCollectionGroup + ] + + name: 'Windows' + properties: { + priority: 1000 + ruleCollections: [ + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Windows KMS' + action: { + type: 'Allow' + } + priority: 100 + rules: [ + { + ruleType: 'NetworkRule' + name: 'Azure Global - IP' + destinationAddresses: [ + '23.102.135.246' + ] + destinationPorts: [ + '1688' + ] + ipProtocols: [ + 'TCP' + ] + sourceAddresses: [ + '*' + ] + } + { + ruleType: 'NetworkRule' + name: 'Azure Global - FQDN' + destinationFqdns: [ + 'kms.core.windows.net' + ] + destinationPorts: [ + '1688' + ] + ipProtocols: [ + 'TCP' + ] + sourceAddresses: [ + '*' + ] + } + { + // https://support.microsoft.com/en-us/topic/windows-activation-or-validation-fails-with-error-code-0x8004fe33-a9afe65e-230b-c1ed-3414-39acd7fddf52 + ruleType: 'NetworkRule' + name: 'Licensing FQDNs' + destinationFqdns: [ + 'activation.sls.microsoft.com' + 'activation-v2.sls.microsoft.com' + + 'crl.microsoft.com' + + 'displaycatalog.mp.microsoft.com' + 'displaycatalog.md.mp.microsoft.com' + + 'licensing.mp.microsoft.com' + 'licensing.md.mp.microsoft.com' + + 'purchase.mp.microsoft.com' + 'purchase.md.mp.microsoft.com' + + 'validation.sls.microsoft.com' + 'validation-v2.sls.microsoft.com' + ] + destinationPorts: [ + '1688' + ] + ipProtocols: [ + 'TCP' + ] + sourceAddresses: [ + '*' + ] + } + ] + } + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Windows NTP' + action: { + type: 'Allow' + } + priority: 150 + rules: [ + { + ruleType: 'NetworkRule' + name: 'time.windows.com - FQDN' + destinationFqdns: [ + 'time.windows.com' + ] + destinationPorts: [ + '123' + ] + ipProtocols: [ + 'UDP' + ] + sourceAddresses: [ + '*' + ] + } + { + ruleType: 'NetworkRule' + name: 'time.windows.com - IP' + destinationAddresses: [ + '168.61.215.74' + ] + destinationPorts: [ + '123' + ] + ipProtocols: [ + 'UDP' + ] + sourceAddresses: [ + '*' + ] + } + ] + } + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Windows Diagnostics' + action: { + type: 'Allow' + } + priority: 200 + rules: [ + { + ruleType: 'ApplicationRule' + name: 'Windows Diagnostics Tag' + fqdnTags: [ + 'WindowsDiagnostics' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + sourceAddresses: [ + '*' + ] + } + { + ruleType: 'ApplicationRule' + name: 'Windows Diagnostics FQDNs' + targetFqdns: [ + 'umwatson.events.data.microsoft.com' + 'v20.events.data.microsoft.com' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + sourceAddresses: [ + '*' + ] + } + ] + } + { + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + name: 'Microsoft Active Protection Service (MAPS) Tag' + action: { + type: 'Allow' + } + priority: 210 + rules: [ + { + ruleType: 'ApplicationRule' + name: 'Microsoft Active Protection Service (MAPS)' + fqdnTags: [ + 'MicrosoftActiveProtectionService' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + sourceAddresses: [ + '*' + ] + } + ] + } + { + // https://docs.microsoft.com/azure/firewall/fqdn-tags + // https://docs.microsoft.com/mem/configmgr/sum/get-started/install-a-software-update-point + name: 'Windows Update' + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + priority: 1000 + action: { + type: 'Allow' + } + rules: [ + { + ruleType: 'ApplicationRule' + name: 'Windows Update Tag' + fqdnTags: [ + 'WindowsUpdate' + ] + protocols: [ + { + port: 443 + protocolType: 'Https' + } + ] + sourceAddresses: [ + '*' + ] + } + ] + } + ] + } + } + + // RedHat / Priority: 2000 + resource redhatCollectionGroup 'ruleCollectionGroups@2021-02-01' = { + dependsOn: [ + windowsCollectionGroup + ] + + name: 'RedHat' + properties: { + priority: 2000 + ruleCollections: [ + { + // https://docs.microsoft.com/azure/virtual-machines/workloads/redhat/redhat-rhui#the-ips-for-the-rhui-content-delivery-servers + name: 'RedHat Update Infrastructure' + ruleCollectionType: 'FirewallPolicyFilterRuleCollection' + priority: 100 + action: { + type: 'Allow' + } + rules: [ + { + ruleType: 'NetworkRule' + name: 'Azure Global' + destinationAddresses: [ + '13.91.47.76' + '40.85.190.91' + '52.187.75.218' + '52.174.163.213' + '52.237.203.198' + ] + destinationPorts: [ + '*' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + { + ruleType: 'NetworkRule' + name: 'Azure US Government' + destinationAddresses: [ + '13.72.186.193' + '13.72.14.155' + '52.244.249.194' + ] + destinationPorts: [ + '*' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + { + ruleType: 'NetworkRule' + name: 'Azure Germany' + destinationAddresses: [ + '51.5.243.77' + '51.4.228.145' + ] + destinationPorts: [ + '*' + ] + sourceAddresses: [ + '*' + ] + ipProtocols: [ + 'TCP' + ] + } + ] + } + ] + } + } +} + +output policyId string = policy.id diff --git a/landingzones/lz-platform-connectivity-hub-azfw/azfw-policy/bicepconfig.json b/landingzones/lz-platform-connectivity-hub-azfw/azfw-policy/bicepconfig.json new file mode 100644 index 00000000..c8e9f449 --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-azfw/azfw-policy/bicepconfig.json @@ -0,0 +1,13 @@ +{ + "analyzers": { + "core": { + "verbose": false, + "enabled": true, + "rules": { + "no-hardcoded-env-urls": { + "level": "off" + } + } + } + } +} \ No newline at end of file diff --git a/landingzones/lz-platform-connectivity-hub-azfw/hub-vnet/hub-vnet-routes.bicep b/landingzones/lz-platform-connectivity-hub-azfw/hub-vnet/hub-vnet-routes.bicep new file mode 100644 index 00000000..02365f30 --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-azfw/hub-vnet/hub-vnet-routes.bicep @@ -0,0 +1,66 @@ +// ---------------------------------------------------------------------------------- +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +@description('Public Access Zone Route Table Name') +param publicAccessZoneUdrName string + +@description('Management Restricted Zone Virtual Network Route Table Name') +param managementRestrictedZoneUdrName string + +@description('Virtual Network address space for RFC 1918.') +param hubVnetAddressPrefixRFC1918 string + +@description('Virtual Network address space for RFC 6598 (CG NAT).') +param hubVnetAddressPrefixRFC6598 string + +@description('Azure Firewall Private IP address') +param azureFirwallPrivateIp string + +resource routeTable 'Microsoft.Network/routeTables@2021-02-01' existing = { + name: publicAccessZoneUdrName +} + +var routes = [ + { + name: 'Hub-AzureFirewall-Default-Route' + properties: { + nextHopType: 'VirtualAppliance' + addressPrefix: '0.0.0.0/0' + nextHopIpAddress: azureFirwallPrivateIp + } + } + { + name: 'Hub-AzureFirewall-RFC1918-Route' + properties: { + nextHopType: 'VirtualAppliance' + addressPrefix: hubVnetAddressPrefixRFC1918 + nextHopIpAddress: azureFirwallPrivateIp + } + } + { + name: 'Hub-AzureFirewall-RFC6598-Route' + properties: { + nextHopType: 'VirtualAppliance' + addressPrefix: hubVnetAddressPrefixRFC6598 + nextHopIpAddress: azureFirwallPrivateIp + } + } +] + +module publicAccessZoneUdr '../../../azresources/network/udr/udr-custom.bicep' = { + name: 'deploy-route-table-${publicAccessZoneUdrName}' + params: { + name: publicAccessZoneUdrName + routes: routes + } +} + +module managementRestrictedZoneUdr '../../../azresources/network/udr/udr-custom.bicep' = { + name: 'deploy-route-table-${managementRestrictedZoneUdrName}' + params: { + name: managementRestrictedZoneUdrName + routes: routes + } +} diff --git a/landingzones/lz-platform-connectivity-hub-azfw/hub-vnet/hub-vnet.bicep b/landingzones/lz-platform-connectivity-hub-azfw/hub-vnet/hub-vnet.bicep new file mode 100644 index 00000000..9fa99f21 --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-azfw/hub-vnet/hub-vnet.bicep @@ -0,0 +1,157 @@ +// ---------------------------------------------------------------------------------- +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// VNET +@description('Virtual Network Name.') +param vnetName string + +@description('Virtual Network Address Space (RFC 1918).') +param vnetAddressPrefixRFC1918 string + +@description('Virtual Network Address Space (RFC 6598) - CGNAT.') +param vnetAddressPrefixRFC6598 string + +@description('Virtual Network Address Space for Azure Bastion (RFC 1918).') +param vnetAddressPrefixBastion string + +// Public Access Zone (i.e. Application Gateways) +@description('Public Access Zone (i.e. Application Gateway) Subnet Name.') +param pazSubnetName string + +@description('Public Access Zone (i.e. Application Gateway) Subnet Address Prefix.') +param pazSubnetAddressPrefix string + +@description('Public Access Zone (i.e. Application Gateway) User Defined Route Resource Id.') +param pazUdrId string + +// Gateway Subnet +@description('Virtual Network Gateway Subnet Address Prefix (based on RFC 1918).') +param gatewaySubnetAddressPrefix string + +// Azure Bastion +@description('Azure Bastion Subnet Address Prefix.') +param bastionSubnetAddressPrefix string + +// Azure Firweall +@description('Azure Firewall Subnet Address Prefix.') +param azureFirewallSubnetAddressPrefix string + +@description('Azure Firewall Management Subnet Address Prefix.') +param azureFirewallManagementSubnetAddressPrefix string + +@description('Azure Firewall is deployed in Forced Tunneling mode where a route table must be added as the next hop.') +param azureFirewallForcedTunnelingEnabled bool + +@description('Next Hop for AzureFirewallSubnet when Azure Firewall is deployed in forced tunneling mode.') +param azureFirewallForcedTunnelingNextHop string + +// DDOS +param ddosStandardPlanId string + +module publicAccessZoneNsg '../../../azresources/network/nsg/nsg-appgwv2.bicep' = { + name: 'deploy-nsg-${pazSubnetName}Nsg' + params: { + name: '${pazSubnetName}Nsg' + } +} + +module bastionNsg '../../../azresources/network/nsg/nsg-bastion.bicep' = { + name: 'deploy-nsg-AzureBastionNsg' + params: { + name: 'AzureBastionNsg' + } +} + +var azureFirewallForcedTunnelRoutes = [ + { + name: 'AzureFirewall-Default-Route' + properties: { + nextHopType: 'VirtualAppliance' + addressPrefix: '0.0.0.0/0' + nextHopIpAddress: azureFirewallForcedTunnelingNextHop + } + } +] + +module azureFirewallSubnetUdr '../../../azresources/network/udr/udr-custom.bicep' = if (azureFirewallForcedTunnelingEnabled) { + name: 'deploy-route-table-AzureFirewallSubnet' + params: { + name: 'AzureFirewallSubnetUdr' + routes: azureFirewallForcedTunnelRoutes + } +} + +resource vnet 'Microsoft.Network/virtualNetworks@2020-06-01' = { + location: resourceGroup().location + name: vnetName + properties: { + enableDdosProtection: !empty(ddosStandardPlanId) + ddosProtectionPlan: (!empty(ddosStandardPlanId)) ? { + id: ddosStandardPlanId + } : null + addressSpace: { + addressPrefixes: [ + vnetAddressPrefixRFC1918 + vnetAddressPrefixRFC6598 + vnetAddressPrefixBastion + ] + } + subnets: [ + { + name: pazSubnetName + properties: { + addressPrefix: pazSubnetAddressPrefix + networkSecurityGroup: { + id: publicAccessZoneNsg.outputs.nsgId + } + routeTable: { + id: pazUdrId + } + } + } + { + name: 'AzureFirewallSubnet' + properties: { + addressPrefix: azureFirewallSubnetAddressPrefix + routeTable: azureFirewallForcedTunnelingEnabled ? { + id: azureFirewallSubnetUdr.outputs.udrId + } : null + } + } + { + name: 'AzureFirewallManagementSubnet' + properties: { + addressPrefix: azureFirewallManagementSubnetAddressPrefix + } + } + { + name: 'AzureBastionSubnet' + properties: { + addressPrefix: bastionSubnetAddressPrefix + networkSecurityGroup: { + id: bastionNsg.outputs.nsgId + } + } + } + { + name: 'GatewaySubnet' + properties: { + addressPrefix: gatewaySubnetAddressPrefix + } + } + ] + } +} + +output vnetName string = vnet.name +output vnetId string = vnet.id + +output publicAccessZoneSubnetId string = '${vnet.id}/subnets/${pazSubnetName}' + +output GatewaySubnetId string = '${vnet.id}/subnets/GatewaySubnet' +output AzureBastionSubnetId string = '${vnet.id}/subnets/AzureBastionSubnet' +output AzureFirewallSubnetId string = '${vnet.id}/subnets/AzureFirewallSubnet' +output AzureFirewallManagementSubnetId string = '${vnet.id}/subnets/AzureFirewallManagementSubnet' diff --git a/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.bicep b/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.bicep new file mode 100644 index 00000000..190fd56f --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.bicep @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------------------------- +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +targetScope = 'subscription' + +// Tags +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact: 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Firewall Policy +@description('Azure Firewall Policy Resource Group Name') +param resourceGroupName string + +@description('Azure Firewall Policy Name') +param policyName string + +resource rgFirewallPolicy 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: resourceGroupName + location: deployment().location + tags: resourceTags +} + +module firewallPolicy 'azfw-policy/azure-firewall-policy.bicep' = { + scope: rgFirewallPolicy + name: 'deploy-azure-firewall-policy' + params: { + name: policyName + } +} + +output firewallPolicyId string = firewallPolicy.outputs.policyId diff --git a/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.parameters-sample.json b/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.parameters-sample.json new file mode 100644 index 00000000..9fc52f4c --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-azfw/main-azfw-policy.parameters-sample.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "resourceGroupName": { + "value": "pubsec-azure-firewall-policy-rg" + }, + "policyName": { + "value": "pubsecAzureFirewallPolicy" + } + } +} \ No newline at end of file diff --git a/landingzones/lz-platform-connectivity-hub-azfw/main.bicep b/landingzones/lz-platform-connectivity-hub-azfw/main.bicep new file mode 100644 index 00000000..d7b74319 --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-azfw/main.bicep @@ -0,0 +1,541 @@ +// ---------------------------------------------------------------------------------- +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- +targetScope = 'subscription' + +// Service Health +// Example (JSON) +// ----------------------------- +// "serviceHealthAlerts": { +// "value": { +// "incidentTypes": [ "Incident", "Security", "Maintenance", "Information", "ActionRequired" ], +// "regions": [ "Global", "Canada East", "Canada Central" ], +// "receivers": { +// "app": [ "email-1@company.com", "email-2@company.com" ], +// "email": [ "email-1@company.com", "email-3@company.com", "email-4@company.com" ], +// "sms": [ { "countryCode": "1", "phoneNumber": "1234567890" }, { "countryCode": "1", "phoneNumber": "0987654321" } ], +// "voice": [ { "countryCode": "1", "phoneNumber": "1234567890" } ] +// }, +// "actionGroupName": "ALZ action group", +// "actionGroupShortName": "alz-alert", +// "alertRuleName": "ALZ alert rule", +// "alertRuleDescription": "Alert rule for Azure Landing Zone" +// } +// } +@description('Service Health alerts') +param serviceHealthAlerts object = {} + +// Log Analytics +@description('Log Analytics Resource Id to integrate Azure Security Center.') +param logAnalyticsWorkspaceResourceId string + +// Azure Security Center +// Example (JSON) +// ----------------------------- +// "securityCenter": { +// "value": { +// "email": "alzcanadapubsec@microsoft.com", +// "phone": "5555555555" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// email: 'alzcanadapubsec@microsoft.com' +// phone: '5555555555' +// } +@description('Security Center configuration. It includes email and phone.') +param securityCenter object + +// Subscription Role Assignments +// Example (JSON) +// ----------------------------- +// [ +// { +// "comments": "Built-in Contributor Role", +// "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", +// "securityGroupObjectIds": [ +// "38f33f7e-a471-4630-8ce9-c6653495a2ee" +// ] +// } +// ] + +// Example (Bicep) +// ----------------------------- +// [ +// { +// comments: 'Built-In Contributor Role' +// roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' +// securityGroupObjectIds: [ +// '38f33f7e-a471-4630-8ce9-c6653495a2ee' +// ] +// } +// ] +@description('Array of role assignments at subscription scope. The array will contain an object with comments, roleDefinitionId and array of securityGroupObjectIds.') +param subscriptionRoleAssignments array = [] + +// Subscription Budget +// Example (JSON) +// --------------------------- +// "subscriptionBudget": { +// "value": { +// "createBudget": false, +// "name": "MonthlySubscriptionBudget", +// "amount": 1000, +// "timeGrain": "Monthly", +// "contactEmails": [ "alzcanadapubsec@microsoft.com" ] +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// createBudget: true +// name: 'MonthlySubscriptionBudget' +// amount: 1000 +// timeGrain: 'Monthly' +// contactEmails: [ +// 'alzcanadapubsec@microsoft.com' +// ] +// } +@description('Subscription budget configuration containing createBudget flag, name, amount, timeGrain and array of contactEmails') +param subscriptionBudget object + +// Tags +// Example (JSON) +// ----------------------------- +// "subscriptionTags": { +// "value": { +// "ISSO": "isso-tag" +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// ISSO: 'isso-tag' +// } +@description('A set of key/value pairs of tags assigned to the subscription.') +param subscriptionTags object + +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact: 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Network Watcher +@description('Azure Network Watcher Resource Group Name. Default: NetworkWatcherRG') +param rgNetworkWatcherName string = 'NetworkWatcherRG' + +// Private Dns Zones +@description('Boolean flag to determine whether Private DNS Zones will be centrally managed in the Hub.') +param deployPrivateDnsZones bool + +@description('Private DNS Zone Resource Group Name.') +param rgPrivateDnsZonesName string + +// DDOS Standard +@description('Boolean flag to determine whether to deploy Azure DDOS Standard.') +param deployDdosStandard bool + +@description('Azure DDOS Standard Resource Group.') +param rgDdosName string + +@description('Azure DDOS Standard Plan Name.') +param ddosPlanName string + +// Azure Firewall +@description('Azure Firewall Name') +param azureFirewallName string //= 'azfw' + +@description('Azure Firewall Availability Zones. Empty array = zonal, an array 1,2,3 is zone-redundant.') +param azureFirewallZones array //= ['1' '2' '3'] + +@description('Azure Firewall is deployed in Forced Tunneling mode where a route table must be added as the next hop.') +param azureFirewallForcedTunnelingEnabled bool + +@description('Next Hop for AzureFirewallSubnet when Azure Firewall is deployed in forced tunneling mode.') +param azureFirewallForcedTunnelingNextHop string + +@description('Azure Firewall Policy Resource Id.') +param azureFirewallExistingPolicyId string //= ARM Resource Id + +// Hub Virtual Network +@description('Hub Virtual Network Resource Group Name.') +param rgHubName string //= 'pubsecPrdHubPbRsg' + +@description('Hub Virtual Network Name.') +param hubVnetName string //= 'pubsecHubVnet' + +@description('Hub Virtual Network address space for RFC 1918.') +param hubVnetAddressPrefixRFC1918 string //= '10.18.0.0/22' + +@description('Hub Virtual Network address space for RFC 6598 (CGNAT).') +param hubVnetAddressPrefixRFC6598 string //= '100.60.0.0/16' + +@description('Hub Virtual Network address space for Azure Bastion (must be RFC 1918).') +param hubVnetAddressPrefixBastion string //= '192.168.0.0/16' + +@description('Hub - Public Access Zone Subnet Name.') +param hubPazSubnetName string //= 'PAZSubnet' + +@description('Hub - Public Access Zone Subnet Name (based on RFC 6598).') +param hubPazSubnetAddressPrefix string //= '100.60.1.0/24' + +@description('Hub - Virtual Network Gateway Subnet Address Prefix (based on RFC 1918).') +param hubGatewaySubnetAddressPrefix string //= '10.18.1.0/27' + +@description('Hub - Azure Firewall Subnet Address Prefix.') +param hubAzureFirewallSubnetAddressPrefix string //= '10.18.2.0/24' + +@description('Hub - Azure Firewall Management Subnet Address Prefix.') +param hubAzureFirewallManagementSubnetAddressPrefix string //= '10.18.3.0/26' + +@description('Azure Bastion Name.') +param bastionName string //= 'pubsecHubBastion' + +@description('Azure Bastion Subnet Address Prefix.') +param hubBastionSubnetAddressPrefix string //= '10.18.4.0/24' + +// Management Restricted Zone Virtual Network +@description('Management Restricted Zone - Resource Group Name.') +param rgMrzName string //= 'pubsecPrdMrzPbRsg' + +@description('Management Restricted Zone - Virtual Network Name.') +param mrzVnetName string //= 'pubsecMrzVnet' + +@description('Management Restricted Zone - Virtual Network Address Space.') +param mrzVnetAddressPrefixRFC1918 string //= '10.18.4.0/22' + +@description('Management Restricted Zone - Management (Access Zone) Subnet Name.') +param mrzMazSubnetName string //= 'MazSubnet' + +@description('Management Restricted Zone - Management (Access Zone) Subnet Address Prefix.') +param mrzMazSubnetAddressPrefix string //= '10.18.4.0/25' + +@description('Management Restricted Zone - Infrastructure Services (Restricted Zone) Subnet Name.') +param mrzInfSubnetName string //= 'InfSubnet' + +@description('Management Restricted Zone - Infrastructure Services (Restricted Zone) Subnet Address Prefix.') +param mrzInfSubnetAddressPrefix string //= '10.18.4.128/25' + +@description('Management Restricted Zone - Security Services (Restricted Zone) Subnet Name.') +param mrzSecSubnetName string //= 'SecSubnet' + +@description('Management Restricted Zone - Security Services (Restricted Zone) Subnet Address Prefix.') +param mrzSecSubnetAddressPrefix string //= '10.18.5.0/26' + +@description('Management Restricted Zone - Logging Services (Restricted Zone) Subnet Name.') +param mrzLogSubnetName string //= 'LogSubnet' + +@description('Management Restricted Zone - Loggin Services (Restricted Zone) Subnet Address Prefix.') +param mrzLogSubnetAddressPrefix string //= '10.18.5.64/26' + +@description('Management Restricted Zone - Core Management Interfaces (Restricted Zone) Subnet Name.') +param mrzMgmtSubnetName string //= 'MgmtSubnet' + +@description('Management Restricted Zone - Core Management Interfaces (Restricted Zone) Subnet Address Prefix.') +param mrzMgmtSubnetAddressPrefix string //= '10.18.5.128/26' + +// Public Access Zone +@description('Public Access Zone Resource Group Name.') +param rgPazName string //= 'pubsecPazRg' + +module subScaffold '../scaffold-subscription.bicep' = { + name: 'configure-subscription' + scope: subscription() + params: { + serviceHealthAlerts: serviceHealthAlerts + + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + securityCenter: securityCenter + + subscriptionBudget: subscriptionBudget + + subscriptionTags: subscriptionTags + resourceTags: resourceTags + + subscriptionRoleAssignments: subscriptionRoleAssignments + } +} + +// Create Network Watcher Resource Group +resource rgNetworkWatcher 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: rgNetworkWatcherName + location: deployment().location + tags: resourceTags +} + +// Create Private DNS Zone Resource Group - optional +resource rgPrivateDnsZones 'Microsoft.Resources/resourceGroups@2020-06-01' = if (deployPrivateDnsZones) { + name: rgPrivateDnsZonesName + location: deployment().location + tags: resourceTags +} + +// Create Azure DDOS Standard Resource Group - optional +resource rgDdos 'Microsoft.Resources/resourceGroups@2020-06-01' = if (deployDdosStandard) { + name: rgDdosName + location: deployment().location + tags: resourceTags +} + +// Create Hub Virtual Network Resource Group +resource rgHubVnet 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: rgHubName + location: deployment().location + tags: resourceTags +} + +// Create Managemend Restricted Virtual Network Resource Group +resource rgMrzVnet 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: rgMrzName + location: deployment().location + tags: resourceTags +} + +// Create Public Access Zone Resource Group +resource rgPaz 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: rgPazName + location: deployment().location + tags: resourceTags +} + +// Enable delete locks +module rgDdosDeleteLock '../../azresources/util/delete-lock.bicep' = if (deployDdosStandard) { + name: 'deploy-delete-lock-${rgDdosName}' + scope: rgDdos +} + +module rgHubDeleteLock '../../azresources/util/delete-lock.bicep' = { + name: 'deploy-delete-lock-${rgHubName}' + scope: rgHubVnet +} + +module rgMrzDeleteLock '../../azresources/util/delete-lock.bicep' = { + name: 'deploy-delete-lock-${rgMrzName}' + scope: rgMrzVnet +} + +module rgPazDeleteLock '../../azresources/util/delete-lock.bicep' = { + name: 'deploy-delete-lock-${rgPazName}' + scope: rgPaz +} + +// Azure DDOS Standard - optional +module ddosPlan '../../azresources/network/ddos-standard.bicep' = if (deployDdosStandard) { + name: 'deploy-ddos-standard-plan' + scope: rgDdos + params: { + name: ddosPlanName + } +} + +// UDRs will be configured after an Azure Firewll instance has been deployed or updated. +// To ensure all traffic is inspected, initiate the UDR with NextHop set to None. This ensures that all traffic through +// the subnet will be blackholed until the Azure Firewall instance is running. When Azure Firewall deployment is +// complete, the route table will be updated with the appropriate routes to force traffic through the Firewall. +// +// Note: When the hub network is updated, all routes in the Route Table will be replaced with the "blackhole" route +// until the Azure Firewall is updated. Once updated, the routes will be replaced with the ones found in the deployment. +// As a result, there will be ~ 30 seconds of network outage for traffic flowing through the Public Access Zone & Management +// Restricted Zone spoke virtual network. +module publicAccessZoneUdr '../../azresources/network/udr/udr-custom.bicep' = { + name: 'deploy-route-table-${hubPazSubnetName}Udr' + scope: rgHubVnet + params: { + name: '${hubPazSubnetName}Udr' + routes: [ + { + name: 'Blackhole' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'None' + } + } + ] + } +} + +module managementRestrictedZoneUdr '../../azresources/network/udr/udr-custom.bicep' = { + name: 'deploy-route-table-MrzSpokeUdr' + scope: rgHubVnet + params: { + name: 'MrzSpokeUdr' + routes: [ + { + name: 'Blackhole' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'None' + } + } + ] + } +} + +// Hub Virtual Network +module hubVnet 'hub-vnet/hub-vnet.bicep' = { + name: 'deploy-hub-vnet-${hubVnetName}' + scope: rgHubVnet + params: { + vnetName: hubVnetName + vnetAddressPrefixRFC1918: hubVnetAddressPrefixRFC1918 + vnetAddressPrefixRFC6598: hubVnetAddressPrefixRFC6598 + vnetAddressPrefixBastion: hubVnetAddressPrefixBastion + + pazSubnetName: hubPazSubnetName + pazSubnetAddressPrefix: hubPazSubnetAddressPrefix + pazUdrId: publicAccessZoneUdr.outputs.udrId + + azureFirewallSubnetAddressPrefix: hubAzureFirewallSubnetAddressPrefix + azureFirewallManagementSubnetAddressPrefix: hubAzureFirewallManagementSubnetAddressPrefix + gatewaySubnetAddressPrefix: hubGatewaySubnetAddressPrefix + bastionSubnetAddressPrefix: hubBastionSubnetAddressPrefix + + azureFirewallForcedTunnelingEnabled: azureFirewallForcedTunnelingEnabled + azureFirewallForcedTunnelingNextHop: azureFirewallForcedTunnelingNextHop + + ddosStandardPlanId: deployDdosStandard ? ddosPlan.outputs.ddosPlanId : '' + } +} + +// Management Restricted Virtual Network +module mrzVnet 'mrz-vnet/mrz-vnet.bicep' = { + name: 'deploy-management-vnet-${mrzVnetName}' + scope: rgMrzVnet + params: { + vnetName: mrzVnetName + vnetAddressPrefix: mrzVnetAddressPrefixRFC1918 + + mazSubnetName: mrzMazSubnetName + mazSubnetAddressPrefix: mrzMazSubnetAddressPrefix + mazSubnetUdrId: managementRestrictedZoneUdr.outputs.udrId + + infSubnetName: mrzInfSubnetName + infSubnetAddressPrefix: mrzInfSubnetAddressPrefix + infSubnetUdrId: managementRestrictedZoneUdr.outputs.udrId + + secSubnetName: mrzSecSubnetName + secSubnetAddressPrefix: mrzSecSubnetAddressPrefix + secSubnetUdrId: managementRestrictedZoneUdr.outputs.udrId + + logSubnetName: mrzLogSubnetName + logSubnetAddressPrefix: mrzLogSubnetAddressPrefix + logSubnetUdrId: managementRestrictedZoneUdr.outputs.udrId + + mgmtSubnetName: mrzMgmtSubnetName + mgmtSubnetAddressPrefix: mrzMgmtSubnetAddressPrefix + mgmtSubnetUdrId: managementRestrictedZoneUdr.outputs.udrId + + ddosStandardPlanId: deployDdosStandard ? ddosPlan.outputs.ddosPlanId : '' + } +} + +// Azure Firewall +module azureFirewall '../../azresources/network/firewall.bicep' = { + name: 'deploy-azure-firewall' + scope: rgHubVnet + params: { + name: azureFirewallName + zones: azureFirewallZones + firewallSubnetId: hubVnet.outputs.AzureFirewallSubnetId + firewallManagementSubnetId: hubVnet.outputs.AzureFirewallManagementSubnetId + existingFirewallPolicyId: azureFirewallExistingPolicyId + forcedTunnelingEnabled: azureFirewallForcedTunnelingEnabled + } +} + +// Route Tables +// Update the Route Tables to force traffic through Azure Firewall. Routes defined in this definition will be the only +// routes remaining once updated. As a result, it's recommended that all routes in the Hub Networking is updated through 'hub-vnet-routes.bicep' +// for consistency. +module hubVnetRoutes 'hub-vnet/hub-vnet-routes.bicep' = { + name: 'deploy-hub-vnet-routes' + scope: rgHubVnet + params: { + azureFirwallPrivateIp: azureFirewall.outputs.firewallPrivateIp + hubVnetAddressPrefixRFC1918: hubVnetAddressPrefixRFC1918 + hubVnetAddressPrefixRFC6598: hubVnetAddressPrefixRFC6598 + + publicAccessZoneUdrName: publicAccessZoneUdr.outputs.udrName + managementRestrictedZoneUdrName: managementRestrictedZoneUdr.outputs.udrName + } +} + +// Private DNS Zones +module privatelinkDnsZones '../../azresources/network/private-dns-zone-privatelinks.bicep' = if (deployPrivateDnsZones) { + name: 'deploy-privatelink-private-dns-zones' + scope: rgPrivateDnsZones + params: { + vnetId: hubVnet.outputs.vnetId + dnsCreateNewZone: true + dnsLinkToVirtualNetwork: true + + // Not required since the private dns zones will be created and linked to hub virtual network. + dnsExistingZoneSubscriptionId: '' + dnsExistingZoneResourceGroupName: '' + } +} + +// Bastion +module bastion '../../azresources/network/bastion.bicep' = { + name: 'deploy-bastion' + scope: rgHubVnet + params: { + name: bastionName + subnetId: hubVnet.outputs.AzureBastionSubnetId + } +} + +// Virtual Network Peering - Management Restricted Zone to Hub +module vnetPeeringSpokeToHub '../../azresources/network/vnet-peering.bicep' = { + name: 'deploy-vnet-peering-spoke-to-hub' + scope: rgMrzVnet + params: { + peeringName: '${mrzVnet.outputs.vnetName}-to-${hubVnet.outputs.vnetName}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: mrzVnet.outputs.vnetName + targetVnetId: hubVnet.outputs.vnetId + useRemoteGateways: false //to be changed once we have ExpressRoute or VPN GWs + } +} + +// Virtual Network Peering - Hub to Management Restricted Zone +module vnetPeeringHubToSpoke '../../azresources/network/vnet-peering.bicep' = { + name: 'deploy-vnet-peering-hub-to-spoke' + scope: rgHubVnet + params: { + peeringName: '${hubVnet.outputs.vnetName}-to-${mrzVnet.outputs.vnetName}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: hubVnet.outputs.vnetName + targetVnetId: mrzVnet.outputs.vnetId + useRemoteGateways: false + } +} diff --git a/landingzones/lz-platform-connectivity-hub-azfw/main.parameters-sample.json b/landingzones/lz-platform-connectivity-hub-azfw/main.parameters-sample.json new file mode 100644 index 00000000..528e0300 --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-azfw/main.parameters-sample.json @@ -0,0 +1,173 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "serviceHealthAlerts": { + "value": { + "resourceGroupName": "pubsec-service-health", + "incidentTypes": [ "Incident", "Security" ], + "regions": [ "Global", "Canada East", "Canada Central" ], + "receivers": { + "app": [ "alzcanadapubsec@microsoft.com" ], + "email": [ "alzcanadapubsec@microsoft.com" ], + "sms": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ], + "voice": [ + { "countryCode": "1", "phoneNumber": "5555555555" } + ] + } + } + }, + "securityCenter": { + "value": { + "email": "alzcanadapubsec@microsoft.com", + "phone": "5555555555" + } + }, + "subscriptionRoleAssignments": { + "value": [] + }, + "subscriptionBudget": { + "value": { + "createBudget": false, + "name": "MonthlySubscriptionBudget", + "amount": 1000, + "timeGrain": "Monthly", + "contactEmails": [ + "alzcanadapubsec@microsoft.com" + ] + } + }, + "subscriptionTags": { + "value": { + "ISSO": "isso-tag" + } + }, + "resourceTags": { + "value": { + "ClientOrganization": "client-organization-tag", + "CostCenter": "cost-center-tag", + "DataSensitivity": "data-sensitivity-tag", + "ProjectContact": "project-contact-tag", + "ProjectName": "project-name-tag", + "TechnicalContact": "technical-contact-tag" + } + }, + "logAnalyticsWorkspaceResourceId": { + "value": "/subscriptions/bc0a4f9f-07fa-4284-b1bd-fbad38578d3a/resourcegroups/pubsec-central-logging-rg/providers/microsoft.operationalinsights/workspaces/log-analytics-workspace" + }, + "deployPrivateDnsZones": { + "value": true + }, + "rgPrivateDnsZonesName": { + "value": "pubsec-dns-rg" + }, + "deployDdosStandard": { + "value": false + }, + "rgDdosName": { + "value": "pubsec-ddos-rg" + }, + "ddosPlanName": { + "value": "ddosStandard" + }, + "bastionName": { + "value": "bastion" + }, + "rgPazName": { + "value": "pubsec-public-access-zone-rg" + }, + "rgMrzName": { + "value": "pubsec-management-restricted-zone-rg" + }, + "mrzVnetName": { + "value": "management-restricted-vnet" + }, + "mrzVnetAddressPrefixRFC1918": { + "value": "10.18.4.0/22" + }, + "mrzMazSubnetName": { + "value": "MazSubnet" + }, + "mrzMazSubnetAddressPrefix": { + "value": "10.18.4.0/25" + }, + "mrzInfSubnetName": { + "value": "InfSubnet" + }, + "mrzInfSubnetAddressPrefix": { + "value": "10.18.4.128/25" + }, + "mrzSecSubnetName": { + "value": "SecSubnet" + }, + "mrzSecSubnetAddressPrefix": { + "value": "10.18.5.0/26" + }, + "mrzLogSubnetName": { + "value": "LogSubnet" + }, + "mrzLogSubnetAddressPrefix": { + "value": "10.18.5.64/26" + }, + "mrzMgmtSubnetName": { + "value": "MgmtSubnet" + }, + "mrzMgmtSubnetAddressPrefix": { + "value": "10.18.5.128/26" + }, + "rgHubName": { + "value": "pubsec-hub-networking-rg" + }, + "hubVnetName": { + "value": "hub-vnet" + }, + "hubVnetAddressPrefixRFC1918": { + "value": "10.18.0.0/22" + }, + "hubVnetAddressPrefixRFC6598": { + "value": "100.60.0.0/16" + }, + "hubVnetAddressPrefixBastion": { + "value": "192.168.0.0/16" + }, + "hubPazSubnetName": { + "value": "PAZSubnet" + }, + "hubPazSubnetAddressPrefix": { + "value": "100.60.1.0/24" + }, + "hubGatewaySubnetAddressPrefix": { + "value": "10.18.0.0/27" + }, + "hubAzureFirewallSubnetAddressPrefix": { + "value": "10.18.1.0/24" + }, + "hubAzureFirewallManagementSubnetAddressPrefix": { + "value": "10.18.2.0/26" + }, + "hubBastionSubnetAddressPrefix": { + "value": "192.168.0.0/24" + }, + "azureFirewallName": { + "value": "pubsecAzureFirewall" + }, + "azureFirewallZones": { + "value": [ + "1", + "2", + "3" + ] + }, + "azureFirewallForcedTunnelingNextHop": { + "value": "10.17.1.4" + }, + "azureFirewallForcedTunnelingEnabled": { + "value": false + }, + "azureFirewallExistingPolicyId": { + "value": "/subscriptions/ed7f4eed-9010-4227-b115-2a5e37728f27/resourcegroups/pubsec-azure-firewall-policy-rg/providers/Microsoft.Network/firewallPolicies/pubsecAzureFirewallPolicy" + } + } +} \ No newline at end of file diff --git a/landingzones/lz-platform-connectivity-hub-azfw/mrz-vnet/mrz-vnet.bicep b/landingzones/lz-platform-connectivity-hub-azfw/mrz-vnet/mrz-vnet.bicep new file mode 100644 index 00000000..fd221bbd --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-azfw/mrz-vnet/mrz-vnet.bicep @@ -0,0 +1,193 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// Management Restricted Zone Virtual Network + +// VNET +@description('Virtual Network Name.') +param vnetName string + +@description('Virtual Network Address Space.') +param vnetAddressPrefix string + +// Management (Access Zone) +@description('Management (Access Zone) Subnet Name.') +param mazSubnetName string + +@description('Management (Access Zone) Subnet Address Prefix.') +param mazSubnetAddressPrefix string + +@description('Management (Access Zone) User Defined Route Resource Id.') +param mazSubnetUdrId string + +// Infra Services (Restricted Zone) +@description('Infrastructure Services (Restricted Zone) Subnet Name.') +param infSubnetName string + +@description('Infrastructure Services (Restricted Zone) Subnet Address Prefix.') +param infSubnetAddressPrefix string + +@description('Infrastructure Services (Restricted Zone) User Defined Route Resource Id.') +param infSubnetUdrId string + +// Security Services (Restricted Zone) +@description('Security Services (Restricted Zone) Subnet Name.') +param secSubnetName string +@description('Security Services (Restricted Zone) Subnet Address Prefis.') +param secSubnetAddressPrefix string + +@description('Security Services (Restricted Zone) User Defined Route Resource Id.') +param secSubnetUdrId string + +// Logging Services (Restricted Zone) +@description('Logging Services (Restricted Zone) Subnet Name.') +param logSubnetName string + +@description('Logging Services (Restricted Zone) Subnet Address Prefix.') +param logSubnetAddressPrefix string + +@description('Logging Services (Restricted Zone) User Defined Route Resource Id.') +param logSubnetUdrId string + +// Core Management Interfaces +@description('Core Management Interfaces (Restricted Zone) Subnet Name.') +param mgmtSubnetName string + +@description('Core Management Interfaces (Restricted Zone) Subnet Address Prefix.') +param mgmtSubnetAddressPrefix string + +@description('Core Management Interfaces (Restricted Zone) User Defined Route Table Resource Id.') +param mgmtSubnetUdrId string + +// DDOS +@description('DDOS Standard Plan Resource Id - optional (blank value = DDOS Standard Plan will not be linked to virtual network).') +param ddosStandardPlanId string + +module nsgmaz '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${mazSubnetName}' + params: { + name: '${mazSubnetName}Nsg' + } +} + +module nsginf '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${infSubnetName}' + params: { + name: '${infSubnetName}Nsg' + } +} + +module nsgsec '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${secSubnetName}' + params: { + name: '${secSubnetName}Nsg' + } +} + +module nsglog '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${logSubnetName}' + params: { + name: '${logSubnetName}Nsg' + } +} + +module nsgmgmt '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${mgmtSubnetName}' + params: { + name: '${mgmtSubnetName}Nsg' + } +} + +resource mrzVnet 'Microsoft.Network/virtualNetworks@2020-06-01' = { + location: resourceGroup().location + name: vnetName + properties: { + enableDdosProtection: !empty(ddosStandardPlanId) + ddosProtectionPlan: (!empty(ddosStandardPlanId)) ? { + id: ddosStandardPlanId + } : null + addressSpace: { + addressPrefixes: [ + vnetAddressPrefix + ] + } + subnets: [ + { + name: mazSubnetName + properties: { + addressPrefix: mazSubnetAddressPrefix + networkSecurityGroup: { + id: nsgmaz.outputs.nsgId + } + routeTable: { + id: mazSubnetUdrId + } + } + } + { + name: infSubnetName + properties: { + addressPrefix: infSubnetAddressPrefix + networkSecurityGroup: { + id: nsginf.outputs.nsgId + } + routeTable: { + id: infSubnetUdrId + } + } + } + { + name: secSubnetName + properties: { + addressPrefix: secSubnetAddressPrefix + networkSecurityGroup: { + id: nsgsec.outputs.nsgId + } + routeTable: { + id: secSubnetUdrId + } + } + } + { + name: logSubnetName + properties: { + addressPrefix: logSubnetAddressPrefix + networkSecurityGroup: { + id: nsglog.outputs.nsgId + } + routeTable: { + id: logSubnetUdrId + } + } + } + { + name: mgmtSubnetName + properties: { + addressPrefix: mgmtSubnetAddressPrefix + networkSecurityGroup: { + id: nsgmgmt.outputs.nsgId + } + routeTable: { + id: mgmtSubnetUdrId + } + } + } + ] + } +} + +// Outputs +output vnetName string = mrzVnet.name +output vnetId string = mrzVnet.id + +output MazSubnetId string = '${mrzVnet.id}/subnets/${mazSubnetName}' +output InfSubnetId string = '${mrzVnet.id}/subnets/${infSubnetName}' +output SecSubnetId string = '${mrzVnet.id}/subnets/${secSubnetName}' +output LogSubnetId string = '${mrzVnet.id}/subnets/${logSubnetName}' +output MgmtSubnetId string = '${mrzVnet.id}/subnets/${mgmtSubnetName}' diff --git a/landingzones/lz-platform-connectivity-hub-nva/hub-vnet/hub-vnet.bicep b/landingzones/lz-platform-connectivity-hub-nva/hub-vnet/hub-vnet.bicep new file mode 100644 index 00000000..1380f64d --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-nva/hub-vnet/hub-vnet.bicep @@ -0,0 +1,249 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// Hub Virtual Network + +// VNET +@description('Virtual Network Name.') +param vnetName string + +@description('Virtual Network Address Space (RFC 1918).') +param vnetAddressPrefixRFC1918 string + +@description('Virtual Network Address Space (RFC 6598) - CGNAT.') +param vnetAddressPrefixRFC6598 string + +@description('Virtual Network Address Space for Azure Bastion (RFC 1918).') +param vnetAddressPrefixBastion string + +// External Facing (Internet/Ground) +@description('External Facing (Internet/Ground) Subnet Name.') +param publicSubnetName string + +@description('External Facing (Internet/Ground) Subnet Address Prefix.') +param publicSubnetAddressPrefix string + +// External Access Network +@description('Enternal Access Network Subnet Name.') +param eanSubnetName string + +@description('Enternal Access Network Subnet Address Prefix.') +param eanSubnetAddressPrefix string + +// Management Restricted Zone (connect Mgmt VNET) +@description('Management Restricted Zone Subnet Name.') +param mrzIntSubnetName string + +@description('Management Restricted Zone Subnet Address Prefix.') +param mrzIntSubnetAddressPrefix string + +// Internal Facing Prod (Connect PROD VNET) +@description('Internal Facing Production Traffic Subnet Name.') +param prodIntSubnetName string + +@description('Internal Facing Production Traffic Subnet Address Prefix.') +param prodIntSubnetAddressPrefix string + +// Internal Facing Dev (Connect Dev VNET) +@description('Internal Facing Non-Production Traffic Subnet Name.') +param devIntSubnetName string + +@description('Internal Facing Non-Production Traffic Subnet Address Prefix.') +param devIntSubnetAddressPrefix string + +// High Availability (FW<=>FW heartbeat) +@description('High Availability (Firewall to Firewall heartbeat) Subnet Name.') +param haSubnetName string + +@description('High Availability (Firewall to Firewall heartbeat) Subnet Address Prefix.') +param haSubnetAddressPrefix string + +// Public Access Zone (i.e. Application Gateways) +@description('Public Access Zone (i.e. Application Gateway) Subnet Name.') +param pazSubnetName string + +@description('Public Access Zone (i.e. Application Gateway) Subnet Address Prefix.') +param pazSubnetAddressPrefix string + +@description('Public Access Zone (i.e. Application Gateway) User Defined Route Resource Id.') +param pazUdrId string + +// Gateway Subnet +@description('Gateway Subnet Address Prefix.') +param gatewaySubnetAddressPrefix string + +// Azure Bastion +@description('Azure Bastion Subnet Address Prefix.') +param bastionSubnetAddressPrefix string + +// DDOS +@description('DDOS Standard Plan Resource Id - optional (blank value = DDOS Standard Plan will not be linked to virtual network).') +param ddosStandardPlanId string + +module nsgpublic '../../../azresources/network/nsg/nsg-allowall.bicep' = { + name: 'deploy-nsg-${publicSubnetName}' + params: { + name: '${publicSubnetName}Nsg' + } +} +module nsgean '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${eanSubnetName}' + params: { + name: '${eanSubnetName}Nsg' + } +} +module nsgprd '../../../azresources/network/nsg/nsg-allowall.bicep' = { + name: 'deploy-nsg-${prodIntSubnetName}' + params: { + name: '${prodIntSubnetName}Nsg' + } +} +module nsgdev '../../../azresources/network/nsg/nsg-allowall.bicep' = { + name: 'deploy-nsg-${devIntSubnetName}' + params: { + name: '${devIntSubnetName}Nsg' + } +} +module nsgha '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${haSubnetName}' + params: { + name: '${haSubnetName}Nsg' + } +} +module nsgmrz '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${mrzIntSubnetName}' + params: { + name: '${mrzIntSubnetName}Nsg' + } +} +module nsgpaz '../../../azresources/network/nsg/nsg-appgwv2.bicep' = { + name: 'deploy-nsg-${pazSubnetName}' + params: { + name: '${pazSubnetName}Nsg' + } +} +module nsgbastion '../../../azresources/network/nsg/nsg-bastion.bicep' = { + name: 'deploy-nsg-AzureBastionNsg' + params: { + name: 'AzureBastionNsg' + } +} + +resource hubVnet 'Microsoft.Network/virtualNetworks@2020-06-01' = { + location: resourceGroup().location + name: vnetName + properties: { + enableDdosProtection: !empty(ddosStandardPlanId) + ddosProtectionPlan: (!empty(ddosStandardPlanId)) ? { + id: ddosStandardPlanId + } : null + addressSpace: { + addressPrefixes: [ + vnetAddressPrefixRFC1918 + vnetAddressPrefixRFC6598 + vnetAddressPrefixBastion + ] + } + subnets: [ + { + name: publicSubnetName + properties: { + addressPrefix: publicSubnetAddressPrefix + networkSecurityGroup: { + id: nsgpublic.outputs.nsgId + } + } + } + { + name: eanSubnetName + properties: { + addressPrefix: eanSubnetAddressPrefix + networkSecurityGroup: { + id: nsgean.outputs.nsgId + } + } + } + { + name: prodIntSubnetName + properties: { + addressPrefix: prodIntSubnetAddressPrefix + networkSecurityGroup: { + id: nsgprd.outputs.nsgId + } + } + } + { + name: devIntSubnetName + properties: { + addressPrefix: devIntSubnetAddressPrefix + networkSecurityGroup: { + id: nsgdev.outputs.nsgId + } + } + } + { + name: mrzIntSubnetName + properties: { + addressPrefix: mrzIntSubnetAddressPrefix + networkSecurityGroup: { + id: nsgmrz.outputs.nsgId + } + } + } + { + name: haSubnetName + properties: { + addressPrefix: haSubnetAddressPrefix + networkSecurityGroup: { + id: nsgha.outputs.nsgId + } + } + } + { + name: pazSubnetName + properties: { + addressPrefix: pazSubnetAddressPrefix + networkSecurityGroup: { + id: nsgpaz.outputs.nsgId + } + routeTable: { + id: pazUdrId + } + } + } + { + name: 'AzureBastionSubnet' + properties: { + addressPrefix: bastionSubnetAddressPrefix + networkSecurityGroup: { + id: nsgbastion.outputs.nsgId + } + } + } + { + name: 'GatewaySubnet' + properties: { + addressPrefix: gatewaySubnetAddressPrefix + } + } + ] + } +} + +output vnetName string = hubVnet.name +output vnetId string = hubVnet.id +output PublicSubnetId string = '${hubVnet.id}/subnets/${publicSubnetName}' +output EANSubnetId string = '${hubVnet.id}/subnets/${eanSubnetName}' +output PrdIntSubnetId string = '${hubVnet.id}/subnets/${prodIntSubnetName}' +output DevIntSubnetId string = '${hubVnet.id}/subnets/${devIntSubnetName}' +output MrzIntSubnetId string = '${hubVnet.id}/subnets/${mrzIntSubnetName}' +output HASubnetId string = '${hubVnet.id}/subnets/${haSubnetName}' +output PAZSubnetId string = '${hubVnet.id}/subnets/${pazSubnetName}' +output GatewaySubnetId string = '${hubVnet.id}/subnets/GatewaySubnet' +output AzureBastionSubnetId string = '${hubVnet.id}/subnets/AzureBastionSubnet' diff --git a/landingzones/lz-platform-connectivity-hub-nva/hub-vnet/lb-firewalls-hub.bicep b/landingzones/lz-platform-connectivity-hub-nva/hub-vnet/lb-firewalls-hub.bicep new file mode 100644 index 00000000..ef844e0c --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-nva/hub-vnet/lb-firewalls-hub.bicep @@ -0,0 +1,214 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +@description('Internal Load Balancer Name.') +param name string + +// vnet +@description('Backend Virtaul Network Resource Id.') +param backendVnetId string + +// backend pool +@description('Boolean flag to determine whether to create an empty backend pool.') +param configureEmptyBackendPool bool + +// external +@description('External Facing - Frontend Subnet Resource Id.') +param frontendSubnetIdExt string + +@description('External Facing - Frontend IP.') +param frontendIPExt string + +@description('External Facing - Backend IP #1.') +param backendIP1Ext string + +@description('External Facing - Backend IP #2.') +param backendIP2Ext string + +// internal +@description('Internal Facing - Frontend Subnet Resource Id.') +param frontendSubnetIdInt string + +@description('Internal Facing - Frontend IP.') +param frontendIPInt string + +@description('Internal Facing - Backend IP #1.') +param backendIP1Int string + +@description('Internal Facing - Backend IP #2.') +param backendIP2Int string + +// probe +@description('Load Balancer Probe Tcp Port.') +param lbProbeTcpPort int + +resource ILB 'Microsoft.Network/loadBalancers@2020-11-01' = { + name: name + location: resourceGroup().location + sku: { + name: 'Standard' + tier: 'Regional' + } + properties: { + frontendIPConfigurations: [ + { + name: '${name}-Frontend-ext' + properties: { + privateIPAddress: frontendIPExt + privateIPAllocationMethod: 'Static' + subnet: { + id: frontendSubnetIdExt + } + privateIPAddressVersion: 'IPv4' + } + zones: [ + '1' + '2' + '3' + ] + } + { + name: '${name}-Frontend-int' + properties: { + privateIPAddress: frontendIPInt + privateIPAllocationMethod: 'Static' + subnet: { + id: frontendSubnetIdInt + } + privateIPAddressVersion: 'IPv4' + } + zones: [ + '1' + '2' + '3' + ] + } + ] + backendAddressPools: [ { + name: '${name}-Backend-ext' + } + { + name: '${name}-Backend-int' + } + ] + loadBalancingRules: [ + { + name: 'lbruleFE2all-ext' + properties: { + frontendIPConfiguration: { + id: resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', name,'${name}-Frontend-ext') + } + frontendPort: 0 + backendPort: 0 + enableFloatingIP: true + idleTimeoutInMinutes: 5 + protocol: 'All' + enableTcpReset: false + loadDistribution: 'Default' + disableOutboundSnat: false + backendAddressPool: { + id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools',name,'${name}-Backend-ext') + } + probe: { + id: resourceId('Microsoft.Network/loadBalancers/probes',name,'lbprobe') + } + } + } + { + name: 'lbruleFE2all-int' + properties: { + frontendIPConfiguration: { + id: resourceId('Microsoft.Network/loadBalancers/frontendIPConfigurations', name,'${name}-Frontend-int') + } + frontendPort: 0 + backendPort: 0 + enableFloatingIP: true + idleTimeoutInMinutes: 5 + protocol: 'All' + enableTcpReset: false + loadDistribution: 'Default' + disableOutboundSnat: false + backendAddressPool: { + id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools',name,'${name}-Backend-int') + } + probe: { + id: resourceId('Microsoft.Network/loadBalancers/probes',name,'lbprobe') + } + } + } + ] + probes: [ + { + name: 'lbprobe' + properties: { + protocol: 'Tcp' + port: lbProbeTcpPort + intervalInSeconds: 5 + numberOfProbes: 2 + } + } + ] + } +} + +// BackendAddressPool +resource ILBBackendExt 'Microsoft.Network/loadBalancers/backendAddressPools@2020-11-01' = { + name: '${ILB.name}/${name}-Backend-ext' + properties: { + loadBalancerBackendAddresses: configureEmptyBackendPool ? null : [ + { + name: '${ILB.name}-ext1' + properties: { + ipAddress: backendIP1Ext + virtualNetwork: { + id: backendVnetId + } + } + } + { + name: '${ILB.name}-ext2' + properties: { + ipAddress: backendIP2Ext + virtualNetwork: { + id: backendVnetId + } + } + } + ] + } +} + +resource ILBBackendInt 'Microsoft.Network/loadBalancers/backendAddressPools@2020-11-01' = { + name: '${ILB.name}/${name}-Backend-int' + properties: { + loadBalancerBackendAddresses: configureEmptyBackendPool ? null : [ + { + name: '${ILB.name}-int1' + properties: { + ipAddress: backendIP1Int + virtualNetwork: { + id: backendVnetId + } + } + } + { + name: '${ILB.name}-int2' + properties: { + ipAddress: backendIP2Int + virtualNetwork: { + id: backendVnetId + } + } + } + ] + } +} + +// Outputs +output lbId string = ILB.id diff --git a/landingzones/lz-platform-connectivity-hub-nva/main.bicep b/landingzones/lz-platform-connectivity-hub-nva/main.bicep new file mode 100644 index 00000000..fcfa7dc2 --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-nva/main.bicep @@ -0,0 +1,893 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +/* + +Hub Networking with Fortigate Virtual Network Appliance archetype infrastructure to support Hub & Spoke network topology. This archetype will provide: + +* Azure Automation Account +* Azure Virtual Network (Hub) +* Azure Virtual Network (Management Restricted Zone) - used for management resources such as IaaS based Logging, Patch Management, etc. +* Two pairs of pay-as-you-go Fortigate Firewalls (one pair for development workload and another for production workload) - customer must configure the fortigate firewalls. +* Enables DDOS Standard (optional) +* Enables Azure Private DNS Zones (optional) +* Role-based access control for Owner, Contributor & Reader +* Integration with Azure Cost Management for Subscription-scoped budget +* Integration with Azure Security Center + +*/ + +targetScope = 'subscription' + +// Service Health +// Example (JSON) +// ----------------------------- +// "serviceHealthAlerts": { +// "value": { +// "incidentTypes": [ "Incident", "Security", "Maintenance", "Information", "ActionRequired" ], +// "regions": [ "Global", "Canada East", "Canada Central" ], +// "receivers": { +// "app": [ "email-1@company.com", "email-2@company.com" ], +// "email": [ "email-1@company.com", "email-3@company.com", "email-4@company.com" ], +// "sms": [ { "countryCode": "1", "phoneNumber": "1234567890" }, { "countryCode": "1", "phoneNumber": "0987654321" } ], +// "voice": [ { "countryCode": "1", "phoneNumber": "1234567890" } ] +// }, +// "actionGroupName": "ALZ action group", +// "actionGroupShortName": "alz-alert", +// "alertRuleName": "ALZ alert rule", +// "alertRuleDescription": "Alert rule for Azure Landing Zone" +// } +// } +@description('Service Health alerts') +param serviceHealthAlerts object = {} + +// Log Analytics +@description('Log Analytics Resource Id to integrate Azure Security Center.') +param logAnalyticsWorkspaceResourceId string + +// Azure Security Center +// Example (JSON) +// ----------------------------- +// "securityCenter": { +// "value": { +// "email": "alzcanadapubsec@microsoft.com", +// "phone": "5555555555" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// email: 'alzcanadapubsec@microsoft.com' +// phone: '5555555555' +// } +@description('Security Center configuration. It includes email and phone.') +param securityCenter object + +// Subscription Role Assignments +// Example (JSON) +// ----------------------------- +// [ +// { +// "comments": "Built-in Contributor Role", +// "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", +// "securityGroupObjectIds": [ +// "38f33f7e-a471-4630-8ce9-c6653495a2ee" +// ] +// } +// ] + +// Example (Bicep) +// ----------------------------- +// [ +// { +// comments: 'Built-In Contributor Role' +// roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' +// securityGroupObjectIds: [ +// '38f33f7e-a471-4630-8ce9-c6653495a2ee' +// ] +// } +// ] +@description('Array of role assignments at subscription scope. The array will contain an object with comments, roleDefinitionId and array of securityGroupObjectIds.') +param subscriptionRoleAssignments array = [] + +// Subscription Budget +// Example (JSON) +// --------------------------- +// "subscriptionBudget": { +// "value": { +// "createBudget": false, +// "name": "MonthlySubscriptionBudget", +// "amount": 1000, +// "timeGrain": "Monthly", +// "contactEmails": [ "alzcanadapubsec@microsoft.com" ] +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// createBudget: true +// name: 'MonthlySubscriptionBudget' +// amount: 1000 +// timeGrain: 'Monthly' +// contactEmails: [ +// 'alzcanadapubsec@microsoft.com' +// ] +// } +@description('Subscription budget configuration containing createBudget flag, name, amount, timeGrain and array of contactEmails') +param subscriptionBudget object + +// Tags +// Example (JSON) +// ----------------------------- +// "subscriptionTags": { +// "value": { +// "ISSO": "isso-tag" +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// ISSO: 'isso-tag' +// } +@description('A set of key/value pairs of tags assigned to the subscription.') +param subscriptionTags object + +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact: 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Network Watcher +@description('Azure Network Watcher Resource Group Name. Default: NetworkWatcherRG') +param rgNetworkWatcherName string = 'NetworkWatcherRG' + +// Private Dns Zones +@description('Boolean flag to determine whether Private DNS Zones will be centrally managed in the Hub.') +param deployPrivateDnsZones bool + +@description('Private DNS Zone Resource Group Name.') +param rgPrivateDnsZonesName string + +// DDOS Standard +@description('Boolean flag to determine whether to deploy Azure DDOS Standard.') +param deployDdosStandard bool + +@description('Azure DDOS Standard Resource Group.') +param rgDdosName string + +@description('Azure DDOS Standard Plan Name.') +param ddosPlanName string + +// Hub Virtual Network +@description('Hub Virtual Network Resource Group Name.') +param rgHubName string //= 'pubsecPrdHubPbRsg' + +@description('Hub Virtual Network Name.') +param hubVnetName string //= 'pubsecHubVnet' + +@description('Hub Virtual Network address space for RFC 1918.') +param hubVnetAddressPrefixRFC1918 string //= '10.18.0.0/22' + +@description('Hub Virtual Network address space for RFC 6598 (CGNAT).') +param hubVnetAddressPrefixRFC6598 string //= '100.60.0.0/16' + +@description('Hub Virtual Network address space for Azure Bastion (must be RFC 1918).') +param hubVnetAddressPrefixBastion string //= '192.168.0.0/16' + +@description('Hub - Enternal Access Network Subnet Name.') +param hubEanSubnetName string //= 'EanSubnet' + +@description('Hub - Enternal Access Network Subnet Address Prefix (based on RFC 1918).') +param hubEanSubnetAddressPrefix string //= '10.18.0.0/27' + +@description('Hub - Public Subnet Name.') +param hubPublicSubnetName string //= 'PublicSubnet + +@description('Hub - Public Subnet Address Prefix (based on RFC 6598).') +param hubPublicSubnetAddressPrefix string //= '100.60.0.0/24' + +@description('Hub - Public Access Zone Subnet Name.') +param hubPazSubnetName string //= 'PAZSubnet' + +@description('Hub - Public Access Zone Subnet Name (based on RFC 6598).') +param hubPazSubnetAddressPrefix string //= '100.60.1.0/24' + +@description('Hub - Non-Production Internal Subnet Name.') +param hubDevIntSubnetName string //= 'DevIntSubnet' + +@description('Hub - Non-Production Internal Subnet Address Prefix (based on RFC 1918).') +param hubDevIntSubnetAddressPrefix string //= '10.18.0.64/27' + +@description('Hub - Production Internal Subnet Name.') +param hubProdIntSubnetName string //= 'PrdIntSubnet' + +@description('Hub - Production Internal Subnet Address Prefix (based on RFC 1918).') +param hubProdIntSubnetAddressPrefix string //= '10.18.0.32/27' + +@description('Hub - Management Resctricted Zone Subnet Name.') +param hubMrzIntSubnetName string //= 'MrzSubnet' + +@description('Hub - Management Resctricted Zone Subnet Address Prefix (based on RFC 1918).') +param hubMrzIntSubnetAddressPrefix string //= '10.18.0.96/27' + +@description('Hub - Firewall High Availability Subnet Name.') +param hubHASubnetName string //= 'HASubnet' + +@description('Hub - Firewall High Availability Subnet Address Prefix (based on RFC 1918).') +param hubHASubnetAddressPrefix string //= '10.18.0.128/28' + +@description('Hub - Virtual Network Gateway Subnet Address Prefix (based on RFC 1918).') +param hubGatewaySubnetPrefix string //= '10.18.1.0/27' + +@description('Hub - Azure Bastion Name.') +param bastionName string //= 'pubsecHubBastion' + +@description('Hub - Azure Bastion Address Prefix (based on RFC 1918 and must be placed in the Azure Bastion Address Space).') +param hubBastionSubnetAddressPrefix string //= '192.168.0.0/24' + +// Firewall Virtual Appliances +@description('Boolean flag to determine whether virtual machines will be deployed, either Ubuntu (for internal testing) or Fortinet (for workloads). Default: true') +param deployFirewallVMs bool = true + +@description('Boolean flag to dtermine whether Fortinet firewalls will eb deployed. Default: true') +param useFortigateFW bool = true + +// Firewall Virtual Appliances - For Non-production Traffic +@description('Non-production NVA - Internal Load Balancer Name.') +param fwDevILBName string //= 'pubsecDevFWs_ILB' + +@description('Non-production NVA - VM SKU.') +param fwDevVMSku string //= 'Standard_D8s_v4' //ensure it can have 4 nics + +@description('Non-production NVA - VM #1 Name.') +param fwDevVM1Name string //= 'pubsecDevFW1' + +@description('Non-production NVA - VM #2 Name.') +param fwDevVM2Name string //= 'pubsecDevFW2' + +@description('Non-production NVA - Internal Load Balancer External Facing IP (based on RFC 6598).') +param fwDevILBExternalFacingIP string //= '100.60.0.7' + +@description('Non-production NVA - VM #1 External Facing IP (based on RFC 6598).') +param fwDevVM1ExternalFacingIP string //= '100.60.0.8' + +@description('Non-production NVA - VM #2 External Facing IP (based on RFC 6598).') +param fwDevVM2ExternalFacingIP string //= '100.60.0.9' + +@description('Non-production NVA - VM #1 Management Restricted Zone IP (based on RFC 1918).') +param fwDevVM1MrzIntIP string //= '10.18.0.104' + +@description('Non-production NVA - VM #2 Management Restricted Zone IP (based on RFC 1918).') +param fwDevVM2MrzIntIP string //= '10.18.0.105' + +@description('Non-production NVA - Internal Load Balancer IP (based on RFC 1918).') +param fwDevILBDevIntIP string //= '10.18.0.68' + +@description('Non-production NVA - VM #1 IP (based on RFC 1918).') +param fwDevVM1DevIntIP string //= '10.18.0.69' + +@description('Non-production NVA - VM #2 IP (based on RFC 1918).') +param fwDevVM2DevIntIP string //= '10.18.0.70' + +@description('Non-production NVA - VM #1 High Availability IP (based on RFC 1918).') +param fwDevVM1HAIP string //= '10.18.0.134' + +@description('Non-production NVA - VM #2 High Availability IP (based on RFC 1918).') +param fwDevVM2HAIP string //= '10.18.0.135' + +@description('Non-production NVA - VM #1 Availability Zone. Default: 2') +param fwDevVM1AvailabilityZone string = '2' + +@description('Non-production NVA - VM #2 Availability Zone. Default: 3') +param fwDevVM2AvailabilityZone string = '3' + +// Firewall Virtual Appliances - For Production Traffic +@description('Production NVA - Internal Load Balancer Name.') +param fwProdILBName string //= 'pubsecProdFWs_ILB' + +@description('Production NVA - VM SKU.') +param fwProdVMSku string //= 'Standard_F8s_v2' //ensure it can have 4 nics + +@description('Production NVA - VM #1 Name.') +param fwProdVM1Name string //= 'pubsecProdFW1' + +@description('Production NVA - VM #2 Name.') +param fwProdVM2Name string //= 'pubsecProdFW2' + +@description('Production NVA - Internal Load Balancer External Facing IP (based on RFC 6598).') +param fwProdILBExternalFacingIP string //= '100.60.0.4' + +@description('Production NVA - VM #1 External Facing IP (based on RFC 6598).') +param fwProdVM1ExternalFacingIP string //= '100.60.0.5' + +@description('Production NVA - VM #2 External Facing IP (based on RFC 6598).') +param fwProdVM2ExternalFacingIP string //= '100.60.0.6' + +@description('Production NVA - VM #1 Management Restricted Zone IP (based on RFC 1918).') +param fwProdVM1MrzIntIP string //= '10.18.0.101' + +@description('Production NVA - VM #2 Management Restricted Zone IP (based on RFC 1918).') +param fwProdVM2MrzIntIP string //= '10.18.0.102' + +@description('Production NVA - Internal Load Balancer IP (based on RFC 1918).') +param fwProdILBPrdIntIP string //= '10.18.0.36' + +@description('Production NVA - VM #1 IP (based on RFC 1918).') +param fwProdVM1PrdIntIP string //= '10.18.0.37' + +@description('Production NVA - VM #2 IP (based on RFC 1918).') +param fwProdVM2PrdIntIP string //= '10.18.0.38' + +@description('Production NVA - VM #1 High Availability IP (based on RFC 1918).') +param fwProdVM1HAIP string //= '10.18.0.132' + +@description('Production NVA - VM #2 High Availability IP (based on RFC 1918).') +param fwProdVM2HAIP string //= '10.18.0.133' + +@description('Production NVA - VM #1 Availability Zone. Default: 1') +param fwProdVM1AvailabilityZone string = '1' + +@description('Production NVA - VM #2 Availability Zone. Default: 2') +param fwProdVM2AvailabilityZone string = '2' + +// Management Restricted Zone Virtual Network +@description('Management Restricted Zone - Resource Group Name.') +param rgMrzName string //= 'pubsecPrdMrzPbRsg' + +@description('Management Restricted Zone - Virtual Network Name.') +param mrzVnetName string //= 'pubsecMrzVnet' + +@description('Management Restricted Zone - Virtual Network Address Space.') +param mrzVnetAddressPrefixRFC1918 string //= '10.18.4.0/22' + +@description('Management Restricted Zone - Management (Access Zone) Subnet Name.') +param mrzMazSubnetName string //= 'MazSubnet' + +@description('Management Restricted Zone - Management (Access Zone) Subnet Address Prefix.') +param mrzMazSubnetAddressPrefix string //= '10.18.4.0/25' + +@description('Management Restricted Zone - Infrastructure Services (Restricted Zone) Subnet Name.') +param mrzInfSubnetName string //= 'InfSubnet' + +@description('Management Restricted Zone - Infrastructure Services (Restricted Zone) Subnet Address Prefix.') +param mrzInfSubnetAddressPrefix string //= '10.18.4.128/25' + +@description('Management Restricted Zone - Security Services (Restricted Zone) Subnet Name.') +param mrzSecSubnetName string //= 'SecSubnet' + +@description('Management Restricted Zone - Security Services (Restricted Zone) Subnet Address Prefix.') +param mrzSecSubnetAddressPrefix string //= '10.18.5.0/26' + +@description('Management Restricted Zone - Logging Services (Restricted Zone) Subnet Name.') +param mrzLogSubnetName string //= 'LogSubnet' + +@description('Management Restricted Zone - Loggin Services (Restricted Zone) Subnet Address Prefix.') +param mrzLogSubnetAddressPrefix string //= '10.18.5.64/26' + +@description('Management Restricted Zone - Core Management Interfaces (Restricted Zone) Subnet Name.') +param mrzMgmtSubnetName string //= 'MgmtSubnet' + +@description('Management Restricted Zone - Core Management Interfaces (Restricted Zone) Subnet Address Prefix.') +param mrzMgmtSubnetAddressPrefix string //= '10.18.5.128/26' + +// Public Access Zone +@description('Public Access Zone Resource Group Name.') +param rgPazName string //= 'pubsecPazPbRsg' + +// Temporary VM Credentials +@description('Temporary username for firewall virtual machines.') +@secure() +param fwUsername string + +@description('Temporary password for firewall virtual machines.') +@secure() +param fwPassword string + +/* + Scaffold the subscription which includes: + * Azure Security Center - Enable Azure Defender (all available options) + * Azure Security Center - Configure Log Analytics Workspace + * Azure Security Center - Configure Security Alert Contact + * Role Assignments to Security Groups + * Service Health Alerts + * Subscription Budget + * Subscription Tags +*/ +module subScaffold '../scaffold-subscription.bicep' = { + name: 'configure-subscription' + scope: subscription() + params: { + serviceHealthAlerts: serviceHealthAlerts + subscriptionRoleAssignments: subscriptionRoleAssignments + subscriptionBudget: subscriptionBudget + subscriptionTags: subscriptionTags + resourceTags: resourceTags + + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + securityCenter: securityCenter + } +} + +// Create Network Watcher Resource Group +resource rgNetworkWatcher 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: rgNetworkWatcherName + location: deployment().location + tags: resourceTags +} + +// Create Private DNS Zone Resource Group - optional +resource rgPrivateDnsZones 'Microsoft.Resources/resourceGroups@2020-06-01' = if (deployPrivateDnsZones) { + name: rgPrivateDnsZonesName + location: deployment().location + tags: resourceTags +} + +// Create Azure DDOS Standard Resource Group - optional +resource rgDdos 'Microsoft.Resources/resourceGroups@2020-06-01' = if (deployDdosStandard) { + name: rgDdosName + location: deployment().location + tags: resourceTags +} + +// Create Hub Virtual Network Resource Group +resource rgHubVnet 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: rgHubName + location: deployment().location + tags: resourceTags +} + +// Create Managemend Restricted Virtual Network Resource Group +resource rgMrzVnet 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: rgMrzName + location: deployment().location + tags: resourceTags +} + +// Create Public Access Zone Resource Group +resource rgPaz 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: rgPazName + location: deployment().location + tags: resourceTags +} + +// Enable delete locks +module rgDdosDeleteLock '../../azresources/util/delete-lock.bicep' = if (deployDdosStandard) { + name: 'deploy-delete-lock-${rgDdosName}' + scope: rgDdos +} + +module rgHubDeleteLock '../../azresources/util/delete-lock.bicep' = { + name: 'deploy-delete-lock-${rgHubName}' + scope: rgHubVnet +} + +module rgMrzDeleteLock '../../azresources/util/delete-lock.bicep' = { + name: 'deploy-delete-lock-${rgMrzName}' + scope: rgMrzVnet +} + +module rgPazDeleteLock '../../azresources/util/delete-lock.bicep' = { + name: 'deploy-delete-lock-${rgPazName}' + scope: rgPaz +} + +// DDOS Standard - optional +module ddosPlan '../../azresources/network/ddos-standard.bicep' = if (deployDdosStandard) { + name: 'deploy-ddos-standard-plan' + scope: rgDdos + params: { + name: ddosPlanName + } +} + +// Route Tables +module udrPrdSpokes '../../azresources/network/udr/udr-custom.bicep' = { + name: 'deploy-route-table-PrdSpokesUdr' + scope: rgHubVnet + params: { + name: 'PrdSpokesUdr' + routes: [ + { + name: 'default' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: fwProdILBPrdIntIP + } + } + // Force Routes to Hub IPs (RFC1918 range) via FW despite knowing that route via peering + { + name: 'PrdSpokesUdrHubRFC1918FWRoute' + properties: { + addressPrefix: hubVnetAddressPrefixRFC1918 + nextHopType: 'VirtualAppliance' + nextHopIpAddress: fwProdILBPrdIntIP + } + } + // Force Routes to Hub IPs (CGNAT range) via FW despite knowing that route via peering + { + name: 'PrdSpokesUdrHubRFC6598FWRoute' + properties: { + addressPrefix: hubVnetAddressPrefixRFC6598 + nextHopType: 'VirtualAppliance' + nextHopIpAddress: fwProdILBPrdIntIP + } + } + ] + } +} + +module udrMrzSpoke '../../azresources/network/udr/udr-custom.bicep' = { + name: 'deploy-route-table-MrzSpokeUdr' + scope: rgHubVnet + params: { + name: 'MrzSpokeUdr' + routes: [ + { + name: 'RouteToEgressFirewall' + properties: { + addressPrefix: '0.0.0.0/0' + nextHopType: 'VirtualAppliance' + nextHopIpAddress: fwProdILBPrdIntIP + } + } + // Force Routes to Hub IPs (RFC1918 range) via FW despite knowing that route via peering + { + name: 'MrzSpokeUdrHubRFC1918FWRoute' + properties: { + addressPrefix: hubVnetAddressPrefixRFC1918 + nextHopType: 'VirtualAppliance' + nextHopIpAddress: fwProdILBPrdIntIP + } + } + // Force Routes to Hub IPs (CGNAT range) via FW despite knowing that route via peering + { + name: 'MrzSpokeUdrHubRFC6598FWRoute' + properties: { + addressPrefix: hubVnetAddressPrefixRFC6598 + nextHopType: 'VirtualAppliance' + nextHopIpAddress: fwProdILBPrdIntIP + } + } + ] + } +} + +module udrPaz '../../azresources/network/udr/udr-custom.bicep' = { + name: 'deploy-route-table-PazSubnetUdr' + scope: rgHubVnet + params: { + name: 'PazSubnetUdr' + routes: [ + { + name: 'PazSubnetUdrMrzFWRoute' + properties: { + addressPrefix: mrzVnetAddressPrefixRFC1918 + nextHopType: 'VirtualAppliance' + nextHopIpAddress: fwProdILBExternalFacingIP + } + } + ] + } +} + +// Hub Virtual Network +module hubVnet 'hub-vnet/hub-vnet.bicep' = { + name: 'deploy-hub-vnet-${hubVnetName}' + scope: rgHubVnet + params: { + vnetName: hubVnetName + vnetAddressPrefixRFC1918: hubVnetAddressPrefixRFC1918 + vnetAddressPrefixRFC6598: hubVnetAddressPrefixRFC6598 + vnetAddressPrefixBastion: hubVnetAddressPrefixBastion + + publicSubnetName: hubPublicSubnetName + publicSubnetAddressPrefix: hubPublicSubnetAddressPrefix + + mrzIntSubnetName: hubMrzIntSubnetName + mrzIntSubnetAddressPrefix: hubMrzIntSubnetAddressPrefix + + prodIntSubnetName: hubProdIntSubnetName + prodIntSubnetAddressPrefix: hubProdIntSubnetAddressPrefix + + devIntSubnetName: hubDevIntSubnetName + devIntSubnetAddressPrefix: hubDevIntSubnetAddressPrefix + + haSubnetName: hubHASubnetName + haSubnetAddressPrefix: hubHASubnetAddressPrefix + + pazSubnetName: hubPazSubnetName + pazSubnetAddressPrefix: hubPazSubnetAddressPrefix + pazUdrId: udrPaz.outputs.udrId + + eanSubnetName: hubEanSubnetName + eanSubnetAddressPrefix: hubEanSubnetAddressPrefix + + gatewaySubnetAddressPrefix: hubGatewaySubnetPrefix + + bastionSubnetAddressPrefix: hubBastionSubnetAddressPrefix + + ddosStandardPlanId: deployDdosStandard ? ddosPlan.outputs.ddosPlanId : '' + } +} + +// Management Restricted Virtual Network +module mrzVnet 'mrz-vnet/mrz-vnet.bicep' = { + name: 'deploy-management-vnet-${mrzVnetName}' + scope: rgMrzVnet + params: { + vnetName: mrzVnetName + vnetAddressPrefix: mrzVnetAddressPrefixRFC1918 + + mazSubnetName: mrzMazSubnetName + mazSubnetAddressPrefix: mrzMazSubnetAddressPrefix + mazSubnetUdrId: udrMrzSpoke.outputs.udrId + + infSubnetName: mrzInfSubnetName + infSubnetAddressPrefix: mrzInfSubnetAddressPrefix + infSubnetUdrId: udrMrzSpoke.outputs.udrId + + secSubnetName: mrzSecSubnetName + secSubnetAddressPrefix: mrzSecSubnetAddressPrefix + secSubnetUdrId: udrMrzSpoke.outputs.udrId + + logSubnetName: mrzLogSubnetName + logSubnetAddressPrefix: mrzLogSubnetAddressPrefix + logSubnetUdrId: udrMrzSpoke.outputs.udrId + + mgmtSubnetName: mrzMgmtSubnetName + mgmtSubnetAddressPrefix: mrzMgmtSubnetAddressPrefix + mgmtSubnetUdrId: udrMrzSpoke.outputs.udrId + + ddosStandardPlanId: deployDdosStandard ? ddosPlan.outputs.ddosPlanId : '' + } +} + +// Private DNS Zones - optional +module privatelinkDnsZones '../../azresources/network/private-dns-zone-privatelinks.bicep' = if (deployPrivateDnsZones) { + name: 'deploy-privatelink-private-dns-zones' + scope: rgPrivateDnsZones + params: { + vnetId: hubVnet.outputs.vnetId + dnsCreateNewZone: true + dnsLinkToVirtualNetwork: true + + // Not required since the private dns zones will be created and linked to hub virtual network. + dnsExistingZoneSubscriptionId: '' + dnsExistingZoneResourceGroupName: '' + } +} + +// Virtual Network Peering - Management Restricted Zone to Hub +module vnetPeeringSpokeToHub '../../azresources/network/vnet-peering.bicep' = { + name: 'deploy-vnet-peering-spoke-to-hub' + scope: rgMrzVnet + params: { + peeringName: '${mrzVnet.outputs.vnetName}-to-${hubVnet.outputs.vnetName}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: mrzVnet.outputs.vnetName + targetVnetId: hubVnet.outputs.vnetId + useRemoteGateways: false //to be changed once we have ExpressRoute or VPN GWs + } +} + +// Virtual Network Peering - Hub to Management Restricted Zone +module vnetPeeringHubToSpoke '../../azresources/network/vnet-peering.bicep' = { + name: 'deploy-vnet-peering-hub-to-spoke' + scope: rgHubVnet + params: { + peeringName: '${hubVnet.outputs.vnetName}-to-${mrzVnet.outputs.vnetName}' + allowForwardedTraffic: true + allowVirtualNetworkAccess: true + sourceVnetName: hubVnet.outputs.vnetName + targetVnetId: mrzVnet.outputs.vnetId + useRemoteGateways: false + } +} + +// Azure Bastion +module bastion '../../azresources/network/bastion.bicep' = { + name: 'deploy-bastion' + scope: rgHubVnet + params: { + name: bastionName + subnetId: hubVnet.outputs.AzureBastionSubnetId + } +} + +// Production traffic - Fortinet Firewall VM +module ProdFW1_fortigate 'nva/fortinet-vm.bicep' = if (deployFirewallVMs && useFortigateFW) { + name: 'deploy-nva-ProdFW1_fortigate' + scope: rgHubVnet + params: { + availabilityZone: '1' //make it a parameter with a default value (in the params.json file) + vmName: fwProdVM1Name + vmSku: fwProdVMSku + nic1PrivateIP: fwProdVM1ExternalFacingIP + nic1SubnetId: hubVnet.outputs.PublicSubnetId + nic2PrivateIP: fwProdVM1MrzIntIP + nic2SubnetId: hubVnet.outputs.MrzIntSubnetId + nic3PrivateIP: fwProdVM1PrdIntIP + nic3SubnetId: hubVnet.outputs.PrdIntSubnetId + nic4PrivateIP: fwProdVM1HAIP + nic4SubnetId: hubVnet.outputs.HASubnetId + username: fwUsername + password: fwPassword + } +} + +// Production traffic - Ubuntu Firewall VM +module ProdFW1_ubuntu 'nva/ubuntu-fw-vm.bicep' = if (deployFirewallVMs && !useFortigateFW) { + name: 'deploy-nva-ProdFW1_ubuntu' + scope: rgHubVnet + params: { + availabilityZone: fwProdVM1AvailabilityZone //make it a parameter with a default value (in the params.json file) + vmName: fwProdVM1Name + vmSku: fwProdVMSku + nic1PrivateIP: fwProdVM1ExternalFacingIP + nic1SubnetId: hubVnet.outputs.PublicSubnetId + nic2PrivateIP: fwProdVM1MrzIntIP + nic2SubnetId: hubVnet.outputs.MrzIntSubnetId + nic3PrivateIP: fwProdVM1PrdIntIP + nic3SubnetId: hubVnet.outputs.PrdIntSubnetId + nic4PrivateIP: fwProdVM1HAIP + nic4SubnetId: hubVnet.outputs.HASubnetId + username: fwUsername + password: fwPassword + } +} + +// Production traffic - Fortinet Firewall VM +module ProdFW2_fortigate 'nva/fortinet-vm.bicep' = if (deployFirewallVMs && useFortigateFW) { + name: 'deploy-nva-ProdFW2_fortigate' + scope: rgHubVnet + params: { + availabilityZone: fwProdVM2AvailabilityZone + vmName: fwProdVM2Name + vmSku: fwProdVMSku + nic1PrivateIP: fwProdVM2ExternalFacingIP + nic1SubnetId: hubVnet.outputs.PublicSubnetId + nic2PrivateIP: fwProdVM2MrzIntIP + nic2SubnetId: hubVnet.outputs.MrzIntSubnetId + nic3PrivateIP: fwProdVM2PrdIntIP + nic3SubnetId: hubVnet.outputs.PrdIntSubnetId + nic4PrivateIP: fwProdVM2HAIP + nic4SubnetId: hubVnet.outputs.HASubnetId + username: fwUsername + password: fwPassword + } +} + +// Production traffic - Ubuntu Firewall VM +module ProdFW2_ubuntu 'nva/ubuntu-fw-vm.bicep' = if (deployFirewallVMs && !useFortigateFW) { + name: 'deploy-nva-ProdFW2_ubuntu' + scope: rgHubVnet + params: { + availabilityZone: '2' + vmName: fwProdVM2Name + vmSku: fwProdVMSku + nic1PrivateIP: fwProdVM2ExternalFacingIP + nic1SubnetId: hubVnet.outputs.PublicSubnetId + nic2PrivateIP: fwProdVM2MrzIntIP + nic2SubnetId: hubVnet.outputs.MrzIntSubnetId + nic3PrivateIP: fwProdVM2PrdIntIP + nic3SubnetId: hubVnet.outputs.PrdIntSubnetId + nic4PrivateIP: fwProdVM2HAIP + nic4SubnetId: hubVnet.outputs.HASubnetId + username: fwUsername + password: fwPassword + } +} + +// Non-Production traffic - Fortinet Firewall VM +module DevFW1 'nva/fortinet-vm.bicep' = if (deployFirewallVMs && useFortigateFW) { + name: 'deploy-nva-DevFW1_fortigate' + scope: rgHubVnet + params: { + availabilityZone: fwDevVM1AvailabilityZone + vmName: fwDevVM1Name + vmSku: fwDevVMSku + nic1PrivateIP: fwDevVM1ExternalFacingIP + nic1SubnetId: hubVnet.outputs.PublicSubnetId + nic2PrivateIP: fwDevVM1MrzIntIP + nic2SubnetId: hubVnet.outputs.MrzIntSubnetId + nic3PrivateIP: fwDevVM1DevIntIP + nic3SubnetId: hubVnet.outputs.DevIntSubnetId + nic4PrivateIP: fwDevVM1HAIP + nic4SubnetId: hubVnet.outputs.HASubnetId + username: fwUsername + password: fwPassword + } +} + +// Non-Production traffic - Fortinet Firewall VM +module DevFW2 'nva/fortinet-vm.bicep' = if (deployFirewallVMs && useFortigateFW) { + name: 'deploy-nva-DevFW2_fortigate' + scope: rgHubVnet + params: { + availabilityZone: fwDevVM2AvailabilityZone + vmName: fwDevVM2Name + vmSku: fwDevVMSku + nic1PrivateIP: fwDevVM2ExternalFacingIP + nic1SubnetId: hubVnet.outputs.PublicSubnetId + nic2PrivateIP: fwDevVM2MrzIntIP + nic2SubnetId: hubVnet.outputs.MrzIntSubnetId + nic3PrivateIP: fwDevVM2DevIntIP + nic3SubnetId: hubVnet.outputs.DevIntSubnetId + nic4PrivateIP: fwDevVM2HAIP + nic4SubnetId: hubVnet.outputs.HASubnetId + username: fwUsername + password: fwPassword + } +} + +// Production traffic - Internal Load Balancer +module ProdFWs_ILB 'hub-vnet/lb-firewalls-hub.bicep' = { + name: 'deploy-internal-loadblancer-ProdFWs_ILB' + scope: rgHubVnet + params: { + name: fwProdILBName + backendVnetId: hubVnet.outputs.vnetId + frontendIPExt: fwProdILBExternalFacingIP + backendIP1Ext: fwProdVM1ExternalFacingIP + backendIP2Ext: fwProdVM2ExternalFacingIP + frontendSubnetIdExt: hubVnet.outputs.PublicSubnetId + frontendIPInt: fwProdILBPrdIntIP + backendIP1Int: fwProdVM1PrdIntIP + backendIP2Int: fwProdVM2PrdIntIP + frontendSubnetIdInt: hubVnet.outputs.PrdIntSubnetId + lbProbeTcpPort: useFortigateFW ? 8008 : 22 + configureEmptyBackendPool: !deployFirewallVMs + } +} + +// Non-Production traffic - Internal Load Balancer +module DevFWs_ILB 'hub-vnet/lb-firewalls-hub.bicep' = { + name: 'deploy-internal-loadblancer-DevFWs_ILB' + scope: rgHubVnet + params: { + name: fwDevILBName + backendVnetId: hubVnet.outputs.vnetId + frontendIPExt: fwDevILBExternalFacingIP + backendIP1Ext: fwDevVM1ExternalFacingIP + backendIP2Ext: fwDevVM2ExternalFacingIP + frontendSubnetIdExt: hubVnet.outputs.PublicSubnetId + frontendIPInt: fwDevILBDevIntIP + backendIP1Int: fwDevVM1DevIntIP + backendIP2Int: fwDevVM2DevIntIP + frontendSubnetIdInt: hubVnet.outputs.DevIntSubnetId + lbProbeTcpPort: useFortigateFW ? 8008 : 22 + configureEmptyBackendPool: !deployFirewallVMs + } +} diff --git a/landingzones/lz-platform-connectivity-hub-nva/mrz-vnet/mrz-vnet.bicep b/landingzones/lz-platform-connectivity-hub-nva/mrz-vnet/mrz-vnet.bicep new file mode 100644 index 00000000..fd221bbd --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-nva/mrz-vnet/mrz-vnet.bicep @@ -0,0 +1,193 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// Management Restricted Zone Virtual Network + +// VNET +@description('Virtual Network Name.') +param vnetName string + +@description('Virtual Network Address Space.') +param vnetAddressPrefix string + +// Management (Access Zone) +@description('Management (Access Zone) Subnet Name.') +param mazSubnetName string + +@description('Management (Access Zone) Subnet Address Prefix.') +param mazSubnetAddressPrefix string + +@description('Management (Access Zone) User Defined Route Resource Id.') +param mazSubnetUdrId string + +// Infra Services (Restricted Zone) +@description('Infrastructure Services (Restricted Zone) Subnet Name.') +param infSubnetName string + +@description('Infrastructure Services (Restricted Zone) Subnet Address Prefix.') +param infSubnetAddressPrefix string + +@description('Infrastructure Services (Restricted Zone) User Defined Route Resource Id.') +param infSubnetUdrId string + +// Security Services (Restricted Zone) +@description('Security Services (Restricted Zone) Subnet Name.') +param secSubnetName string +@description('Security Services (Restricted Zone) Subnet Address Prefis.') +param secSubnetAddressPrefix string + +@description('Security Services (Restricted Zone) User Defined Route Resource Id.') +param secSubnetUdrId string + +// Logging Services (Restricted Zone) +@description('Logging Services (Restricted Zone) Subnet Name.') +param logSubnetName string + +@description('Logging Services (Restricted Zone) Subnet Address Prefix.') +param logSubnetAddressPrefix string + +@description('Logging Services (Restricted Zone) User Defined Route Resource Id.') +param logSubnetUdrId string + +// Core Management Interfaces +@description('Core Management Interfaces (Restricted Zone) Subnet Name.') +param mgmtSubnetName string + +@description('Core Management Interfaces (Restricted Zone) Subnet Address Prefix.') +param mgmtSubnetAddressPrefix string + +@description('Core Management Interfaces (Restricted Zone) User Defined Route Table Resource Id.') +param mgmtSubnetUdrId string + +// DDOS +@description('DDOS Standard Plan Resource Id - optional (blank value = DDOS Standard Plan will not be linked to virtual network).') +param ddosStandardPlanId string + +module nsgmaz '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${mazSubnetName}' + params: { + name: '${mazSubnetName}Nsg' + } +} + +module nsginf '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${infSubnetName}' + params: { + name: '${infSubnetName}Nsg' + } +} + +module nsgsec '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${secSubnetName}' + params: { + name: '${secSubnetName}Nsg' + } +} + +module nsglog '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${logSubnetName}' + params: { + name: '${logSubnetName}Nsg' + } +} + +module nsgmgmt '../../../azresources/network/nsg/nsg-empty.bicep' = { + name: 'deploy-nsg-${mgmtSubnetName}' + params: { + name: '${mgmtSubnetName}Nsg' + } +} + +resource mrzVnet 'Microsoft.Network/virtualNetworks@2020-06-01' = { + location: resourceGroup().location + name: vnetName + properties: { + enableDdosProtection: !empty(ddosStandardPlanId) + ddosProtectionPlan: (!empty(ddosStandardPlanId)) ? { + id: ddosStandardPlanId + } : null + addressSpace: { + addressPrefixes: [ + vnetAddressPrefix + ] + } + subnets: [ + { + name: mazSubnetName + properties: { + addressPrefix: mazSubnetAddressPrefix + networkSecurityGroup: { + id: nsgmaz.outputs.nsgId + } + routeTable: { + id: mazSubnetUdrId + } + } + } + { + name: infSubnetName + properties: { + addressPrefix: infSubnetAddressPrefix + networkSecurityGroup: { + id: nsginf.outputs.nsgId + } + routeTable: { + id: infSubnetUdrId + } + } + } + { + name: secSubnetName + properties: { + addressPrefix: secSubnetAddressPrefix + networkSecurityGroup: { + id: nsgsec.outputs.nsgId + } + routeTable: { + id: secSubnetUdrId + } + } + } + { + name: logSubnetName + properties: { + addressPrefix: logSubnetAddressPrefix + networkSecurityGroup: { + id: nsglog.outputs.nsgId + } + routeTable: { + id: logSubnetUdrId + } + } + } + { + name: mgmtSubnetName + properties: { + addressPrefix: mgmtSubnetAddressPrefix + networkSecurityGroup: { + id: nsgmgmt.outputs.nsgId + } + routeTable: { + id: mgmtSubnetUdrId + } + } + } + ] + } +} + +// Outputs +output vnetName string = mrzVnet.name +output vnetId string = mrzVnet.id + +output MazSubnetId string = '${mrzVnet.id}/subnets/${mazSubnetName}' +output InfSubnetId string = '${mrzVnet.id}/subnets/${infSubnetName}' +output SecSubnetId string = '${mrzVnet.id}/subnets/${secSubnetName}' +output LogSubnetId string = '${mrzVnet.id}/subnets/${logSubnetName}' +output MgmtSubnetId string = '${mrzVnet.id}/subnets/${mgmtSubnetName}' diff --git a/landingzones/lz-platform-connectivity-hub-nva/nva/fortinet-vm.bicep b/landingzones/lz-platform-connectivity-hub-nva/nva/fortinet-vm.bicep new file mode 100644 index 00000000..cb79059e --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-nva/nva/fortinet-vm.bicep @@ -0,0 +1,269 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// VM +@description('Virtual Machine Name.') +param vmName string + +@description('Virtual Machine SKU.') +param vmSku string + +@description('Virtual Machine Availability Zone') +param availabilityZone string + +// Network Interfaces +@description('NIC #1 - Private IP') +param nic1PrivateIP string + +@description('NIC #1 - Subnet Resource Id') +param nic1SubnetId string + +@description('NIC #2 - Private IP') +param nic2PrivateIP string + +@description('NIC #2 - Subnet Resource Id') +param nic2SubnetId string + +@description('NIC #3 - Private IP') +param nic3PrivateIP string +@description('NIC #3 - Subnet Resource Id') +param nic3SubnetId string + +@description('NIC #4 - Private IP') +param nic4PrivateIP string +@description('NIC #4 - Subnet Resource Id') +param nic4SubnetId string + + +//fortinet:fortinet_fortigate-vm_v5:fortinet_fg-vm:6.4.5 //BYOL - works with MSDN free subs +//fortinet:fortinet_fortigate-vm_v5:fortinet_fg-vm-payg_20190624:6.4.5 //PAYG - needs credit card + +// VM Image +@description('Fortigate Firewall - Publisher. Default: fortinet') +param vmImagePublisher string = 'fortinet' + +@description('Fortigate Firewall - Image Offer. Default: fortinet_fortigate') +param vmImageOffer string = 'fortinet_fortigate-vm_v5' + +@description('Fortigate Firewall - Plan. Default: fortinet_fg-vm') +param vmImagePlanName string = 'fortinet_fg-vm' + +@description('Fortigate Firewall - SKU. Default: fortinet_fg-vm') +param vmImageSku string = 'fortinet_fg-vm' + +@description('Fortigate Firewall - Image Version. Default: 6.4.5') +param vmImageVersion string = '6.4.5' + +@description('Temporary username for firewall virtual machine.') +@secure() +param username string + +@description('Temporary password for firewall virtual machine.') +@secure() +param password string + +resource nic1 'Microsoft.Network/networkInterfaces@2020-11-01' = { + name: '${vmName}-nic1' + location: resourceGroup().location + tags: {} + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + privateIPAddress: nic1PrivateIP + privateIPAllocationMethod: 'Static' + subnet: { + id: nic1SubnetId + } + primary: true + privateIPAddressVersion: 'IPv4' + } + } + ] + dnsSettings: { + dnsServers: [] + } + enableAcceleratedNetworking: true + enableIPForwarding: true + } +} + +resource nic2 'Microsoft.Network/networkInterfaces@2020-11-01' = { + name: '${vmName}-nic2' + location: resourceGroup().location + tags: {} + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + privateIPAddress: nic2PrivateIP + privateIPAllocationMethod: 'Static' + subnet: { + id: nic2SubnetId + } + primary: true + privateIPAddressVersion: 'IPv4' + } + } + ] + dnsSettings: { + dnsServers: [] + } + enableAcceleratedNetworking: true + enableIPForwarding: true + } +} +resource nic3 'Microsoft.Network/networkInterfaces@2020-11-01' = { + name: '${vmName}-nic3' + location: resourceGroup().location + tags: {} + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + privateIPAddress: nic3PrivateIP + privateIPAllocationMethod: 'Static' + subnet: { + id: nic3SubnetId + } + primary: true + privateIPAddressVersion: 'IPv4' + } + } + ] + dnsSettings: { + dnsServers: [] + } + enableAcceleratedNetworking: true + enableIPForwarding: true + } +} +resource nic4 'Microsoft.Network/networkInterfaces@2020-11-01' = { + name: '${vmName}-nic4' + location: resourceGroup().location + tags: {} + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + privateIPAddress: nic4PrivateIP + privateIPAllocationMethod: 'Static' + subnet: { + id: nic4SubnetId + } + primary: true + privateIPAddressVersion: 'IPv4' + } + } + ] + dnsSettings: { + dnsServers: [] + } + enableAcceleratedNetworking: true + enableIPForwarding: true + } +} + +resource vm 'Microsoft.Compute/virtualMachines@2020-12-01' = { + name: vmName + location: resourceGroup().location + tags: {} + zones: [ + availabilityZone + ] + plan: { + name: vmImagePlanName + product: vmImageOffer + publisher: vmImagePublisher + } + properties: { + hardwareProfile: { + vmSize: vmSku + } + storageProfile: { + imageReference: { + publisher: vmImagePublisher + offer: vmImageOffer + sku: vmImageSku + version: vmImageVersion + } + osDisk: { + osType: 'Linux' + name: '${vmName}_OsDisk_1' + createOption: 'FromImage' + caching: 'ReadWrite' + managedDisk: { + storageAccountType: 'Premium_LRS' + } + } + dataDisks: [ + { + lun: 0 + name: '${vmName}_disk2' + createOption: 'Empty' + caching: 'None' + diskSizeGB: 128 + managedDisk: { + storageAccountType: 'Premium_LRS' + } + toBeDetached: false + } + ] + } + osProfile: { + computerName: vmName + adminUsername: username + adminPassword: password + linuxConfiguration: { + disablePasswordAuthentication: false + provisionVMAgent: true + patchSettings: { + patchMode: 'ImageDefault' + } + } + secrets: [] + allowExtensionOperations: true + } + networkProfile: { + networkInterfaces: [ + { + id: nic1.id + properties: { + primary: true + } + } + { + id: nic2.id + properties: { + primary: false + } + } + { + id: nic3.id + properties: { + primary: false + } + } + { + id: nic4.id + properties: { + primary: false + } + } + ] + } + } +} + +output vmName string = vm.name +output vmId string = vm.id diff --git a/landingzones/lz-platform-connectivity-hub-nva/nva/ubuntu-fw-vm.bicep b/landingzones/lz-platform-connectivity-hub-nva/nva/ubuntu-fw-vm.bicep new file mode 100644 index 00000000..543a776a --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-nva/nva/ubuntu-fw-vm.bicep @@ -0,0 +1,244 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +// VM +@description('Virtual Machine Name.') +param vmName string + +@description('Virtual Machine SKU.') +param vmSku string + +@description('Virtual Machine Availability Zone') +param availabilityZone string + +// Network Interfaces +@description('NIC #1 - Private IP') +param nic1PrivateIP string + +@description('NIC #1 - Subnet Resource Id') +param nic1SubnetId string + +@description('NIC #2 - Private IP') +param nic2PrivateIP string + +@description('NIC #2 - Subnet Resource Id') +param nic2SubnetId string + +@description('NIC #3 - Private IP') +param nic3PrivateIP string +@description('NIC #3 - Subnet Resource Id') +param nic3SubnetId string + +@description('NIC #4 - Private IP') +param nic4PrivateIP string +@description('NIC #4 - Subnet Resource Id') +param nic4SubnetId string + +// VM Image +@description('Ubuntu - Publisher. Default: Canonical') +param vmImagePublisher string = 'Canonical' + +@description('Ubuntu - Image Offer. Default: UbuntuServer') +param vmImageOffer string = 'UbuntuServer' + +@description('Ubuntu - SKU. Default: 18.04-LTS') +param vmImageSku string = '18.04-LTS' + +@description('Ubuntu - Image Version. Default: latest') +param vmImageVersion string = 'latest' + +@description('Temporary username for firewall virtual machine.') +@secure() +param username string + +@description('Temporary password for firewall virtual machine.') +@secure() +param password string + +resource nic1 'Microsoft.Network/networkInterfaces@2020-11-01' = { + name: '${vmName}-nic1' + location: resourceGroup().location + tags: {} + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + privateIPAddress: nic1PrivateIP + privateIPAllocationMethod: 'Static' + subnet: { + id: nic1SubnetId + } + primary: true + privateIPAddressVersion: 'IPv4' + } + } + ] + dnsSettings: { + dnsServers: [] + } + enableAcceleratedNetworking: true + enableIPForwarding: true + } +} + +resource nic2 'Microsoft.Network/networkInterfaces@2020-11-01' = { + name: '${vmName}-nic2' + location: resourceGroup().location + tags: {} + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + privateIPAddress: nic2PrivateIP + privateIPAllocationMethod: 'Static' + subnet: { + id: nic2SubnetId + } + primary: true + privateIPAddressVersion: 'IPv4' + } + } + ] + dnsSettings: { + dnsServers: [] + } + enableAcceleratedNetworking: true + enableIPForwarding: true + } +} +resource nic3 'Microsoft.Network/networkInterfaces@2020-11-01' = { + name: '${vmName}-nic3' + location: resourceGroup().location + tags: {} + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + privateIPAddress: nic3PrivateIP + privateIPAllocationMethod: 'Static' + subnet: { + id: nic3SubnetId + } + primary: true + privateIPAddressVersion: 'IPv4' + } + } + ] + dnsSettings: { + dnsServers: [] + } + enableAcceleratedNetworking: true + enableIPForwarding: true + } +} +resource nic4 'Microsoft.Network/networkInterfaces@2020-11-01' = { + name: '${vmName}-nic4' + location: resourceGroup().location + tags: {} + properties: { + ipConfigurations: [ + { + name: 'ipconfig1' + properties: { + privateIPAddress: nic4PrivateIP + privateIPAllocationMethod: 'Static' + subnet: { + id: nic4SubnetId + } + primary: true + privateIPAddressVersion: 'IPv4' + } + } + ] + dnsSettings: { + dnsServers: [] + } + enableAcceleratedNetworking: true + enableIPForwarding: true + } +} + +resource vm 'Microsoft.Compute/virtualMachines@2020-12-01' = { + name: vmName + location: resourceGroup().location + tags: {} + zones: [ + availabilityZone + ] + properties: { + hardwareProfile: { + vmSize: vmSku + } + storageProfile: { + imageReference: { + publisher: vmImagePublisher + offer: vmImageOffer + sku: vmImageSku + version: vmImageVersion + } + osDisk: { + osType: 'Linux' + name: '${vmName}_OsDisk_1' + createOption: 'FromImage' + caching: 'ReadWrite' + managedDisk: { + storageAccountType: 'Premium_LRS' + } + } + } + osProfile: { + computerName: vmName + adminUsername: username + adminPassword: password + linuxConfiguration: { + disablePasswordAuthentication: false + provisionVMAgent: true + patchSettings: { + patchMode: 'ImageDefault' + } + } + secrets: [] + allowExtensionOperations: true + } + networkProfile: { + networkInterfaces: [ + { + id: nic1.id + properties: { + primary: true + } + } + { + id: nic2.id + properties: { + primary: false + } + } + { + id: nic3.id + properties: { + primary: false + } + } + { + id: nic4.id + properties: { + primary: false + } + } + ] + } + } +} + +output vmName string = vm.name +output vmId string = vm.id diff --git a/landingzones/lz-platform-connectivity-hub-nva/nva/ubuntu-fw.sh b/landingzones/lz-platform-connectivity-hub-nva/nva/ubuntu-fw.sh new file mode 100644 index 00000000..062f43a2 --- /dev/null +++ b/landingzones/lz-platform-connectivity-hub-nva/nva/ubuntu-fw.sh @@ -0,0 +1,43 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +sudo apt install apache2 #in case we want an HTTP probe +echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf +#the rp_filter below can be avoided if we add routes to the proper Spokes IP Ranges to the proper NIC (i.e. prod spokes to the INT device, mrz spoke to MRZ device) +echo "net.ipv4.conf.default.rp_filter = 0" | sudo tee -a /etc/sysctl.conf +echo "net.ipv4.conf.all.rp_filter = 0" | sudo tee -a /etc/sysctl.conf +for i in /proc/sys/net/ipv4/conf/*/rp_filter ; do echo 0 | sudo tee -a $i ; done + +sudo sysctl -p +# READ THIS !! https://github.com/erjosito/azure-networking-lab/blob/master/README.bak.md - step 6 sudo vi /etc/iproute2/rt_tables +#and add 201 mrz and 202 int +echo "201 mrz" | sudo tee -a /etc/iproute2/rt_tables +echo "202 int" | sudo tee -a /etc/iproute2/rt_tables +sudo ip rule add from 10.18.0.37 to 168.63.129.16 lookup mrz +sudo ip rule add from 10.18.0.101 to 168.63.129.16 lookup int + +#WARNING this is not permanent!! also watch out for the ETHX number +sudo ip route add 168.63.129.16 via 10.18.0.33 dev eth2 table mrz +sudo ip route add 168.63.129.16 via 10.18.0.97 dev eth1 table int + +#the routes below aren't enough, as it duplicates a route but the default GW takes precedence +#that's why you need multiple rt_tables +sudo route add 168.63.129.16 gw 10.18.0.33 metric 100 +sudo route add 168.63.129.16 gw 10.18.0.97 metric 100 + +#now a general route to MRZ +route add -net 10.18.4.0/22 gw 10.18.0.97 dev eth1 +sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE +sudo iptables -A PREROUTING -t nat -i eth0 -p tcp --dport 8080 -j DNAT --to 10.18.4.132:80 +sudo iptables -A FORWARD -p tcp -d 10.18.4.132 --dport 80 -j ACCEPT + +sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent +sudo iptables-save | sudo tee -a /etc/iptables/rules.v4 + +#more tips in https://medium.com/contino-engineering/azure-egress-nat-with-linux-vm-595f6abd2f77 \ No newline at end of file diff --git a/landingzones/lz-platform-logging/main.bicep b/landingzones/lz-platform-logging/main.bicep new file mode 100644 index 00000000..f34b6619 --- /dev/null +++ b/landingzones/lz-platform-logging/main.bicep @@ -0,0 +1,225 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +/* + +Platform Logging archetype provides infrastructure for centrally managed Log Analytics Workspace & Sentinel that includes: + +* Azure Automation Account +* Log Analytics Workspace +* Log Analytics Workspace Solutions + * AgentHealthAssessment + * AntiMalware + * AzureActivity + * ChangeTracking + * Security + * SecurityInsights (Azure Sentinel) + * ServiceMap + * SQLAssessment + * Updates + * VMInsights +* Role-based access control for Owner, Contributor & Reader +* Integration between Azure Automation Account & Log Analytics Workspace +* Integration with Azure Cost Management for Subscription-scoped budget +* Integration with Azure Security Center + +*/ + +targetScope = 'subscription' + +// Service Health +// Example (JSON) +// ----------------------------- +// "serviceHealthAlerts": { +// "value": { +// "incidentTypes": [ "Incident", "Security", "Maintenance", "Information", "ActionRequired" ], +// "regions": [ "Global", "Canada East", "Canada Central" ], +// "receivers": { +// "app": [ "email-1@company.com", "email-2@company.com" ], +// "email": [ "email-1@company.com", "email-3@company.com", "email-4@company.com" ], +// "sms": [ { "countryCode": "1", "phoneNumber": "1234567890" }, { "countryCode": "1", "phoneNumber": "0987654321" } ], +// "voice": [ { "countryCode": "1", "phoneNumber": "1234567890" } ] +// }, +// "actionGroupName": "ALZ action group", +// "actionGroupShortName": "alz-alert", +// "alertRuleName": "ALZ alert rule", +// "alertRuleDescription": "Alert rule for Azure Landing Zone" +// } +// } +@description('Service Health alerts') +param serviceHealthAlerts object = {} + +// Azure Security Center +// Example (JSON) +// ----------------------------- +// "securityCenter": { +// "value": { +// "email": "alzcanadapubsec@microsoft.com", +// "phone": "5555555555" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// email: 'alzcanadapubsec@microsoft.com' +// phone: '5555555555' +// } +@description('Security Center configuration. It includes email and phone.') +param securityCenter object + +// Subscription Role Assignments +// Example (JSON) +// ----------------------------- +// [ +// { +// "comments": "Built-in Contributor Role", +// "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", +// "securityGroupObjectIds": [ +// "38f33f7e-a471-4630-8ce9-c6653495a2ee" +// ] +// } +// ] + +// Example (Bicep) +// ----------------------------- +// [ +// { +// comments: 'Built-In Contributor Role' +// roleDefinitionId: 'b24988ac-6180-42a0-ab88-20f7382dd24c' +// securityGroupObjectIds: [ +// '38f33f7e-a471-4630-8ce9-c6653495a2ee' +// ] +// } +// ] +@description('Array of role assignments at subscription scope. The array will contain an object with comments, roleDefinitionId and array of securityGroupObjectIds.') +param subscriptionRoleAssignments array = [] + +// Subscription Budget +// Example (JSON) +// --------------------------- +// "subscriptionBudget": { +// "value": { +// "createBudget": false, +// "name": "MonthlySubscriptionBudget", +// "amount": 1000, +// "timeGrain": "Monthly", +// "contactEmails": [ "alzcanadapubsec@microsoft.com" ] +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// createBudget: true +// name: 'MonthlySubscriptionBudget' +// amount: 1000 +// timeGrain: 'Monthly' +// contactEmails: [ +// 'alzcanadapubsec@microsoft.com' +// ] +// } +@description('Subscription budget configuration containing createBudget flag, name, amount, timeGrain and array of contactEmails') +param subscriptionBudget object + +// Tags +// Example (JSON) +// ----------------------------- +// "subscriptionTags": { +// "value": { +// "ISSO": "isso-tag" +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// ISSO: 'isso-tag' +// } +@description('A set of key/value pairs of tags assigned to the subscription.') +param subscriptionTags object + +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// ClientOrganization: 'client-organization-tag' +// CostCenter: 'cost-center-tag' +// DataSensitivity: 'data-sensitivity-tag' +// ProjectContact: 'project-contact-tag' +// ProjectName: 'project-name-tag' +// TechnicalContact: 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Logging +@description('Log Analytics Resource Group name.') +param logAnalyticsResourceGroupName string + +@description('Log Analytics Workspace name.') +param logAnalyticsWorkspaceName string + +@description('Automation account name.') +param logAnalyticsAutomationAccountName string + +// Create Log Analytics Workspace Resource Group +resource rgLogging 'Microsoft.Resources/resourceGroups@2020-06-01' = { + name: logAnalyticsResourceGroupName + location: deployment().location + tags: resourceTags +} + +// Create Log Analytics Workspace +module logAnalytics '../../azresources/monitor/log-analytics.bicep' = { + name: logAnalyticsWorkspaceName + scope: rgLogging + params: { + workspaceName: logAnalyticsWorkspaceName + automationAccountName: logAnalyticsAutomationAccountName + tags: resourceTags + } +} + +/* + Scaffold the subscription which includes: + * Azure Security Center - Enable Azure Defender (all available options) + * Azure Security Center - Configure Log Analytics Workspace (using the Log Analytics Workspace created in this deployment) + * Azure Security Center - Configure Security Alert Contact + * Role Assignments to Security Groups + * Service Health Alerts + * Subscription Budget + * Subscription Tags +*/ +module subScaffold '../scaffold-subscription.bicep' = { + name: 'subscription-scaffold' + scope: subscription() + params: { + serviceHealthAlerts: serviceHealthAlerts + subscriptionRoleAssignments: subscriptionRoleAssignments + subscriptionBudget: subscriptionBudget + subscriptionTags: subscriptionTags + resourceTags: resourceTags + + logAnalyticsWorkspaceResourceId: logAnalytics.outputs.workspaceResourceId + securityCenter: securityCenter + } +} diff --git a/landingzones/scaffold-subscription.bicep b/landingzones/scaffold-subscription.bicep new file mode 100644 index 00000000..ab5e6545 --- /dev/null +++ b/landingzones/scaffold-subscription.bicep @@ -0,0 +1,216 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +// Service Health +// Example (JSON) +// ----------------------------- +// "serviceHealthAlerts": { +// "value": { +// "incidentTypes": [ "Incident", "Security", "Maintenance", "Information", "ActionRequired" ], +// "regions": [ "Global", "Canada East", "Canada Central" ], +// "receivers": { +// "app": [ "email-1@company.com", "email-2@company.com" ], +// "email": [ "email-1@company.com", "email-3@company.com", "email-4@company.com" ], +// "sms": [ { "countryCode": "1", "phoneNumber": "1234567890" }, { "countryCode": "1", "phoneNumber": "0987654321" } ], +// "voice": [ { "countryCode": "1", "phoneNumber": "1234567890" } ] +// }, +// "actionGroupName": "ALZ action group", +// "actionGroupShortName": "alz-alert", +// "alertRuleName": "ALZ alert rule", +// "alertRuleDescription": "Alert rule for Azure Landing Zone" +// } +// } +@description('Service Health alerts') +param serviceHealthAlerts object = {} + +// Log Analytics +@description('Log Analytics Resource Id to integrate Azure Security Center.') +param logAnalyticsWorkspaceResourceId string + +// Azure Security Center +// Example (JSON) +// ----------------------------- +// "securityCenter": { +// "value": { +// "email": "alzcanadapubsec@microsoft.com", +// "phone": "5555555555" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// 'email': 'alzcanadapubsec@microsoft.com' +// 'phone': '5555555555' +// } +@description('Security Center configuration. It includes email and phone.') +param securityCenter object + +// Subscription Role Assignments +// Example (JSON) +// ----------------------------- +// [ +// { +// "comments": "Built-in Contributor Role", +// "roleDefinitionId": "b24988ac-6180-42a0-ab88-20f7382dd24c", +// "securityGroupObjectIds": [ +// "38f33f7e-a471-4630-8ce9-c6653495a2ee" +// ] +// } +// ] + +// Example (Bicep) +// ----------------------------- +// [ +// { +// 'comments': 'Built-In Contributor Role' +// 'roleDefinitionId': 'b24988ac-6180-42a0-ab88-20f7382dd24c' +// 'securityGroupObjectIds': [ +// '38f33f7e-a471-4630-8ce9-c6653495a2ee' +// ] +// } +// ] +@description('Array of role assignments at subscription scope. The array will contain an object with comments, roleDefinitionId and array of securityGroupObjectIds.') +param subscriptionRoleAssignments array = [] + +// Subscription Budget +// Example (JSON) +// --------------------------- +// "subscriptionBudget": { +// "value": { +// "createBudget": false, +// "name": "MonthlySubscriptionBudget", +// "amount": 1000, +// "timeGrain": "Monthly", +// "contactEmails": [ "alzcanadapubsec@microsoft.com" ] +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// createBudget: true +// name: 'MonthlySubscriptionBudget' +// amount: 1000 +// timeGrain: 'Monthly' +// contactEmails: [ +// 'alzcanadapubsec@microsoft.com' +// ] +// } +@description('Subscription budget configuration containing createBudget flag, name, amount, timeGrain and array of contactEmails') +param subscriptionBudget object + +// Tags +// Example (JSON) +// ----------------------------- +// "subscriptionTags": { +// "value": { +// "ISSO": "isso-tag" +// } +// } + +// Example (Bicep) +// --------------------------- +// { +// 'ISSO': 'isso-tag' +// } +@description('A set of key/value pairs of tags assigned to the subscription.') +param subscriptionTags object + +// Example (JSON) +// ----------------------------- +// "resourceTags": { +// "value": { +// "ClientOrganization": "client-organization-tag", +// "CostCenter": "cost-center-tag", +// "DataSensitivity": "data-sensitivity-tag", +// "ProjectContact": "project-contact-tag", +// "ProjectName": "project-name-tag", +// "TechnicalContact": "technical-contact-tag" +// } +// } + +// Example (Bicep) +// ----------------------------- +// { +// 'ClientOrganization': 'client-organization-tag' +// 'CostCenter': 'cost-center-tag' +// 'DataSensitivity': 'data-sensitivity-tag' +// 'ProjectContact': 'project-contact-tag' +// 'ProjectName': 'project-name-tag' +// 'TechnicalContact': 'technical-contact-tag' +// } +@description('A set of key/value pairs of tags assigned to the resource group and resources.') +param resourceTags object + +// Configure Tags +resource setTagISSO 'Microsoft.Resources/tags@2020-10-01' = { + name: 'default' + scope: subscription() + properties: { + tags: subscriptionTags + } +} + +// Configure Security Center +module asc '../azresources/security-center/asc.bicep' = { + name: 'configure-security-center' + scope: subscription() + params: { + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + securityContactEmail: securityCenter.email + securityContactPhone: securityCenter.phone + } +} + +// Configure Budget +module budget '../azresources/cost/budget-subscription.bicep' = if (!empty(subscriptionBudget) && subscriptionBudget.createBudget) { + name: 'configure-budget' + scope: subscription() + params: { + budgetName: subscriptionBudget.name + budgetAmount: subscriptionBudget.amount + timeGrain: subscriptionBudget.timeGrain + contactEmails: subscriptionBudget.contactEmails + } +} + +// Create Service Health resource group for managing alerts and action groups +resource rgServiceHealth 'Microsoft.Resources/resourceGroups@2021-04-01' = if (!empty(serviceHealthAlerts)) { + name: (!empty(serviceHealthAlerts)) ? serviceHealthAlerts.resourceGroupName : 'rgServiceHealth' + location: deployment().location + tags: resourceTags +} + +// Create Service Health alerts +module serviceHealth '../azresources/service-health/service-health.bicep' = if (!empty(serviceHealthAlerts)) { + name: 'deploy-service-health' + scope: rgServiceHealth + params: { + incidentTypes: (!empty(serviceHealthAlerts)) ? serviceHealthAlerts.incidentTypes : [] + regions: (!empty(serviceHealthAlerts)) ? serviceHealthAlerts.regions : [] + receivers: (!empty(serviceHealthAlerts)) ? serviceHealthAlerts.receivers : {} + actionGroupName: (!empty(serviceHealthAlerts)) ? serviceHealthAlerts.actionGroupName : '' + actionGroupShortName: (!empty(serviceHealthAlerts)) ? serviceHealthAlerts.actionGroupShortName : '' + alertRuleName: (!empty(serviceHealthAlerts)) ? serviceHealthAlerts.alertRuleName : '' + alertRuleDescription: (!empty(serviceHealthAlerts)) ? serviceHealthAlerts.alertRuleDescription : '' + } +} + +// Role Assignments based on Security Groups +module assignSubscriptionRBAC '../azresources/iam/subscription/role-assignment-to-group.bicep' = [for roleAssignment in subscriptionRoleAssignments: { + name: 'rbac-${roleAssignment.roleDefinitionId}' + scope: subscription() + params: { + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionId) + groupObjectIds: roleAssignment.securityGroupObjectIds + } +}] diff --git a/landingzones/utils/mg-move/move-subscription.bicep b/landingzones/utils/mg-move/move-subscription.bicep new file mode 100644 index 00000000..79aca22e --- /dev/null +++ b/landingzones/utils/mg-move/move-subscription.bicep @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Target management group id.') +param managementGroupId string + +@description('Subscription that is being moved to a new management group.') +param subscriptionId string + +resource move 'Microsoft.Management/managementGroups/subscriptions@2020-05-01' = { + name: '${managementGroupId}/${subscriptionId}' + scope: tenant() +} diff --git a/management-groups/structure.bicep b/management-groups/structure.bicep new file mode 100644 index 00000000..c4492a2c --- /dev/null +++ b/management-groups/structure.bicep @@ -0,0 +1,106 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Top Level Management Group Name') +@minLength(2) +@maxLength(10) +param topLevelManagementGroupName string + +@description('Parent Management Group used to create all management groups, including Top Level Management Group.') +param parentManagementGroupId string + +// Level 1 +resource topLevel 'Microsoft.Management/managementGroups@2020-05-01' = { + name: topLevelManagementGroupName + scope: tenant() + properties: { + details: { + parent: { + id: tenantResourceId('Microsoft.Management/managementGroups', parentManagementGroupId) + } + } + } +} + +// Level 2 +resource platform 'Microsoft.Management/managementGroups@2020-05-01' = { + name: '${topLevel.name}Platform' + scope: tenant() + properties: { + details: { + parent: { + id: topLevel.id + } + } + } +} + +resource landingzones 'Microsoft.Management/managementGroups@2020-05-01' = { + name: '${topLevel.name}LandingZones' + scope: tenant() + properties: { + details: { + parent: { + id: topLevel.id + } + } + } +} + +resource sandbox 'Microsoft.Management/managementGroups@2020-05-01' = { + name: '${topLevel.name}Sandbox' + scope: tenant() + properties: { + details: { + parent: { + id: topLevel.id + } + } + } +} + +// Level 3 - Landing Zones + +resource devtest 'Microsoft.Management/managementGroups@2020-05-01' = { + name: '${landingzones.name}DevTest' + scope: tenant() + properties: { + details: { + parent: { + id: landingzones.id + } + } + } +} + +resource qa 'Microsoft.Management/managementGroups@2020-05-01' = { + name: '${landingzones.name}QA' + scope: tenant() + properties: { + details: { + parent: { + id: landingzones.id + } + } + } +} + +resource prod 'Microsoft.Management/managementGroups@2020-05-01' = { + name: '${landingzones.name}Prod' + scope: tenant() + properties: { + details: { + parent: { + id: landingzones.id + } + } + } +} diff --git a/policy/builtin/assignments/asb.bicep b/policy/builtin/assignments/asb.bicep new file mode 100644 index 00000000..1908fc8d --- /dev/null +++ b/policy/builtin/assignments/asb.bicep @@ -0,0 +1,37 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +var policyId = '1f3afdf9-d0c9-4c3d-847f-89da613e70a8' // Azure Security Benchmark +var assignmentName = 'Azure Security Benchmark' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'asb-${uniqueString('asb-',policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [ + ] + parameters: { + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} diff --git a/policy/builtin/assignments/asb.parameters.json b/policy/builtin/assignments/asb.parameters.json new file mode 100644 index 00000000..d877c248 --- /dev/null +++ b/policy/builtin/assignments/asb.parameters.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + } + } +} \ No newline at end of file diff --git a/policy/builtin/assignments/cis-msft-130.bicep b/policy/builtin/assignments/cis-msft-130.bicep new file mode 100644 index 00000000..4ef7f55a --- /dev/null +++ b/policy/builtin/assignments/cis-msft-130.bicep @@ -0,0 +1,61 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +@description('Log Analytics Workspace Data Retention in days.') +param requiredRetentionDays string + +@description('An array of approved VM extensions.') +param approvedVMExtensions array + +@description('Network Watcher Resource Group Name. Default: NetworkWatcherRG') +param networkWatcherRgName string = 'NetworkWatcherRG' + +@description('Linux Python Version.') +param linuxPythonLatestVersion string + +var policyId = '612b5213-9160-4969-8578-1518bd2a000c' // CIS Microsoft Azure Foundations Benchmark 1.3.0 +var assignmentName = 'CIS Microsoft Azure Foundations Benchmark 1.3.0' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'cis130-${uniqueString('cis-msft-130-',policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [ + ] + parameters: { + requiredRetentionDays: { + value: requiredRetentionDays + } + 'resourceGroupName-b6e2945c-0b7b-40f5-9233-7a5323b5cdc6': { + value: networkWatcherRgName + } + 'approvedExtensions-c0e996f8-39cf-4af9-9f45-83fbde810432': { + value: approvedVMExtensions + } + LinuxPythonLatestVersion: { + value: linuxPythonLatestVersion + } + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} diff --git a/policy/builtin/assignments/cis-msft-130.parameters.json b/policy/builtin/assignments/cis-msft-130.parameters.json new file mode 100644 index 00000000..dd6127ed --- /dev/null +++ b/policy/builtin/assignments/cis-msft-130.parameters.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "requiredRetentionDays": { + "value": "730" + }, + "approvedVMExtensions": { + "value": [ + "AzureDiskEncryption", + "AzureDiskEncryptionForLinux", + "ConfigurationforWindows", + "DependencyAgentWindows", + "DependencyAgentLinux", + "IaaSAntimalware", + "IaaSDiagnostics", + "LinuxDiagnostic", + "MicrosoftMonitoringAgent", + "NetworkWatcherAgentLinux", + "NetworkWatcherAgentWindows", + "OmsAgentForLinux", + "VMSnapshot", + "VMSnapshotLinux", + "WindowsAgent.AzureSecurityCenter" + ] + }, + "linuxPythonLatestVersion": { + "value": "3.9" + } + } +} \ No newline at end of file diff --git a/policy/builtin/assignments/fedramp-moderate.bicep b/policy/builtin/assignments/fedramp-moderate.bicep new file mode 100644 index 00000000..8468e5a5 --- /dev/null +++ b/policy/builtin/assignments/fedramp-moderate.bicep @@ -0,0 +1,54 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +@description('Log Analytics Workspace Data Retention in days.') +param requiredRetentionDays string + +var policyId = 'e95f5a9f-57ad-4d03-bb0b-b1d16db93693' // FedRAMP Moderate +var assignmentName = 'FedRAMP Moderate' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'fedramp-m-${uniqueString('fedramp-moderate-',policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [ + ] + parameters: { + requiredRetentionDays: { + value: requiredRetentionDays + } + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// These role assignments are required to allow Policy Assignment to remediate. +resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'fedramp-moderate-Contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/policy/builtin/assignments/fedramp-moderate.parameters.json b/policy/builtin/assignments/fedramp-moderate.parameters.json new file mode 100644 index 00000000..e69e3d12 --- /dev/null +++ b/policy/builtin/assignments/fedramp-moderate.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "requiredRetentionDays": { + "value": "730" + } + } +} \ No newline at end of file diff --git a/policy/builtin/assignments/hitrust-hipaa.bicep b/policy/builtin/assignments/hitrust-hipaa.bicep new file mode 100644 index 00000000..7c71b271 --- /dev/null +++ b/policy/builtin/assignments/hitrust-hipaa.bicep @@ -0,0 +1,121 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +@description('A semicolon-separated list of the names of the applications that should be installed. e.g. \'Microsoft SQL Server 2014 (64-bit); Microsoft Visual Studio Code\' or \'Microsoft SQL Server 2014*\' (to match any application starting with \'Microsoft SQL Server 2014\')') +param installedApplicationsOnWindowsVM string + +@description('This prefix will be combined with the network security group location to form the created storage account name.') +param deployDiagnosticSettingsforNetworkSecurityGroupsStoragePrefix string + +@description('The resource group that the storage account will be created in. This resource group must already exist.') +param deployDiagnosticSettingsforNetworkSecurityGroupsRgName string + +@description('A semicolon-separated list of certificate thumbprints that should exist under the Trusted Root certificate store (Cert:\\LocalMachine\\Root). e.g. THUMBPRINT1;THUMBPRINT2;THUMBPRINT3') +param certificateThumbprints string + +var policyId = 'a169a624-5599-4385-a696-c8d643089fab' // HITRUST/HIPAA +var assignmentName = 'HITRUST/HIPAA' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'hipaa-${uniqueString('hitrust-hipaa-', policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [] + parameters: { + // A semicolon-separated list of the names of the applications that should be installed. + // e.g. 'Microsoft SQL Server 2014 (64-bit); Microsoft Visual Studio Code' or 'Microsoft SQL Server 2014*' + // (to match any application starting with 'Microsoft SQL Server 2014') + installedApplicationsOnWindowsVM: { + value: installedApplicationsOnWindowsVM + } + + // This prefix will be combined with the network security group location to form the created storage account name. + DeployDiagnosticSettingsforNetworkSecurityGroupsstoragePrefix: { + value: deployDiagnosticSettingsforNetworkSecurityGroupsStoragePrefix + } + + // The resource group that the storage account will be created in. This resource group must already exist. + DeployDiagnosticSettingsforNetworkSecurityGroupsrgName: { + value: deployDiagnosticSettingsforNetworkSecurityGroupsRgName + } + + // A semicolon-separated list of certificate thumbprints that should exist under the Trusted Root certificate store + // (Cert:\LocalMachine\Root). e.g. THUMBPRINT1;THUMBPRINT2;THUMBPRINT3 + CertificateThumbprints: { + value: certificateThumbprints + } + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// These role assignments are required to allow Policy Assignment to remediate. +resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'hitrust-hipaa-contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policySetRoleAssignmentVMContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'hitrust-hipaa-virtual-machine-contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/9980e02c-c2be-4d73-94e8-173b1dc7cf3c' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policySetRoleAssignmentNetworkContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'hitrust-hipaa-network-contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policySetRoleAssignmentMonitoringContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'hitrust-hipaa-monitoring-contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policySetRoleAssignmentStorageAccountContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'hitrust-hipaa-storage-account-contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/17d1049b-9a84-46fb-8f53-869881c3d3ab' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/policy/builtin/assignments/hitrust-hipaa.parameters.json b/policy/builtin/assignments/hitrust-hipaa.parameters.json new file mode 100644 index 00000000..ed17f458 --- /dev/null +++ b/policy/builtin/assignments/hitrust-hipaa.parameters.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "deployDiagnosticSettingsforNetworkSecurityGroupsStoragePrefix": { + "value": "{{var-logging-diagnosticSettingsforNetworkSecurityGroupsStoragePrefix}}" + }, + "deployDiagnosticSettingsforNetworkSecurityGroupsRgName": { + "value": "NetworkWatcherRG" + }, + "installedApplicationsOnWindowsVM": { + "value": "__tbd__implementation_specific__" + }, + "certificateThumbprints": { + "value": "__tbd__implementation_specific__" + } + } +} \ No newline at end of file diff --git a/policy/builtin/assignments/location.bicep b/policy/builtin/assignments/location.bicep new file mode 100644 index 00000000..1ce73bb1 --- /dev/null +++ b/policy/builtin/assignments/location.bicep @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +@description('An array of allowed Azure Regions.') +param allowedLocations array + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) + +resource rgLocationAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'locrg-${uniqueString('rg-location-', policyAssignmentManagementGroupId)}' + properties: { + displayName: 'Restrict to Canada Central and Canada East regions for Resource Groups' + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/e765b5de-1225-4ba3-bd56-1ac6695af988' + scope: scope + notScopes: [] + parameters: { + listOfAllowedLocations: { + value: allowedLocations + } + } + enforcementMode: 'Default' + } + location: deployment().location +} + +resource resourceLocationAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'locr-${uniqueString('resource-location-', policyAssignmentManagementGroupId)}' + properties: { + displayName: 'Restrict to Canada Central and Canada East regions for Resources' + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/e56962a6-4747-49cd-b67b-bf8b01975c4c' + scope: scope + notScopes: [] + parameters: { + listOfAllowedLocations: { + value: allowedLocations + } + } + enforcementMode: 'Default' + } + location: deployment().location +} diff --git a/policy/builtin/assignments/location.parameters.json b/policy/builtin/assignments/location.parameters.json new file mode 100644 index 00000000..9b81ad3c --- /dev/null +++ b/policy/builtin/assignments/location.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "allowedLocations": { + "value": [ + "canadacentral", + "canadaeast" + ] + } + } +} \ No newline at end of file diff --git a/policy/builtin/assignments/nist80053r4.bicep b/policy/builtin/assignments/nist80053r4.bicep new file mode 100644 index 00000000..25cc7415 --- /dev/null +++ b/policy/builtin/assignments/nist80053r4.bicep @@ -0,0 +1,66 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +@description('Log Analytics Resource Id to integrate Azure Security Center.') +param logAnalyticsWorkspaceId string + +@description('List of members that should be excluded from Windows VM Administrator Group.') +param listOfMembersToExcludeFromWindowsVMAdministratorsGroup string + +@description('List of members that should be included in Windows VM Administrator Group.') +param listOfMembersToIncludeInWindowsVMAdministratorsGroup string + +var policyId = 'cf25b9c1-bd23-4eb6-bd2c-f4f3ac644a5f' // NIST SP 800-53 R4 +var assignmentName = 'NIST SP 800-53 R4' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'nistr4-${uniqueString('nist-sp-800-53-r4-',policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [ + ] + parameters: { + logAnalyticsWorkspaceIdforVMReporting: { + value: logAnalyticsWorkspaceId + } + listOfMembersToExcludeFromWindowsVMAdministratorsGroup: { + value: listOfMembersToExcludeFromWindowsVMAdministratorsGroup + } + listOfMembersToIncludeInWindowsVMAdministratorsGroup: { + value: listOfMembersToIncludeInWindowsVMAdministratorsGroup + } + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// These role assignments are required to allow Policy Assignment to remediate. +resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'nist-sp-800-53-r4-contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/policy/builtin/assignments/nist80053r4.parameters.json b/policy/builtin/assignments/nist80053r4.parameters.json new file mode 100644 index 00000000..0c56dc3b --- /dev/null +++ b/policy/builtin/assignments/nist80053r4.parameters.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "logAnalyticsWorkspaceId": { + "value": "{{var-logging-logAnalyticsWorkspaceId}}" + }, + "listOfMembersToExcludeFromWindowsVMAdministratorsGroup": { + "value": "__tbd__implementation_specific__" + }, + "listOfMembersToIncludeInWindowsVMAdministratorsGroup": { + "value": "__tbd__implementation_specific__" + } + } +} \ No newline at end of file diff --git a/policy/builtin/assignments/nist80053r5.bicep b/policy/builtin/assignments/nist80053r5.bicep new file mode 100644 index 00000000..60e3940e --- /dev/null +++ b/policy/builtin/assignments/nist80053r5.bicep @@ -0,0 +1,54 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +@description('Log Analytics Workspace Data Retention in days.') +param requiredRetentionDays string + +var policyId = '179d1daa-458f-4e47-8086-2a68d0d6c38f' // NIST SP 800-53 R5 +var assignmentName = 'NIST SP 800-53 R5' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'nistr5-${uniqueString('nist-sp-800-53-r5-',policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [ + ] + parameters: { + requiredRetentionDays: { + value: requiredRetentionDays + } + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// These role assignments are required to allow Policy Assignment to remediate. +resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'nist-sp-800-53-r5-contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/policy/builtin/assignments/nist80053r5.parameters.json b/policy/builtin/assignments/nist80053r5.parameters.json new file mode 100644 index 00000000..e69e3d12 --- /dev/null +++ b/policy/builtin/assignments/nist80053r5.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "requiredRetentionDays": { + "value": "730" + } + } +} \ No newline at end of file diff --git a/policy/builtin/assignments/pbmm.bicep b/policy/builtin/assignments/pbmm.bicep new file mode 100644 index 00000000..9c116abb --- /dev/null +++ b/policy/builtin/assignments/pbmm.bicep @@ -0,0 +1,66 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +@description('Log Analytics Resource Id to integrate Azure Security Center.') +param logAnalyticsWorkspaceId string + +@description('List of members that should be excluded from Windows VM Administrator Group.') +param listOfMembersToExcludeFromWindowsVMAdministratorsGroup string + +@description('List of members that should be included in Windows VM Administrator Group.') +param listOfMembersToIncludeInWindowsVMAdministratorsGroup string + +var policyId = '4c4a5f27-de81-430b-b4e5-9cbd50595a87' // Canada Federal PBMM +var assignmentName = 'Canada Federal PBMM' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = resourceId('Microsoft.Authorization/policySetDefinitions', policyId) + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'pbmm-${uniqueString('pbmm-',policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [ + ] + parameters: { + logAnalyticsWorkspaceIdforVMReporting: { + value: logAnalyticsWorkspaceId + } + listOfMembersToExcludeFromWindowsVMAdministratorsGroup: { + value: listOfMembersToExcludeFromWindowsVMAdministratorsGroup + } + listOfMembersToIncludeInWindowsVMAdministratorsGroup: { + value: listOfMembersToIncludeInWindowsVMAdministratorsGroup + } + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// These role assignments are required to allow Policy Assignment to remediate. +resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'pbmm-Contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/policy/builtin/assignments/pbmm.parameters.json b/policy/builtin/assignments/pbmm.parameters.json new file mode 100644 index 00000000..0c56dc3b --- /dev/null +++ b/policy/builtin/assignments/pbmm.parameters.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "logAnalyticsWorkspaceId": { + "value": "{{var-logging-logAnalyticsWorkspaceId}}" + }, + "listOfMembersToExcludeFromWindowsVMAdministratorsGroup": { + "value": "__tbd__implementation_specific__" + }, + "listOfMembersToIncludeInWindowsVMAdministratorsGroup": { + "value": "__tbd__implementation_specific__" + } + } +} \ No newline at end of file diff --git a/policy/custom/assignments/aks.bicep b/policy/custom/assignments/aks.bicep new file mode 100644 index 00000000..0a6383b0 --- /dev/null +++ b/policy/custom/assignments/aks.bicep @@ -0,0 +1,83 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +var policyId = 'custom-aks' +var assignmentName = 'Custom - Azure Kubernetes Service' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${policyId}' + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'aks-${uniqueString(policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [] + parameters: {} + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +resource podSecurityRestrictedStandardsPolicySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'aks-res-${uniqueString(policyAssignmentManagementGroupId)}' + properties: { + displayName: 'Kubernetes cluster pod security restricted standards for Linux-based workloads' + policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/42b8ef37-b724-4e24-bbc8-7a7708edfe00' + scope: scope + notScopes: [] + parameters: {} + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +resource podSecurityBaselineStandardsPolicySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'aks-std-${uniqueString(policyAssignmentManagementGroupId)}' + properties: { + displayName: 'Kubernetes cluster pod security baseline standards for Linux-based workloads' + policyDefinitionId: '/providers/Microsoft.Authorization/policySetDefinitions/a8640138-9b0a-4a28-b8cb-1666c838647d' + scope: scope + notScopes: [] + parameters: {} + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// These role assignments are required to allow Policy Assignment to remediate. +// Contributor role is required to support customer-managed keys for AKS. Permission: Microsoft.Compute/diskEncryptionSets/read +// A custom role can be created to support this scenario as well. +resource policySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'aks', 'Contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/policy/custom/assignments/aks.parameters.json b/policy/custom/assignments/aks.parameters.json new file mode 100644 index 00000000..18ad4e68 --- /dev/null +++ b/policy/custom/assignments/aks.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + } + } +} \ No newline at end of file diff --git a/policy/custom/assignments/asc.bicep b/policy/custom/assignments/asc.bicep new file mode 100644 index 00000000..a7580a20 --- /dev/null +++ b/policy/custom/assignments/asc.bicep @@ -0,0 +1,61 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +var policyId = 'custom-enable-azure-defender' +var assignmentName = 'Custom - Azure Defender for Azure Services' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${policyId}' + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'asc-${uniqueString('asc-',policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [ + ] + parameters: { + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// These role assignments are required to allow Policy Assignment to remediate. +resource policySetRoleAssignmentSecurityAdmin 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'asc', 'Security Admin') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policySetRoleAssignmentVirtualMachineContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'asc', 'Virtual Machine Contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/9980e02c-c2be-4d73-94e8-173b1dc7cf3c' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/policy/custom/assignments/asc.parameters.json b/policy/custom/assignments/asc.parameters.json new file mode 100644 index 00000000..18ad4e68 --- /dev/null +++ b/policy/custom/assignments/asc.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + } + } +} \ No newline at end of file diff --git a/policy/custom/assignments/ddos.bicep b/policy/custom/assignments/ddos.bicep new file mode 100644 index 00000000..5010ea2a --- /dev/null +++ b/policy/custom/assignments/ddos.bicep @@ -0,0 +1,56 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +@description('Azure DDOS Standard Plan Resource Id.') +param ddosStandardPlanId string + +var policyId = 'Network-Deploy-DDoS-Standard' +var assignmentName = 'Custom - Enable DDoS Standard on Virtual Networks' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policyDefinitions/${policyId}' + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'ddos-${uniqueString(policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [] + parameters: { + planId: { + value: ddosStandardPlanId + } + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// These role assignments are required to allow Policy Assignment to remediate. +resource policySetRoleAssignmentNetworkContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'ddos-standard', 'Network Contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/policy/custom/assignments/ddos.parameters.json b/policy/custom/assignments/ddos.parameters.json new file mode 100644 index 00000000..a256d08c --- /dev/null +++ b/policy/custom/assignments/ddos.parameters.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "ddosStandardPlanId": { + "value": "{{var-hubnetworking-ddosStandardPlanId}}" + } + } +} \ No newline at end of file diff --git a/policy/custom/assignments/dns-private-endpoints.bicep b/policy/custom/assignments/dns-private-endpoints.bicep new file mode 100644 index 00000000..4af1696e --- /dev/null +++ b/policy/custom/assignments/dns-private-endpoints.bicep @@ -0,0 +1,66 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +@description('Private DNS Zone Subscription Id') +param privateDNSZoneSubscriptionId string + +@description('Private DNS Zone Resource Group Name') +param privateDNSZoneResourceGroupName string + +var policyId = 'custom-central-dns-private-endpoints' +var assignmentName = 'Custom - Central DNS for Private Endpoints' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${policyId}' + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'dns-pe-${uniqueString(policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [ + // exclude the resource group where the private dns zones will be created. This allows for Private DNS Zone creation in this resource group + // but blocked in all other scopes + subscriptionResourceId(privateDNSZoneSubscriptionId, 'Microsoft.Resources/resourceGroups', privateDNSZoneResourceGroupName) + ] + parameters: { + privateDNSZoneSubscriptionId: { + value: privateDNSZoneSubscriptionId + } + privateDNSZoneResourceGroupName: { + value: privateDNSZoneResourceGroupName + } + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// These role assignments are required to allow Policy Assignment to remediate. +resource policySetRoleAssignmentNetworkContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'dns-private-endpoint', 'Network Contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/policy/custom/assignments/dns-private-endpoints.parameters.json b/policy/custom/assignments/dns-private-endpoints.parameters.json new file mode 100644 index 00000000..b5475f37 --- /dev/null +++ b/policy/custom/assignments/dns-private-endpoints.parameters.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "privateDNSZoneSubscriptionId": { + "value": "{{var-hubnetwork-subscriptionId}}" + }, + "privateDNSZoneResourceGroupName": { + "value": "{{var-hubnetwork-rgPrivateDnsZonesName}}" + } + } +} \ No newline at end of file diff --git a/policy/custom/assignments/loganalytics.bicep b/policy/custom/assignments/loganalytics.bicep new file mode 100644 index 00000000..e1e6d129 --- /dev/null +++ b/policy/custom/assignments/loganalytics.bicep @@ -0,0 +1,83 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +@description('Log Analytics Workspace Resource Id') +param logAnalyticsResourceId string + +@description('Log Analytics Workspace Id') +param logAnalyticsWorkspaceId string + +var policyId = 'custom-enable-logging-to-loganalytics' +var assignmentName = 'Custom - Log Analytics for Azure Services' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${policyId}' + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'logging-${uniqueString('law-',policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [ + ] + parameters: { + logAnalytics: { + value: logAnalyticsResourceId + } + logAnalyticsWorkspaceId: { + value: logAnalyticsWorkspaceId + } + } + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// These role assignments are required to allow Policy Assignment to remediate. +resource policySetRoleAssignmentLogAnalyticsContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'loganalytics', 'Log Analytics Contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policySetRoleAssignmentVirtualMachineContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'loganalytics', 'Virtual Machine Contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/9980e02c-c2be-4d73-94e8-173b1dc7cf3c' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} + +resource policySetRoleAssignmentMonitoringContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'loganalytics', 'Monitoring Contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa' + principalId: policySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} diff --git a/policy/custom/assignments/loganalytics.parameters.json b/policy/custom/assignments/loganalytics.parameters.json new file mode 100644 index 00000000..baa53059 --- /dev/null +++ b/policy/custom/assignments/loganalytics.parameters.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "logAnalyticsWorkspaceId": { + "value": "{{var-logging-logAnalyticsWorkspaceId}}" + }, + "logAnalyticsResourceId": { + "value": "{{var-logging-logAnalyticsWorkspaceResourceId}}" + } + } +} \ No newline at end of file diff --git a/policy/custom/assignments/network.bicep b/policy/custom/assignments/network.bicep new file mode 100644 index 00000000..f20ac421 --- /dev/null +++ b/policy/custom/assignments/network.bicep @@ -0,0 +1,38 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +var policyId = 'custom-network' +var assignmentName = 'Custom - Network' + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) +var policyScopedId = '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${policyId}' + +resource policySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'network-${uniqueString(policyAssignmentManagementGroupId)}' + properties: { + displayName: assignmentName + policyDefinitionId: policyScopedId + scope: scope + notScopes: [] + parameters: {} + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} diff --git a/policy/custom/assignments/network.parameters.json b/policy/custom/assignments/network.parameters.json new file mode 100644 index 00000000..18ad4e68 --- /dev/null +++ b/policy/custom/assignments/network.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + } + } +} \ No newline at end of file diff --git a/policy/custom/assignments/tags.bicep b/policy/custom/assignments/tags.bicep new file mode 100644 index 00000000..1e42925a --- /dev/null +++ b/policy/custom/assignments/tags.bicep @@ -0,0 +1,88 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +@description('Management Group scope for the policy assignment.') +param policyAssignmentManagementGroupId string + +var scope = tenantResourceId('Microsoft.Management/managementGroups', policyAssignmentManagementGroupId) + +// Tags Inherited from Resource Groups +var rgInheritedPolicyId = 'custom-tags-inherited-from-resource-group' +var rgInheritedAssignmentName = 'Custom - Tags inherited from resource group if missing' + +resource rgInheritedPolicySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'tags-rg-${uniqueString('tags-from-rg-', policyAssignmentManagementGroupId)}' + properties: { + displayName: rgInheritedAssignmentName + policyDefinitionId: '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${rgInheritedPolicyId}' + scope: scope + notScopes: [] + parameters: {} + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +resource rgPolicySetRoleAssignmentContributor 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(policyAssignmentManagementGroupId, 'RgRemediation', 'Contributor') + scope: managementGroup() + properties: { + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: rgInheritedPolicySetAssignment.identity.principalId + principalType: 'ServicePrincipal' + } +} + +// Required Tags on Resource Group +var rgRequiredPolicyId = 'required-tags-on-resource-group' +var rgRequiredAssignmentName = 'Custom - Required tags on resource group' + +resource rgRequiredPolicySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'tags-rg-${uniqueString('tags-required-', policyAssignmentManagementGroupId)}' + properties: { + displayName: rgRequiredAssignmentName + policyDefinitionId: '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${rgRequiredPolicyId}' + scope: scope + notScopes: [] + parameters: {} + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} + +// Audit for Tags on Resources +var resourcesPolicyId = 'audit-required-tags-on-resources' +var resourcesAssignmentName = 'Custom - Audit for required tags on resources' + +resource resourcesAuditPolicySetAssignment 'Microsoft.Authorization/policyAssignments@2020-03-01' = { + name: 'tags-r-${uniqueString('tags-missing-', policyAssignmentManagementGroupId)}' + properties: { + displayName: resourcesAssignmentName + policyDefinitionId: '/providers/Microsoft.Management/managementGroups/${policyDefinitionManagementGroupId}/providers/Microsoft.Authorization/policySetDefinitions/${resourcesPolicyId}' + scope: scope + notScopes: [] + parameters: {} + enforcementMode: 'Default' + } + identity: { + type: 'SystemAssigned' + } + location: deployment().location +} diff --git a/policy/custom/assignments/tags.parameters.json b/policy/custom/assignments/tags.parameters.json new file mode 100644 index 00000000..18ad4e68 --- /dev/null +++ b/policy/custom/assignments/tags.parameters.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "policyAssignmentManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-ACR/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-ACR/azurepolicy.config.json new file mode 100644 index 00000000..87714c29 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-ACR/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for Azure Container Registry", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-ACR/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-ACR/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-ACR/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-ACR/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-ACR/azurepolicy.rules.json new file mode 100644 index 00000000..6683e874 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-ACR/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "ContainerRegistry", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "ContainerRegistry", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-AKS/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKS/azurepolicy.config.json new file mode 100644 index 00000000..7aee35dc --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKS/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for Azure Kubernetes Service", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-AKS/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKS/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKS/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-AKS/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKS/azurepolicy.rules.json new file mode 100644 index 00000000..f51fcec9 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKS/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "KubernetesService", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "KubernetesService", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-AKV/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKV/azurepolicy.config.json new file mode 100644 index 00000000..6d7a4ea6 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKV/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for Azure Key Vault", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-AKV/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKV/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKV/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-AKV/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKV/azurepolicy.rules.json new file mode 100644 index 00000000..abf2f837 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-AKV/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "KeyVaults", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "KeyVaults", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-ARM/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-ARM/azurepolicy.config.json new file mode 100644 index 00000000..97c6398c --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-ARM/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for Azure Resource Manager", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-ARM/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-ARM/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-ARM/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-ARM/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-ARM/azurepolicy.rules.json new file mode 100644 index 00000000..6a63259b --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-ARM/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "Arm", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "Arm", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-AppService/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-AppService/azurepolicy.config.json new file mode 100644 index 00000000..eace72e6 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-AppService/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for Azure App Services", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-AppService/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-AppService/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-AppService/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-AppService/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-AppService/azurepolicy.rules.json new file mode 100644 index 00000000..72b63044 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-AppService/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "AppServices", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "AppServices", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-DNS/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-DNS/azurepolicy.config.json new file mode 100644 index 00000000..094f0b0c --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-DNS/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for DNS", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-DNS/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-DNS/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-DNS/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-DNS/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-DNS/azurepolicy.rules.json new file mode 100644 index 00000000..04c8a549 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-DNS/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "Dns", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "Dns", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-OSSDB/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-OSSDB/azurepolicy.config.json new file mode 100644 index 00000000..b763536e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-OSSDB/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for Open-source relational databases", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-OSSDB/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-OSSDB/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-OSSDB/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-OSSDB/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-OSSDB/azurepolicy.rules.json new file mode 100644 index 00000000..a5981f3b --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-OSSDB/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "OpenSourceRelationalDatabases", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "OpenSourceRelationalDatabases", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDB/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDB/azurepolicy.config.json new file mode 100644 index 00000000..5a350300 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDB/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for Azure SQL Databases", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDB/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDB/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDB/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDB/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDB/azurepolicy.rules.json new file mode 100644 index 00000000..deb4e9cb --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDB/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "SqlServers", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "SqlServers", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDBVM/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDBVM/azurepolicy.config.json new file mode 100644 index 00000000..4a9eaac5 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDBVM/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for SQL on Virtual Machines", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDBVM/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDBVM/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDBVM/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDBVM/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDBVM/azurepolicy.rules.json new file mode 100644 index 00000000..66eb38b1 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-SQLDBVM/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "SqlServerVirtualMachines", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "SqlServerVirtualMachines", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-Storage/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-Storage/azurepolicy.config.json new file mode 100644 index 00000000..c2e09f0e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-Storage/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for Storage Account", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-Storage/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-Storage/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-Storage/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-Storage/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-Storage/azurepolicy.rules.json new file mode 100644 index 00000000..8cf0d94f --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-Storage/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "StorageAccounts", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "StorageAccounts", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-VM/azurepolicy.config.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-VM/azurepolicy.config.json new file mode 100644 index 00000000..dd01ea8c --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-VM/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Azure Defender for Virtual Machines", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-VM/azurepolicy.parameters.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-VM/azurepolicy.parameters.json new file mode 100644 index 00000000..e26bb99e --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-VM/azurepolicy.parameters.json @@ -0,0 +1,26 @@ +{ + "pricingTier": { + "type": "string", + "metadata": { + "displayName": "Azure Defender pricing tier", + "description": "Azure Defender pricing tier" + }, + "allowedValues": [ + "Standard", + "Free" + ], + "defaultValue": "Standard" + }, + "effect": { + "type": "string", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/ASC-Deploy-Defender-VM/azurepolicy.rules.json b/policy/custom/definitions/policy/ASC-Deploy-Defender-VM/azurepolicy.rules.json new file mode 100644 index 00000000..dabbc361 --- /dev/null +++ b/policy/custom/definitions/policy/ASC-Deploy-Defender-VM/azurepolicy.rules.json @@ -0,0 +1,69 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Security/pricings", + "name": "VirtualMachines", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/fb1c8493-542b-48eb-b624-b4c8fea62acd" + ], + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Security/pricings/pricingTier", + "equals": "[parameters('pricingTier')]" + }, + { + "field": "type", + "equals": "Microsoft.Security/pricings" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "incremental", + "parameters": { + "pricingTier": { + "value": "[parameters('pricingTier')]" + } + }, + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "pricingTier": { + "type": "string", + "metadata": { + "description": "Azure Defender pricing tier" + } + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Security/pricings", + "apiVersion": "2018-06-01", + "name": "VirtualMachines", + "properties": { + "pricingTier": "[parameters('pricingTier')]" + } + } + ], + "outputs": {} + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/Block-AzureBastionHosts/azurepolicy.config.json b/policy/custom/definitions/policy/Block-AzureBastionHosts/azurepolicy.config.json new file mode 100644 index 00000000..9465d112 --- /dev/null +++ b/policy/custom/definitions/policy/Block-AzureBastionHosts/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deny Azure Bastion Hosts resource creation", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/Block-AzureBastionHosts/azurepolicy.parameters.json b/policy/custom/definitions/policy/Block-AzureBastionHosts/azurepolicy.parameters.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/policy/custom/definitions/policy/Block-AzureBastionHosts/azurepolicy.parameters.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/policy/custom/definitions/policy/Block-AzureBastionHosts/azurepolicy.rules.json b/policy/custom/definitions/policy/Block-AzureBastionHosts/azurepolicy.rules.json new file mode 100644 index 00000000..37605437 --- /dev/null +++ b/policy/custom/definitions/policy/Block-AzureBastionHosts/azurepolicy.rules.json @@ -0,0 +1,13 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/bastionHosts" + } + ] + }, + "then": { + "effect": "deny" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/DNS-PE-BlockPrivateDNSZones-PrivateLinks/azurepolicy.config.json b/policy/custom/definitions/policy/DNS-PE-BlockPrivateDNSZones-PrivateLinks/azurepolicy.config.json new file mode 100644 index 00000000..b182bfd4 --- /dev/null +++ b/policy/custom/definitions/policy/DNS-PE-BlockPrivateDNSZones-PrivateLinks/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "DNS - Deny privatelinks Private DNS Zones", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/DNS-PE-BlockPrivateDNSZones-PrivateLinks/azurepolicy.parameters.json b/policy/custom/definitions/policy/DNS-PE-BlockPrivateDNSZones-PrivateLinks/azurepolicy.parameters.json new file mode 100644 index 00000000..9e26dfee --- /dev/null +++ b/policy/custom/definitions/policy/DNS-PE-BlockPrivateDNSZones-PrivateLinks/azurepolicy.parameters.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/policy/custom/definitions/policy/DNS-PE-BlockPrivateDNSZones-PrivateLinks/azurepolicy.rules.json b/policy/custom/definitions/policy/DNS-PE-BlockPrivateDNSZones-PrivateLinks/azurepolicy.rules.json new file mode 100644 index 00000000..12ebef73 --- /dev/null +++ b/policy/custom/definitions/policy/DNS-PE-BlockPrivateDNSZones-PrivateLinks/azurepolicy.rules.json @@ -0,0 +1,17 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/privateDnsZones" + }, + { + "field": "name", + "contains": "privatelink." + } + ] + }, + "then": { + "effect": "Deny" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Logs-Diagnostic-Settings/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Logs-Diagnostic-Settings/azurepolicy.config.json new file mode 100644 index 00000000..9d492ced --- /dev/null +++ b/policy/custom/definitions/policy/LA-Logs-Diagnostic-Settings/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Audit diagnostic setting - Logs", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Logs-Diagnostic-Settings/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Logs-Diagnostic-Settings/azurepolicy.parameters.json new file mode 100644 index 00000000..1a4950ba --- /dev/null +++ b/policy/custom/definitions/policy/LA-Logs-Diagnostic-Settings/azurepolicy.parameters.json @@ -0,0 +1,9 @@ +{ + "listOfResourceTypes": { + "type": "Array", + "metadata": { + "displayName": "Resource Types", + "strongType": "resourceTypes" + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Logs-Diagnostic-Settings/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Logs-Diagnostic-Settings/azurepolicy.rules.json new file mode 100644 index 00000000..65467be4 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Logs-Diagnostic-Settings/azurepolicy.rules.json @@ -0,0 +1,20 @@ +{ + "if": { + "field": "type", + "in": "[parameters('listOfResourceTypes')]" + }, + "then": { + "effect": "AuditIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "true" + } + ] + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Automation-automationAccounts/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Automation-automationAccounts/azurepolicy.config.json new file mode 100644 index 00000000..95fa94bc --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Automation-automationAccounts/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Automation Account to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Automation-automationAccounts/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Automation-automationAccounts/azurepolicy.parameters.json new file mode 100644 index 00000000..cd26717e --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Automation-automationAccounts/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} diff --git a/policy/custom/definitions/policy/LA-Microsoft.Automation-automationAccounts/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Automation-automationAccounts/azurepolicy.rules.json new file mode 100644 index 00000000..a3334587 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Automation-automationAccounts/azurepolicy.rules.json @@ -0,0 +1,131 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Automation/automationAccounts" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Automation/automationAccounts/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "JobLogs", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "JobStreams", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DscNodeStatus", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} diff --git a/policy/custom/definitions/policy/LA-Microsoft.CognitiveServices-accounts-CognitiveServices/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.CognitiveServices-accounts-CognitiveServices/azurepolicy.config.json new file mode 100644 index 00000000..5725b87d --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.CognitiveServices-accounts-CognitiveServices/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Azure Cognitive Services to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.CognitiveServices-accounts-CognitiveServices/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.CognitiveServices-accounts-CognitiveServices/azurepolicy.parameters.json new file mode 100644 index 00000000..5a27b1d8 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.CognitiveServices-accounts-CognitiveServices/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.CognitiveServices-accounts-CognitiveServices/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.CognitiveServices-accounts-CognitiveServices/azurepolicy.rules.json new file mode 100644 index 00000000..3fc4ceab --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.CognitiveServices-accounts-CognitiveServices/azurepolicy.rules.json @@ -0,0 +1,134 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.CognitiveServices/accounts" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + }, + { + "field": "kind", + "equals": "CognitiveServices" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.CognitiveServices/accounts/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "Audit", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "RequestResponse", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "Trace", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.ContainerRegistry-registries/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.ContainerRegistry-registries/azurepolicy.config.json new file mode 100644 index 00000000..9caa5337 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.ContainerRegistry-registries/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Azure Container Registry to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.ContainerRegistry-registries/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.ContainerRegistry-registries/azurepolicy.parameters.json new file mode 100644 index 00000000..5a27b1d8 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.ContainerRegistry-registries/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.ContainerRegistry-registries/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.ContainerRegistry-registries/azurepolicy.rules.json new file mode 100644 index 00000000..bfae3f4f --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.ContainerRegistry-registries/azurepolicy.rules.json @@ -0,0 +1,127 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.ContainerRegistry/registries" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "ContainerRegistryRepositoryEvents", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "ContainerRegistryLoginEvents", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.DataFactory-factories/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.DataFactory-factories/azurepolicy.config.json new file mode 100644 index 00000000..da5aeb1b --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.DataFactory-factories/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Data Factory to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.DataFactory-factories/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.DataFactory-factories/azurepolicy.parameters.json new file mode 100644 index 00000000..c81357aa --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.DataFactory-factories/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.DataFactory-factories/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.DataFactory-factories/azurepolicy.rules.json new file mode 100644 index 00000000..bddd7c98 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.DataFactory-factories/azurepolicy.rules.json @@ -0,0 +1,163 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.DataFactory/factories" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.DataFactory/factories/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "ActivityRuns", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "PipelineRuns", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "TriggerRuns", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SandboxPipelineRuns", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SandboxActivityRuns", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SSISPackageEventMessages", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SSISPackageExecutableStatistics", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SSISPackageEventMessageContext", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SSISPackageExecutionComponentPhases", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SSISPackageExecutionDataStatistics", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SSISIntegrationRuntimeLogs", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Databricks-workspaces/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Databricks-workspaces/azurepolicy.config.json new file mode 100644 index 00000000..449350c2 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Databricks-workspaces/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Databricks to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Databricks-workspaces/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Databricks-workspaces/azurepolicy.parameters.json new file mode 100644 index 00000000..5da8290f --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Databricks-workspaces/azurepolicy.parameters.json @@ -0,0 +1,50 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } + } + \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Databricks-workspaces/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Databricks-workspaces/azurepolicy.rules.json new file mode 100644 index 00000000..c2be8d26 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Databricks-workspaces/azurepolicy.rules.json @@ -0,0 +1,182 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Databricks/workspaces" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Databricks/workspaces/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "logs": [ + { + "category": "dbfs", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "clusters", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "accounts", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "jobs", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "notebook", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "ssh", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "workspace", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "secrets", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "sqlPermissions", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "instancePools", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "sqlanalytics", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "genie", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "globalInitScripts", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "iamRole", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "mlflowExperiment", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "featureStore", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "RemoteHistoryService", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "mlflowAcledArtifact", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } + } + \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-R4/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-R4/azurepolicy.config.json new file mode 100644 index 00000000..91c75e6b --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-R4/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for FHIR R4 to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-R4/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-R4/azurepolicy.parameters.json new file mode 100644 index 00000000..c81357aa --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-R4/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-R4/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-R4/azurepolicy.rules.json new file mode 100644 index 00000000..4d004f63 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-R4/azurepolicy.rules.json @@ -0,0 +1,130 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.HealthcareApis/services" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + }, + { + "field": "kind", + "equals": "fhir-R4" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.HealthcareApis/services/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "AuditLogs", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DiagnosticLogs", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-STU3/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-STU3/azurepolicy.config.json new file mode 100644 index 00000000..1cdc850b --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-STU3/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for FHIR STU3 to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-STU3/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-STU3/azurepolicy.parameters.json new file mode 100644 index 00000000..c81357aa --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-STU3/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-STU3/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-STU3/azurepolicy.rules.json new file mode 100644 index 00000000..a777f072 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.HealthcareApis-services-fhir-STU3/azurepolicy.rules.json @@ -0,0 +1,130 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.HealthcareApis/services" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + }, + { + "field": "kind", + "equals": "fhir-Stu3" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.HealthcareApis/services/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "AuditLogs", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DiagnosticLogs", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.MachineLearningServices-workspaces/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.MachineLearningServices-workspaces/azurepolicy.config.json new file mode 100644 index 00000000..b50b436a --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.MachineLearningServices-workspaces/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Azure Machine Learning workspaces to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.MachineLearningServices-workspaces/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.MachineLearningServices-workspaces/azurepolicy.parameters.json new file mode 100644 index 00000000..c81357aa --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.MachineLearningServices-workspaces/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.MachineLearningServices-workspaces/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.MachineLearningServices-workspaces/azurepolicy.rules.json new file mode 100644 index 00000000..75b06a02 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.MachineLearningServices-workspaces/azurepolicy.rules.json @@ -0,0 +1,223 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.MachineLearningServices/workspaces" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.MachineLearningServices/workspaces/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "AmlComputeClusterEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "AmlComputeClusterNodeEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "AmlComputeJobEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "AmlComputeCpuGpuUtilization", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "AmlRunStatusChangedEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "ModelsChangeEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "ModelsReadEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "ModelsActionEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DeploymentReadEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DeploymentEventACI", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DeploymentEventAKS", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "InferencingOperationAKS", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "InferencingOperationACI", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "EnvironmentChangeEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "EnvironmentReadEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DataLabelChangeEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DataLabelReadEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "ComputeInstanceEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DataStoreChangeEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DataStoreReadEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DataSetChangeEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DataSetReadEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "PipelineChangeEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "PipelineReadEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "RunEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "RunReadEvent", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-applicationGateways/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Network-applicationGateways/azurepolicy.config.json new file mode 100644 index 00000000..6fc12e21 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-applicationGateways/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Azure Application Gateway to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-applicationGateways/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Network-applicationGateways/azurepolicy.parameters.json new file mode 100644 index 00000000..5a27b1d8 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-applicationGateways/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-applicationGateways/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Network-applicationGateways/azurepolicy.rules.json new file mode 100644 index 00000000..a1f27f72 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-applicationGateways/azurepolicy.rules.json @@ -0,0 +1,131 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/applicationGateways" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Network/applicationGateways/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "ApplicationGatewayAccessLog", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "ApplicationGatewayPerformanceLog", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "ApplicationGatewayFirewallLog", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-azureFirewalls/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Network-azureFirewalls/azurepolicy.config.json new file mode 100644 index 00000000..056e89f3 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-azureFirewalls/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Azure Firewall to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-azureFirewalls/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Network-azureFirewalls/azurepolicy.parameters.json new file mode 100644 index 00000000..5a27b1d8 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-azureFirewalls/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-azureFirewalls/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Network-azureFirewalls/azurepolicy.rules.json new file mode 100644 index 00000000..19f8b01e --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-azureFirewalls/azurepolicy.rules.json @@ -0,0 +1,130 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/azureFirewalls" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Network/azureFirewalls/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "AzureFirewallApplicationRule", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "AzureFirewallNetworkRule", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "AzureFirewallDnsProxy", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-bastionHosts/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Network-bastionHosts/azurepolicy.config.json new file mode 100644 index 00000000..34f21845 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-bastionHosts/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Bastion Hosts to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-bastionHosts/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Network-bastionHosts/azurepolicy.parameters.json new file mode 100644 index 00000000..cd26717e --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-bastionHosts/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-bastionHosts/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Network-bastionHosts/azurepolicy.rules.json new file mode 100644 index 00000000..f7292028 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-bastionHosts/azurepolicy.rules.json @@ -0,0 +1,123 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/bastionHosts" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Network/bastionHosts/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "BastionAuditLogs", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-networkSecurityGroups/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Network-networkSecurityGroups/azurepolicy.config.json new file mode 100644 index 00000000..3e9c5fbf --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-networkSecurityGroups/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Network Security Groups to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-networkSecurityGroups/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Network-networkSecurityGroups/azurepolicy.parameters.json new file mode 100644 index 00000000..5da8290f --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-networkSecurityGroups/azurepolicy.parameters.json @@ -0,0 +1,50 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } + } + \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-networkSecurityGroups/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Network-networkSecurityGroups/azurepolicy.rules.json new file mode 100644 index 00000000..ea0cecef --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-networkSecurityGroups/azurepolicy.rules.json @@ -0,0 +1,118 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/networkSecurityGroups" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Network/networkSecurityGroups/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "logs": [ + { + "category": "NetworkSecurityGroupEvent", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "NetworkSecurityGroupRuleCounter", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } + } + \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-virtualNetworks/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Network-virtualNetworks/azurepolicy.config.json new file mode 100644 index 00000000..f9728279 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-virtualNetworks/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Virtual Network to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-virtualNetworks/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Network-virtualNetworks/azurepolicy.parameters.json new file mode 100644 index 00000000..cd26717e --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-virtualNetworks/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} diff --git a/policy/custom/definitions/policy/LA-Microsoft.Network-virtualNetworks/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Network-virtualNetworks/azurepolicy.rules.json new file mode 100644 index 00000000..7ae47aa3 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Network-virtualNetworks/azurepolicy.rules.json @@ -0,0 +1,123 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/virtualNetworks" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "VMProtectionAlerts", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} diff --git a/policy/custom/definitions/policy/LA-Microsoft.Resources-Subscriptions/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Resources-Subscriptions/azurepolicy.config.json new file mode 100644 index 00000000..dd9ecc3d --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Resources-Subscriptions/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Subscriptions to Log Analytics Workspaces", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Resources-Subscriptions/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Resources-Subscriptions/azurepolicy.parameters.json new file mode 100644 index 00000000..222882b9 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Resources-Subscriptions/azurepolicy.parameters.json @@ -0,0 +1,37 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "logsEnabled": { + "type": "String", + "defaultValue": "True", + "allowedValues": [ + "True", + "False" + ], + "metadata": { + "displayName": "Enable logs", + "description": "Whether to enable logs stream to the Log Analytics workspace - True or False" + } + }, + "location": { + "type": "string", + "defaultValue": "canadacentral", + "metadata": { + "displayName": "Region", + "description": "Azure region that will be used for policy remediation" + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Resources-Subscriptions/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Resources-Subscriptions/azurepolicy.rules.json new file mode 100644 index 00000000..32039d1e --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Resources-Subscriptions/azurepolicy.rules.json @@ -0,0 +1,119 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "deploymentScope": "Subscription", + "existenceScope": "Subscription", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "true" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "deployment": { + "location": "canadacentral", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "logAnalytics": { + "type": "String" + }, + "logsEnabled": { + "type": "String" + }, + "profileName": { + "type": "string" + }, + "location": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "name": "[parameters('profileName')]", + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "location": "[parameters('location')]", + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "logs": [ + { + "category": "Administrative", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "Security", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "ServiceHealth", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "Alert", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "Recommendation", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "Policy", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "Autoscale", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "ResourceHealth", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": {} + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + }, + "location": { + "value": "[parameters('location')]" + } + } + } + }, + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/749f88d5-cbae-40b8-bcfc-e573ddc772fa", + "/providers/microsoft.authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ] + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Sql-managedInstances/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Sql-managedInstances/azurepolicy.config.json new file mode 100644 index 00000000..b2e035fb --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Sql-managedInstances/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for SQL Managed Instance to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Sql-managedInstances/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Sql-managedInstances/azurepolicy.parameters.json new file mode 100644 index 00000000..5a27b1d8 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Sql-managedInstances/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Sql-managedInstances/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Sql-managedInstances/azurepolicy.rules.json new file mode 100644 index 00000000..7b46b37d --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Sql-managedInstances/azurepolicy.rules.json @@ -0,0 +1,131 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Sql/managedInstances" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Sql/managedInstances/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "ResourceUsageStats", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DevOpsOperationsAudit", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SQLSecurityAuditEvents", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Sql-servers-databases/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Sql-servers-databases/azurepolicy.config.json new file mode 100644 index 00000000..44e708f2 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Sql-servers-databases/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for SQLDB Database to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Sql-servers-databases/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Sql-servers-databases/azurepolicy.parameters.json new file mode 100644 index 00000000..5a27b1d8 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Sql-servers-databases/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Sql-servers-databases/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Sql-servers-databases/azurepolicy.rules.json new file mode 100644 index 00000000..16945e63 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Sql-servers-databases/azurepolicy.rules.json @@ -0,0 +1,163 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Sql/servers/databases" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Sql/servers/databases/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "dependsOn": [], + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "SQLInsights", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "AutomaticTuning", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "QueryStoreRuntimeStatistics", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "QueryStoreWaitStatistics", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "Errors", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DatabaseWaitStatistics", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "Timeouts", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "Blocks", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "Deadlocks", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "DevOpsOperationsAudit", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SQLSecurityAuditEvents", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('fullName')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Synapse-workspaces/azurepolicy.config.json b/policy/custom/definitions/policy/LA-Microsoft.Synapse-workspaces/azurepolicy.config.json new file mode 100644 index 00000000..d5475594 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Synapse-workspaces/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy Diagnostic Settings for Synapse workspace to Log Analytics Workspaces", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Synapse-workspaces/azurepolicy.parameters.json b/policy/custom/definitions/policy/LA-Microsoft.Synapse-workspaces/azurepolicy.parameters.json new file mode 100644 index 00000000..5a27b1d8 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Synapse-workspaces/azurepolicy.parameters.json @@ -0,0 +1,49 @@ +{ + "profileName": { + "type": "String", + "metadata": { + "displayName": "Profile Name for Config", + "description": "The profile name Azure Diagnostics" + } + }, + "logAnalytics": { + "type": "string", + "metadata": { + "displayName": "logAnalytics", + "description": "The target Log Analytics Workspace for Azure Diagnostics", + "strongType": "omsWorkspace" + } + }, + "azureRegions": { + "type": "Array", + "metadata": { + "displayName": "Allowed Locations", + "description": "The list of locations that can be specified when deploying resources", + "strongType": "location" + } + }, + "metricsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Metrics", + "description": "Enable Metrics - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "False" + }, + "logsEnabled": { + "type": "String", + "metadata": { + "displayName": "Enable Logs", + "description": "Enable Logs - True or False" + }, + "allowedValues": [ + "True", + "False" + ], + "defaultValue": "True" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/LA-Microsoft.Synapse-workspaces/azurepolicy.rules.json b/policy/custom/definitions/policy/LA-Microsoft.Synapse-workspaces/azurepolicy.rules.json new file mode 100644 index 00000000..004c0be8 --- /dev/null +++ b/policy/custom/definitions/policy/LA-Microsoft.Synapse-workspaces/azurepolicy.rules.json @@ -0,0 +1,146 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Synapse/workspaces" + }, + { + "field": "location", + "in": "[parameters('AzureRegions')]" + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Insights/diagnosticSettings", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Insights/diagnosticSettings/logs.enabled", + "equals": "[parameters('LogsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/metrics.enabled", + "equals": "[parameters('MetricsEnabled')]" + }, + { + "field": "Microsoft.Insights/diagnosticSettings/workspaceId", + "equals": "[parameters('logAnalytics')]" + } + ] + }, + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/92aaf0da-9dab-42b6-94a3-d43ce8d16293" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "name": { + "type": "string" + }, + "location": { + "type": "string" + }, + "logAnalytics": { + "type": "string" + }, + "metricsEnabled": { + "type": "string" + }, + "logsEnabled": { + "type": "string" + }, + "profileName": { + "type": "string" + } + }, + "variables": {}, + "resources": [ + { + "type": "Microsoft.Synapse/workspaces/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('name'), '/', 'Microsoft.Insights/', parameters('profileName'))]", + "location": "[parameters('location')]", + "properties": { + "workspaceId": "[parameters('logAnalytics')]", + "metrics": [ + { + "category": "AllMetrics", + "enabled": "[parameters('metricsEnabled')]", + "retentionPolicy": { + "enabled": false, + "days": 0 + } + } + ], + "logs": [ + { + "category": "SynapseRbacOperations", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "GatewayApiRequests", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "SQLSecurityAuditEvents", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "BuiltinSqlReqsEnded", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "IntegrationPipelineRuns", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "IntegrationActivityRuns", + "enabled": "[parameters('logsEnabled')]" + }, + { + "category": "IntegrationTriggerRuns", + "enabled": "[parameters('logsEnabled')]" + } + ] + } + } + ], + "outputs": { + "policy": { + "type": "string", + "value": "[concat(parameters('logAnalytics'), 'configured for diagnostic logs for ', ': ', parameters('name'))]" + } + } + }, + "parameters": { + "logAnalytics": { + "value": "[parameters('logAnalytics')]" + }, + "location": { + "value": "[field('location')]" + }, + "name": { + "value": "[field('name')]" + }, + "metricsEnabled": { + "value": "[parameters('metricsEnabled')]" + }, + "logsEnabled": { + "value": "[parameters('logsEnabled')]" + }, + "profileName": { + "value": "[parameters('profileName')]" + } + } + } + } + } + } +} diff --git a/policy/custom/definitions/policy/Network-Audit-Missing-UDR/azurepolicy.config.json b/policy/custom/definitions/policy/Network-Audit-Missing-UDR/azurepolicy.config.json new file mode 100644 index 00000000..56de3e1a --- /dev/null +++ b/policy/custom/definitions/policy/Network-Audit-Missing-UDR/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Audit for missing UDR on subnets", + "mode": "all" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/Network-Audit-Missing-UDR/azurepolicy.parameters.json b/policy/custom/definitions/policy/Network-Audit-Missing-UDR/azurepolicy.parameters.json new file mode 100644 index 00000000..7a73a41b --- /dev/null +++ b/policy/custom/definitions/policy/Network-Audit-Missing-UDR/azurepolicy.parameters.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/Network-Audit-Missing-UDR/azurepolicy.rules.json b/policy/custom/definitions/policy/Network-Audit-Missing-UDR/azurepolicy.rules.json new file mode 100644 index 00000000..2cb5a715 --- /dev/null +++ b/policy/custom/definitions/policy/Network-Audit-Missing-UDR/azurepolicy.rules.json @@ -0,0 +1,45 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/virtualNetworks" + }, + { + "count": { + "field": "Microsoft.Network/virtualNetworks/subnets[*]", + "where": { + "allOf": [ + { + "field": "Microsoft.Network/virtualNetworks/subnets[*].privateEndpointNetworkPolicies", + "notEquals": "Disabled" + }, + { + "field": "Microsoft.Network/virtualNetworks/subnets[*].routeTable", + "exists": false + }, + { + "not": { + "anyOf": [ + { + "field": "Microsoft.Network/virtualNetworks/subnets[*].name", + "equals": "AzureBastionSubnet" + }, + { + "field": "Microsoft.Network/virtualNetworks/subnets[*].name", + "equals": "GatewaySubnet" + } + ] + } + } + ] + } + }, + "greater": 0 + } + ] + }, + "then": { + "effect": "audit" + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/Network-Deploy-DDoS-Standard/azurepolicy.config.json b/policy/custom/definitions/policy/Network-Deploy-DDoS-Standard/azurepolicy.config.json new file mode 100644 index 00000000..7a4b2fcc --- /dev/null +++ b/policy/custom/definitions/policy/Network-Deploy-DDoS-Standard/azurepolicy.config.json @@ -0,0 +1,4 @@ +{ + "name": "Deploy DDoS Standard on Virtual Networks", + "mode": "indexed" +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/Network-Deploy-DDoS-Standard/azurepolicy.parameters.json b/policy/custom/definitions/policy/Network-Deploy-DDoS-Standard/azurepolicy.parameters.json new file mode 100644 index 00000000..324f7daa --- /dev/null +++ b/policy/custom/definitions/policy/Network-Deploy-DDoS-Standard/azurepolicy.parameters.json @@ -0,0 +1,22 @@ +{ + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "auditIfNotExists", + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + }, + "planId": { + "type": "String", + "metadata": { + "displayName": "DDoS Protection Plan Resource ID", + "description": "Full resource ID of the DDoS Protection Plan to be associated to VNets" + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/Network-Deploy-DDoS-Standard/azurepolicy.rules.json b/policy/custom/definitions/policy/Network-Deploy-DDoS-Standard/azurepolicy.rules.json new file mode 100644 index 00000000..c32f7081 --- /dev/null +++ b/policy/custom/definitions/policy/Network-Deploy-DDoS-Standard/azurepolicy.rules.json @@ -0,0 +1,94 @@ +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/virtualNetworks" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Network/virtualNetworks", + "name": "[field('name')]", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Network/virtualNetworks/enableDdosProtection", + "equals": "true" + }, + { + "field": "Microsoft.Network/virtualNetworks/ddosProtectionPlan", + "notEquals": "" + } + ] + }, + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "vnetName": { + "type": "string" + }, + "planId": { + "type": "string" + }, + "vnetLocation": { + "type": "string" + } + }, + "resources": [ + { + "apiVersion": "2017-08-01", + "name": "ApplyDDoS", + "type": "Microsoft.Resources/deployments", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2019-11-01", + "name": "[parameters('vnetName')]", + "location": "[parameters('vnetLocation')]", + "properties": { + "addressSpace": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), '2020-07-01', 'Full').properties.addressSpace]", + "subnets": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), '2020-07-01', 'Full').properties.subnets]", + "enableDdosProtection": true, + "ddosProtectionPlan": { + "id": "[parameters('planId')]" + } + } + } + ] + } + } + } + ] + }, + "parameters": { + "vnetName": { + "value": "[field('name')]" + }, + "planId": { + "value": "[parameters('planId')]" + }, + "vnetLocation": { + "value": "[field('location')]" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policy/readme.md b/policy/custom/definitions/policy/readme.md new file mode 100644 index 00000000..dc6d64a4 --- /dev/null +++ b/policy/custom/definitions/policy/readme.md @@ -0,0 +1,204 @@ +# Custom Policy Definitions + +## Folder Structure + +Each policy is organized into it's own folder. The folder name must not have any spaces nor special characters. Each folder contains 3 files: + +1. azurepolicy.config.json - metadata used by Azure DevOps Pipeline to configure the policy. +2. azurepolicy.parameters.json - contains parameters used in the policy. +3. azurepolicy.rules.json - the policy rule definition. + + +## Defining Policy + +### azurepolicy.config.json + +Example: + +```yml +{ + "name": "My custom policy name", + "mode": "all | indexed" +} +``` + +The `mode` determines which resource types are evaluated for a policy definition. The supported modes are: + +* all: evaluate resource groups, subscriptions, and all resource types +* indexed: only evaluate resource types that support tags and location + +See [Azure Policy Reference](https://docs.microsoft.com/azure/governance/policy/concepts/definition-structure#mode) for more information. + + +### azurepolicy.parameters.json + +See [Azure Parameter Reference](https://docs.microsoft.com/en-us/azure/governance/policy/concepts/definition-structure#parameters) for more information. + +Example: +```yml +{ + "effect": { + "type": "String", + "metadata": { + "displayName": "Effect", + "description": "Enable or disable the execution of the policy" + }, + "allowedValues": [ + "auditIfNotExists", + "DeployIfNotExists", + "Disabled" + ], + "defaultValue": "DeployIfNotExists" + }, + "planId": { + "type": "String", + "metadata": { + "displayName": "DDoS Protection Plan Resource ID", + "description": "Full resource ID of the DDoS Protection Plan to be associated to VNets" + } + } +} +``` + +### azurepolicy.rules.json + +Describes the policy rule that will be evaluated by Azure Policy. The rule can have any effect such as Audit, Deny, DeployIfNotExists. + +Example: + +```yml +{ + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/virtualNetworks" + } + ] + }, + "then": { + "effect": "[parameters('effect')]", + "details": { + "type": "Microsoft.Network/virtualNetworks", + "name": "[field('name')]", + "existenceCondition": { + "allOf": [ + { + "field": "Microsoft.Network/virtualNetworks/enableDdosProtection", + "equals": "true" + }, + { + "field": "Microsoft.Network/virtualNetworks/ddosProtectionPlan", + "notEquals": "" + } + ] + }, + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7" + ], + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "vnetName": { + "type": "string" + }, + "planId": { + "type": "string" + }, + "vnetLocation": { + "type": "string" + } + }, + "resources": [ + { + "apiVersion": "2017-08-01", + "name": "ApplyDDoS", + "type": "Microsoft.Resources/deployments", + "resourceGroup": "[resourceGroup().name]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [ + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2019-11-01", + "name": "[parameters('vnetName')]", + "location": "[parameters('vnetLocation')]", + "properties": { + "addressSpace": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), '2020-07-01', 'Full').properties.addressSpace]", + "subnets": "[reference(resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName')), '2020-07-01', 'Full').properties.subnets]", + "enableDdosProtection": true, + "ddosProtectionPlan": { + "id": "[parameters('planId')]" + } + } + } + ] + } + } + } + ] + }, + "parameters": { + "vnetName": { + "value": "[field('name')]" + }, + "planId": { + "value": "[parameters('planId')]" + }, + "vnetLocation": { + "value": "[field('location')]" + } + } + } + } + } + } +} +``` + + +## Policy Reference for Assignment or Initiative Definition + +By default, all custom policies are deployed to the top level management group. This management group is configured in the environment configuration yaml (i.e. config\variables\CanadaESLZ-main.yml). + +```yml + var-topLevelManagementGroupName: pubsec +``` + +To reference a definition for either a policy assignment or a policy set definition, use the folder name as the reference name. + +For example, to reference the policy `Network-Deploy-DDoS-Standard`: + +Format: + +``` +/providers/Microsoft.Management/managementGroups/TOP_LEVEL_MANAGEMENT_GROUP_ID/providers/Microsoft.Authorization/policyDefinitions/POLICY_ID +``` + +Replace: + +* __POLICY_ID__ = Network-Deploy-DDoS-Standard +* __TOP_LEVEL_MANAGEMENT_GROUP_ID__ = value from `var-topLevelManagementGroupName` + +Example: + +``` +/providers/Microsoft.Management/managementGroups/pubsec/providers/Microsoft.Authorization/policyDefinitions/Network-Deploy-DDoS-Standard +``` + +## Generate Log Analytics Diagnostic Settings Policy Definitions + +The easiest approach is to use the scripts from GitHub ([JimGBritt/AzurePolicy](https://github.com/JimGBritt/AzurePolicy/tree/master/AzureMonitor/Scripts)). The steps are: + +1. Deploy an instance of the Azure Service (i.e. Azure Bastion) +2. Execute `Create-AzDiagPolicy.PS1` - Follow instructions in [GitHub](https://github.com/JimGBritt/AzurePolicy/blob/master/AzureMonitor/Scripts/README.md#overview-of-create-azdiagpolicyps1) +3. Copy the contents of the generated files into `azurepolicy.parameters.json` and `azurepolicy.rules.json`. +4. Create `azurepolicy.config.json` with policy name and mode. +5. Delete the instance created in step 1. diff --git a/policy/custom/definitions/policyset/AKS.bicep b/policy/custom/definitions/policyset/AKS.bicep new file mode 100644 index 00000000..c9efb846 --- /dev/null +++ b/policy/custom/definitions/policyset/AKS.bicep @@ -0,0 +1,33 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +resource aksPolicySet 'Microsoft.Authorization/policySetDefinitions@2020-03-01' = { + name: 'custom-aks' + properties: { + displayName: 'Custom - Azure Kubernetes Service' + policyDefinitionGroups: [ + { + name: 'AKS' + displayName: 'AKS Controls' + } + ] + policyDefinitions: [ + { + groupNames: [ + 'AKS' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/a8eff44f-8c92-45c3-a3fb-9880802d67a7' + policyDefinitionReferenceId: toLower(replace('Deploy Azure Policy Add-on to Azure Kubernetes Service clusters', ' ', '-')) + parameters: {} + } + ] + } +} diff --git a/policy/custom/definitions/policyset/AKS.parameters.json b/policy/custom/definitions/policyset/AKS.parameters.json new file mode 100644 index 00000000..1be78a2d --- /dev/null +++ b/policy/custom/definitions/policyset/AKS.parameters.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": {} +} \ No newline at end of file diff --git a/policy/custom/definitions/policyset/DNSPrivateEndpoints.bicep b/policy/custom/definitions/policyset/DNSPrivateEndpoints.bicep new file mode 100644 index 00000000..633f02d6 --- /dev/null +++ b/policy/custom/definitions/policyset/DNSPrivateEndpoints.bicep @@ -0,0 +1,113 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +/* +Format of the array of objects +[ + { + privateLinkServiceNamespace: 'Microsoft.AzureCosmosDB/databaseAccounts' + zone: 'privatelink.documents.azure.com' + filterLocationLike: "*" // when Private DNS Zone is not scoped to a region + groupId: 'SQL' + } + { + privateLinkServiceNamespace: 'Microsoft.ContainerService/managedCluster' + zone: 'privatelink.canadacentral.azmk8s.io' + filterLocationLike: "canadacentral" // when Private DNS Zone is scoped to a region + groupId: 'management' + } +] +*/ +@description('An array of Private DNS Zones to define as policies.') +param privateDNSZones array + +var policySetName = 'custom-central-dns-private-endpoints' +var policySetDisplayName = 'Custom - Central DNS for Private Endpoints' + +var customPolicyDefinitionMgScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) +var customPolicyDefinition = json(loadTextContent('templates/DNS-PrivateEndpoints/azurepolicy.json')) + +var policySetDefinitionsPrivateDNSZonesDINE = [for (privateDNSZone, i) in privateDNSZones: { + groupNames: [ + 'NETWORK' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', policy[i].name) + policyDefinitionReferenceId: toLower('${privateDNSZone.zone}-${privateDNSZone.groupId}-${uniqueString(privateDNSZone.privateLinkServiceNamespace)}') + parameters: { + privateLinkServiceNamespace: { + value: privateDNSZone.privateLinkServiceNamespace + } + privateDnsZoneId: { + value: '[[concat(\'/subscriptions/\',parameters(\'privateDNSZoneSubscriptionId\'),\'/resourcegroups/\',parameters(\'privateDNSZoneResourceGroupName\'),\'/providers/Microsoft.Network/privateDnsZones/${privateDNSZone.zone}\')]' + } + groupId: { + value: privateDNSZone.groupId + } + filterLocationLike: { + value: privateDNSZone.filterLocationLike + } + } +}] + +var policySetDefinitionsPrivateDNSZonesDeny = [ + { + groupNames: [ + 'NETWORK' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'DNS-PE-BlockPrivateDNSZones-PrivateLinks') + policyDefinitionReferenceId: toLower(replace('DNS - Deny privatelinks Private DNS Zones', ' ', '-')) + parameters: {} + } +] + +// To batch delete policies using Azure CLI, use: +// az policy definition list --management-group pubsec --query "[?contains(id,'dns-pe-')].name" -o tsv | xargs -tn1 -P 5 az policy definition delete --management-group pubsec --name + +resource policy 'Microsoft.Authorization/policyDefinitions@2020-09-01' = [for privateDNSZone in privateDNSZones: { + name: 'dns-pe-${uniqueString(privateDNSZone.privateLinkServiceNamespace, privateDNSZone.zone, privateDNSZone.groupId)}' + properties: { + metadata: { + privateLinkServiceNamespace: privateDNSZone.privateLinkServiceNamespace + zone: privateDNSZone.zone + groupId: privateDNSZone.groupId + filterLocationLike: privateDNSZone.filterLocationLike + } + displayName: '${customPolicyDefinition.properties.displayName} - ${privateDNSZone.zone} - ${privateDNSZone.privateLinkServiceNamespace} - ${privateDNSZone.groupId}' + mode: customPolicyDefinition.properties.mode + policyRule: customPolicyDefinition.properties.policyRule + parameters: customPolicyDefinition.properties.parameters + } +}] + +resource policySet 'Microsoft.Authorization/policySetDefinitions@2020-09-01' = { + name: policySetName + properties: { + displayName: policySetDisplayName + parameters: { + privateDNSZoneSubscriptionId: { + type: 'String' + } + privateDNSZoneResourceGroupName: { + type: 'String' + } + } + policyDefinitionGroups: [ + { + name: 'NETWORK' + displayName: 'DNS for Private Endpoints' + } + ] + policyDefinitions: union(policySetDefinitionsPrivateDNSZonesDINE, policySetDefinitionsPrivateDNSZonesDeny) + } +} diff --git a/policy/custom/definitions/policyset/DNSPrivateEndpoints.parameters.json b/policy/custom/definitions/policyset/DNSPrivateEndpoints.parameters.json new file mode 100644 index 00000000..05418614 --- /dev/null +++ b/policy/custom/definitions/policyset/DNSPrivateEndpoints.parameters.json @@ -0,0 +1,301 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "privateDNSZones": { + "value": [ + { + "privateLinkServiceNamespace": "Microsoft.Automation/automationAccounts", + "zone": "privatelink.azure-automation.net", + "filterLocationLike": "*", + "groupId": "Webhook" + }, + { + "privateLinkServiceNamespace": "Microsoft.Automation/automationAccounts", + "zone": "privatelink.azure-automation.net", + "filterLocationLike": "*", + "groupId": "DSCAndHybridWorker" + }, + { + "privateLinkServiceNamespace": "Microsoft.Sql/servers", + "zone": "privatelink.database.windows.net", + "filterLocationLike": "*", + "groupId": "sqlServer" + }, + { + "privateLinkServiceNamespace": "Microsoft.Synapse/workspaces", + "zone": "privatelink.sql.azuresynapse.net", + "filterLocationLike": "*", + "groupId": "Sql" + }, + { + "privateLinkServiceNamespace": "Microsoft.Synapse/workspaces", + "zone": "privatelink.sql.azuresynapse.net", + "filterLocationLike": "*", + "groupId": "SqlOnDemand" + }, + { + "privateLinkServiceNamespace": "Microsoft.Synapse/workspaces", + "zone": "privatelink.dev.azuresynapse.net", + "filterLocationLike": "*", + "groupId": "Dev" + }, + { + "privateLinkServiceNamespace": "Microsoft.Synapse/privateLinkHubs", + "zone": "privatelink.azuresynapse.net", + "filterLocationLike": "*", + "groupId": "Web" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.blob.core.windows.net", + "filterLocationLike": "*", + "groupId": "blob" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.blob.core.windows.net", + "filterLocationLike": "*", + "groupId": "blob_secondary" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.table.core.windows.net", + "filterLocationLike": "*", + "groupId": "table" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.table.core.windows.net", + "filterLocationLike": "*", + "groupId": "table_secondary" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.queue.core.windows.net", + "filterLocationLike": "*", + "groupId": "queue" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.queue.core.windows.net", + "filterLocationLike": "*", + "groupId": "queue_secondary" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.file.core.windows.net", + "filterLocationLike": "*", + "groupId": "file" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.file.core.windows.net", + "filterLocationLike": "*", + "groupId": "file_secondary" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.web.core.windows.net", + "filterLocationLike": "*", + "groupId": "web" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.web.core.windows.net", + "filterLocationLike": "*", + "groupId": "web_secondary" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.dfs.core.windows.net", + "filterLocationLike": "*", + "groupId": "dfs" + }, + { + "privateLinkServiceNamespace": "Microsoft.Storage/storageAccounts", + "zone": "privatelink.dfs.core.windows.net", + "filterLocationLike": "*", + "groupId": "dfs_secondary" + }, + { + "privateLinkServiceNamespace": "Microsoft.AzureCosmosDB/databaseAccounts", + "zone": "privatelink.documents.azure.com", + "filterLocationLike": "*", + "groupId": "SQL" + }, + { + "privateLinkServiceNamespace": "Microsoft.AzureCosmosDB/databaseAccounts", + "zone": "privatelink.mongo.cosmos.azure.com", + "filterLocationLike": "*", + "groupId": "MongoDB" + }, + { + "privateLinkServiceNamespace": "Microsoft.AzureCosmosDB/databaseAccounts", + "zone": "privatelink.cassandra.cosmos.azure.com", + "filterLocationLike": "*", + "groupId": "Cassandra" + }, + { + "privateLinkServiceNamespace": "Microsoft.AzureCosmosDB/databaseAccounts", + "zone": "privatelink.gremlin.cosmos.azure.com", + "filterLocationLike": "*", + "groupId": "Gremlin" + }, + { + "privateLinkServiceNamespace": "Microsoft.AzureCosmosDB/databaseAccounts", + "zone": "privatelink.table.cosmos.azure.com", + "filterLocationLike": "*", + "groupId": "Table" + }, + { + "privateLinkServiceNamespace": "Microsoft.DBforPostgreSQL/servers", + "zone": "privatelink.postgres.database.azure.com", + "filterLocationLike": "*", + "groupId": "postgresqlServer" + }, + { + "privateLinkServiceNamespace": "Microsoft.DBforMySQL/servers", + "zone": "privatelink.mysql.database.azure.com", + "filterLocationLike": "*", + "groupId": "mysqlServer" + }, + { + "privateLinkServiceNamespace": "Microsoft.DBforMariaDB/servers", + "zone": "privatelink.mariadb.database.azure.com", + "filterLocationLike": "*", + "groupId": "mariadbServer" + }, + { + "privateLinkServiceNamespace": "Microsoft.KeyVault/vaults", + "zone": "privatelink.vaultcore.azure.net", + "filterLocationLike": "*", + "groupId": "vault" + }, + { + "privateLinkServiceNamespace": "Microsoft.ContainerService/managedCluster", + "zone": "privatelink.canadacentral.azmk8s.io", + "filterLocationLike": "canadacentral", + "groupId": "management" + }, + { + "privateLinkServiceNamespace": "Microsoft.ContainerService/managedCluster", + "zone": "privatelink.canadaeast.azmk8s.io", + "filterLocationLike": "canadaeast", + "groupId": "management" + }, + { + "privateLinkServiceNamespace": "Microsoft.Search/searchServices", + "zone": "privatelink.search.windows.net", + "filterLocationLike": "*", + "groupId": "searchService" + }, + { + "privateLinkServiceNamespace": "Microsoft.ContainerRegistry/registries", + "zone": "privatelink.azurecr.io", + "filterLocationLike": "*", + "groupId": "registry" + }, + { + "privateLinkServiceNamespace": "Microsoft.RecoveryServices/vaults", + "zone": "privatelink.cnc.backup.windowsazure.com", + "filterLocationLike": "canadacentral", + "groupId": "AzureBackup" + }, + { + "privateLinkServiceNamespace": "Microsoft.RecoveryServices/vaults", + "zone": "privatelink.cne.backup.windowsazure.com", + "filterLocationLike": "canadaeast", + "groupId": "AzureBackup" + }, + { + "privateLinkServiceNamespace": "Microsoft.RecoveryServices/vaults", + "zone": "privatelink.siterecovery.windowsazure.com", + "filterLocationLike": "*", + "groupId": "AzureSiteRecovery" + }, + { + "privateLinkServiceNamespace": "Microsoft.EventHub/namespaces", + "zone": "privatelink.servicebus.windows.net", + "filterLocationLike": "*", + "groupId": "namespace" + }, + { + "privateLinkServiceNamespace": "Microsoft.ServiceBus/namespaces", + "zone": "privatelink.servicebus.windows.net", + "filterLocationLike": "*", + "groupId": "namespace" + }, + { + "privateLinkServiceNamespace": "Microsoft.EventGrid/topics", + "zone": "privatelink.eventgrid.azure.net", + "filterLocationLike": "*", + "groupId": "topic" + }, + { + "privateLinkServiceNamespace": "Microsoft.EventGrid/domains", + "zone": "privatelink.eventgrid.azure.net", + "filterLocationLike": "*", + "groupId": "domain" + }, + { + "privateLinkServiceNamespace": "Microsoft.Web/sites", + "zone": "privatelink.azurewebsites.net", + "filterLocationLike": "*", + "groupId": "sites" + }, + { + "privateLinkServiceNamespace": "Microsoft.MachineLearningServices/workspaces", + "zone": "privatelink.api.azureml.ms", + "filterLocationLike": "*", + "groupId": "amlworkspace" + }, + { + "privateLinkServiceNamespace": "Microsoft.MachineLearningServices/workspaces", + "zone": "privatelink.notebooks.azure.net", + "filterLocationLike": "*", + "groupId": "amlworkspace" + }, + { + "privateLinkServiceNamespace": "Microsoft.CognitiveServices/accounts", + "zone": "privatelink.cognitiveservices.azure.com", + "filterLocationLike": "*", + "groupId": "account" + }, + { + "privateLinkServiceNamespace": "Microsoft.StorageSync/storageSyncServices", + "zone": "privatelink.afs.azure.net", + "filterLocationLike": "*", + "groupId": "afs" + }, + { + "privateLinkServiceNamespace": "Microsoft.DataFactory/factories", + "zone": "privatelink.datafactory.azure.net", + "filterLocationLike": "*", + "groupId": "dataFactory" + }, + { + "privateLinkServiceNamespace": "Microsoft.Cache/Redis", + "zone": "privatelink.redis.cache.windows.net", + "filterLocationLike": "*", + "groupId": "redisCache" + }, + { + "privateLinkServiceNamespace": "Microsoft.Cache/RedisEnterprise", + "zone": "privatelink.redisenterprise.cache.azure.net", + "filterLocationLike": "*", + "groupId": "redisCache" + }, + { + "privateLinkServiceNamespace": "Microsoft.HealthcareApis/services", + "zone": "privatelink.azurehealthcareapis.com", + "filterLocationLike": "*", + "groupId": "fhir" + } + ] + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policyset/EnableAzureDefender.bicep b/policy/custom/definitions/policyset/EnableAzureDefender.bicep new file mode 100644 index 00000000..12225e05 --- /dev/null +++ b/policy/custom/definitions/policyset/EnableAzureDefender.bicep @@ -0,0 +1,182 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +var customPolicyDefinitionMgScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) + +resource ascAzureDefender 'Microsoft.Authorization/policySetDefinitions@2020-03-01' = { + name: 'custom-enable-azure-defender' + properties: { + displayName: 'Custom - Azure Defender for Azure Services' + policyDefinitionGroups: [ + { + name: 'EXTRA' + displayName: 'Additional Controls' + } + ] + policyDefinitions: [ + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/361c2074-3595-4e5d-8cab-4f21dffc835c' + policyDefinitionReferenceId: toLower(replace('Deploy Advanced Threat Protection on Storage Accounts', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/36d49e87-48c4-4f2e-beed-ba4ed02b71f5' + policyDefinitionReferenceId: toLower(replace('Deploy Threat Detection on SQL servers', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/feedbf84-6b99-488c-acc2-71c829aa5ffc' + policyDefinitionReferenceId: toLower(replace('Vulnerabilities on your SQL databases should be remediated', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/6134c3db-786f-471e-87bc-8f479dc890f6' + policyDefinitionReferenceId: toLower(replace('Deploy Advanced Data Security on SQL servers', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/3c735d8a-a4ba-4a3a-b7cf-db7754cf57f4' + policyDefinitionReferenceId: toLower(replace('Vulnerabilities in security configuration on your virtual machine scale sets should be remediated', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/e1e5fd5d-3e4c-4ce1-8661-7d1873ae6b15' + policyDefinitionReferenceId: toLower(replace('Vulnerabilities in security configuration on your machines should be remediated', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/501541f7-f7e7-4cd6-868c-4190fdad3ac9' + policyDefinitionReferenceId: toLower(replace('vulnerability assessment solution should be enabled on your virtual machines', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/13ce0167-8ca6-4048-8e6b-f996402e3c1b' + policyDefinitionReferenceId: toLower(replace('Configure machines to receive the Qualys vulnerability assessment agent', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-ACR') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for ACR', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-AKS') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for AKS', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-AKV') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for AKV', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-AppService') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for App Service', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-ARM') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for ARM', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-DNS') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for DNS', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-OSSDB') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for Open-source relational databases', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-SQLDB') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for SQLDB', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-SQLDBVM') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for SQL on VM', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-Storage') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for Storage Account', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'EXTRA' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'ASC-Deploy-Defender-VM') + policyDefinitionReferenceId: toLower(replace('Deploy Azure Defender for VM', ' ', '-')) + parameters: {} + } + ] + } +} diff --git a/policy/custom/definitions/policyset/EnableAzureDefender.parameters.json b/policy/custom/definitions/policyset/EnableAzureDefender.parameters.json new file mode 100644 index 00000000..434f8920 --- /dev/null +++ b/policy/custom/definitions/policyset/EnableAzureDefender.parameters.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policyset/EnableLogAnalytics.bicep b/policy/custom/definitions/policyset/EnableLogAnalytics.bicep new file mode 100644 index 00000000..d35d4ee2 --- /dev/null +++ b/policy/custom/definitions/policyset/EnableLogAnalytics.bicep @@ -0,0 +1,755 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +var customPolicyDefinitionMgScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) + +resource policyset_name 'Microsoft.Authorization/policySetDefinitions@2020-03-01' = { + name: 'custom-enable-logging-to-loganalytics' + properties: { + displayName: 'Custom - Log Analytics for Azure Services' + parameters: { + logAnalytics: { + type: 'String' + metadata: { + strongType: 'omsWorkspace' + displayName: 'Log Analytics workspace resource id' + description: 'Select Log Analytics workspace from dropdown list. If this workspace is outside of the scope of the assignment you must manually grant \'Log Analytics Contributor\' permissions (or similar) to the policy assignment\'s principal ID.' + } + } + logAnalyticsWorkspaceId: { + type: 'String' + metadata: { + displayName: 'Log Analytics workspace id' + description: 'Log Analytics workspace id that should be used for VM logs' + } + } + listOfResourceTypesToAuditDiagnosticSettings: { + type: 'Array' + metadata: { + displayName: 'Resource Types' + strongType: 'resourceTypes' + } + defaultValue: [ + 'Microsoft.AnalysisServices/servers' + 'Microsoft.ApiManagement/service' + 'Microsoft.Network/applicationGateways' + 'Microsoft.Automation/automationAccounts' + 'Microsoft.ContainerInstance/containerGroups' + 'Microsoft.ContainerRegistry/registries' + 'Microsoft.ContainerService/managedClusters' + 'Microsoft.Batch/batchAccounts' + 'Microsoft.Cdn/profiles/endpoints' + 'Microsoft.CognitiveServices/accounts' + 'Microsoft.DocumentDB/databaseAccounts' + 'Microsoft.DataFactory/factories' + 'Microsoft.DataLakeAnalytics/accounts' + 'Microsoft.DataLakeStore/accounts' + 'Microsoft.EventGrid/eventSubscriptions' + 'Microsoft.EventGrid/topics' + 'Microsoft.EventHub/namespaces' + 'Microsoft.Network/expressRouteCircuits' + 'Microsoft.Network/azureFirewalls' + 'Microsoft.HDInsight/clusters' + 'Microsoft.Devices/IotHubs' + 'Microsoft.KeyVault/vaults' + //'Microsoft.Network/loadBalancers' # Removed since it doesn't have any logs + 'Microsoft.Logic/integrationAccounts' + 'Microsoft.Logic/workflows' + 'Microsoft.DBforMySQL/servers' + //'Microsoft.Network/networkInterfaces' # Removed since it doesn't have any logs + 'Microsoft.Network/networkSecurityGroups' + 'Microsoft.Network/bastionHosts' + 'Microsoft.DBforPostgreSQL/servers' + 'Microsoft.PowerBIDedicated/capacities' + 'Microsoft.Network/publicIPAddresses' + 'Microsoft.RecoveryServices/vaults' + 'Microsoft.Cache/redis' + 'Microsoft.Relay/namespaces' + 'Microsoft.Search/searchServices' + 'Microsoft.ServiceBus/namespaces' + 'Microsoft.SignalRService/SignalR' + 'Microsoft.Sql/servers/databases' + //'Microsoft.Sql/servers/elasticPools' # Removed since it doesn't have any logs + 'Microsoft.StreamAnalytics/streamingjobs' + 'Microsoft.TimeSeriesInsights/environments' + 'Microsoft.Network/trafficManagerProfiles' + //'Microsoft.Compute/virtualMachines' # Logs are collected through Microsoft Monitoring Agent + //'Microsoft.Compute/virtualMachineScaleSets' Removed since it is not supported + 'Microsoft.Network/virtualNetworks' + 'Microsoft.Network/virtualNetworkGateways' + ] + } + } + policyDefinitionGroups: [ + { + name: 'BUILTIN' + displayName: 'Additional Controls as Builtin Policies' + } + { + name: 'CUSTOM' + displayName: 'Additional Controls as Custom Policies' + } + ] + policyDefinitions: [ + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/5ee9e9ed-0b42-41b7-8c9c-3cfb2fbe2069' + policyDefinitionReferenceId: toLower(replace('Deploy Log Analytics agent for Linux virtual machine scale sets', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/3c1b3629-c8f8-4bf6-862c-037cb9094038' + policyDefinitionReferenceId: toLower(replace('Deploy Log Analytics agent for Windows virtual machine scale sets', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/4da21710-ce6f-4e06-8cdb-5cc4c93ffbee' + policyDefinitionReferenceId: toLower(replace('Deploy Dependency agent for Linux virtual machines', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/1c210e94-a481-4beb-95fa-1571b434fb04' + policyDefinitionReferenceId: toLower(replace('Deploy Dependency agent for Windows virtual machines', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/765266ab-e40e-4c61-bcb2-5a5275d0b7c0' + policyDefinitionReferenceId: toLower(replace('Deploy Dependency agent for Linux virtual machine scale sets', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/3be22e3b-d919-47aa-805e-8985dbeb0ad9' + policyDefinitionReferenceId: toLower(replace('Deploy Dependency agent for Windows virtual machine scale sets', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/6f8f98a4-f108-47cb-8e98-91a0d85cd474' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic settings for storage accounts to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + StorageDelete: { + value: 'True' + } + StorageWrite: { + value: 'True' + } + StorageRead: { + value: 'True' + } + Transaction: { + value: 'True' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/c84e5349-db6d-4769-805e-e14037dab9b5' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Batch Account to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/d56a5a7c-72d7-42bc-8ceb-3baf4c0eae03' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Data Lake Analytics to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/25763a0a-5783-4f14-969e-79d4933eb74b' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Data Lake Storage Gen1 to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/1f6e93e8-6b31-41b1-83f6-36e449a42579' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Event Hub to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/bef3f64c-5290-43b7-85b0-9b254eef4c47' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Key Vault to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/b889a06c-ec72-4b03-910a-cb169ee18721' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Logic Apps to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/08ba64b8-738f-4918-9686-730d2ed79c7d' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Search Services to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/04d53d87-841c-4f23-8a5b-21564380b55e' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Service Bus to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/237e0f7e-b0e8-4ec4-ad46-8c12cb66d673' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Stream Analytics to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/c717fb0c-d118-4c43-ab3d-ece30ac81fb3' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Recovery Services Vault to Log Analytics workspace for resource specific categories', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/6c66c325-74c8-42fd-a286-a74b0e2939d8' + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Azure Kubernetes Service to Log Analytics workspace', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/053d3325-282c-4e5c-b944-24faffd30d77' + policyDefinitionReferenceId: toLower(replace('Deploy Log Analytics agent for Linux VMs', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/0868462e-646c-4fe3-9ced-a733534b6a2c' + policyDefinitionReferenceId: toLower(replace('Deploy Log Analytics agent for Windows VMs', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/32133ab0-ee4b-4b44-98d6-042180979d50' + policyDefinitionReferenceId: toLower(replace('Audit Log Analytics Agent Deployment - VM Image (OS) unlisted', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/5c3bc7b8-a64c-4e08-a9cd-7ff0f31e1138' + policyDefinitionReferenceId: toLower(replace('Audit Log Analytics agent deployment in virtual machine scale sets - VM Image (OS) unlisted', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/f47b5582-33ec-4c5c-87c0-b010a6b2e917' + policyDefinitionReferenceId: toLower(replace('Audit Log Analytics Workspace for VM - Report Mismatch', ' ', '-')) + parameters: { + logAnalyticsWorkspaceId: { + value: '[parameters(\'logAnalyticsWorkspaceId\')]' + } + } + } + { + groupNames: [ + 'BUILTIN' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/752154a7-1e0f-45c6-a880-ac75a7e4f648' + policyDefinitionReferenceId: toLower(replace('Public IP addresses should have resource logs enabled for Azure DDoS Protection Standard', ' ', '-')) + parameters: { + effect: { + value: 'DeployIfNotExists' + } + profileName: { + value: 'setByPolicy' + } + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Logs-Diagnostic-Settings') + policyDefinitionReferenceId: toLower(replace('Audit diagnostic setting', ' ', '-')) + parameters: { + listOfResourceTypes: { + value: '[parameters(\'listOfResourceTypesToAuditDiagnosticSettings\')]' + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Resources-Subscriptions') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Subscriptions to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + location: { + value: 'canadacentral' + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Network-bastionHosts') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Bastion Hosts to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Databricks-workspaces') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Databricks to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Network-virtualNetworks') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Virtual Network to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Network-networkSecurityGroups') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Network Security Groups to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Network-applicationGateways') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Azure Application Gateway to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Network-azureFirewalls') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Azure Firewall to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Automation-automationAccounts') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Automation Account to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Sql-managedInstances') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for SQL Managed Instance to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Sql-servers-databases') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for SQLDB Database to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.DataFactory-factories') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Data Factory to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.MachineLearningServices-workspaces') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Azure Machine Learning workspaces to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.ContainerRegistry-registries') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Azure Container Registry to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.Synapse-workspaces') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Synapse workspace to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.CognitiveServices-accounts-CognitiveServices') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for Azure Cognitive Services to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.HealthcareApis-services-fhir-R4') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for FHIR R4 to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + { + groupNames: [ + 'CUSTOM' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'LA-Microsoft.HealthcareApis-services-fhir-STU3') + policyDefinitionReferenceId: toLower(replace('Deploy Diagnostic Settings for FHIR STU3 to Log Analytics Workspaces', ' ', '-')) + parameters: { + logAnalytics: { + value: '[parameters(\'logAnalytics\')]' + } + profileName: { + value: 'setByPolicy' + } + azureRegions: { + value: [ + 'canadacentral' + 'canadaeast' + ] + } + } + } + ] + } +} diff --git a/policy/custom/definitions/policyset/EnableLogAnalytics.parameters.json b/policy/custom/definitions/policyset/EnableLogAnalytics.parameters.json new file mode 100644 index 00000000..434f8920 --- /dev/null +++ b/policy/custom/definitions/policyset/EnableLogAnalytics.parameters.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policyset/Network.bicep b/policy/custom/definitions/policyset/Network.bicep new file mode 100644 index 00000000..a65db6d2 --- /dev/null +++ b/policy/custom/definitions/policyset/Network.bicep @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +var customPolicyDefinitionMgScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) + +resource networkPolicySet 'Microsoft.Authorization/policySetDefinitions@2020-03-01' = { + name: 'custom-network' + properties: { + displayName: 'Custom - Network' + policyDefinitionGroups: [ + { + name: 'NETWORK' + displayName: 'Networking Controls' + } + ] + policyDefinitions: [ + { + groupNames: [ + 'NETWORK' + ] + policyDefinitionId: '/providers/Microsoft.Authorization/policyDefinitions/83a86a26-fd1f-447c-b59d-e51f44264114' + policyDefinitionReferenceId: toLower(replace('Network interfaces should not have public IPs', ' ', '-')) + parameters: {} + } + { + groupNames: [ + 'NETWORK' + ] + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', 'Network-Audit-Missing-UDR') + policyDefinitionReferenceId: toLower(replace('Audit for missing UDR on subnets', ' ', '-')) + parameters: {} + } + ] + } +} diff --git a/policy/custom/definitions/policyset/Network.parameters.json b/policy/custom/definitions/policyset/Network.parameters.json new file mode 100644 index 00000000..434f8920 --- /dev/null +++ b/policy/custom/definitions/policyset/Network.parameters.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policyset/Tags.bicep b/policy/custom/definitions/policyset/Tags.bicep new file mode 100644 index 00000000..d8dba843 --- /dev/null +++ b/policy/custom/definitions/policyset/Tags.bicep @@ -0,0 +1,113 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group scope for the policy definition.') +param policyDefinitionManagementGroupId string + +@description('Required set of tags that must exist on resource groups and resources.') +param requiredResourceTags array = [] + +var customPolicyDefinitionMgScope = tenantResourceId('Microsoft.Management/managementGroups', policyDefinitionManagementGroupId) + +// Retrieve the templated azure policies as json object +var tagsInheritedFromResourceGroupPolicyTemplate = json(loadTextContent('templates/Tags-Inherit-Tag-From-ResourceGroup/azurepolicy.json')) +var tagsRequiredOnResourceGroupPolicyTemplate = json(loadTextContent('templates/Tags-Require-Tag-ResourceGroup/azurepolicy.json')) +var tagsAuditOnResourcePolicyTemplate = json(loadTextContent('templates/Tags-Audit-Missing-Tag-Resource/azurepolicy.json')) + +// Inherit tags from resource group to resources +resource tagsInheritedFromResourceGroupPolicy 'Microsoft.Authorization/policyDefinitions@2020-09-01' = [for tag in requiredResourceTags: { + name: toLower(replace('tags-inherited-from-rg-${tag}', ' ', '-')) + properties: { + metadata: { + 'tag': tag + } + displayName: '${tagsInheritedFromResourceGroupPolicyTemplate.properties.displayName}: ${tag}' + mode: tagsInheritedFromResourceGroupPolicyTemplate.properties.mode + policyRule: tagsInheritedFromResourceGroupPolicyTemplate.properties.policyRule + parameters: tagsInheritedFromResourceGroupPolicyTemplate.properties.parameters + } +}] + +resource tagsInheritedFromResourceGroupPolicySet 'Microsoft.Authorization/policySetDefinitions@2020-09-01' = { + name: 'custom-tags-inherited-from-resource-group' + properties: { + displayName: 'Custom - Inherited tags from resource group if missing' + policyDefinitions: [for (tag, i) in requiredResourceTags: { + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', tagsInheritedFromResourceGroupPolicy[i].name) + policyDefinitionReferenceId: toLower(replace('Inherit ${tag} tag from the resource group if missing', ' ', '-')) + parameters: { + tagName: { + value: tag + } + } + }] + } +} + +// Required tags on resource groups +resource tagsRequiredOnResourceGroupPolicy 'Microsoft.Authorization/policyDefinitions@2020-09-01' = [for tag in requiredResourceTags: { + name: toLower(replace('Tags-Require-Tag-ResourceGroup-${tag}', ' ', '-')) + properties: { + metadata: { + 'tag': tag + } + displayName: '${tagsRequiredOnResourceGroupPolicyTemplate.properties.displayName}: ${tag}' + mode: tagsRequiredOnResourceGroupPolicyTemplate.properties.mode + policyRule: tagsRequiredOnResourceGroupPolicyTemplate.properties.policyRule + parameters: tagsRequiredOnResourceGroupPolicyTemplate.properties.parameters + } +}] + +resource tagsRequiredOnResourceGroupPolicySet 'Microsoft.Authorization/policySetDefinitions@2020-09-01' = { + name: 'required-tags-on-resource-group' + properties: { + displayName: 'Custom - Tags required on resource group' + policyDefinitions: [for (tag, i) in requiredResourceTags: { + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', tagsRequiredOnResourceGroupPolicy[i].name) + policyDefinitionReferenceId: toLower(replace('Require ${tag} tag on resource group', ' ', '-')) + parameters: { + tagName: { + value: tag + } + } + }] + } +} + +// Audit for tags on resources +resource tagsAuditOnResourcePolicy 'Microsoft.Authorization/policyDefinitions@2020-09-01' = [for tag in requiredResourceTags: { + name: toLower(replace('Tags-Audit-Missing-Tag-Resource-${tag}', ' ', '-')) + properties: { + metadata: { + 'tag': tag + } + displayName: '${tagsAuditOnResourcePolicyTemplate.properties.displayName}: ${tag}' + mode: tagsAuditOnResourcePolicyTemplate.properties.mode + policyRule: tagsAuditOnResourcePolicyTemplate.properties.policyRule + parameters: tagsAuditOnResourcePolicyTemplate.properties.parameters + } +}] + +resource tagsAuditOnResourcePolicySet 'Microsoft.Authorization/policySetDefinitions@2020-09-01' = { + name: 'audit-required-tags-on-resources' + properties: { + displayName: 'Custom - Audit for required tags on resources' + policyDefinitions: [for (tag, i) in requiredResourceTags: { + policyDefinitionId: extensionResourceId(customPolicyDefinitionMgScope, 'Microsoft.Authorization/policyDefinitions', tagsAuditOnResourcePolicy[i].name) + policyDefinitionReferenceId: toLower(replace('Audit for ${tag} tag on resource', ' ', '-')) + parameters: { + tagName: { + value: tag + } + } + }] + } +} diff --git a/policy/custom/definitions/policyset/Tags.parameters.json b/policy/custom/definitions/policyset/Tags.parameters.json new file mode 100644 index 00000000..10aacf9d --- /dev/null +++ b/policy/custom/definitions/policyset/Tags.parameters.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "policyDefinitionManagementGroupId": { + "value": "{{var-topLevelManagementGroupName}}" + }, + "requiredResourceTags": { + "value": [ + "ClientOrganization", + "CostCenter", + "DataSensitivity", + "ProjectContact", + "ProjectName", + "TechnicalContact" + ] + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policyset/templates/DNS-PrivateEndpoints/azurepolicy.json b/policy/custom/definitions/policyset/templates/DNS-PrivateEndpoints/azurepolicy.json new file mode 100644 index 00000000..20df9df1 --- /dev/null +++ b/policy/custom/definitions/policyset/templates/DNS-PrivateEndpoints/azurepolicy.json @@ -0,0 +1,139 @@ +{ + "type": "Microsoft.Authorization/policyDefinitions", + "name": "DNS-PE", + "properties": { + "displayName": "DNS", + "mode": "Indexed", + "parameters": { + "privateLinkServiceNamespace": { + "type": "String", + "metadata": { + "displayName": "privateLinkServiceNamespace" + } + }, + "privateDnsZoneId": { + "type": "String", + "metadata": { + "displayName": "privateDnsZoneId", + "strongType": "Microsoft.Network/privateDnsZones" + } + }, + "groupId": { + "type": "String", + "metadata": { + "displayName": "groupId" + } + }, + "filterLocationLike": { + "type": "String", + "metadata": { + "displayName": "filterLocationLike" + } + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Network/privateEndpoints" + }, + { + "count": { + "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*]", + "where": { + "allOf": [ + { + "field": "location", + "like": "[parameters('filterLocationLike')]" + }, + { + "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].privateLinkServiceId", + "contains": "[parameters('privateLinkServiceNamespace')]" + }, + { + "field": "Microsoft.Network/privateEndpoints/privateLinkServiceConnections[*].groupIds[*]", + "equals": "[parameters('groupId')]" + } + ] + } + }, + "greaterOrEquals": 1 + } + ] + }, + "then": { + "effect": "deployIfNotExists", + "details": { + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "roleDefinitionIds": [ + "/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7" + ], + "existenceCondition": { + "allOf": [ + { + "count": { + "field": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups/privateDnsZoneConfigs[*]", + "where": { + "field": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups/privateDnsZoneConfigs[*].privateDnsZoneId", + "equals": "[parameters('privateDnsZoneId')]" + } + }, + "greaterOrEquals": 1 + } + ] + }, + "deployment": { + "properties": { + "mode": "incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "privateDnsZoneId": { + "type": "string" + }, + "privateEndpointName": { + "type": "string" + }, + "location": { + "type": "string" + } + }, + "resources": [ + { + "name": "[concat(parameters('privateEndpointName'), '/default')]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2020-03-01", + "location": "[parameters('location')]", + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "[replace(parameters('privateDnsZoneId'), '.', '_')]", + "properties": { + "privateDnsZoneId": "[parameters('privateDnsZoneId')]" + } + } + ] + } + } + ] + }, + "parameters": { + "privateDnsZoneId": { + "value": "[parameters('privateDnsZoneId')]" + }, + "privateEndpointName": { + "value": "[field('name')]" + }, + "location": { + "value": "[field('location')]" + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policyset/templates/Tags-Audit-Missing-Tag-Resource/azurepolicy.json b/policy/custom/definitions/policyset/templates/Tags-Audit-Missing-Tag-Resource/azurepolicy.json new file mode 100644 index 00000000..0808d1b7 --- /dev/null +++ b/policy/custom/definitions/policyset/templates/Tags-Audit-Missing-Tag-Resource/azurepolicy.json @@ -0,0 +1,27 @@ +{ + "type": "Microsoft.Authorization/policyDefinitions", + "name": "Tags-Audit-Missing-Tag-Resource", + "properties": { + "displayName": "Audit a tag on resources", + "mode": "Indexed", + "description": "Audit existence of a tag. Does not apply to resource groups.", + "parameters": { + "tagName": { + "type": "String", + "metadata": { + "displayName": "Tag Name", + "description": "Name of the tag, such as 'environment'" + } + } + }, + "policyRule": { + "if": { + "field": "[concat('tags[', parameters('tagName'), ']')]", + "exists": "false" + }, + "then": { + "effect": "audit" + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policyset/templates/Tags-Inherit-Tag-From-ResourceGroup/azurepolicy.json b/policy/custom/definitions/policyset/templates/Tags-Inherit-Tag-From-ResourceGroup/azurepolicy.json new file mode 100644 index 00000000..61c39273 --- /dev/null +++ b/policy/custom/definitions/policyset/templates/Tags-Inherit-Tag-From-ResourceGroup/azurepolicy.json @@ -0,0 +1,47 @@ +{ + "type": "Microsoft.Authorization/policyDefinitions", + "name": "Tags-Inherit-Tag-From-ResourceGroup", + "properties": { + "displayName": "Inherit a tag from the resource group if missing", + "mode": "Indexed", + "description": "Adds the specified tag with its value from the parent resource group when any resource missing this tag is created or updated. Existing resources can be remediated by triggering a remediation task. If the tag exists with a different value it will not be changed.", + "parameters": { + "tagName": { + "type": "String", + "metadata": { + "displayName": "Tag Name", + "description": "Name of the tag, such as 'environment'" + } + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "[concat('tags[', parameters('tagName'), ']')]", + "exists": "false" + }, + { + "value": "[resourceGroup().tags[parameters('tagName')]]", + "notEquals": "" + } + ] + }, + "then": { + "effect": "modify", + "details": { + "roleDefinitionIds": [ + "/providers/microsoft.authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" + ], + "operations": [ + { + "operation": "add", + "field": "[concat('tags[', parameters('tagName'), ']')]", + "value": "[resourceGroup().tags[parameters('tagName')]]" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/policy/custom/definitions/policyset/templates/Tags-Require-Tag-ResourceGroup/azurepolicy.json b/policy/custom/definitions/policyset/templates/Tags-Require-Tag-ResourceGroup/azurepolicy.json new file mode 100644 index 00000000..0299d119 --- /dev/null +++ b/policy/custom/definitions/policyset/templates/Tags-Require-Tag-ResourceGroup/azurepolicy.json @@ -0,0 +1,35 @@ +{ + "name": "Tags-Require-Tag-ResourceGroup", + "properties": { + "displayName": "Require a tag on resource groups", + "mode": "All", + "description": "Enforces existence of a tag on resource groups.", + "parameters": { + "tagName": { + "type": "String", + "metadata": { + "displayName": "Tag Name", + "description": "Name of the tag, such as 'environment'" + } + } + }, + "policyRule": { + "if": { + "allOf": [ + { + "field": "type", + "equals": "Microsoft.Resources/subscriptions/resourceGroups" + }, + { + "field": "[concat('tags[', parameters('tagName'), ']')]", + "exists": "false" + } + ] + }, + "then": { + "effect": "deny" + } + } + }, + "type": "Microsoft.Authorization/policyDefinitions" +} \ No newline at end of file diff --git a/roles/la-vminsights-readonly.bicep b/roles/la-vminsights-readonly.bicep new file mode 100644 index 00000000..3c5267c4 --- /dev/null +++ b/roles/la-vminsights-readonly.bicep @@ -0,0 +1,46 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group Id for assignable scope.') +param assignableMgId string + +var scope = tenantResourceId('Microsoft.Management/managementGroups', assignableMgId) +var roleName = 'Custom - Log Analytics - Read Only for VM Insights' + +resource roleDefn 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' = { + name: guid(roleName) + scope: managementGroup() + properties: { + roleName: roleName + description: '' + permissions: [ + { + actions: [ + 'Microsoft.operationalinsights/workspaces/features/generateMap/read' + 'Microsoft.operationalinsights/workspaces/features/servergroups/members/read' + 'Microsoft.operationalinsights/workspaces/features/clientgroups/memebers/read' + 'Microsoft.operationalinsights/workspaces/features/machineGroups/read' + 'Microsoft.OperationalInsights/workspaces/query/VMBoundPort/read' + 'Microsoft.OperationalInsights/workspaces/query/VMComputer/read' + 'Microsoft.OperationalInsights/workspaces/query/VMConnection/read' + 'Microsoft.OperationalInsights/workspaces/query/VMProcess/read' + 'Microsoft.OperationalInsights/workspaces/query/InsightsMetrics/read' + ] + notActions: [] + dataActions: [] + notDataActions: [] + } + ] + assignableScopes: [ + scope + ] + } +} diff --git a/roles/lz-appowner.bicep b/roles/lz-appowner.bicep new file mode 100644 index 00000000..5823fa8a --- /dev/null +++ b/roles/lz-appowner.bicep @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group Id for assignable scope.') +param assignableMgId string + +var scope = tenantResourceId('Microsoft.Management/managementGroups', assignableMgId) +var roleName = 'Custom - Landing Zone Application Owner' + +resource roleDefn 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' = { + name: guid(roleName) + scope: managementGroup() + properties: { + roleName: roleName + description: '' + permissions: [ + { + actions: [] + notActions: [ + 'Microsoft.Authorization/*/write' + 'Microsoft.Network/publicIPAddresses/write' + 'Microsoft.Network/virtualNetworks/write' + 'Microsoft.KeyVault/locations/deletedVaults/purge/action' + ] + dataActions: [] + notDataActions: [] + } + ] + assignableScopes: [ + scope + ] + } +} diff --git a/roles/lz-subowner.bicep b/roles/lz-subowner.bicep new file mode 100644 index 00000000..17032743 --- /dev/null +++ b/roles/lz-subowner.bicep @@ -0,0 +1,42 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'managementGroup' + +@description('Management Group Id for assignable scope.') +param assignableMgId string + +var scope = tenantResourceId('Microsoft.Management/managementGroups', assignableMgId) +var roleName = 'Custom - Landing Zone Subscription Owner' + +resource roleDefn 'Microsoft.Authorization/roleDefinitions@2018-01-01-preview' = { + name: guid(roleName) + scope: managementGroup() + properties: { + roleName: roleName + description: '' + permissions: [ + { + actions: [] + notActions: [ + 'Microsoft.Authorization/*/write' + 'Microsoft.Network/vpnGateways/*' + 'Microsoft.Network/expressRouteCircuits/*' + 'Microsoft.Network/routeTables/write' + 'Microsoft.Network/vpnSites/*' + ] + dataActions: [] + notDataActions: [] + } + ] + assignableScopes: [ + scope + ] + } +} diff --git a/tests/landingzones/lz-healthcare/deployment-tests/main.bicep b/tests/landingzones/lz-healthcare/deployment-tests/main.bicep new file mode 100644 index 00000000..3a0cde21 --- /dev/null +++ b/tests/landingzones/lz-healthcare/deployment-tests/main.bicep @@ -0,0 +1,134 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +var testScenarios = [ + { + enabled: true + deploySQLDB: true + useCMK: false + } + { + enabled: true + deploySQLDB: true + useCMK: true + } + { + enabled: true + deploySQLDB: false + useCMK: false + } + { + enabled: true + deploySQLDB: false + useCMK: true + } +] + +var testRunnerCleanupAfterDeployment = true + +var tags = { + ClientOrganization: 'tbd' + CostCenter: 'tbd' + DataSensitivity: 'tbd' + ProjectContact: 'tbd' + ProjectName: 'tbd' + TechnicalContact: 'tbd' +} + +resource rgTestHarnessInfraAssets 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: 'test-harness-infra-assets' + location: deployment().location + tags: tags +} + +resource rgTestHarnessSupportingAssets 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: 'test-harness-supporting-assets' + location: deployment().location + tags: tags +} + +module rgTestHarnessManagedIdentity '../../../../azresources/iam/user-assigned-identity.bicep' = { + scope: rgTestHarnessInfraAssets + name: 'deploy-test-harness-managed-identity' + params: { + name: 'test-harness-healthcare-lz-managed-identity' + } +} + +module rgTestHarnessManagedIdentityRBAC '../../../../azresources/iam/subscription/role-assignment-to-sp.bicep' = { + name: 'rbac-ds-${rgTestHarnessSupportingAssets.name}' + params: { + // Owner - this role is cleaned up as part of this deployment + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + resourceSPObjectIds: array(rgTestHarnessManagedIdentity.outputs.identityPrincipalId) + } +} + +module logAnalyticsWorkspace '../../../../azresources/monitor/log-analytics.bicep' = { + scope: rgTestHarnessSupportingAssets + name: 'deploy-test-harness-log-analytics-workspace' + params: { + workspaceName: 'workspace-${uniqueString(rgTestHarnessSupportingAssets.name)}' + automationAccountName: 'automation-${uniqueString(rgTestHarnessSupportingAssets.name)}' + tags: tags + } +} + +@batchSize(1) +module runner 'test-runner.bicep' = [for (scenario, i) in testScenarios: if (scenario.enabled) { + dependsOn: [ + rgTestHarnessManagedIdentityRBAC + ] + + name: 'execute-runner-scenario-${i + 1}' + scope: subscription() + params: { + deploymentScriptIdentityId: rgTestHarnessManagedIdentity.outputs.identityId + deploymentScriptResourceGroupName: rgTestHarnessInfraAssets.name + + hubVnetId: '' + egressVirtualApplianceIp: '10.18.1.4' + hubRFC1918IPRange: '10.18.0.0/22' + hubRFC6598IPRange: '100.60.0.0/16' + + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspace.outputs.workspaceResourceId + + deploySQLDB: scenario.deploySQLDB + useCMK: scenario.useCMK + + testRunnerCleanupAfterDeployment: testRunnerCleanupAfterDeployment + } +}] + +var cleanUpScript = ''' + az account set -s {0} + + echo 'Delete Test Harness Supporting Assets' + az group delete --name {1} --yes + + echo 'Delete Role Assignment for Test Harness Managed Identity' + az role assignment delete --assignee {2} --scope {3} +''' + +module harnessCleanup '../../../../azresources/util/deployment-script.bicep' = { + dependsOn: [ + rgTestHarnessManagedIdentityRBAC + runner + ] + + scope: rgTestHarnessInfraAssets + name: 'cleanup-test-harness' + params: { + deploymentScript: format(cleanUpScript, subscription().subscriptionId, rgTestHarnessSupportingAssets.name, rgTestHarnessManagedIdentity.outputs.identityPrincipalId, subscription().id) + deploymentScriptName: 'cleanup-test-harness' + deploymentScriptIdentityId: rgTestHarnessManagedIdentity.outputs.identityId + } +} diff --git a/tests/landingzones/lz-healthcare/deployment-tests/test-runner.bicep b/tests/landingzones/lz-healthcare/deployment-tests/test-runner.bicep new file mode 100644 index 00000000..d0c8e8cd --- /dev/null +++ b/tests/landingzones/lz-healthcare/deployment-tests/test-runner.bicep @@ -0,0 +1,194 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +param deploymentScriptIdentityId string +param deploymentScriptResourceGroupName string + +param hubVnetId string +param egressVirtualApplianceIp string +param hubRFC1918IPRange string +param hubRFC6598IPRange string + +param logAnalyticsWorkspaceResourceId string + +param deploySQLDB bool +param useCMK bool + +param testRunnerCleanupAfterDeployment bool = true +param testRunnerId string = 'dt${uniqueString(utcNow())}' + +var rgNetworking = '${testRunnerId}Network' +var rgAutomationName = '${testRunnerId}Automation' +var rgStorageName = '${testRunnerId}Storage' +var rgComputeName = '${testRunnerId}Compute' +var rgSecurityName = '${testRunnerId}Security' +var rgMonitorName = '${testRunnerId}Monitor' + +var tagProjectName = '${testRunnerId}ProjectName' + +module test '../../../../landingzones/lz-healthcare/main.bicep' = { + name: 'execute-test-${testRunnerId}' + scope: subscription() + params: { + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + + securityCenter: { + email: 'alzcanadapubsec@microsoft.com' + phone: '555-555-5555' + } + + subscriptionBudget: { + createBudget: false + } + + subscriptionTags: { + ISSO: '${testRunnerId}ISSO' + } + + resourceTags: { + ClientOrganization: '${testRunnerId}Org' + CostCenter: '${testRunnerId}CostCenter' + DataSensitivity: '${testRunnerId}DataSensitivity' + ProjectContact: '${testRunnerId}ProjectContact' + ProjectName: tagProjectName + TechnicalContact: '${testRunnerId}TechContact' + } + + resourceGroups: { + automation: rgAutomationName + compute: rgComputeName + monitor: rgMonitorName + networking: rgNetworking + networkWatcher: 'NetworkWatcherRG' + security: rgSecurityName + storage: rgStorageName + } + + useCMK: useCMK + + automation: { + name: '${testRunnerId}AutomationAccount' + } + + keyVault: { + secretExpiryInDays: 365 + } + + sqldb: { + enabled: deploySQLDB + username: 'azadmin' + } + + synapse: { + username: 'azadmin' + } + + hubNetwork: { + virtualNetworkId: hubVnetId + egressVirtualApplianceIp: egressVirtualApplianceIp + + rfc1918IPRange: hubRFC1918IPRange + rfc6598IPRange: hubRFC6598IPRange + + privateDnsManagedByHub: false + privateDnsManagedByHubSubscriptionId: '' + privateDnsManagedByHubResourceGroupName: '' + } + + network: { + peerToHubVirtualNetwork: true + useRemoteGateway: false + name: 'vnet' + dnsServers: [] + addressPrefixes: [ + '10.1.0.0/16' + ] + subnets: { + oz: { + comments: 'Foundational Elements Zone (OZ)' + name: 'oz' + addressPrefix: '10.1.1.0/25' + } + paz: { + comments: 'Presentation Zone (PAZ)' + name: 'paz' + addressPrefix: '10.1.2.0/25' + } + rz: { + comments: 'Application Zone (RZ)' + name: 'rz' + addressPrefix: '10.1.3.0/25' + } + hrz: { + comments: 'Data Zone (HRZ)' + name: 'hrz' + addressPrefix: '10.1.4.0/25' + } + databricksPublic: { + comments: 'Databricks Public Delegated Subnet' + name: 'databrickspublic' + addressPrefix: '10.1.5.0/25' + } + databricksPrivate: { + comments: 'Databricks Private Delegated Subnet' + name: 'databricksprivate' + addressPrefix: '10.1.6.0/25' + } + privateEndpoints: { + comments: 'Private Endpoints Subnet' + name: 'privateendpoints' + addressPrefix: '10.1.7.0/25' + } + web: { + comments: 'Azure Web App Delegated Subnet' + name: 'webapp' + addressPrefix: '10.1.8.0/25' + } + } + } + } +} + +/* + Clean up script will: + - Delete the private endpoints in the Storage resource group + - Delete all resource groups created by the landing zone +*/ +var cleanUpScript = ''' + + az account set -s {0} + + az network private-endpoint list -g {6} --query "[].id" -o json | jq -r '. | join(" ")' | xargs -t az network private-endpoint delete --ids + + az group delete --name NetworkWatcherRG --yes --no-wait + az group delete --name {1} --yes --no-wait + az group delete --name {2} --yes --no-wait + az group delete --name {3} --yes + az group delete --name {4} --yes + az group delete --name {5} --yes + az group delete --name {6} --yes + +''' + +module testCleanup '../../../../azresources/util/deployment-script.bicep' = if (testRunnerCleanupAfterDeployment) { + dependsOn: [ + test + ] + + scope: resourceGroup(deploymentScriptResourceGroupName) + name: 'cleanup-test-${testRunnerId}' + params: { + deploymentScript: format(cleanUpScript, subscription().subscriptionId, rgAutomationName, rgMonitorName, rgSecurityName, rgComputeName, rgStorageName, rgNetworking) + deploymentScriptName: 'cleanup-test-${testRunnerId}' + deploymentScriptIdentityId: deploymentScriptIdentityId + timeout: 'PT6H' + } +} diff --git a/tests/landingzones/lz-healthcare/e2e-flow-tests/aml-terminal-acr.sh b/tests/landingzones/lz-healthcare/e2e-flow-tests/aml-terminal-acr.sh new file mode 100644 index 00000000..f625e923 --- /dev/null +++ b/tests/landingzones/lz-healthcare/e2e-flow-tests/aml-terminal-acr.sh @@ -0,0 +1,16 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +az upgrade +az login +az acr login --name +docker pull hello-world +docker run hello-world +docker tag hello-world .azurecr.io/hello-world:v1 +docker push .azurecr.io/hello-world:v1 \ No newline at end of file diff --git a/tests/landingzones/lz-healthcare/e2e-flow-tests/aml_key_vault_test.py b/tests/landingzones/lz-healthcare/e2e-flow-tests/aml_key_vault_test.py new file mode 100644 index 00000000..fa336fa4 --- /dev/null +++ b/tests/landingzones/lz-healthcare/e2e-flow-tests/aml_key_vault_test.py @@ -0,0 +1,23 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +from azureml.core import Workspace, Dataset + +subscription_id = '' +resource_group = '' +workspace_name = '' + +workspace = Workspace(subscription_id, resource_group, workspace_name) + +dataset = Dataset.get_by_name(workspace, name='testdataset') +dataset.to_pandas_dataframe() + + +keyvault = workspace.get_default_keyvault() +secret = keyvault.get_secret(name = '') \ No newline at end of file diff --git a/tests/landingzones/lz-healthcare/e2e-flow-tests/databricks_integration_tests.py b/tests/landingzones/lz-healthcare/e2e-flow-tests/databricks_integration_tests.py new file mode 100644 index 00000000..1d76f5ef --- /dev/null +++ b/tests/landingzones/lz-healthcare/e2e-flow-tests/databricks_integration_tests.py @@ -0,0 +1,66 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +# Databricks notebook source +# MAGIC %md #### Test Plan integration step +# MAGIC +# MAGIC Here are the steps for the integration tests: +# MAGIC +# MAGIC * Get secret from Key Vault +# MAGIC * Connect to data from SQL Database / SQL MI +# MAGIC * Connect to data lake (storage account) + +# COMMAND ---------- + +# MAGIC %md Key Vault integration + +# COMMAND ---------- + +dbutils.library.installPyPI('azure-identity') +dbutils.library.installPyPI('azure-keyvault-secrets') + +from azure.keyvault.secrets import SecretClient +from azure.identity import DeviceCodeCredential + +keyVaultName = '' +KVUri = f"https://{keyVaultName}.vault.azure.net" + +credential = DeviceCodeCredential() +client = SecretClient(vault_url=KVUri, credential=credential) + +retrieved_secret = client.get_secret('') + +secret1pw = retrieved_secret.value + +# dbutils.secrets.list(scope = 'test') + +# COMMAND ---------- + +# MAGIC %md Storage Account + +# COMMAND ---------- + +# MAGIC %md ... with credential passthrough + +# COMMAND ---------- + +import pandas as pd +spark_df = spark.createDataFrame(pd.DataFrame({'hello':[1,2]})).write.csv('abfss://test@storageaccount.dfs.core.windows.net/test.csv') + +# COMMAND ---------- + +dbutils.fs.ls("abfss://test@storageaccount.dfs.core.windows.net") + +# COMMAND ---------- + +sparkDf = spark.read.csv('abfss://test@storageaccount.dfs.core.windows.net/test.csv') + +# COMMAND ---------- + +display(sparkDf) \ No newline at end of file diff --git a/tests/landingzones/lz-healthcare/e2e-flow-tests/synapse-spark.py b/tests/landingzones/lz-healthcare/e2e-flow-tests/synapse-spark.py new file mode 100644 index 00000000..35e5846b --- /dev/null +++ b/tests/landingzones/lz-healthcare/e2e-flow-tests/synapse-spark.py @@ -0,0 +1,18 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +# User-identity passthrough for ADLS Gen 2 + + +%%pyspark +df = spark.read.load('abfss://container@saname.dfs.core.windows.net/test.csv', format='csv' +## If header exists uncomment line below +##, header=True +) +display(df.limit(10)) \ No newline at end of file diff --git a/tests/landingzones/lz-healthcare/e2e-flow-tests/synapse-sql-dw.sql b/tests/landingzones/lz-healthcare/e2e-flow-tests/synapse-sql-dw.sql new file mode 100644 index 00000000..e2975f52 --- /dev/null +++ b/tests/landingzones/lz-healthcare/e2e-flow-tests/synapse-sql-dw.sql @@ -0,0 +1,31 @@ +-- ---------------------------------------------------------------------------------- +-- Copyright (c) Microsoft Corporation. +-- Licensed under the MIT license. +-- +-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +-- EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +-- OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +-- ---------------------------------------------------------------------------------- + +CREATE TABLE [dbo].[test] +( + [col1] [nvarchar](200) NOT NULL, + [col2] [nvarchar](255) NULL, + [col3] [nvarchar](500) NULL +) +WITH +( + DISTRIBUTION = HASH([col1]), + CLUSTERED COLUMNSTORE INDEX + --HEAP +); + + +COPY INTO [dbo].[test] FROM 'https://saname.dfs.core.windows.net/synapsecontainer/test.csv' +WITH ( + FIELDTERMINATOR=',', + ROWTERMINATOR='0x0A' +) + + +select * from [dbo].[test] \ No newline at end of file diff --git a/tests/landingzones/lz-healthcare/e2e-flow-tests/synapse-sql-serverless.sql b/tests/landingzones/lz-healthcare/e2e-flow-tests/synapse-sql-serverless.sql new file mode 100644 index 00000000..c1fa771f --- /dev/null +++ b/tests/landingzones/lz-healthcare/e2e-flow-tests/synapse-sql-serverless.sql @@ -0,0 +1,44 @@ +-- ---------------------------------------------------------------------------------- +-- Copyright (c) Microsoft Corporation. +-- Licensed under the MIT license. +-- +-- THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +-- EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +-- OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +-- ---------------------------------------------------------------------------------- + +-- User-identity passthrough for ADLS Gen 2 + +SELECT +    TOP 100 * +FROM +    OPENROWSET( +        BULK 'https://saname.dfs.core.windows.net/container/test.csv', +        FORMAT = 'CSV', +        PARSER_VERSION='2.0' + ) AS [result] + + +-- Managed Identity for ADLS Gen 2 + +CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'Y*********0' + +CREATE DATABASE test + +CREATE DATABASE SCOPED CREDENTIAL WorkspaceIdentity +WITH IDENTITY = 'Managed Identity' + +CREATE EXTERNAL DATA SOURCE mysample +WITH ( LOCATION = 'https://.dfs.core.windows.net//', + CREDENTIAL = WorkspaceIdentity +) + +CREATE EXTERNAL FILE FORMAT [SynapseFormat] WITH ( FORMAT_TYPE = DELIMITEDTEXT) + +CREATE EXTERNAL TABLE dbo.userData ( [col1] varchar(100), [col2] varchar(100), [col3] varchar(100) ) +WITH ( LOCATION = 'test.csv', + DATA_SOURCE = [mysample], + FILE_FORMAT = [SynapseFormat] ); + + +select * FROM dbo.userdata; \ No newline at end of file diff --git a/tests/landingzones/lz-machinelearning/deployment-tests/main.bicep b/tests/landingzones/lz-machinelearning/deployment-tests/main.bicep new file mode 100644 index 00000000..e60b1fc7 --- /dev/null +++ b/tests/landingzones/lz-machinelearning/deployment-tests/main.bicep @@ -0,0 +1,152 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +var testScenarios = [ + { + enabled: true + deploySQLDB: true + deploySQLMI: false + useCMK: false + } + { + enabled: true + deploySQLDB: true + deploySQLMI: false + useCMK: true + } + { + enabled: false + deploySQLDB: false + deploySQLMI: true + useCMK: false + } + { + enabled: false + deploySQLDB: false + deploySQLMI: true + useCMK: true + } + { + enabled: false + deploySQLDB: true + deploySQLMI: true + useCMK: false + } + { + enabled: false + deploySQLDB: true + deploySQLMI: true + useCMK: true + } +] + +var testRunnerCleanupAfterDeployment = true + +var tags = { + ClientOrganization: 'tbd' + CostCenter: 'tbd' + DataSensitivity: 'tbd' + ProjectContact: 'tbd' + ProjectName: 'tbd' + TechnicalContact: 'tbd' +} + +resource rgTestHarnessInfraAssets 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: 'test-harness-infra-assets' + location: deployment().location + tags: tags +} + +resource rgTestHarnessSupportingAssets 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: 'test-harness-supporting-assets' + location: deployment().location + tags: tags +} + +module rgTestHarnessManagedIdentity '../../../../azresources/iam/user-assigned-identity.bicep' = { + scope: rgTestHarnessInfraAssets + name: 'deploy-test-harness-managed-identity' + params: { + name: 'test-harness-machine-learning-lz-managed-identity' + } +} + +module rgTestHarnessManagedIdentityRBAC '../../../../azresources/iam/subscription/role-assignment-to-sp.bicep' = { + name: 'rbac-ds-${rgTestHarnessSupportingAssets.name}' + params: { + // Owner - this role is cleaned up as part of this deployment + roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + resourceSPObjectIds: array(rgTestHarnessManagedIdentity.outputs.identityPrincipalId) + } +} + + +module logAnalyticsWorkspace '../../../../azresources/monitor/log-analytics.bicep' = { + scope: rgTestHarnessSupportingAssets + name: 'deploy-test-harness-log-analytics-workspace' + params: { + workspaceName: 'workspace-${uniqueString(rgTestHarnessSupportingAssets.name)}' + automationAccountName: 'automation-${uniqueString(rgTestHarnessSupportingAssets.name)}' + tags: tags + } +} + +@batchSize(1) +module runner 'test-runner.bicep' = [for (scenario, i) in testScenarios: if (scenario.enabled) { + dependsOn: [ + rgTestHarnessManagedIdentityRBAC + ] + + name: 'execute-runner-scenario-${i + 1}' + scope: subscription() + params: { + deploymentScriptIdentityId: rgTestHarnessManagedIdentity.outputs.identityId + deploymentScriptResourceGroupName: rgTestHarnessInfraAssets.name + + hubVnetId: '' + egressVirtualApplianceIp: '10.18.1.4' + hubRFC1918IPRange: '10.18.0.0/22' + hubRFC6598IPRange: '100.60.0.0/16' + + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspace.outputs.workspaceResourceId + + deploySQLDB: scenario.deploySQLDB + deploySQLMI: scenario.deploySQLMI + useCMK: scenario.useCMK + + testRunnerCleanupAfterDeployment: testRunnerCleanupAfterDeployment + } +}] + +var cleanUpScript = ''' + az account set -s {0} + + echo 'Delete Test Harness Supporting Assets' + az group delete --name {1} --yes + + echo 'Delete Role Assignment for Test Harness Managed Identity' + az role assignment delete --assignee {2} --scope {3} +''' + +module harnessCleanup '../../../../azresources/util/deployment-script.bicep' = { + dependsOn: [ + rgTestHarnessManagedIdentityRBAC + runner + ] + + scope: rgTestHarnessInfraAssets + name: 'cleanup-test-harness' + params: { + deploymentScript: format(cleanUpScript, subscription().subscriptionId, rgTestHarnessSupportingAssets.name, rgTestHarnessManagedIdentity.outputs.identityPrincipalId, subscription().id) + deploymentScriptName: 'cleanup-test-harness' + deploymentScriptIdentityId: rgTestHarnessManagedIdentity.outputs.identityId + } +} diff --git a/tests/landingzones/lz-machinelearning/deployment-tests/test-runner.bicep b/tests/landingzones/lz-machinelearning/deployment-tests/test-runner.bicep new file mode 100644 index 00000000..ac8df0bc --- /dev/null +++ b/tests/landingzones/lz-machinelearning/deployment-tests/test-runner.bicep @@ -0,0 +1,209 @@ +// ---------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +// ---------------------------------------------------------------------------------- + +targetScope = 'subscription' + +param deploymentScriptIdentityId string +param deploymentScriptResourceGroupName string + +param hubVnetId string +param egressVirtualApplianceIp string +param hubRFC1918IPRange string +param hubRFC6598IPRange string + +param logAnalyticsWorkspaceResourceId string + +param deploySQLDB bool +param deploySQLMI bool +param useCMK bool + +param testRunnerCleanupAfterDeployment bool = true +param testRunnerId string = 'dt${uniqueString(utcNow())}' + +var rgNetworking = '${testRunnerId}Network' +var rgAutomationName = '${testRunnerId}Automation' +var rgStorageName = '${testRunnerId}Storage' +var rgComputeName = '${testRunnerId}Compute' +var rgSecurityName = '${testRunnerId}Security' +var rgMonitorName = '${testRunnerId}Monitor' + +var tagProjectName = '${testRunnerId}ProjectName' + +module test '../../../../landingzones/lz-machinelearning/main.bicep' = { + name: 'execute-test-${testRunnerId}' + scope: subscription() + params: { + logAnalyticsWorkspaceResourceId: logAnalyticsWorkspaceResourceId + + securityCenter: { + email: 'alzcanadapubsec@microsoft.com' + phone: '555-555-5555' + } + + subscriptionTags: { + ISSO: '${testRunnerId}ISSO' + } + + resourceTags: { + ClientOrganization: '${testRunnerId}Org' + CostCenter: '${testRunnerId}CostCenter' + DataSensitivity: '${testRunnerId}DataSensitivity' + ProjectContact: '${testRunnerId}ProjectContact' + ProjectName: tagProjectName + TechnicalContact: '${testRunnerId}TechContact' + } + + subscriptionBudget: { + createBudget: false + } + + resourceGroups: { + automation: rgAutomationName + compute: rgComputeName + monitor: rgMonitorName + networking: rgNetworking + networkWatcher: 'NetworkWatcherRG' + security: rgSecurityName + storage: rgStorageName + } + + useCMK: useCMK + + automation: { + name: '${testRunnerId}AutomationAccount' + } + + aks: { + version: '1.21.2' + } + + keyVault: { + secretExpiryInDays: 365 + } + + sqldb: { + enabled: deploySQLDB + username: 'azadmin' + } + + sqlmi: { + enabled: deploySQLMI + username: 'azadmin' + } + + aml: { + enableHbiWorkspace: false + } + + hubNetwork: { + virtualNetworkId: hubVnetId + egressVirtualApplianceIp: egressVirtualApplianceIp + + rfc1918IPRange: hubRFC1918IPRange + rfc6598IPRange: hubRFC6598IPRange + + privateDnsManagedByHub: false + privateDnsManagedByHubSubscriptionId: '' + privateDnsManagedByHubResourceGroupName: '' + } + + network: { + peerToHubVirtualNetwork: true + useRemoteGateway: false + name: 'vnet' + dnsServers: [] + addressPrefixes: [ + '10.2.0.0/16' + ] + subnets: { + oz: { + comments: 'Foundational Elements Zone (OZ)' + name: 'oz' + addressPrefix: '10.2.1.0/25' + } + paz: { + comments: 'Presentation Zone (PAZ)' + name: 'paz' + addressPrefix: '10.2.2.0/25' + } + rz: { + comments: 'Application Zone (RZ)' + name: 'rz' + addressPrefix: '10.2.3.0/25' + } + hrz: { + comments: 'Data Zone (HRZ)' + name: 'hrz' + addressPrefix: '10.2.4.0/25' + } + privateEndpoints: { + comments: 'Private Endpoints Subnet' + name: 'privateendpoints' + addressPrefix: '10.2.5.0/25' + } + sqlmi: { + comments: 'SQL Managed Instances Delegated Subnet' + name: 'sqlmi' + addressPrefix: '10.2.6.0/25' + } + databricksPublic: { + comments: 'Databricks Public Delegated Subnet' + name: 'databrickspublic' + addressPrefix: '10.2.7.0/25' + } + databricksPrivate: { + comments: 'Databricks Private Delegated Subnet' + name: 'databricksprivate' + addressPrefix: '10.2.8.0/25' + } + aks: { + comments: 'AKS Subnet' + name: 'aks' + addressPrefix: '10.2.9.0/25' + } + } + } + } +} + +/* + Clean up script will: + - Delete the private endpoints in the Storage resource group + - Delete all resource groups created by the landing zone +*/ +var cleanUpScript = ''' + + az account set -s {0} + + az network private-endpoint list -g {6} --query "[].id" -o json | jq -r '. | join(" ")' | xargs -t az network private-endpoint delete --ids + + az group delete --name NetworkWatcherRG --yes --no-wait + az group delete --name {1} --yes --no-wait + az group delete --name {2} --yes --no-wait + az group delete --name {3} --yes + az group delete --name {4} --yes + az group delete --name {5} --yes + az group delete --name {6} --yes + +''' + +module testCleanup '../../../../azresources/util/deployment-script.bicep' = if (testRunnerCleanupAfterDeployment) { + dependsOn: [ + test + ] + + scope: resourceGroup(deploymentScriptResourceGroupName) + name: 'cleanup-test-${testRunnerId}' + params: { + deploymentScript: format(cleanUpScript, subscription().subscriptionId, rgAutomationName, rgMonitorName, rgSecurityName, rgComputeName, rgStorageName, rgNetworking) + deploymentScriptName: 'cleanup-test-${testRunnerId}' + deploymentScriptIdentityId: deploymentScriptIdentityId + timeout: 'PT6H' + } +} diff --git a/tests/landingzones/lz-machinelearning/e2e-flow-tests/aml-terminal-acr.sh b/tests/landingzones/lz-machinelearning/e2e-flow-tests/aml-terminal-acr.sh new file mode 100644 index 00000000..f625e923 --- /dev/null +++ b/tests/landingzones/lz-machinelearning/e2e-flow-tests/aml-terminal-acr.sh @@ -0,0 +1,16 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +az upgrade +az login +az acr login --name +docker pull hello-world +docker run hello-world +docker tag hello-world .azurecr.io/hello-world:v1 +docker push .azurecr.io/hello-world:v1 \ No newline at end of file diff --git a/tests/landingzones/lz-machinelearning/e2e-flow-tests/aml_sql_key_vault_test.py b/tests/landingzones/lz-machinelearning/e2e-flow-tests/aml_sql_key_vault_test.py new file mode 100644 index 00000000..5ddcf119 --- /dev/null +++ b/tests/landingzones/lz-machinelearning/e2e-flow-tests/aml_sql_key_vault_test.py @@ -0,0 +1,23 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +from azureml.core import Workspace, Dataset + +subscription_id = '' +resource_group = '' +workspace_name = '' + +workspace = Workspace(subscription_id, resource_group, workspace_name) + +dataset = Dataset.get_by_name(workspace, name='testdataset') +dataset.to_pandas_dataframe() + + +keyvault = workspace.get_default_keyvault() +secret = keyvault.get_secret(name = 'sqldbpassword') \ No newline at end of file diff --git a/tests/landingzones/lz-machinelearning/e2e-flow-tests/azureml-deployment-scripts/echo_score.py b/tests/landingzones/lz-machinelearning/e2e-flow-tests/azureml-deployment-scripts/echo_score.py new file mode 100644 index 00000000..237077a4 --- /dev/null +++ b/tests/landingzones/lz-machinelearning/e2e-flow-tests/azureml-deployment-scripts/echo_score.py @@ -0,0 +1,18 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +import json + +def init(): + print('This is init') + +def run(data): + test = json.loads(data) + print(f'received data {test}') + return(f'test is {test}') diff --git a/tests/landingzones/lz-machinelearning/e2e-flow-tests/databricks_integration_tests.py b/tests/landingzones/lz-machinelearning/e2e-flow-tests/databricks_integration_tests.py new file mode 100644 index 00000000..e66640bd --- /dev/null +++ b/tests/landingzones/lz-machinelearning/e2e-flow-tests/databricks_integration_tests.py @@ -0,0 +1,122 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +# Databricks notebook source +# MAGIC %md #### Test Plan integration step +# MAGIC +# MAGIC Here are the steps for the integration tests: +# MAGIC +# MAGIC * Get secret from Key Vault +# MAGIC * Connect to data from SQL Database / SQL MI +# MAGIC * Connect to data lake (storage account) + +# COMMAND ---------- + +# MAGIC %md Key Vault integration + +# COMMAND ---------- + +dbutils.library.installPyPI('azure-identity') +dbutils.library.installPyPI('azure-keyvault-secrets') + +from azure.keyvault.secrets import SecretClient +from azure.identity import DeviceCodeCredential + +keyVaultName = '' +KVUri = f"https://{keyVaultName}.vault.azure.net" + +credential = DeviceCodeCredential() +client = SecretClient(vault_url=KVUri, credential=credential) + +retrieved_secret = client.get_secret('') + +secret1pw = retrieved_secret.value + +# dbutils.secrets.list(scope = 'test') + +# COMMAND ---------- + +# MAGIC %md Storage Account + +# COMMAND ---------- + +# MAGIC %md ... with credential passthrough + +# COMMAND ---------- + +import pandas as pd +spark_df = spark.createDataFrame(pd.DataFrame({'hello':[1,2]})).write.csv('abfss://test@.dfs.core.windows.net/test.csv') + +# COMMAND ---------- + +dbutils.fs.ls("abfss://test@.dfs.core.windows.net") + +# COMMAND ---------- + +sparkDf = spark.read.csv('abfss://test@.dfs.core.windows.net/test.csv') + +# COMMAND ---------- + +display(sparkDf) + +# COMMAND ---------- + +# MAGIC %md SQL Database + +# COMMAND ---------- + +secret1pw = dbutils.secrets.get(scope = 'test', key = 'sqldbPassword') + +# COMMAND ---------- + +jdbcHostname = "" +jdbcDatabase = "test" +jdbcPort = 1433 +jdbcUrl = "jdbc:sqlserver://{0}:{1};databaseName={2};user={3};password={4}".format(jdbcHostname, jdbcPort, jdbcDatabase, "login", secret1pw) +connectionProperties = { + "driver" : "com.microsoft.sqlserver.jdbc.SQLServerDriver" +} + +# COMMAND ---------- + +sparkDf.write.jdbc(url=jdbcUrl, table="test", properties=connectionProperties) + +# COMMAND ---------- + +display(spark.read.jdbc(url=jdbcUrl, table="test", properties=connectionProperties)) + +# COMMAND ---------- + +# MAGIC %md SQL Managed Instance + +# COMMAND ---------- + +secret2pw = dbutils.secrets.get(scope = 'test', key = 'sqlmiPassword') + +# COMMAND ---------- + +jdbcHostname = "" +jdbcDatabase = "test" +jdbcPort = 1433 +jdbcUrl = "jdbc:sqlserver://{0}:{1};databaseName={2};user={3};password={4}".format(jdbcHostname, jdbcPort, jdbcDatabase, "login", secret1pw) +connectionProperties = { + "driver" : "com.microsoft.sqlserver.jdbc.SQLServerDriver" +} + +# COMMAND ---------- + +sparkDf.write.jdbc(url=jdbcUrl, table="test", properties=connectionProperties) + +# COMMAND ---------- + +display(spark.read.jdbc(url=jdbcUrl, table="test", properties=connectionProperties)) + +# COMMAND ---------- + + diff --git a/tests/landingzones/lz-machinelearning/e2e-flow-tests/hello-world_deploy.py b/tests/landingzones/lz-machinelearning/e2e-flow-tests/hello-world_deploy.py new file mode 100644 index 00000000..c4bbb4d6 --- /dev/null +++ b/tests/landingzones/lz-machinelearning/e2e-flow-tests/hello-world_deploy.py @@ -0,0 +1,91 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +from azureml.core import Workspace, Dataset + +subscription_id = '' +resource_group = '' +workspace_name = '' + +ws = Workspace(subscription_id=subscription_id, + resource_group=resource_group, + workspace_name=workspace_name) + +import urllib.request +from azureml.core.model import Model +# Download model +urllib.request.urlretrieve("https://aka.ms/bidaf-9-model", 'model.onnx') + +# Register model +model = Model.register(ws, model_name='bidaf_onnx', model_path='./model.onnx') + + +from azureml.core import Environment +from azureml.core.model import InferenceConfig + +env = Environment(name='project_environment') +inf_config = InferenceConfig(environment=env, source_directory='./azureml-deployment-scripts', entry_script='./echo_score.py') + + +from azureml.core.webservice import LocalWebservice + +deploy_config = LocalWebservice.deploy_configuration(port=6789) + + +ws.update(image_build_compute = 'test') + + +service = Model.deploy(ws, "myservice", [model], inf_config, deploy_config) +service.wait_for_deployment(show_output=True) +print(service.get_logs()) + + +import requests +import json +uri = service.scoring_uri +requests.get('http://localhost:6789') +headers = {'Content-Type': 'application/json'} +data = {"query": "What color is the fox", "context": "The quick brown fox jumped over the lazy dog."} +data = json.dumps(data) +response = requests.post(uri, data=data, headers=headers) +print(response.json()) + + + +from azureml.core.webservice import AksWebservice, Webservice +from azureml.core.model import Model +from azureml.core.compute import AksCompute + +aks_target = AksCompute(ws,"aks") +# If deploying to a cluster configured for dev/test, ensure that it was created with enough +# cores and memory to handle this deployment configuration. Note that memory is also used by +# things such as dependencies and AML components. +deployment_config = AksWebservice.deploy_configuration(cpu_cores = 1, memory_gb = 1) + + + +service = Model.deploy(ws, "myservice2", [model], inf_config, deployment_config, aks_target) +service.wait_for_deployment(show_output=True) +print(service.get_logs()) + +scoring_uri = service.scoring_uri + +# If the service is authenticated, set the key or token +primary_key, _ = service.get_keys() + +# Set the appropriate headers +headers = {'Content-Type': 'application/json'} +headers['Authorization'] = f'Bearer {primary_key}' + +# Make the request and display the response and logs +data = {"query": "What color is the fox", "context": "The quick brown fox jumped over the lazy dog."} +data = json.dumps(data) +resp = requests.post(scoring_uri, data=data, headers=headers) +print(resp.text) +print(service.get_logs()) diff --git a/tests/utils/build-all-biceps.sh b/tests/utils/build-all-biceps.sh new file mode 100644 index 00000000..ce405a4a --- /dev/null +++ b/tests/utils/build-all-biceps.sh @@ -0,0 +1,16 @@ +# ---------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT license. +# +# THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +# EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. +# ---------------------------------------------------------------------------------- + +#!/bin/sh + +mkdir -p /tmp/bicepbuild + +find $1 -type f -name '*.bicep' | xargs -tn1 az bicep build --outdir /tmp/bicepbuild -f + +rm -rf /tmp/bicepbuild \ No newline at end of file