diff --git a/.github/workflows/deploy_tre.yml b/.github/workflows/deploy_tre.yml index 54689239ae..614a068fc6 100644 --- a/.github/workflows/deploy_tre.yml +++ b/.github/workflows/deploy_tre.yml @@ -32,6 +32,7 @@ jobs: ${{ (github.event_name == 'push' && 'extended or extended_aad') || 'extended or extended_aad or shared_services or airlock' }} environmentName: ${{ github.event.inputs.environment || 'CICD' }} + E2E_TESTS_NUMBER_PROCESSES: 1 secrets: AAD_TENANT_ID: ${{ secrets.AAD_TENANT_ID }} ACR_NAME: ${{ secrets.ACR_NAME }} @@ -60,4 +61,3 @@ jobs: CORE_APP_SERVICE_PLAN_SKU: ${{ secrets.CORE_APP_SERVICE_PLAN_SKU }} WORKSPACE_APP_SERVICE_PLAN_SKU: ${{ secrets.WORKSPACE_APP_SERVICE_PLAN_SKU }} RESOURCE_PROCESSOR_NUMBER_PROCESSES_PER_INSTANCE: ${{ secrets.RESOURCE_PROCESSOR_NUMBER_PROCESSES_PER_INSTANCE }} - E2E_TESTS_NUMBER_PROCESSES: "1" diff --git a/.github/workflows/deploy_tre_branch.yml b/.github/workflows/deploy_tre_branch.yml index 1b74f69846..d28d95f53d 100644 --- a/.github/workflows/deploy_tre_branch.yml +++ b/.github/workflows/deploy_tre_branch.yml @@ -58,6 +58,7 @@ jobs: prHeadSha: ${{ github.sha }} e2eTestsCustomSelector: ${{ github.event.inputs.e2eTestsCustomSelector }} environmentName: ${{ github.event.inputs.environment }} + E2E_TESTS_NUMBER_PROCESSES: 1 secrets: AAD_TENANT_ID: ${{ secrets.AAD_TENANT_ID }} ACR_NAME: ${{ format('tre{0}', needs.prepare-not-main.outputs.refid) }} @@ -87,4 +88,3 @@ jobs: CORE_APP_SERVICE_PLAN_SKU: ${{ secrets.CORE_APP_SERVICE_PLAN_SKU }} WORKSPACE_APP_SERVICE_PLAN_SKU: ${{ secrets.WORKSPACE_APP_SERVICE_PLAN_SKU }} RESOURCE_PROCESSOR_NUMBER_PROCESSES_PER_INSTANCE: ${{ secrets.RESOURCE_PROCESSOR_NUMBER_PROCESSES_PER_INSTANCE }} - E2E_TESTS_NUMBER_PROCESSES: "1" diff --git a/.github/workflows/deploy_tre_reusable.yml b/.github/workflows/deploy_tre_reusable.yml index e569ae14a0..5ed631701d 100644 --- a/.github/workflows/deploy_tre_reusable.yml +++ b/.github/workflows/deploy_tre_reusable.yml @@ -27,6 +27,10 @@ on: # yamllint disable-line rule:truthy description: The name of the Github Action's environment this will deploy into type: string required: true + E2E_TESTS_NUMBER_PROCESSES: + description: "" + type: number + required: false secrets: AAD_TENANT_ID: description: "" @@ -112,9 +116,6 @@ on: # yamllint disable-line rule:truthy RESOURCE_PROCESSOR_NUMBER_PROCESSES_PER_INSTANCE: description: "" required: false - E2E_TESTS_NUMBER_PROCESSES: - description: "" - required: false # This will prevent multiple runs of this entire workflow. # We should NOT cancel in progress runs as that can destabilize the environment. @@ -787,7 +788,7 @@ jobs: TRE_ID: "${{ secrets.TRE_ID }}" IS_API_SECURED: false WORKSPACE_APP_SERVICE_PLAN_SKU: ${{ secrets.WORKSPACE_APP_SERVICE_PLAN_SKU }} - E2E_TESTS_NUMBER_PROCESSES: ${{ secrets.E2E_TESTS_NUMBER_PROCESSES }} + E2E_TESTS_NUMBER_PROCESSES: ${{ inputs.E2E_TESTS_NUMBER_PROCESSES }} - name: Upload Test Results if: always() diff --git a/.github/workflows/pr_comment_bot.yml b/.github/workflows/pr_comment_bot.yml index 9698084c52..d45f22a212 100644 --- a/.github/workflows/pr_comment_bot.yml +++ b/.github/workflows/pr_comment_bot.yml @@ -150,6 +150,7 @@ jobs: (needs.pr_comment.outputs.command == 'run-tests-shared-services' && 'shared_services') || (needs.pr_comment.outputs.command == 'run-tests' && '') }} environmentName: CICD + E2E_TESTS_NUMBER_PROCESSES: 1 secrets: AAD_TENANT_ID: ${{ secrets.AAD_TENANT_ID }} ACR_NAME: ${{ format('tre{0}', needs.pr_comment.outputs.prRefId) }} @@ -176,4 +177,3 @@ jobs: TRE_ID: ${{ format('tre{0}', needs.pr_comment.outputs.prRefId) }} CI_CACHE_ACR_NAME: ${{ secrets.ACR_NAME }} TF_LOG: ${{ secrets.TF_LOG }} - E2E_TESTS_NUMBER_PROCESSES: "1" diff --git a/CHANGELOG.md b/CHANGELOG.md index 61625862bf..372a0b3486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,28 @@ ## 0.9.0 (Unreleased) + **BREAKING CHANGES & MIGRATIONS**: +* Move to Azure **Firewall Policy** [#3107](https://github.com/microsoft/AzureTRE/pull/3107). This is a major version for the firewall shared service and will fail to automatically upgrade. You should follow these steps to complete it: + 1. Let the system try to do the upgrade (via CI or `make tre-deploy`). It will fail but it's fine since now we have the new version published and registered. + 2. Make a temporary network change with either of the following options: + * Azure Portal: find your TRE resource group and select the route table resource (named `rt-YOUR_TRE_ID`). + In the overview screen, find the `ResourceProcessorSubnet` (should be last in the subnet list), click on the `...` and select `Dissociate`. + * Azure CLI: + ```shell + az network vnet subnet update --resource-group rg-YOUR_TRE_ID --vnet-name vnet-YOUR_TRE_ID --name ResourceProcessorSubnet --remove routeTable + ``` + 4. Issue a patch API request to `force-update` the firewall to its new version. + + One way to accomplish this is with the Swagger endpoint (/api/docs). + ![Force-update a service](./docs/assets/firewall-policy-migrate1.png) + + If this endpoint is not on in your deployment - include `enable_swagger` in your `config.yaml` (see the sample file), or temporarly via the API resource on azure (named `api-YOUR_TRE-ID`) -> Configuration -> `ENABLE_SWAGGER` item. + ![Update API setting](./docs/assets/firewall-policy-migrate2.png) + + + :warning: Any custom rules you have added manually will be **lost** and you'll need to add it back after the upgrade has been completed. + FEATURES: ENHANCEMENTS: @@ -339,7 +360,7 @@ BUG FIXES: * API health check is also returned by accessing the root path at / ([#2469](https://github.com/microsoft/AzureTRE/pull/2469)) * Temporary disable AppInsight's private endpoint in base workspace ([#2543](https://github.com/microsoft/AzureTRE/pull/2543)) * Resource Processor execution optimization (`porter show`) for long-standing services ([#2542](https://github.com/microsoft/AzureTRE/pull/2542)) -* Move AML Compute deployment to use AzApi Terraform Provider {[#2555]((https://github.com/microsoft/AzureTRE/pull/2555)) +* Move AML Compute deployment to use AzApi Terraform Provider ([#2555](https://github.com/microsoft/AzureTRE/pull/2555)) * Invalid token exceptions in the API app are caught, throwing 401 instead of 500 Internal server error ([#2572](https://github.com/microsoft/AzureTRE/pull/2572)) COMPONENTS: diff --git a/core/terraform/.terraform.lock.hcl b/core/terraform/.terraform.lock.hcl index 54b50d8193..a1ac89e072 100644 --- a/core/terraform/.terraform.lock.hcl +++ b/core/terraform/.terraform.lock.hcl @@ -22,42 +22,42 @@ provider "registry.terraform.io/hashicorp/azurerm" { } provider "registry.terraform.io/hashicorp/http" { - version = "3.1.0" - constraints = "~> 3.1.0" + version = "3.2.1" + constraints = "~> 3.2.0" hashes = [ - "h1:0QHdTeDcRFKD4YybtVl1F95/qo8n4DY5fANQVYBvt10=", - "zh:04160b9c74dfe105f64678c0521279cda6516a3b8cdb6748078318af64563faf", - "zh:2d9b4df29aab50496b6371d925d6d6b3c45788850599fd7ba553411abc9c8326", - "zh:3d36344fae7cfafabfb7fd1108916d7251dcfd550d13b129c25437b43bc2e461", - "zh:58ea39aab145edb067f0fe183c2def1bfc93b57bd9ab0289074dba511bc17644", - "zh:6e2d491f02ba4e4134ca8a8cb7312b3a691bdad80a33a29f69d58a5740fade0c", - "zh:70a8d3fa67fd5a5fb5d9baba22be01986e38dd0f84f1e40f341fe55b491b0a03", + "h1:DfxMa1zM/0NCFWN5PAxivSHJMNkOAFZvDYQkO72ZQmw=", + "zh:088b3b3128034485e11dff8da16e857d316fbefeaaf5bef24cceda34c6980641", + "zh:09ed1f2462ea4590b112e048c4af556f0b6eafc7cf2c75bb2ac21cd87ca59377", + "zh:39c6b0b4d3f0f65e783c467d3f634e2394820b8aef907fcc24493f21dcf73ca3", + "zh:47aab45327daecd33158a36c1a36004180a518bf1620cdd5cfc5e1fe77d5a86f", + "zh:4d70a990aa48116ab6f194eef393082c21cf58bece933b63575c63c1d2b66818", + "zh:65470c43fda950c7e9ac89417303c470146de984201fff6ef84299ea29e02d30", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:88490f4c31bebc185f4eb7b8e3a79e3b5f92b1343f6b0c14a5c5d8c5e1de9261", - "zh:8a2ba55c5621e28faed582218213812803481765f8faea681c5c3edc61646889", - "zh:8c401d8e0c99d9733287c5ad1309692d5c7e166af6711164ad41e3579f48e45f", - "zh:ce344855648da2c575ceb7b3af18e98519d46629e6eb20358f022370745a76d2", - "zh:f9f9fe99000bc7c6b778ce23e5fe16375acad644aa1b4b4894b3cb2e9a2c7903", + "zh:842b4dd63e438f5cd5fdfba1c09b8fdf268e8766e6690988ee24e8b25bfd9e8d", + "zh:a167a057f7e2d80c78d4b4057538588131fceb983d5c93b07675ad9eb1aa5790", + "zh:d0ba69b62b6db788cfe3cf8f7dc6e9a0eabe2927dc119d7fe3fe6573ee559e66", + "zh:e28d24c1d5ff24b1d1cc6f0074a1f41a6974f473f4ff7a37e55c7b6dca68308a", + "zh:fde8a50554960e5366fd0e1ca330a7c1d24ae6bbb2888137a5c83d83ce14fd18", ] } provider "registry.terraform.io/hashicorp/local" { - version = "2.2.3" - constraints = ">= 2.2.0, ~> 2.2.0" + version = "2.3.0" + constraints = ">= 2.2.0, ~> 2.3.0" hashes = [ - "h1:aWp5iSUxBGgPv1UnV5yag9Pb0N+U1I0sZb38AXBFO8A=", - "zh:04f0978bb3e052707b8e82e46780c371ac1c66b689b4a23bbc2f58865ab7d5c0", - "zh:6484f1b3e9e3771eb7cc8e8bab8b35f939a55d550b3f4fb2ab141a24269ee6aa", - "zh:78a56d59a013cb0f7eb1c92815d6eb5cf07f8b5f0ae20b96d049e73db915b238", + "h1:+l9ZTDGmGdwnuYI5ftUjwP8UgoLw4f4V9xoCzal4LW0=", + "zh:1f1920b3f78c31c6b69cdfe1e016a959667c0e2d01934e1a084b94d5a02cd9d2", + "zh:550a3cdae0ddb350942624e7b2e8b31d28bc15c20511553432413b1f38f4b214", + "zh:68d1d9ccbfce2ce56b28a23b22833a5369d4c719d6d75d50e101a8a8dbe33b9b", + "zh:6ae3ad6d865a906920c313ec2f413d080efe32c230aca711fd106b4cb9022ced", "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:8aa9950f4c4db37239bcb62e19910c49e47043f6c8587e5b0396619923657797", - "zh:996beea85f9084a725ff0e6473a4594deb5266727c5f56e9c1c7c62ded6addbb", - "zh:9a7ef7a21f48fabfd145b2e2a4240ca57517ad155017e86a30860d7c0c109de3", - "zh:a63e70ac052aa25120113bcddd50c1f3cfe61f681a93a50cea5595a4b2cc3e1c", - "zh:a6e8d46f94108e049ad85dbed60354236dc0b9b5ec8eabe01c4580280a43d3b8", - "zh:bb112ce7efbfcfa0e65ed97fa245ef348e0fd5bfa5a7e4ab2091a9bd469f0a9e", - "zh:d7bec0da5c094c6955efed100f3fe22fca8866859f87c025be1760feb174d6d9", - "zh:fb9f271b72094d07cef8154cd3d50e9aa818a0ea39130bc193132ad7b23076fd", + "zh:a0f413d50f54124057ae3dcd9353a797b84e91dc34bcf85c34a06f8aef1f9b12", + "zh:a2ac6d4088ceddcd73d88505e18b8226a6e008bff967b9e2d04254ef71b4ac6b", + "zh:a851010672e5218bdd4c4ea1822706c9025ef813a03da716d647dd6f8e2cffb0", + "zh:aa797561755041ef2fad99ee9ffc12b5e724e246bb019b21d7409afc2ece3232", + "zh:c6afa960a20d776f54bb1fc260cd13ead17280ebd87f05b9abcaa841ed29d289", + "zh:df0975e86b30bb89717b8c8d6d4690b21db66de06e79e6d6cfda769f3304afe6", + "zh:f0d3cc3da72135efdbe8f4cfbfb0f2f7174827887990a5545e6db1981f0d3a7c", ] } @@ -103,7 +103,7 @@ provider "registry.terraform.io/hashicorp/random" { provider "registry.terraform.io/hashicorp/template" { version = "2.2.0" - constraints = ">= 2.2.0, ~> 2.2.0" + constraints = ">= 2.2.0" hashes = [ "h1:94qn780bi1qjrbC3uQtjJh3Wkfwd5+tTtJHOb7KTg9w=", "zh:01702196f0a0492ec07917db7aaa595843d8f171dc195f4c988d2ffca2a06386", diff --git a/core/terraform/main.tf b/core/terraform/main.tf index 305f7f13a2..63e6e3fdff 100644 --- a/core/terraform/main.tf +++ b/core/terraform/main.tf @@ -11,11 +11,11 @@ terraform { } local = { source = "hashicorp/local" - version = "~> 2.2.0" + version = "~> 2.3.0" } http = { source = "hashicorp/http" - version = "~> 3.1.0" + version = "~> 3.2.0" } } diff --git a/core/terraform/migrate.sh b/core/terraform/migrate.sh index b498503a89..cbe9fc3574 100755 --- a/core/terraform/migrate.sh +++ b/core/terraform/migrate.sh @@ -158,6 +158,38 @@ if [ -n "${api_vnet_integration}" ]; then terraform apply -input=false -auto-approve ${PLAN_FILE}" fi +# support changing the resource processor subnet size +rp_subnet=$(echo "${terraform_show_json}" \ + | jq -r 'select(.values.root_module.child_modules != null) .values.root_module.child_modules[] | select (.address=="module.network") | .resources[] | select(.address=="module.network.azurerm_subnet.resource_processor") | .values.id') +if [ -n "${rp_subnet}" ]; then + set +o errexit + terraform plan -target "module.network.azurerm_subnet.resource_processor" -detailed-exitcode + plan_exit_code=$? + set -o errexit + + if [ "${plan_exit_code}" == "2" ]; then + echo "Migrating ${rp_subnet}" + PLAN_FILE="tfplan$$" + TS=$(date +"%s") + LOG_FILE="${TS}-tre-core-migrate-rp-subnet.log" + + # This variables are loaded in for us + # shellcheck disable=SC2154 + "${terraform_wrapper_path}" \ + -g "${TF_VAR_mgmt_resource_group_name}" \ + -s "${TF_VAR_mgmt_storage_account_name}" \ + -n "${TF_VAR_terraform_state_container_name}" \ + -k "${TRE_ID}" \ + -l "${LOG_FILE}" \ + -c "terraform plan -destroy -target module.resource_processor_vmss_porter[0] \ + -target azurerm_private_endpoint.sbpe \ + -target azurerm_private_endpoint.mongo \ + -out ${PLAN_FILE} && \ + terraform apply -input=false -auto-approve ${PLAN_FILE}" + fi +fi + + # this isn't a classic migration, but impacts how terraform handles the deployment in the next phase state_store_serverless=$(echo "${terraform_show_json}" \ | jq 'select(.values.root_module.resources != null) | .values.root_module.resources[] | select(.address=="azurerm_cosmosdb_account.tre_db_account") | any(.values.capabilities[]; .name=="EnableServerless")') diff --git a/core/terraform/network/locals.tf b/core/terraform/network/locals.tf index a759f6f7f7..713425e563 100644 --- a/core/terraform/network/locals.tf +++ b/core/terraform/network/locals.tf @@ -1,22 +1,26 @@ locals { - core_services_vnet_subnets = cidrsubnets(var.core_address_space, 4, 4, 4, 4, 2, 4, 4, 4, 4, 2) - # .1 + core_services_vnet_subnets = cidrsubnets(var.core_address_space, 4, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4) + # Addresses examples are based on /22 CIDR + # .0 firewall_subnet_address_space = local.core_services_vnet_subnets[0] # .0 - .63 app_gw_subnet_address_prefix = local.core_services_vnet_subnets[1] # .64 - .127 bastion_subnet_address_prefix = local.core_services_vnet_subnets[2] # .128 - .191 web_app_subnet_address_prefix = local.core_services_vnet_subnets[3] # .192 - .254 - # .2 + # .1 shared_services_subnet_address_prefix = local.core_services_vnet_subnets[4] # .0 - .254 - # replacing the aci + # .2 airlock_processor_subnet_address_prefix = local.core_services_vnet_subnets[5] # .0 - .63 airlock_storage_subnet_address_prefix = local.core_services_vnet_subnets[6] # .64 - .127 airlock_events_subnet_address_prefix = local.core_services_vnet_subnets[7] # .128 - .191 - airlock_notifications_subnet_address_prefix = local.core_services_vnet_subnets[8] # .128 - .191 + airlock_notifications_subnet_address_prefix = local.core_services_vnet_subnets[8] # .192 - .254 # .3 - resource_processor_subnet_address_prefix = local.core_services_vnet_subnets[9] # .0 - .254 + resource_processor_subnet_address_prefix = local.core_services_vnet_subnets[9] # .0 - .63 + firewall_management_subnet_address_prefix = local.core_services_vnet_subnets[10] # .64 - .127 + # FREE = local.core_services_vnet_subnets[11] # .128 - .191 + # FREE = local.core_services_vnet_subnets[12] # .192 - .254 tre_core_tags = { tre_id = var.tre_id diff --git a/core/terraform/network/network.tf b/core/terraform/network/network.tf index 848fe514de..ff5110da0c 100644 --- a/core/terraform/network/network.tf +++ b/core/terraform/network/network.tf @@ -130,7 +130,7 @@ resource "azurerm_subnet" "airlock_events" { address_prefixes = [local.airlock_events_subnet_address_prefix] # notice that private endpoints do not adhere to NSG rules private_endpoint_network_policies_enabled = false - depends_on = [azurerm_subnet.airlock_events] + depends_on = [azurerm_subnet.airlock_storage] # Eventgrid CAN'T send messages over private endpoints, hence we need to allow service endpoints to the service bus # We are using service endpoints + managed identity to send these messaages @@ -138,6 +138,14 @@ resource "azurerm_subnet" "airlock_events" { service_endpoints = ["Microsoft.ServiceBus"] } +resource "azurerm_subnet" "firewall_management" { + name = "AzureFirewallManagementSubnet" + virtual_network_name = azurerm_virtual_network.core.name + resource_group_name = var.resource_group_name + address_prefixes = [local.firewall_management_subnet_address_prefix] + depends_on = [azurerm_subnet.airlock_events] +} + resource "azurerm_ip_group" "resource_processor" { name = "ipg-resource-processor" location = var.location @@ -146,3 +154,21 @@ resource "azurerm_ip_group" "resource_processor" { tags = local.tre_core_tags lifecycle { ignore_changes = [tags] } } + +resource "azurerm_ip_group" "shared" { + name = "ipg-shared" + location = var.location + resource_group_name = var.resource_group_name + cidrs = [local.shared_services_subnet_address_prefix] + tags = local.tre_core_tags + lifecycle { ignore_changes = [tags] } +} + +resource "azurerm_ip_group" "webapp" { + name = "ipg-web-app" + location = var.location + resource_group_name = var.resource_group_name + cidrs = [local.web_app_subnet_address_prefix] + tags = local.tre_core_tags + lifecycle { ignore_changes = [tags] } +} diff --git a/core/terraform/network/network_security_groups.tf b/core/terraform/network/network_security_groups.tf index 16238c9815..d89d711d6b 100644 --- a/core/terraform/network/network_security_groups.tf +++ b/core/terraform/network/network_security_groups.tf @@ -107,7 +107,7 @@ resource "azurerm_subnet_network_security_group_association" "bastion" { subnet_id = azurerm_subnet.bastion.id network_security_group_id = azurerm_network_security_group.bastion.id # depend on the last subnet we created in the vnet - depends_on = [azurerm_subnet.airlock_events] + depends_on = [azurerm_subnet.firewall_management] } # Network security group for Application Gateway diff --git a/core/terraform/notebooks.tf b/core/terraform/notebooks.tf new file mode 100644 index 0000000000..23ab97fa33 --- /dev/null +++ b/core/terraform/notebooks.tf @@ -0,0 +1,15 @@ +data "http" "firewall_workbook_json" { + url = "https://raw.githubusercontent.com/Azure/Azure-Network-Security/master/Azure%20Firewall/Workbook%20-%20Azure%20Firewall%20Monitor%20Workbook/Azure%20Firewall_Gallery.json" +} + +resource "random_uuid" "firewall_workbook" { +} + +resource "azurerm_application_insights_workbook" "firewall" { + name = random_uuid.firewall_workbook.result + location = azurerm_resource_group.core.location + resource_group_name = azurerm_resource_group.core.name + display_name = "Azure Firewall Workbook ${var.tre_id}" + data_json = data.http.firewall_workbook_json.response_body + tags = local.tre_core_tags +} diff --git a/core/terraform/variables.tf b/core/terraform/variables.tf index c39336ec97..f0ed9c495a 100644 --- a/core/terraform/variables.tf +++ b/core/terraform/variables.tf @@ -20,6 +20,10 @@ variable "acr_name" { variable "core_address_space" { type = string description = "Core services VNET Address Space" + validation { + condition = parseint(element(split("/", var.core_address_space), 1), 10) > 0 && parseint(element(split("/", var.core_address_space), 1), 10) <= 22 + error_message = "core_address_space size should be /22 or larger" + } } variable "tre_address_space" { diff --git a/core/version.txt b/core/version.txt index 43a1e95ba4..906d362f7d 100644 --- a/core/version.txt +++ b/core/version.txt @@ -1 +1 @@ -__version__ = "0.5.3" +__version__ = "0.6.0" diff --git a/devops/scripts/control_tre.sh b/devops/scripts/control_tre.sh index aa7bd58372..c5a04f5a71 100755 --- a/devops/scripts/control_tre.sh +++ b/devops/scripts/control_tre.sh @@ -66,13 +66,14 @@ if [[ "$1" == *"start"* ]]; then elif [[ "$1" == *"stop"* ]]; then if [[ $(az network firewall list --output json --query "[?resourceGroup=='${core_rg_name}'&&name=='${fw_name}'] | length(@)") != 0 ]]; then + fw_sku=$(az network firewall show -n "${fw_name}" -g "${core_rg_name}" --query "sku.tier" -o tsv) IPCONFIG_NAME=$(az network firewall ip-config list -f "${fw_name}" -g "${core_rg_name}" --query "[0].name" -o tsv) - if [ -n "$IPCONFIG_NAME" ]; then + if [ -n "$IPCONFIG_NAME" ] && [ "${fw_sku}" != "Basic" ]; then echo "Deleting Firewall ip-config: $IPCONFIG_NAME" az network firewall ip-config delete -f "${fw_name}" -n "$IPCONFIG_NAME" -g "${core_rg_name}" & else - echo "No Firewall ip-config found" + echo "No Firewall ip-config found or SKU (${fw_sku}) doesn't allow deallocation" fi fi diff --git a/docs/assets/firewall-policy-migrate1.png b/docs/assets/firewall-policy-migrate1.png new file mode 100644 index 0000000000..3c975a3cfa Binary files /dev/null and b/docs/assets/firewall-policy-migrate1.png differ diff --git a/docs/assets/firewall-policy-migrate2.png b/docs/assets/firewall-policy-migrate2.png new file mode 100644 index 0000000000..c5020c14cb Binary files /dev/null and b/docs/assets/firewall-policy-migrate2.png differ diff --git a/templates/shared_services/firewall/parameters.json b/templates/shared_services/firewall/parameters.json index a10b6e1ddb..0207a586ae 100755 --- a/templates/shared_services/firewall/parameters.json +++ b/templates/shared_services/firewall/parameters.json @@ -45,6 +45,12 @@ "source": { "env": "NETWORK_RULE_COLLECTIONS" } + }, + { + "name": "sku_tier", + "source": { + "env": "SKU_TIER" + } } ] } diff --git a/templates/shared_services/firewall/porter.yaml b/templates/shared_services/firewall/porter.yaml index 1ed8eabd78..41f7b4e755 100644 --- a/templates/shared_services/firewall/porter.yaml +++ b/templates/shared_services/firewall/porter.yaml @@ -1,7 +1,7 @@ --- schemaVersion: 1.0.0 name: tre-shared-service-firewall -version: 0.8.0 +version: 1.0.0 description: "An Azure TRE Firewall shared service" dockerfile: Dockerfile.tmpl registry: azuretre @@ -45,6 +45,10 @@ parameters: type: string default: "W10=" # b64 for [] description: "Network rule collection array" + - name: sku_tier + type: string + default: Standard + description: The firewall and its policy SKU tier mixins: - terraform: @@ -58,6 +62,7 @@ install: tre_resource_id: ${ bundle.parameters.id } api_driven_rule_collections_b64: ${ bundle.parameters.rule_collections } api_driven_network_rule_collections_b64: ${ bundle.parameters.network_rule_collections } + sku_tier: ${ bundle.parameters.sku_tier } backendConfig: resource_group_name: ${ bundle.parameters.tfstate_resource_group_name } storage_account_name: ${ bundle.parameters.tfstate_storage_account_name } @@ -72,6 +77,7 @@ upgrade: tre_resource_id: ${ bundle.parameters.id } api_driven_rule_collections_b64: ${ bundle.parameters.rule_collections } api_driven_network_rule_collections_b64: ${ bundle.parameters.network_rule_collections } + sku_tier: ${ bundle.parameters.sku_tier } backendConfig: resource_group_name: ${ bundle.parameters.tfstate_resource_group_name } storage_account_name: ${ bundle.parameters.tfstate_storage_account_name } @@ -86,6 +92,7 @@ uninstall: tre_resource_id: ${ bundle.parameters.id } api_driven_rule_collections_b64: ${ bundle.parameters.rule_collections } api_driven_network_rule_collections_b64: ${ bundle.parameters.network_rule_collections } + sku_tier: ${ bundle.parameters.sku_tier } backendConfig: resource_group_name: ${ bundle.parameters.tfstate_resource_group_name } storage_account_name: ${ bundle.parameters.tfstate_storage_account_name } diff --git a/templates/shared_services/firewall/template_schema.json b/templates/shared_services/firewall/template_schema.json index fd40401b71..5bd8660a8e 100644 --- a/templates/shared_services/firewall/template_schema.json +++ b/templates/shared_services/firewall/template_schema.json @@ -8,7 +8,7 @@ "properties": { "rule_collections": { "$id": "#properties/rule_collections", - "title": "rule_collections", + "title": "application rule collections", "type": "array", "default": [], "updateable": true, @@ -29,16 +29,8 @@ ], "pattern": "^.*$" }, - "priority": { - "title": "priority", - "type": "integer", - "description": "Numeric priority for rule collection - leave blank for auto numbering", - "examples": [ - 110 - ] - }, "action": { - "title": "action", + "title": "action DEPRECATED", "type": "string", "examples": [ "Allow" @@ -53,7 +45,7 @@ "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "object", "required": [ "name" @@ -81,7 +73,7 @@ "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "object", "required": [ "port", @@ -112,11 +104,11 @@ } }, "fqdn_tags": { - "title": "fqdn_tags", + "title": "fqdn tags", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "enum":[ @@ -135,11 +127,11 @@ } }, "target_fqdns": { - "title": "target_fqdns", + "title": "destination fqdns", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -148,11 +140,11 @@ } }, "source_addresses": { - "title": "Source_addresses", + "title": "source addresses", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -161,11 +153,11 @@ } }, "source_ip_group_ids": { - "title": "Source_ip_group_ids", + "title": "source ip group ids", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -174,11 +166,11 @@ } }, "source_ip_groups_in_core": { - "title": "Source_ip_groups_in_core", + "title": "source ip group names in core", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -194,7 +186,7 @@ }, "network_rule_collections": { "$id": "#properties/network_rule_collections", - "title": "network_rule_collections", + "title": "network rule collections", "type": "array", "default": [], "updateable": true, @@ -203,7 +195,6 @@ "type": "object", "required": [ "name", - "action", "rules" ], "properties": { @@ -215,16 +206,8 @@ ], "pattern": "^.*$" }, - "priority": { - "title": "priority", - "type": "integer", - "description": "Numeric priority for rule collection - leave blank for auto numbering", - "examples": [ - 110 - ] - }, "action": { - "title": "action", + "title": "action DEPRECATED", "type": "string", "examples": [ "Allow" @@ -239,7 +222,7 @@ "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "object", "required": [ "name" @@ -254,7 +237,7 @@ "pattern": "^.{5,80}$" }, "description": { - "title": "description", + "title": "description DEPRECATED", "type": "string", "default": "", "examples": [ @@ -263,11 +246,11 @@ "pattern": "^.*$" }, "source_addresses": { - "title": "Source_addresses", + "title": "source addresses", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -276,11 +259,11 @@ } }, "source_ip_group_ids": { - "title": "Source_ip_group_ids", + "title": "source ip group ids", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -289,11 +272,11 @@ } }, "source_ip_groups_in_core": { - "title": "Source_ip_groups_in_core", + "title": "source ip group names in core", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -302,11 +285,11 @@ } }, "destination_addresses": { - "title": "Destination_addresses", + "title": "destination addresses", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -315,11 +298,11 @@ } }, "destination_ip_group_ids": { - "title": "Destination_ip_group_ids", + "title": "destination ip group ids", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -328,11 +311,11 @@ } }, "destination_fqdns": { - "title": "Destination_fqdns", + "title": "destination fqdns", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -341,11 +324,11 @@ } }, "destination_ports": { - "title": "Destination_ports", + "title": "destination ports", "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "default": "", "examples": [ @@ -360,7 +343,7 @@ "type": "array", "default": [], "items":{ - "title": "Items", + "title": "items", "type": "string", "enum": [ "Any", diff --git a/templates/shared_services/firewall/terraform/.terraform.lock.hcl b/templates/shared_services/firewall/terraform/.terraform.lock.hcl index 4be435ce82..cd2351d9ff 100644 --- a/templates/shared_services/firewall/terraform/.terraform.lock.hcl +++ b/templates/shared_services/firewall/terraform/.terraform.lock.hcl @@ -2,21 +2,21 @@ # Manual edits may be lost in future updates. provider "registry.terraform.io/hashicorp/azurerm" { - version = "3.33.0" - constraints = "3.33.0" + version = "3.40.0" + constraints = "3.40.0" hashes = [ - "h1:pXB6SKE4NKdf+LepsQjrLcBnVTL5ejeKvx/kyojai6c=", - "zh:136d9c642746d8d84e62ecd8ab0c7dc015eac504c1f068e06fad438ae222d934", - "zh:266e64b8e32a94ddcc20954ebad1d8ff3921d318addf576e981b1390e5d5ba79", - "zh:3bd84a1e5b3bbe34a5870f271d6a5bf9b35a4c924db32b450a1fb53bc910c37a", - "zh:3c6604041472bb4691b502877cf9d886ed9f973fbadf11389ec9499fdc66045e", - "zh:680c00a73c8054c36a58115a44d02d1ebb675c2ad3afaaab2d74a01f978f16ce", - "zh:6dab47ef64f90e43b75ed240a974c4119f5268be4433f3c1c3e97559e7ef2f38", - "zh:9f73f19fdc340c443693dc03f1a145c6bd0ee5fd425eab7473d06abbe39b99d7", - "zh:9ff008b6737e880f191b4be6dfcef95ff019969dd787c44a58c2d7d6aaf6623b", - "zh:be297f1515e9ac63886e3e092a0bcd10aa8aa2b69c2b0995ce4e069176b07a95", + "h1:/Jbhw/zNAsDYDoASaG6w+0KZyay9BkUVOpR8b7m0CsA=", + "zh:00fa6dc05bf2643c6a3c741edb7d88263698086835a8a613f1d7bd76d1b918fd", + "zh:0da9b788e773272a7aa9d59bd9e3d5842edd4acc8c3895bea469e66dc14205a0", + "zh:25a8c39d1f042fc7c83ba9dd745c3569ea9e577fadb57563a575fb115ac2b9f1", + "zh:4423666dbeae8bc22c6e8898ffbb88745681dc27668ca9104b665dd7f3d7292c", + "zh:78c07308e7407b558d15737a98fb5eaf15529d297fc3798de6a7d61e0466e2e3", + "zh:894aca7e6f4f331ee8eb51957a180dc03d399d2b1727e0d7842e9b3f022a8c6a", + "zh:bb0e620c2161b4c4892a6f50b1c4c69ed70f66bb5e92543a03d79d0e4b1d9441", + "zh:c7d8e6a791159ca63b30908c9efe72ab65f60d64b30f0c1eb5a64972f4994844", + "zh:d04c11bfd346c1ac34d16bbdca70b23b006e822f6beb236b85375e8343888eb4", + "zh:f4edea9660327c7c70a823d786fd1b1c1b186c8759770447f63da72f23e1a73c", "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - "zh:fb29a566e7698cfae477f3efa3bba38526ec8343355763178c6e9c96e51399f3", - "zh:fbc3b625733ce5f0970fa8d9743f6db51064c168d6be5fc7a5e3d1a54af28bb7", + "zh:f986e268949cf445ff53a66af48a87c6f6dba5964e8a5b1dc0ea02afabdd71f7", ] } diff --git a/templates/shared_services/firewall/terraform/data.tf b/templates/shared_services/firewall/terraform/data.tf index 0feb9c5ad2..00a304329e 100644 --- a/templates/shared_services/firewall/terraform/data.tf +++ b/templates/shared_services/firewall/terraform/data.tf @@ -1,50 +1,49 @@ data "azurerm_subnet" "firewall" { name = "AzureFirewallSubnet" virtual_network_name = "vnet-${var.tre_id}" + resource_group_name = local.core_resource_group_name +} - resource_group_name = local.core_resource_group_name +data "azurerm_subnet" "firewall_management" { + name = "AzureFirewallManagementSubnet" + virtual_network_name = "vnet-${var.tre_id}" + resource_group_name = local.core_resource_group_name } data "azurerm_subnet" "shared" { name = "SharedSubnet" virtual_network_name = "vnet-${var.tre_id}" - - resource_group_name = local.core_resource_group_name + resource_group_name = local.core_resource_group_name } data "azurerm_subnet" "resource_processor" { name = "ResourceProcessorSubnet" virtual_network_name = "vnet-${var.tre_id}" - - resource_group_name = local.core_resource_group_name + resource_group_name = local.core_resource_group_name } data "azurerm_subnet" "web_app" { name = "WebAppSubnet" virtual_network_name = "vnet-${var.tre_id}" - - resource_group_name = local.core_resource_group_name + resource_group_name = local.core_resource_group_name } data "azurerm_subnet" "airlock_processor" { name = "AirlockProcessorSubnet" virtual_network_name = "vnet-${var.tre_id}" - - resource_group_name = local.core_resource_group_name + resource_group_name = local.core_resource_group_name } data "azurerm_subnet" "airlock_storage" { name = "AirlockStorageSubnet" virtual_network_name = "vnet-${var.tre_id}" - - resource_group_name = local.core_resource_group_name + resource_group_name = local.core_resource_group_name } data "azurerm_subnet" "airlock_events" { name = "AirlockEventsSubnet" virtual_network_name = "vnet-${var.tre_id}" - - resource_group_name = local.core_resource_group_name + resource_group_name = local.core_resource_group_name } data "azurerm_log_analytics_workspace" "tre" { @@ -56,6 +55,21 @@ data "azurerm_resource_group" "rg" { name = local.core_resource_group_name } +data "azurerm_ip_group" "resource_processor" { + name = "ipg-resource-processor" + resource_group_name = local.core_resource_group_name +} + +data "azurerm_ip_group" "shared" { + name = "ipg-shared" + resource_group_name = local.core_resource_group_name +} + +data "azurerm_ip_group" "web" { + name = "ipg-web-app" + resource_group_name = local.core_resource_group_name +} + data "azurerm_ip_group" "referenced" { for_each = toset(distinct(flatten( [for collection in concat(local.api_driven_network_rule_collection, local.api_driven_application_rule_collection) : diff --git a/templates/shared_services/firewall/terraform/firewall.tf b/templates/shared_services/firewall/terraform/firewall.tf index e9c0ec93a8..cef388c478 100644 --- a/templates/shared_services/firewall/terraform/firewall.tf +++ b/templates/shared_services/firewall/terraform/firewall.tf @@ -1,4 +1,4 @@ -resource "azurerm_public_ip" "fwpip" { +resource "azurerm_public_ip" "fwtransit" { name = "pip-fw-${var.tre_id}" resource_group_name = local.core_resource_group_name location = data.azurerm_resource_group.rg.location @@ -9,18 +9,45 @@ resource "azurerm_public_ip" "fwpip" { lifecycle { ignore_changes = [tags, zones] } } +moved { + from = azurerm_public_ip.fwpip + to = azurerm_public_ip.fwtransit +} + +resource "azurerm_public_ip" "fwmanagement" { + count = var.sku_tier == "Basic" ? 1 : 0 + name = "pip-fw-management-${var.tre_id}" + resource_group_name = local.core_resource_group_name + location = data.azurerm_resource_group.rg.location + allocation_method = "Static" + sku = "Standard" + tags = local.tre_shared_service_tags + + lifecycle { ignore_changes = [tags, zones] } +} + + resource "azurerm_firewall" "fw" { - depends_on = [azurerm_public_ip.fwpip] name = local.firewall_name resource_group_name = local.core_resource_group_name location = data.azurerm_resource_group.rg.location - sku_tier = "Standard" + sku_tier = var.sku_tier sku_name = "AZFW_VNet" + firewall_policy_id = azurerm_firewall_policy.root.id tags = local.tre_shared_service_tags ip_configuration { name = "fw-ip-configuration" subnet_id = data.azurerm_subnet.firewall.id - public_ip_address_id = azurerm_public_ip.fwpip.id + public_ip_address_id = azurerm_public_ip.fwtransit.id + } + + dynamic "management_ip_configuration" { + for_each = var.sku_tier == "Basic" ? [1] : [] + content { + name = "mgmtconfig" + subnet_id = data.azurerm_subnet.firewall_management.id + public_ip_address_id = azurerm_public_ip.fwmanagement[0].id + } } lifecycle { ignore_changes = [tags] } @@ -36,14 +63,13 @@ resource "azurerm_monitor_diagnostic_setting" "firewall" { log_analytics_workspace_id = data.azurerm_log_analytics_workspace.tre.id log_analytics_destination_type = "AzureDiagnostics" - dynamic "log" { - for_each = data.azurerm_monitor_diagnostic_categories.firewall.log_category_types + dynamic "enabled_log" { + for_each = setintersection(data.azurerm_monitor_diagnostic_categories.firewall.log_category_types, local.firewall_diagnostic_categories_enabled) content { - category = log.value - enabled = contains(local.firewall_diagnostic_categories_enabled, log.value) ? true : false + category = enabled_log.value retention_policy { - enabled = contains(local.firewall_diagnostic_categories_enabled, log.value) ? true : false + enabled = true days = 365 } } @@ -60,333 +86,12 @@ resource "azurerm_monitor_diagnostic_setting" "firewall" { } } -resource "azurerm_firewall_application_rule_collection" "shared_subnet" { - name = "arc-shared_subnet" - azure_firewall_name = azurerm_firewall.fw.name - resource_group_name = azurerm_firewall.fw.resource_group_name - priority = 100 - action = "Allow" - - rule { - name = "admin-resources" - - protocol { - port = "443" - type = "Https" - } - - protocol { - port = "80" - type = "Http" - } - - target_fqdns = [ - "go.microsoft.com", - "*.azureedge.net", - "*github.com", - "*powershellgallery.com", - "git-scm.com", - "*githubusercontent.com", - "*core.windows.net", - "aka.ms", - "management.azure.com", - "graph.microsoft.com", - "login.microsoftonline.com", - "aadcdn.msftauth.net", - "graph.windows.net", - "keyserver.ubuntu.com", - "packages.microsoft.com", - "download.docker.com" - ] - - source_addresses = data.azurerm_subnet.shared.address_prefixes - } - - rule { - name = "nexus-bootstrap" - - protocol { - port = "443" - type = "Https" - } - - protocol { - port = "80" - type = "Http" - } - - target_fqdns = [ - "keyserver.ubuntu.com", - "packages.microsoft.com", - "download.docker.com", - "azure.archive.ubuntu.com" - ] - - source_addresses = data.azurerm_subnet.shared.address_prefixes - } -} - -resource "azurerm_firewall_application_rule_collection" "resource_processor_subnet" { - name = "arc-resource_processor_subnet" - azure_firewall_name = azurerm_firewall.fw.name - resource_group_name = azurerm_firewall.fw.resource_group_name - priority = 101 - action = "Allow" - - rule { - name = "os-package-sources" - protocol { - port = "443" - type = "Https" - } - protocol { - port = "80" - type = "Http" - } - - target_fqdns = [ - "packages.microsoft.com", - "keyserver.ubuntu.com", - "api.snapcraft.io", - "azure.archive.ubuntu.com", - "security.ubuntu.com", - "entropy.ubuntu.com", - ] - source_addresses = data.azurerm_subnet.resource_processor.address_prefixes - } - - rule { - name = "docker-sources" - protocol { - port = "443" - type = "Https" - } - protocol { - port = "80" - type = "Http" - } - - target_fqdns = [ - "download.docker.com", - "registry-1.docker.io", - "auth.docker.io", - ] - source_addresses = data.azurerm_subnet.resource_processor.address_prefixes - } - - # TODO: remove this rule when all bundles have mirrored their plugins - # https://github.com/microsoft/AzureTRE/issues/2445 - rule { - name = "terraform-sources" - protocol { - port = "443" - type = "Https" - } - protocol { - port = "80" - type = "Http" - } - - target_fqdns = [ - "registry.terraform.io", - "releases.hashicorp.com", - ] - source_addresses = data.azurerm_subnet.resource_processor.address_prefixes - } - - depends_on = [ - azurerm_firewall_application_rule_collection.shared_subnet - ] -} - -resource "azurerm_firewall_network_rule_collection" "general" { - name = "general" - azure_firewall_name = azurerm_firewall.fw.name - resource_group_name = azurerm_firewall.fw.resource_group_name - priority = 100 - action = "Allow" - - rule { - name = "time" - - protocols = [ - "UDP" - ] - - destination_addresses = [ - "*" - ] - - destination_ports = [ - "123" - ] - source_addresses = [ - "*" - ] - } - - depends_on = [ - azurerm_firewall_application_rule_collection.resource_processor_subnet - ] -} - -resource "azurerm_firewall_network_rule_collection" "resource_processor_subnet" { - name = "nrc-resource_processor_subnet" - azure_firewall_name = azurerm_firewall.fw.name - resource_group_name = azurerm_firewall.fw.resource_group_name - priority = 101 - action = "Allow" - - rule { - name = "AzureServiceTags" - - protocols = [ - "TCP" - ] - - destination_addresses = [ - "AzureActiveDirectory", - "AzureResourceManager", - "AzureContainerRegistry", - "Storage", - "AzureKeyVault" - ] - - destination_ports = [ - "443" - ] - source_addresses = data.azurerm_subnet.resource_processor.address_prefixes - } - - depends_on = [ - azurerm_firewall_network_rule_collection.general - ] -} - -resource "azurerm_firewall_network_rule_collection" "web_app_subnet" { - name = "nrc-web_app_subnet" - azure_firewall_name = azurerm_firewall.fw.name - resource_group_name = azurerm_firewall.fw.resource_group_name - priority = 102 - action = "Allow" - - rule { - name = "Azure-Services" - - protocols = [ - "TCP" - ] - - destination_addresses = [ - "AzureActiveDirectory", - "AzureContainerRegistry", - "AzureResourceManager" - ] - - destination_ports = [ - "443" - ] - source_addresses = data.azurerm_subnet.web_app.address_prefixes - } - - depends_on = [ - azurerm_firewall_network_rule_collection.resource_processor_subnet - ] -} - -resource "azurerm_firewall_application_rule_collection" "web_app_subnet" { - name = "arc-web_app_subnet" - azure_firewall_name = azurerm_firewall.fw.name - resource_group_name = azurerm_firewall.fw.resource_group_name - priority = 102 - action = "Allow" - - rule { - name = "microsoft-graph" - protocol { - port = "443" - type = "Https" - } - - target_fqdns = [ - "graph.microsoft.com" - ] - source_addresses = data.azurerm_subnet.web_app.address_prefixes - } - - depends_on = [ - azurerm_firewall_network_rule_collection.web_app_subnet - ] -} - -# these rule collections are driven by the API, through resource properties -resource "azurerm_firewall_application_rule_collection" "api_driven_application_rules" { - for_each = { for i, v in local.api_driven_application_rule_collection : i => v } - name = each.value.name - azure_firewall_name = azurerm_firewall.fw.name - resource_group_name = azurerm_firewall.fw.resource_group_name - priority = try(each.value.priority, (200 + each.key)) - action = each.value.action - - dynamic "rule" { - for_each = each.value.rules - content { - name = rule.value.name - description = rule.value.description - dynamic "protocol" { - for_each = rule.value.protocols - content { - port = protocol.value.port - type = protocol.value.type - } - } - target_fqdns = try(rule.value.target_fqdns, []) - source_addresses = try(rule.value.source_addresses, []) - source_ip_groups = concat( - try(rule.value.source_ip_group_ids, []), - try([for item in rule.value.source_ip_groups_in_core : data.azurerm_ip_group.referenced[item].id], []) - ) - fqdn_tags = try(rule.value.fqdn_tags, []) - } - } - - depends_on = [ - azurerm_firewall_application_rule_collection.web_app_subnet - ] -} - -moved { - from = azurerm_firewall_application_rule_collection.api_driven_rules - to = azurerm_firewall_application_rule_collection.api_driven_application_rules -} - -resource "azurerm_firewall_network_rule_collection" "api_driven_network_rules" { - for_each = { for i, v in local.api_driven_network_rule_collection : i => v } - name = each.value.name - azure_firewall_name = azurerm_firewall.fw.name - resource_group_name = azurerm_firewall.fw.resource_group_name - priority = try(each.value.priority, (200 + each.key)) - action = each.value.action - - dynamic "rule" { - for_each = each.value.rules - content { - name = rule.value.name - description = rule.value.description - source_addresses = try(rule.value.source_addresses, []) - source_ip_groups = concat( - try(rule.value.source_ip_group_ids, []), - try([for item in rule.value.source_ip_groups_in_core : data.azurerm_ip_group.referenced[item].id], []) - ) - destination_addresses = try(rule.value.destination_addresses, []) - destination_ip_groups = try(rule.value.destination_ip_group_ids, []) - destination_fqdns = try(rule.value.destination_fqdns, []) - destination_ports = try(rule.value.destination_ports, []) - protocols = try(rule.value.protocols, []) - } - } +resource "azurerm_firewall_policy" "root" { + name = local.firewall_policy_name + resource_group_name = local.core_resource_group_name + location = data.azurerm_resource_group.rg.location + sku = var.sku_tier + tags = local.tre_shared_service_tags - depends_on = [ - azurerm_firewall_application_rule_collection.api_driven_application_rules - ] + lifecycle { ignore_changes = [tags] } } diff --git a/templates/shared_services/firewall/terraform/locals.tf b/templates/shared_services/firewall/terraform/locals.tf index fdc59e868e..3eb2a41c33 100644 --- a/templates/shared_services/firewall/terraform/locals.tf +++ b/templates/shared_services/firewall/terraform/locals.tf @@ -2,10 +2,9 @@ locals { core_resource_group_name = "rg-${var.tre_id}" firewall_name = "fw-${var.tre_id}" firewall_diagnostic_categories_enabled = [ - "AzureFirewallApplicationRule", - "AzureFirewallNetworkRule", - "AzureFirewallDnsProxy", - "AzureFirewallNetworkRule" + "AZFWApplicationRule", + "AZFWNetworkRule", + "AZFWDnsProxy", ] tre_shared_service_tags = { tre_id = var.tre_id @@ -14,4 +13,6 @@ locals { api_driven_application_rule_collection = jsondecode(base64decode(var.api_driven_rule_collections_b64)) api_driven_network_rule_collection = jsondecode(base64decode(var.api_driven_network_rule_collections_b64)) + + firewall_policy_name = "fw-policy-${var.tre_id}" } diff --git a/templates/shared_services/firewall/terraform/providers.tf b/templates/shared_services/firewall/terraform/providers.tf index c395cbf420..9d4d7d7060 100644 --- a/templates/shared_services/firewall/terraform/providers.tf +++ b/templates/shared_services/firewall/terraform/providers.tf @@ -3,7 +3,7 @@ terraform { required_providers { azurerm = { source = "hashicorp/azurerm" - version = "=3.33.0" + version = "=3.40.0" } } diff --git a/templates/shared_services/firewall/terraform/routetable.tf b/templates/shared_services/firewall/terraform/routetable.tf index b574ebfb14..d5b82072f6 100644 --- a/templates/shared_services/firewall/terraform/routetable.tf +++ b/templates/shared_services/firewall/terraform/routetable.tf @@ -13,11 +13,6 @@ resource "azurerm_route_table" "rt" { next_hop_type = "VirtualAppliance" next_hop_in_ip_address = azurerm_firewall.fw.ip_configuration[0].private_ip_address } - - # Needs to depend on the last rule so that the traffic doesn't get denied before - depends_on = [ - azurerm_firewall_application_rule_collection.web_app_subnet - ] } resource "azurerm_subnet_route_table_association" "rt_shared_subnet_association" { @@ -28,6 +23,12 @@ resource "azurerm_subnet_route_table_association" "rt_shared_subnet_association" resource "azurerm_subnet_route_table_association" "rt_resource_processor_subnet_association" { subnet_id = data.azurerm_subnet.resource_processor.id route_table_id = azurerm_route_table.rt.id + + # Not waiting for the rules will block traffic prematurally. + depends_on = [ + azurerm_firewall.fw, + azurerm_firewall_policy_rule_collection_group.core, + ] } resource "azurerm_subnet_route_table_association" "rt_web_app_subnet_association" { diff --git a/templates/shared_services/firewall/terraform/rules.tf b/templates/shared_services/firewall/terraform/rules.tf new file mode 100644 index 0000000000..605aead486 --- /dev/null +++ b/templates/shared_services/firewall/terraform/rules.tf @@ -0,0 +1,278 @@ +resource "azurerm_firewall_policy_rule_collection_group" "core" { + name = "rcg-core" + firewall_policy_id = azurerm_firewall_policy.root.id + priority = 500 + + network_rule_collection { + name = "nrc-general" + priority = 201 + action = "Allow" + + rule { + name = "time" + protocols = [ + "UDP" + ] + destination_addresses = [ + "*" + ] + destination_ports = [ + "123" + ] + source_addresses = [ + "*" + ] + } + } + + network_rule_collection { + name = "nrc-resource-processor-subnet" + priority = 202 + action = "Allow" + + rule { + name = "azure-services" + protocols = [ + "TCP" + ] + destination_addresses = [ + "AzureActiveDirectory", + "AzureResourceManager", + "AzureContainerRegistry", + "Storage", + "AzureKeyVault" + ] + destination_ports = [ + "443" + ] + source_ip_groups = [data.azurerm_ip_group.resource_processor.id] + } + } + + network_rule_collection { + name = "nrc-web-app-subnet" + priority = 203 + action = "Allow" + + rule { + name = "azure-services" + protocols = [ + "TCP" + ] + destination_addresses = [ + "AzureActiveDirectory", + "AzureContainerRegistry", + "AzureResourceManager" + ] + destination_ports = [ + "443" + ] + source_ip_groups = [data.azurerm_ip_group.web.id] + } + } + + application_rule_collection { + name = "arc-resource-processor-subnet" + priority = 301 + action = "Allow" + + rule { + name = "os-package-sources" + protocols { + port = "443" + type = "Https" + } + protocols { + port = "80" + type = "Http" + } + destination_fqdns = [ + "packages.microsoft.com", + "keyserver.ubuntu.com", + "api.snapcraft.io", + "azure.archive.ubuntu.com", + "security.ubuntu.com", + "entropy.ubuntu.com", + ] + source_ip_groups = [data.azurerm_ip_group.resource_processor.id] + } + + rule { + name = "docker-sources" + protocols { + port = "443" + type = "Https" + } + protocols { + port = "80" + type = "Http" + } + destination_fqdns = [ + "download.docker.com", + "registry-1.docker.io", + "auth.docker.io", + ] + source_ip_groups = [data.azurerm_ip_group.resource_processor.id] + } + } + + application_rule_collection { + name = "arc-shared-subnet" + priority = 302 + action = "Allow" + + rule { + name = "admin-resources" + protocols { + port = "443" + type = "Https" + } + protocols { + port = "80" + type = "Http" + } + destination_fqdns = [ + "go.microsoft.com", + "*.azureedge.net", + "*github.com", + "*powershellgallery.com", + "git-scm.com", + "*githubusercontent.com", + "*core.windows.net", + "aka.ms", + "management.azure.com", + "graph.microsoft.com", + "login.microsoftonline.com", + "aadcdn.msftauth.net", + "graph.windows.net", + "keyserver.ubuntu.com", + "packages.microsoft.com", + "download.docker.com" + ] + source_ip_groups = [data.azurerm_ip_group.shared.id] + } + + rule { + name = "nexus-bootstrap" + protocols { + port = "443" + type = "Https" + } + protocols { + port = "80" + type = "Http" + } + destination_fqdns = [ + "keyserver.ubuntu.com", + "packages.microsoft.com", + "download.docker.com", + "azure.archive.ubuntu.com" + ] + source_ip_groups = [data.azurerm_ip_group.shared.id] + } + } + + application_rule_collection { + name = "arc-web-app-subnet" + priority = 303 + action = "Allow" + + rule { + name = "microsoft-graph" + protocols { + port = "443" + type = "Https" + } + destination_fqdns = [ + "graph.microsoft.com" + ] + source_ip_groups = [data.azurerm_ip_group.web.id] + } + } +} + + +resource "azurerm_firewall_policy_rule_collection_group" "dynamic_network" { + name = "rcg-dynamic-network" + firewall_policy_id = azurerm_firewall_policy.root.id + priority = 510 + + dynamic "network_rule_collection" { + for_each = { for i, v in local.api_driven_network_rule_collection : i => v } + + content { + name = network_rule_collection.value.name + priority = 200 + network_rule_collection.key + action = "Allow" + + dynamic "rule" { + for_each = network_rule_collection.value.rules + + content { + name = rule.value.name + # description = rule.value.description + source_addresses = try(rule.value.source_addresses, []) + source_ip_groups = concat( + try(rule.value.source_ip_group_ids, []), + try([for item in rule.value.source_ip_groups_in_core : data.azurerm_ip_group.referenced[item].id], []) + ) + destination_addresses = try(rule.value.destination_addresses, []) + destination_ip_groups = try(rule.value.destination_ip_group_ids, []) + destination_fqdns = try(rule.value.destination_fqdns, []) + destination_ports = try(rule.value.destination_ports, []) + protocols = try(rule.value.protocols, []) + } + } + } + } + + depends_on = [ + azurerm_firewall_policy_rule_collection_group.core + ] +} + +resource "azurerm_firewall_policy_rule_collection_group" "dynamic_application" { + name = "rcg-dynamic-application" + firewall_policy_id = azurerm_firewall_policy.root.id + priority = 520 + + dynamic "application_rule_collection" { + for_each = { for i, v in local.api_driven_application_rule_collection : i => v } + + content { + name = application_rule_collection.value.name + priority = 200 + application_rule_collection.key + action = "Allow" + + dynamic "rule" { + for_each = application_rule_collection.value.rules + + content { + name = rule.value.name + description = rule.value.description + + dynamic "protocols" { + for_each = rule.value.protocols + + content { + port = protocols.value.port + type = protocols.value.type + } + } + + destination_fqdns = try(rule.value.target_fqdns, []) + source_addresses = try(rule.value.source_addresses, []) + source_ip_groups = concat( + try(rule.value.source_ip_group_ids, []), + try([for item in rule.value.source_ip_groups_in_core : data.azurerm_ip_group.referenced[item].id], []) + ) + destination_fqdn_tags = try(rule.value.fqdn_tags, []) + } + } + } + } + + depends_on = [ + azurerm_firewall_policy_rule_collection_group.dynamic_network + ] +} diff --git a/templates/shared_services/firewall/terraform/variables.tf b/templates/shared_services/firewall/terraform/variables.tf index 9ac2916b11..2196a5a369 100644 --- a/templates/shared_services/firewall/terraform/variables.tf +++ b/templates/shared_services/firewall/terraform/variables.tf @@ -17,3 +17,8 @@ variable "api_driven_network_rule_collections_b64" { type = string default = "W10=" #b64 for [] } + +variable "sku_tier" { + type = string + default = "Standard" +}