diff --git a/CHANGELOG.md b/CHANGELOG.md index 680d74b..261c2fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0. ## [Unreleased] +## [1.2.0] - 2024-04-08 + +### Added +- platform rules for bastion + +### Changed +- made rules for private dns resolver and azure dcs optional +- changed internal ordering of rules + +### Removed + +### Fixed + + ## [1.1.0] - 2023-11-17 ### Added diff --git a/README.md b/README.md index c5bd91e..22e6682 100644 --- a/README.md +++ b/README.md @@ -14,51 +14,14 @@ It's very easy to use! ```hcl provider "azurerm" { features {} + skip_provider_registration = true } resource "azurerm_resource_group" "example" { - name = "rg-example-fw" + name = "rg-example-fwp" location = local.location } -resource "azurerm_virtual_network" "example" { - name = "vnet-example" - address_space = ["10.0.0.0/16"] - location = local.location - resource_group_name = azurerm_resource_group.example.name -} - -resource "azurerm_subnet" "example" { - name = "AzureFirewallSubnet" # Must be exact 'AzureFirewallSubnet' - resource_group_name = azurerm_resource_group.example.name - virtual_network_name = azurerm_virtual_network.example.name - address_prefixes = ["10.0.0.0/16"] -} - -resource "azurerm_public_ip" "example" { - name = "pip-example" - location = local.location - resource_group_name = azurerm_resource_group.example.name - allocation_method = "Static" - sku = "Standard" -} - -resource "azurerm_firewall" "example" { - name = "fw-example" - location = local.location - resource_group_name = azurerm_resource_group.example.name - sku_name = "AZFW_VNet" - sku_tier = "Standard" - - firewall_policy_id = azurerm_firewall_policy.example.id - - ip_configuration { - name = "ip-config" - subnet_id = azurerm_subnet.example.id - public_ip_address_id = azurerm_public_ip.example.id - } -} - resource "azurerm_firewall_policy" "example" { name = "fwp-example" resource_group_name = azurerm_resource_group.example.name @@ -79,11 +42,8 @@ module "firewall_rules" { stage = "prd" default_location = local.location - ipg_azure_dc_id = azurerm_ip_group.azure_dc.id - ipg_onpremise_dc_id = azurerm_ip_group.onpremise_dc.id - ipg_application_lz_id = azurerm_ip_group.application_lz.id - ipg_dnsprivateresolver_id = azurerm_ip_group.dnsprivateresolver.id - ipg_platform_id = azurerm_ip_group.platform.id + ipg_application_lz_id = azurerm_ip_group.application_lz.id + ipg_platform_id = azurerm_ip_group.platform.id } ``` @@ -99,37 +59,39 @@ module "firewall_rules" { |------|-------------|------|---------|:--------:| | [default\_location](#input\_default\_location) | The default location used for this module. | `string` | n/a | yes | | [ipg\_application\_lz\_id](#input\_ipg\_application\_lz\_id) | IP ranges for all application landing zones. | `string` | n/a | yes | -| [ipg\_azure\_dc\_id](#input\_ipg\_azure\_dc\_id) | The ip addresses of the domain controller located in azure. | `string` | n/a | yes | -| [ipg\_dnsprivateresolver\_id](#input\_ipg\_dnsprivateresolver\_id) | The ip address of the private dns resolver inbound endpoint. | `string` | n/a | yes | | [ipg\_platform\_id](#input\_ipg\_platform\_id) | IP ranges for the whole platform service, defined by the azure landing zone core modules. | `string` | n/a | yes | | [resource\_group\_name](#input\_resource\_group\_name) | The name of the resource group in which the firewall policy and the azure firewall are located. | `string` | n/a | yes | | [stage](#input\_stage) | The stage that the resource is located in, e.g. prod, dev. | `string` | n/a | yes | +| [bastion\_config](#input\_bastion\_config) |
ipg_bastion_id: If the customer uses bastion, provide the bastion ip-group in this variable.|
ipg_rdp_access_ids: If rdp access is needed, provide vm ip-groups in this variable. Every ip-group provided in this list, will be accessible by bastion.
ipg_ssh_access_ids: If ssh access is needed, provide vm ip-groups in this variable. Every ip-group provided in this list, will be accessible by bastion.
object({| `null` | no | | [firewall\_policy\_id](#input\_firewall\_policy\_id) | For testing use this | `string` | `null` | no | +| [ipg\_azure\_dc\_id](#input\_ipg\_azure\_dc\_id) | The ip addresses of the domain controller located in azure. If the value is not provided, this network rule collection will not be created. | `string` | `null` | no | +| [ipg\_dnsprivateresolver\_id](#input\_ipg\_dnsprivateresolver\_id) | The ip address of the private dns resolver inbound endpoint. If the value is not provided, this network rule collection will not be created | `string` | `null` | no | | [ipg\_onpremise\_dc\_id](#input\_ipg\_onpremise\_dc\_id) | If the customer still operates domain controller on premise, provide these in this variable. | `string` | `null` | no | | [responsibility](#input\_responsibility) | The responsibility means who is responsible for the rule collection, e.g. is this rule collection in this module used as general rule set for the firewall, other responsibilities would be the customer etc. | `string` | `"Platform"` | no | ## Outputs No outputs. -## Resource types - -| Type | Used | -|------|-------| -| [azurerm_firewall_policy_rule_collection_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/firewall_policy_rule_collection_group) | 1 | + ## Resource types -**`Used` only includes resource blocks.** `for_each` and `count` meta arguments, as well as resource blocks of modules are not considered. + | Type | Used | + |------|-------| + | [azurerm_firewall_policy_rule_collection_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/firewall_policy_rule_collection_group) | 1 | + **`Used` only includes resource blocks.** `for_each` and `count` meta arguments, as well as resource blocks of modules are not considered. + ## Modules No modules. -## Resources by Files + ## Resources by Files -### main.tf + ### main.tf -| Name | Type | -|------|------| -| [azurerm_firewall_policy_rule_collection_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/firewall_policy_rule_collection_group) | resource | + | Name | Type | + |------|------| + | [azurerm_firewall_policy_rule_collection_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/firewall_policy_rule_collection_group) | resource | + ## Contribute diff --git a/examples/advanced/.terraform.lock.hcl b/examples/advanced/.terraform.lock.hcl new file mode 100644 index 0000000..32fa42e --- /dev/null +++ b/examples/advanced/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.63.0" + constraints = ">= 3.7.0" + hashes = [ + "h1:Ryn16R1e1IdMooHkTdYpMQ5Lnrex7wzd6lsEEV+2GWY=", + "zh:0bb8263de0abf1a7457168673f9fcf7e404ae59d03d0e8eda91f1e024f8d9253", + "zh:24dd5883c95801f2d4a88be22b9a3bb4e20de7b6b65e8b7cc90b12a0895d7adf", + "zh:4fe19fe81a68811d09d33aeea05f20987bef84dc7377c4b34782b94dda2c658f", + "zh:673fcd9d15b3f1307a1c41323598678b9a705751851d0f65e6abdf78c9e5361f", + "zh:6c401d348d04436ed891482c3e2151c34d6fbce0a6ee8880c6025de589e22e9a", + "zh:6f6b9909d62e9928d56b1d02c88a514d45022fae72048166e58e288759c73493", + "zh:7de2aa6636ba657166ef992a3b7a822394a2d1f8c319fdbabec69b99950990ea", + "zh:976ca97ab21708f8707c360c18dc64c03a6e497b7a157bb0ff7a8a54c03ebc55", + "zh:af220c20ce6e76c4c072fbc9aa3c02597260117ba7deaa0e0d585fb1957a775b", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:fb4055762069cf0f58a36110853811f63e52c7077e332d8731a66fade1ba9f15", + "zh:fc95c1c80e98c317ec38fa33d16d55b5a62058dc893fbf578d100fb91401356e", + ] +} diff --git a/examples/advanced/ip_groups.tf b/examples/advanced/ip_groups.tf new file mode 100644 index 0000000..edd7aca --- /dev/null +++ b/examples/advanced/ip_groups.tf @@ -0,0 +1,71 @@ +resource "azurerm_ip_group" "azure_dc" { + name = "ipg-azure-dcs" + location = local.location + resource_group_name = azurerm_resource_group.example.name + + cidrs = ["10.0.0.10/32", "10.0.0.11/32"] + + lifecycle { + ignore_changes = [tags] + } +} + +resource "azurerm_ip_group" "onpremise_dc" { + name = "ipg-onprem-dcs" + location = local.location + resource_group_name = azurerm_resource_group.example.name + + cidrs = [] + + lifecycle { + ignore_changes = [tags] + } +} + +resource "azurerm_ip_group" "dnsprivateresolver" { + name = "ipg-DNSPrivateResolver" + location = local.location + resource_group_name = azurerm_resource_group.example.name + + cidrs = ["10.0.1.0/24"] + + lifecycle { + ignore_changes = [tags] + } +} + +resource "azurerm_ip_group" "application_lz" { + name = "ipg-application-landing-zone" + location = local.location + resource_group_name = azurerm_resource_group.example.name + + cidrs = ["10.0.2.0/24"] + + lifecycle { + ignore_changes = [tags] + } +} + +resource "azurerm_ip_group" "platform" { + name = "ipg-platform" + location = local.location + resource_group_name = azurerm_resource_group.example.name + + cidrs = ["10.0.2.0/24"] + + lifecycle { + ignore_changes = [tags] + } +} + +resource "azurerm_ip_group" "bastion" { + name = "ipg-bastion" + location = local.location + resource_group_name = azurerm_resource_group.example.name + + cidrs = ["10.0.2.0/24"] + + lifecycle { + ignore_changes = [tags] + } +} \ No newline at end of file diff --git a/examples/advanced/locals.tf b/examples/advanced/locals.tf new file mode 100644 index 0000000..816ce75 --- /dev/null +++ b/examples/advanced/locals.tf @@ -0,0 +1,3 @@ +locals { + location = "West Europe" +} diff --git a/examples/advanced/main.tf b/examples/advanced/main.tf new file mode 100644 index 0000000..38bac5e --- /dev/null +++ b/examples/advanced/main.tf @@ -0,0 +1,40 @@ +provider "azurerm" { + features {} + skip_provider_registration = true +} + +resource "azurerm_resource_group" "example" { + name = "rg-example-fwp" + location = local.location +} + +resource "azurerm_firewall_policy" "example" { + name = "fwp-example" + resource_group_name = azurerm_resource_group.example.name + location = local.location + + dns { + proxy_enabled = true + } +} + +module "firewall_rules" { + source = "../.." + + firewall_policy_id = azurerm_firewall_policy.example.id + resource_group_name = azurerm_resource_group.example.name + + responsibility = "Platform" + stage = "prd" + default_location = local.location + + ipg_dnsprivateresolver_id = azurerm_ip_group.dnsprivateresolver.id + ipg_azure_dc_id = azurerm_ip_group.azure_dc.id + ipg_application_lz_id = azurerm_ip_group.application_lz.id + ipg_platform_id = azurerm_ip_group.platform.id + bastion_config = { + ipg_bastion_id = azurerm_ip_group.bastion.id + ipg_rdp_access_ids = [azurerm_ip_group.application_lz.id] + ipg_ssh_access_ids = [azurerm_ip_group.application_lz.id] + } +} diff --git a/examples/basic/ip_groups.tf b/examples/basic/ip_groups.tf index 75b584a..edd7aca 100644 --- a/examples/basic/ip_groups.tf +++ b/examples/basic/ip_groups.tf @@ -47,7 +47,7 @@ resource "azurerm_ip_group" "application_lz" { } resource "azurerm_ip_group" "platform" { - name = "ipg-application-landing-zone" + name = "ipg-platform" location = local.location resource_group_name = azurerm_resource_group.example.name @@ -57,3 +57,15 @@ resource "azurerm_ip_group" "platform" { ignore_changes = [tags] } } + +resource "azurerm_ip_group" "bastion" { + name = "ipg-bastion" + location = local.location + resource_group_name = azurerm_resource_group.example.name + + cidrs = ["10.0.2.0/24"] + + lifecycle { + ignore_changes = [tags] + } +} \ No newline at end of file diff --git a/examples/basic/main.tf b/examples/basic/main.tf index c796141..82108e7 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -1,50 +1,13 @@ provider "azurerm" { features {} + skip_provider_registration = true } resource "azurerm_resource_group" "example" { - name = "rg-example-fw" + name = "rg-example-fwp" location = local.location } -resource "azurerm_virtual_network" "example" { - name = "vnet-example" - address_space = ["10.0.0.0/16"] - location = local.location - resource_group_name = azurerm_resource_group.example.name -} - -resource "azurerm_subnet" "example" { - name = "AzureFirewallSubnet" # Must be exact 'AzureFirewallSubnet' - resource_group_name = azurerm_resource_group.example.name - virtual_network_name = azurerm_virtual_network.example.name - address_prefixes = ["10.0.0.0/16"] -} - -resource "azurerm_public_ip" "example" { - name = "pip-example" - location = local.location - resource_group_name = azurerm_resource_group.example.name - allocation_method = "Static" - sku = "Standard" -} - -resource "azurerm_firewall" "example" { - name = "fw-example" - location = local.location - resource_group_name = azurerm_resource_group.example.name - sku_name = "AZFW_VNet" - sku_tier = "Standard" - - firewall_policy_id = azurerm_firewall_policy.example.id - - ip_configuration { - name = "ip-config" - subnet_id = azurerm_subnet.example.id - public_ip_address_id = azurerm_public_ip.example.id - } -} - resource "azurerm_firewall_policy" "example" { name = "fwp-example" resource_group_name = azurerm_resource_group.example.name @@ -65,9 +28,6 @@ module "firewall_rules" { stage = "prd" default_location = local.location - ipg_azure_dc_id = azurerm_ip_group.azure_dc.id - ipg_onpremise_dc_id = azurerm_ip_group.onpremise_dc.id - ipg_application_lz_id = azurerm_ip_group.application_lz.id - ipg_dnsprivateresolver_id = azurerm_ip_group.dnsprivateresolver.id - ipg_platform_id = azurerm_ip_group.platform.id + ipg_application_lz_id = azurerm_ip_group.application_lz.id + ipg_platform_id = azurerm_ip_group.platform.id } diff --git a/main.tf b/main.tf index 0b9df6e..abf6c2c 100644 --- a/main.tf +++ b/main.tf @@ -3,40 +3,9 @@ resource "azurerm_firewall_policy_rule_collection_group" "this" { firewall_policy_id = var.firewall_policy_id priority = 100 - network_rule_collection { - name = "rc-DomainController-${var.stage}" - priority = 100 - action = "Allow" - - rule { - name = "allow-alz-to-dc-inbound" - protocols = ["TCP", "UDP"] - source_ip_groups = [var.ipg_application_lz_id] - destination_ip_groups = var.ipg_onpremise_dc_id != null ? [var.ipg_azure_dc_id, var.ipg_onpremise_dc_id] : [var.ipg_azure_dc_id] - destination_ports = [ - "53", "88", "123", "135", "137", "138", "139", - "389", "445", "464", "636", "3268", "3269", "9389" - ] - } - } - - network_rule_collection { - name = "rc-DNSPrivateResolver-${var.stage}" - priority = 110 - action = "Allow" - - rule { - name = "allow-dc-to-dnsresolver-inbound" - protocols = ["Any"] - source_ip_groups = var.ipg_onpremise_dc_id != null ? [var.ipg_azure_dc_id, var.ipg_onpremise_dc_id] : [var.ipg_azure_dc_id] - destination_ip_groups = [var.ipg_dnsprivateresolver_id] - destination_ports = ["*"] - } - } - network_rule_collection { name = "rc-internet_outbound-${var.stage}" - priority = 120 + priority = 100 action = "Allow" rule { @@ -77,9 +46,77 @@ resource "azurerm_firewall_policy_rule_collection_group" "this" { } } + dynamic "network_rule_collection" { + for_each = var.ipg_azure_dc_id == null ? [] : [var.ipg_azure_dc_id] + content { + name = "rc-DomainController-${var.stage}" + priority = 105 + action = "Allow" + + rule { + name = "allow-alz-to-dc-inbound" + protocols = ["TCP", "UDP"] + source_ip_groups = [var.ipg_application_lz_id] + destination_ip_groups = var.ipg_onpremise_dc_id != null ? [var.ipg_azure_dc_id, var.ipg_onpremise_dc_id] : [var.ipg_azure_dc_id] + destination_ports = [ + "53", "88", "123", "135", "137", "138", "139", + "389", "445", "464", "636", "3268", "3269", "9389" + ] + } + } + } + + dynamic "network_rule_collection" { + for_each = var.ipg_dnsprivateresolver_id == null ? [] : [var.ipg_dnsprivateresolver_id] + content { + name = "rc-DNSPrivateResolver-${var.stage}" + priority = 110 + action = "Allow" + + rule { + name = "allow-dc-to-dnsresolver-inbound" + protocols = ["Any"] + source_ip_groups = var.ipg_onpremise_dc_id != null ? [var.ipg_azure_dc_id, var.ipg_onpremise_dc_id] : [var.ipg_azure_dc_id] + destination_ip_groups = [var.ipg_dnsprivateresolver_id] + destination_ports = ["*"] + } + } + } + + dynamic "network_rule_collection" { + for_each = var.bastion_config == null ? [] : [var.bastion_config.ipg_bastion_id] + content { + name = "rc-Bastion-${var.stage}" + priority = 115 + action = "Allow" + + dynamic "rule" { + for_each = var.bastion_config.ipg_rdp_access_ids + content { + name = "allow-bastion-to-${regex(".+\\/(.+)?", rule.value)[0]}-rdp" + protocols = ["TCP"] + source_ip_groups = [network_rule_collection.value] + destination_ip_groups = [rule.value] + destination_ports = ["3389"] + } + } + + dynamic "rule" { + for_each = var.bastion_config.ipg_ssh_access_ids + content { + name = "allow-bastion-to-${regex(".+\\/(.+)?", rule.value)[0]}-ssh" + protocols = ["TCP"] + source_ip_groups = [network_rule_collection.value] + destination_ip_groups = [rule.value] + destination_ports = ["22"] + } + } + } + } + application_rule_collection { name = "rc-application_internet_outbound-${var.stage}" - priority = 130 + priority = 150 action = "Allow" rule { diff --git a/variables.tf b/variables.tf index 57cfb92..b6ec422 100644 --- a/variables.tf +++ b/variables.tf @@ -29,7 +29,8 @@ variable "default_location" { variable "ipg_azure_dc_id" { type = string - description = "The ip addresses of the domain controller located in azure." + description = "The ip addresses of the domain controller located in azure. If the value is not provided, this network rule collection will not be created." + default = null } variable "ipg_onpremise_dc_id" { @@ -40,7 +41,8 @@ variable "ipg_onpremise_dc_id" { variable "ipg_dnsprivateresolver_id" { type = string - description = "The ip address of the private dns resolver inbound endpoint." + description = "The ip address of the private dns resolver inbound endpoint. If the value is not provided, this network rule collection will not be created" + default = null } variable "ipg_application_lz_id" { @@ -52,3 +54,19 @@ variable "ipg_platform_id" { type = string description = "IP ranges for the whole platform service, defined by the azure landing zone core modules." } + +variable "bastion_config" { + type = object({ + ipg_bastion_id = string + ipg_rdp_access_ids = optional(list(string), []) + ipg_ssh_access_ids = optional(list(string), []) + }) + default = null + description = <<-DOC + ``` + ipg_bastion_id: If the customer uses bastion, provide the bastion ip-group in this variable. + ipg_rdp_access_ids: If rdp access is needed, provide vm ip-groups in this variable. Every ip-group provided in this list, will be accessible by bastion. + ipg_ssh_access_ids: If ssh access is needed, provide vm ip-groups in this variable. Every ip-group provided in this list, will be accessible by bastion. + ``` + DOC +} \ No newline at end of file
ipg_bastion_id = string
ipg_rdp_access_ids = optional(list(string), [])
ipg_ssh_access_ids = optional(list(string), [])
})