diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ddbb1ac..de97c1a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -44,3 +44,31 @@ updates: - "Crew" # Allow up to 3 open pull requests for pip dependencies open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/examples/firewall-with-isolated-rules" # Location of package manifests + schedule: + interval: "daily" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + - "Crew" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/examples/firewall-with-public-ip-prefix" # Location of package manifests + schedule: + interval: "daily" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + - "Crew" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml deleted file mode 100644 index 1ee6f78..0000000 --- a/.github/workflows/changelog.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: changelog -permissions: write-all -on: - push: - tags: - - "*" - workflow_dispatch: -jobs: - changelog: - uses: clouddrove/github-shared-workflows/.github/workflows/changelog.yml@master - secrets: inherit - with: - branch: 'master' diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml deleted file mode 100644 index 444164d..0000000 --- a/.github/workflows/readme.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Readme Workflow -on: - push: - branches: - - master - paths-ignore: - - 'README.md' - - 'docs/**' - workflow_dispatch: -jobs: - README: - uses: clouddrove/github-shared-workflows/.github/workflows/readme.yml@master - secrets: - TOKEN : ${{ secrets.GITHUB }} - SLACK_WEBHOOK_TERRAFORM: ${{ secrets.SLACK_WEBHOOK_TERRAFORM }} \ No newline at end of file diff --git a/.github/workflows/terraform-diff.yml b/.github/workflows/terraform-diff.yml index b009b3d..5bf49fc 100644 --- a/.github/workflows/terraform-diff.yml +++ b/.github/workflows/terraform-diff.yml @@ -1,4 +1,4 @@ -name: Terraform plan Difference +name: Terraform Plan Difference on: pull_request: branches: @@ -12,6 +12,26 @@ jobs: provider: 'azurerm' terraform_directory: 'examples/complete' target_branch: 'master' + secrets: + AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + + firewall-with-isolated-rules-example: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-pr-checks.yaml@master + with: + provider: 'azurerm' + terraform_directory: 'examples/firewall-with-isolated-rules' + target_branch: 'master' + secrets: + AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} + ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} + + firewall-with-public-ip-prefix-example: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-pr-checks.yaml@master + with: + provider: 'azurerm' + terraform_directory: 'examples/firewall-with-public-ip-prefix' + target_branch: 'master' secrets: AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }} \ No newline at end of file diff --git a/.github/workflows/tf-checks.yml b/.github/workflows/tf-checks.yml index 3fcc04e..d4fd5fe 100644 --- a/.github/workflows/tf-checks.yml +++ b/.github/workflows/tf-checks.yml @@ -9,8 +9,17 @@ jobs: complete-example: uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master with: - working_directory: './examples/complete/' + working_directory: 'examples/complete/' + + firewall-with-isolated-rules-example: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: 'examples/firewall-with-isolated-rules/' + firewall-with-public-ip-prefix-example: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: 'examples/firewall-with-public-ip-prefix/' # Seperate Job for TFlint workflow call tf-lint: uses: clouddrove/github-shared-workflows/.github/workflows/tf-lint.yml@master diff --git a/README.md b/README.md index 03015cf..9ee000a 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ Changelog -

@@ -32,17 +31,16 @@ - + - +


- We are a group of DevOps engineers and architects collaborating to build standardized, scalable, and secure infrastructure in today's ever-evolving digital landscape. Rooted in a strong belief in automation and modular designβ€”much like microservicesβ€”we focus on decomposing infrastructure into smaller, reusable components such as databases, clusters, and more. These components are built to follow industry best practices and are easy to manage, scale, and secure. This repository is part of the **terraform-az-modules** organization and provides open-source, reusable Terraform modules. It includes practical examples and workflows to help users quickly understand, implement, and improve their infrastructure with minimal configuration and high maintainability. @@ -57,7 +55,7 @@ This table contains both Prerequisites and Providers: | Description | Name | Version | |:-------------:|:-------------------------------------------:|:---------:| | **Prerequisite** | [Terraform](https://learn.hashicorp.com/terraform/getting-started/install.html) | >= 1.6.6 | -| **Provider** | [azure](https://azure.microsoft.com/) | >= 3.90.0 | +| **Provider** | [azure](https://azure.microsoft.com/) | >= 3.116.0 | @@ -65,35 +63,107 @@ This table contains both Prerequisites and Providers: ## Examples -**IMPORTANT:** Since the master branch used in source varies based on new modifications, we recommend using the [release versions](https://github.com/terraform-az-modules/terraform-module-template/releases). +**IMPORTANT:** Since the master branch used in source varies based on new modifications, we recommend using the [release versions](https://github.com/terraform-az-modules/terraform-azure-firewall/releases). πŸ“Œ For additional usage examples, check the complete list under [`examples/`](./examples) directory. +## Providers -## Inputs and Outputs +| Name | Version | +|------|---------| +| [azurerm](#provider\_azurerm) | >=3.116.0 | -### Inputs +## Modules -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| label_order | Label order, e.g. `name`,`application`,`centralus`. | `list(any)` |
["name","environment",  "location"]
| no | +| Name | Source | Version | +|------|--------|---------| +| [labels](#module\_labels) | terraform-az-modules/tags/azure | 1.0.0 | -### Outputs - -| Name | Description | -|------|-------------| -| label_order | Label order, e.g. `name`,`application`,`centralus`. | +## Resources +| Name | Type | +|------|------| +| [azurerm_firewall.firewall](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/firewall) | resource | +| [azurerm_firewall_policy.policy](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/firewall_policy) | resource | +| [azurerm_firewall_policy_rule_collection_group.app_policy_rule_collection_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/firewall_policy_rule_collection_group) | resource | +| [azurerm_firewall_policy_rule_collection_group.nat_policy_rule_collection_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/firewall_policy_rule_collection_group) | resource | +| [azurerm_firewall_policy_rule_collection_group.network_policy_rule_collection_group](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/firewall_policy_rule_collection_group) | resource | +| [azurerm_monitor_diagnostic_setting.firewall_diagnostic_setting](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/monitor_diagnostic_setting) | resource | +| [azurerm_public_ip.primary_public_ip](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | +| [azurerm_public_ip.public_ip](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip) | resource | +| [azurerm_public_ip_prefix.pip-prefix](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip_prefix) | resource | +| [azurerm_user_assigned_identity.identity](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/user_assigned_identity) | resource | +## Inputs - +| Name | Description | +|------|-------------| +| [firewall\_id](#output\_firewall\_id) | Firewall ID | +| [firewall\_name](#output\_firewall\_name) | Firewall name | +| [firewall\_policy\_id](#output\_firewall\_policy\_id) | value of firewall policy ID | +| [primary\_public\_ip\_address](#output\_primary\_public\_ip\_address) | Primary public IP address (clean) | +| [primary\_public\_ip\_id](#output\_primary\_public\_ip\_id) | ID of the primary public IP (clean) | +| [primary\_public\_ip\_name](#output\_primary\_public\_ip\_name) | Name of the primary public IP (clean) | +| [public\_ip\_addresses](#output\_public\_ip\_addresses) | value of public IP addresses | +| [public\_ip\_ids](#output\_public\_ip\_ids) | The IDs of all public IPs | +| [public\_ip\_prefix\_id](#output\_public\_ip\_prefix\_id) | value of public IP prefix ID | ## Module Dependencies @@ -107,8 +177,6 @@ This module has dependencies on: Refer [here](CHANGELOG.md). - - ## ✨ Contributors Big thanks to our contributors for elevating our project with their dedication and expertise! But, we do not wish to stop there, would like to invite contributions from the community in improving these projects and making them more versatile for better reach. Remember, every bit of contribution is immensely valuable, as, together, we are moving in only 1 direction, i.e. forward. diff --git a/README.yaml b/README.yaml index e8b3a21..273a829 100644 --- a/README.yaml +++ b/README.yaml @@ -5,13 +5,13 @@ # # Name of this project -name : Terraform Azure Module Template +name: Terraform Azure Firewall Module # License of this project license: "APACHE" # Canonical GitHub repo -github_repo: terraform-az-modules/terraform-module-template +github_repo: terraform-az-modules/terraform-azure-firewall # Badges to display badges: @@ -37,13 +37,267 @@ providers: version: ">= 3.116.0" # description of this project +# description of this project description: |- - Terraform Azure Module Template to create new modules using this as baseline + The Terraform Azure Firewall Module is designed to simplify the deployment and management of Azure Firewall resources. + It provides a comprehensive solution for securing your Azure environment by enabling centralized network security policies, + traffic filtering, and threat protection. This module supports advanced features such as DDoS protection, + firewall policies, rule collections (application, network, and NAT), and diagnostic settings for monitoring and auditing. + + Key Features: + - Deploy Azure Firewall with customizable configurations. + - Manage public IPs and public IP prefixes for scalability. + - Define firewall policies with advanced security features like TLS inspection and IDPS. + - Configure application, network, and NAT rule collections for granular traffic control. + - Enable diagnostic settings for centralized monitoring and troubleshooting. + - Support for tagging to ensure traceability and cost management. + + Use this module to enhance the security posture of your Azure environment while maintaining flexibility and scalability. -# How to use this project # How to use this project usage: |- Here are some examples of how you can use this module in your inventory structure: ```hcl - locals {} - ``` \ No newline at end of file + ### Simple Example + Here is an example of how you can use this module in your inventory structure: + ### Default example + ```hcl + module "firewall" { + depends_on = [module.name_specific_subnet] + source = "clouddrove/firewall/azure" + name = "app" + environment = "test" + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + subnet_id = module.name_specific_subnet.subnet_ids["AzureFirewallSubnet"] + public_ip_names = ["ingress", "vnet"] // Name of public ips you want to create. + + # additional_public_ips = [{ + # name = "public-ip_name", + # public_ip_address_id = "public-ip_resource_id" + # } ] + firewall_enable = true + policy_rule_enabled = true + enable_diagnostic = true + log_analytics_workspace_id = module.log-analytics.workspace_id + + application_rule_collection = [ + { + name = "example_app_policy" + priority = 200 + action = "Allow" + rules = [ + { + name = "app_test" + source_addresses = ["*"] // ["X.X.X.X"] + destination_fqdns = ["*"] // ["X.X.X.X"] + protocols = [ + { + port = "443" + type = "Https" + }, + { + port = "80" + type = "Http" + } + ] + } + ] + } + ] + + network_rule_collection = [ + { + name = "example_network_policy" + priority = "100" + action = "Allow" + rules = [ + { + name = "ssh" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["22"] + } + + ] + }, + { + name = "example_network_policy-2" + priority = "101" + action = "Allow" + rules = [ + { + name = "smtp" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["587"] + } + ] + } + ] + + nat_rule_collection = [ + { + name = "example_nat_policy-1" + priority = "101" + rules = [ + { + name = "http" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["80"] + source_addresses = ["*"] + translated_port = "80" + translated_address = "X.X.X.X" #provide private ip address to translate + destination_address = module.firewall.public_ip_address //Public ip associated with firewall. + + }, + ] + }, + } + + ``` + ### firewall-with-isolated-rules + ```hcl + module "firewall" { + depends_on = [module.name_specific_subnet] + source = "clouddrove/firewall/azure" + name = "app" + environment = "test" + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + subnet_id = module.name_specific_subnet.subnet_ids["AzureFirewallSubnet"] + public_ip_names = ["ingress", "vnet"] // Name of public ips you want to create. + + # additional_public_ips = [{ + # name = "public-ip_name", + # public_ip_address_id = "public-ip_resource_id" + # } ] + firewall_enable = true + enable_diagnostic = true + log_analytics_workspace_id = module.log-analytics.workspace_id + + } + module "firewall-rules" { + depends_on = [module.firewall] + source = "clouddrove/firewall/azure" + name = "app" + environment = "test" + policy_rule_enabled= true + firewall_policy_id = module.firewall.firewall_policy_id + + application_rule_collection = [ + { + name = "example_app_policy" + priority = 200 + action = "Allow" + rules = [ + { + name = "app_test" + source_addresses = ["*"] // ["X.X.X.X"] + destination_fqdns = ["*"] // ["X.X.X.X"] + protocols = [ + { + port = "443" + type = "Https" + }, + { + port = "80" + type = "Http" + } + ] + } + ] + } + ] + + network_rule_collection = [ + { + name = "example_network_policy" + priority = 100 + action = "Allow" + rules = [ + { + name = "ssh" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["22"] + } + + ] + }, + { + name = "example_network_policy-2" + priority = "101" + action = "Allow" + rules = [ + { + name = "smtp" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["587"] + } + ] + } + ] + + nat_rule_collection = [ + { + name = "example_nat_policy-1" + priority = "101" + rules = [ + { + name = "http" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["80"] + source_addresses = ["*"] + translated_port = "80" + translated_address = "10.1.1.1" #provide private ip address to translate + destination_address = module.firewall.public_ip_address[1] //Public ip associated with firewall. + }, + { + name = "https" + protocols = ["TCP"] + destination_ports = ["443"] + source_addresses = ["*"] + translated_port = "443" + translated_address = "10.1.1.1" #provide private ip address to translate + destination_address = module.firewall.public_ip_address[1] //Public ip associated with firewall + + } + ] + }, + + { + name = "example-nat-policy-2" + priority = "100" + rules = [ + { + name = "http" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["80"] + translated_port = "80" + translated_address = "10.1.1.2" #provide private ip address to translate + destination_address = module.firewall.public_ip_address[0] //Public ip associated with firewall + }, + { + name = "https" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["443"] + translated_port = "443" + translated_address = "10.1.1.2" #provide private ip address to translate + destination_address = module.firewall.public_ip_address[0] //Public ip associated with firewall + } + ] + } + ] + } + + ``` diff --git a/examples/complete/README.md b/examples/complete/README.md index 6dced76..3947e9f 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -1,18 +1,17 @@ -# Terraform Azure Module Template +# Terraform Azure Application Insights -This directory contains an example usage of the **terraform-azure-module-template**. It demonstrates how to use the module with default settings or with custom configurations. +This directory contains an example usage of the **terraform-azure-firewall**. It demonstrates how to use the module with default settings or with custom configurations. --- ## πŸ“‹ Requirements -| Name | Version | -|-----------|-----------| -| Terraform | >= 1.6.6 | -| Azurerm | >= 3.116.0| - +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.6.6 | +| [azurerm](#requirement\_azurerm) | >=3.116.0 | --- ## πŸ”Œ Providers @@ -22,9 +21,13 @@ None specified in this example. --- ## πŸ“¦ Modules - -None specified in this example. - +| Name | Source | Version | +|------|--------|---------| +| [firewall](#module\_firewall) | ../.. | n/a | +| [log-analytics](#module\_log-analytics) | terraform-az-modules/log-analytics/azure | 1.0.0 | +| [name\_specific\_subnet](#module\_name\_specific\_subnet) | terraform-az-modules/subnet/azure | 1.0.0 | +| [resource\_group](#module\_resource\_group) | terraform-az-modules/resource-group/azure | 1.0.0 | +| [vnet](#module\_vnet) | terraform-az-modules/vnet/azure | 1.0.0 | --- ## πŸ—οΈ Resources @@ -41,6 +44,12 @@ No input variables are defined in this example. ## πŸ“€ Outputs -No outputs are defined in this example. +| Name | Description | +|------|-------------| +| [firewall\_id](#output\_firewall\_id) | Firewall generated id | +| [primary\_public\_ip\_address](#output\_primary\_public\_ip\_address) | The Primary public IP address associated with the firewall | +| [primary\_public\_ip\_id](#output\_primary\_public\_ip\_id) | The Primary public IP associated with the firewall | +| [public\_ip\_address](#output\_public\_ip\_address) | The public IP address associated with the firewall | +| [public\_ip\_id](#output\_public\_ip\_id) | The public IP associated with the firewall | diff --git a/examples/complete/example.tf b/examples/complete/example.tf index 773bdbd..f2756b3 100644 --- a/examples/complete/example.tf +++ b/examples/complete/example.tf @@ -2,6 +2,193 @@ provider "azurerm" { features {} } +locals { + name = "app" + environment = "test" + # Define public IPs as a local variable for reusability + public_ip_names = ["vnet", "app"] +} +##----------------------------------------------------------------------------- +## Resource Group module call +## Resource group in which all resources will be deployed. ##----------------------------------------------------------------------------- -## Resources +module "resource_group" { + source = "terraform-az-modules/resource-group/azure" + version = "1.0.0" + name = local.name + environment = local.environment + label_order = ["name", "environment", ] + location = "East US" +} + +##----------------------------------------------------------------------------- +## Virtual Network module call. +## Virtual Network in firewall specific subnet will be created. +##----------------------------------------------------------------------------- +module "vnet" { + depends_on = [module.resource_group] + source = "terraform-az-modules/vnet/azure" + version = "1.0.0" + name = local.name + environment = local.environment + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + address_spaces = ["10.0.0.0/16"] +} + +##----------------------------------------------------------------------------- +## Subnet module call. +## Name specific subnet for firewall will be created. +##----------------------------------------------------------------------------- +module "name_specific_subnet" { + depends_on = [module.vnet] + source = "terraform-az-modules/subnet/azure" + version = "1.0.0" + environment = "test" + label_order = ["name", "environment", ] + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + virtual_network_name = module.vnet.vnet_name + subnets = [ + { + name = "AzureFirewallSubnet" + subnet_prefixes = ["10.0.1.0/24"] + } + ] + enable_route_table = true + route_tables = [ + { + name = "route-table" + routes = [ + { + name = "route-table" + address_prefix = "0.0.0.0/0" + next_hop_type = "Internet" + } + ] + } + ] +} + +##----------------------------------------------------------------------------- +## Log Analytic Module Call. +## Log Analytic workspace for firerwall diagnostic setting. ##----------------------------------------------------------------------------- +module "log-analytics" { + source = "terraform-az-modules/log-analytics/azure" + version = "1.0.0" + name = local.name + environment = local.environment + label_order = ["name", "environment"] + log_analytics_workspace_sku = "PerGB2018" + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location +} + +##----------------------------------------------------------------------------- +## Firewall module call. +## All firewall related resources will be deployed from this module, i.e. including firewall and firewall rules. +##----------------------------------------------------------------------------- +module "firewall" { + depends_on = [module.name_specific_subnet] + source = "../.." + name = local.name + environment = local.environment + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + subnet_id = module.name_specific_subnet.subnet_ids["AzureFirewallSubnet"] + public_ip_names = local.public_ip_names + firewall_enable = true + public_ip_prefix_enable = true + public_ip_prefix_length = 28 + policy_rule_enabled = true + enable_diagnostic = true + log_analytics_workspace_id = module.log-analytics.workspace_id + logs = [ + { + category = "AzureFirewallApplicationRule" + }, + { + category = "AzureFirewallNetworkRule" + }, + { + category = "AzureFirewallDnsProxy" + }, + ] + + application_rule_collection = [ + { + name = "example_app_policy" + priority = 200 + action = "Allow" + rules = [ + { + name = "app_test" + source_addresses = ["*"] // ["X.X.X.X"] + destination_fqdns = ["*"] // ["X.X.X.X"] + protocols = [ + { + port = "443" + type = "Https" + }, + { + port = "80" + type = "Http" + } + ] + } + ] + } + ] + + network_rule_collection = [ + { + name = "example_network_policy" + priority = 100 + action = "Allow" + rules = [ + { + name = "ssh" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["22"] + } + + ] + }, + { + name = "example_network_policy-2" + priority = "101" + action = "Allow" + rules = [ + { + name = "smtp" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["587"] + } + ] + } + ] + + nat_rule_collection = [ + { + name = "web_server_nat_policy" + priority = 100 + description = "Redirects external traffic to internal web server" + rules = [ + { + name = "web_server_nat" + protocols = ["TCP"] + source_addresses = ["*"] # Any source + destination_address = module.firewall.public_ip_addresses["vnet"] # Your firewall's PUBLIC IP + destination_ports = ["8080"] # External port + translated_address = "10.0.1.20" # Internal server IP + translated_port = "80" # Internal port + } + ] + } + ] +} diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index 35553e0..ae82a4a 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -1,3 +1,13 @@ -##----------------------------------------------------------------------------- -## Outputs -##----------------------------------------------------------------------------- +output "firewall_id" { + description = "Firewall generated id" + value = module.firewall.firewall_id +} +output "public_ip_id" { + value = module.firewall.public_ip_ids + description = "The public IP associated with the firewall" +} + +output "public_ip_address" { + value = module.firewall.public_ip_addresses + description = "The public IP address associated with the firewall" +} diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf deleted file mode 100644 index d5a4b06..0000000 --- a/examples/complete/variables.tf +++ /dev/null @@ -1,3 +0,0 @@ -##----------------------------------------------------------------------------- -## Variables -##----------------------------------------------------------------------------- diff --git a/examples/complete/versions.tf b/examples/complete/version.tf similarity index 100% rename from examples/complete/versions.tf rename to examples/complete/version.tf diff --git a/examples/firewall-with-isolated-rules/README.md b/examples/firewall-with-isolated-rules/README.md new file mode 100644 index 0000000..612f271 --- /dev/null +++ b/examples/firewall-with-isolated-rules/README.md @@ -0,0 +1,60 @@ + + +# Terraform Azure Application Insights + +This directory contains an example usage of the **terraform-azure-firewall**. It demonstrates how to use the module with default settings or with custom configurations. + +--- + +## πŸ“‹ Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.6.6 | +| [azurerm](#requirement\_azurerm) | >=3.116.0 | + +--- + +## πŸ”Œ Providers + +None specified in this example. + +--- + +## πŸ“¦ Modules + +| Name | Source | Version | +|------|--------|---------| +| [firewall](#module\_firewall) | ../.. | n/a | +| [firewall-rules](#module\_firewall-rules) | ../.. | n/a | +| [log-analytics](#module\_log-analytics) | terraform-az-modules/log-analytics/azure | 1.0.0 | +| [name\_specific\_subnet](#module\_name\_specific\_subnet) | terraform-az-modules/subnet/azure | 1.0.0 | +| [resource\_group](#module\_resource\_group) | terraform-az-modules/resource-group/azure | 1.0.0 | +| [vnet](#module\_vnet) | terraform-az-modules/vnet/azure | 1.0.0 | + +--- + +## πŸ—οΈ Resources + +No resources are directly created in this example. + +--- + +## πŸ”§ Inputs + +No input variables are defined in this example. + +--- + +## πŸ“€ Outputs + +| Name | Description | +|------|-------------| +| [firewall\_id](#output\_firewall\_id) | Firewall generated id | +| [firewall\_name](#output\_firewall\_name) | The name of the Firewall | +| [public\_ip\_address](#output\_public\_ip\_address) | value of public IP Addresses | +| [public\_ip\_id](#output\_public\_ip\_id) | value of public IP IDs | + + + + diff --git a/examples/firewall-with-isolated-rules/example.tf b/examples/firewall-with-isolated-rules/example.tf new file mode 100644 index 0000000..9f01c66 --- /dev/null +++ b/examples/firewall-with-isolated-rules/example.tf @@ -0,0 +1,196 @@ +provider "azurerm" { + features {} +} + +locals { + name = "app" + environment = "test" +} + +##----------------------------------------------------------------------------- +## Resource Group module call +## Resource group in which all resources will be deployed. +##----------------------------------------------------------------------------- +module "resource_group" { + source = "../../../terraform-azure-resource-group" #"terraform-az-modules/resource-group/azure" + # version = "1.0.1" + name = local.name + environment = local.environment + label_order = ["name", "environment"] + location = "East US" +} + +##----------------------------------------------------------------------------- +## Virtual Network module call. +## Virtual Network in firewall specific subnet will be created. +##----------------------------------------------------------------------------- +module "vnet" { + depends_on = [module.resource_group] + source = "../../../terraform-azure-vnet" #"terraform-az-modules/vnet/azure" + #version = "1.0.0" + name = local.name + environment = local.environment + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + address_spaces = ["10.0.0.0/16"] +} + +##----------------------------------------------------------------------------- +## Subnet module call. +## Name specific subnet for firewall will be created. +##----------------------------------------------------------------------------- +module "name_specific_subnet" { + depends_on = [module.vnet] + source = "../../../terraform-azure-subnet" #"terraform-az-modules/subnet/azure" + #version = "1.0.0" + environment = "test" + label_order = ["name", "environment", ] + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + virtual_network_name = module.vnet.vnet_name + subnets = [ + { + name = "AzureFirewallSubnet" + subnet_prefixes = ["10.0.1.0/24"] + } + ] + enable_route_table = true + route_tables = [ + { + name = "route-table" + routes = [ + { + name = "route-table" + address_prefix = "0.0.0.0/0" + next_hop_type = "Internet" + } + ] + } + ] +} + +##----------------------------------------------------------------------------- +## Log Analytic Module Call. +## Log Analytic workspace for firerwall diagnostic setting. +##----------------------------------------------------------------------------- +module "log-analytics" { + source = "../../../terraform-azure-log-analytics" #"terraform-az-modules/log-analytics/azure" + #version = "1.0.0" + name = local.name + environment = local.environment + label_order = ["name", "environment", "location"] + log_analytics_workspace_sku = "PerGB2018" + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + +} + + +##----------------------------------------------------------------------------- +## Firewall module call. +## From this module call firewall rules will not be deployed and thus no rule collection group will be created. +##----------------------------------------------------------------------------- +module "firewall" { + depends_on = [module.name_specific_subnet] + source = "../.." + name = local.name + environment = local.environment + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + subnet_id = module.name_specific_subnet.subnet_ids["AzureFirewallSubnet"] + public_ip_names = ["ingress", "vnet"] // Name of public ips you want to create. + firewall_enable = true + enable_diagnostic = true + log_analytics_workspace_id = module.log-analytics.workspace_id + logs = [{ + category = "AzureFirewallApplicationRule" + }, + ] + +} + +##----------------------------------------------------------------------------- +## Firewall-Rules module call. +## This is same module as 'firewall module', but from this module only firewall rules and rule collection group will be deployed. +##----------------------------------------------------------------------------- +module "firewall-rules" { + depends_on = [module.firewall] + source = "../.." + name = local.name + environment = local.environment + policy_rule_enabled = true + firewall_policy_id = module.firewall.firewall_policy_id + application_rule_collection = [ + { + name = "example_app_policy" + priority = 200 + action = "Allow" + rules = [ + { + name = "app_test" + source_addresses = ["*"] // ["X.X.X.X"] + destination_fqdns = ["*"] // ["X.X.X.X"] + protocols = [ + { + port = "443" + type = "Https" + }, + { + port = "80" + type = "Http" + } + ] + } + ] + } + ] + network_rule_collection = [ + { + name = "example_network_policy" + priority = "100" + action = "Allow" + rules = [ + { + name = "ssh" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["22"] + } + + ] + }, + { + name = "example_network_policy-2" + priority = "101" + action = "Allow" + rules = [ + { + name = "smtp" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["587"] + } + ] + } + ] + + nat_rule_collection = [ + { + name = "example_nat_policy-1" + priority = "101" + rules = [ + { + name = "nat_rule_collection1_rule1" + protocols = ["TCP", "UDP"] + source_addresses = ["10.0.0.1", "10.0.0.2"] + destination_address = module.firewall.public_ip_addresses["vnet"] + destination_ports = ["80"] + translated_address = "192.168.0.1" + translated_port = "8080" + }, + ] + }, + ] +} diff --git a/examples/firewall-with-isolated-rules/outputs.tf b/examples/firewall-with-isolated-rules/outputs.tf new file mode 100644 index 0000000..9d05239 --- /dev/null +++ b/examples/firewall-with-isolated-rules/outputs.tf @@ -0,0 +1,20 @@ +output "firewall_id" { + description = "Firewall generated id" + value = module.firewall.firewall_id +} + +output "firewall_name" { + value = module.firewall.firewall_name + description = "The name of the Firewall" + +} + +output "public_ip_id" { + value = module.firewall.public_ip_ids + description = "value of public IP IDs" +} + +output "public_ip_address" { + value = module.firewall.public_ip_addresses + description = "value of public IP Addresses" +} diff --git a/examples/firewall-with-isolated-rules/version.tf b/examples/firewall-with-isolated-rules/version.tf new file mode 100644 index 0000000..e56b7ac --- /dev/null +++ b/examples/firewall-with-isolated-rules/version.tf @@ -0,0 +1,16 @@ +##----------------------------------------------------------------------------- +## Versions +##----------------------------------------------------------------------------- +# Terraform version +terraform { + required_version = ">= 1.6.6" +} + +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">=3.116.0" + } + } +} \ No newline at end of file diff --git a/examples/firewall-with-public-ip-prefix/README.md b/examples/firewall-with-public-ip-prefix/README.md new file mode 100644 index 0000000..69876be --- /dev/null +++ b/examples/firewall-with-public-ip-prefix/README.md @@ -0,0 +1,57 @@ + + +# Terraform Azure Application Insights + +This directory contains an example usage of the **terraform-azure-firewall**. It demonstrates how to use the module with default settings or with custom configurations. + +--- + +## πŸ“‹ Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.6.6 | +| [azurerm](#requirement\_azurerm) | >=3.116.0 | + +--- + +## πŸ”Œ Providers + +None specified in this example. + +--- + +## πŸ“¦ Modules + +| Name | Source | Version | +|------|--------|---------| +| [firewall](#module\_firewall) | ../.. | n/a | +| [log-analytics](#module\_log-analytics) | terraform-az-modules/log-analytics/azure | 1.0.0 | +| [name\_specific\_subnet](#module\_name\_specific\_subnet) | terraform-az-modules/subnet/azure | 1.0.0 | +| [resource\_group](#module\_resource\_group) | terraform-az-modules/resource-group/azure | 1.0.0 | +| [vnet](#module\_vnet) | terraform-az-modules/vnet/azure | 1.0.0 | + +--- + +## πŸ—οΈ Resources + +No resources are directly created in this example. + +--- + +## πŸ”§ Inputs + +No input variables are defined in this example. + +--- + +## πŸ“€ Outputs + +| Name | Description | +|------|-------------| +| [firewall\_id](#output\_firewall\_id) | Firewall generated id | +| [firewall\_name](#output\_firewall\_name) | The name of the Firewall | +| [public\_ip\_address](#output\_public\_ip\_address) | value of public IP Addresses | +| [public\_ip\_id](#output\_public\_ip\_id) | value of public IP IDs | + + diff --git a/examples/firewall-with-public-ip-prefix/example.tf b/examples/firewall-with-public-ip-prefix/example.tf new file mode 100644 index 0000000..3448e69 --- /dev/null +++ b/examples/firewall-with-public-ip-prefix/example.tf @@ -0,0 +1,182 @@ +provider "azurerm" { + features {} +} + +locals { + name = "app2-firewall" + environment = "test" +} + +##----------------------------------------------------------------------------- +## Resource Group module call +## Resource group in which all resources will be deployed. +##----------------------------------------------------------------------------- +module "resource_group" { + source = "terraform-az-modules/resource-group/azure" + version = "1.0.0" + name = local.name + environment = local.environment + label_order = ["name", "environment", ] + location = "East US" +} + +##----------------------------------------------------------------------------- +## Virtual Network module call. +## Virtual Network in firewall specific subnet will be created. +##----------------------------------------------------------------------------- +module "vnet" { + depends_on = [module.resource_group] + source = "terraform-azure-vnet" #"terraform-az-modules/vnet/azure" + version = "1.0.0" + name = local.name + environment = local.environment + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + address_spaces = ["10.0.0.0/16"] +} + +##----------------------------------------------------------------------------- +## Subnet module call. +## Name specific subnet for firewall will be created. +##----------------------------------------------------------------------------- +module "name_specific_subnet" { + depends_on = [module.vnet] + source = "terraform-az-modules/subnet/azure" + version = "1.0.0" + environment = "test" + label_order = ["name", "environment", "location"] + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + virtual_network_name = module.vnet.vnet_name + subnets = [ + { + name = "AzureFirewallSubnet" + subnet_prefixes = ["10.0.1.0/24"] + } + ] + enable_route_table = true + route_tables = [ + { + name = "route-table" + routes = [ + { + name = "route-table" + address_prefix = "0.0.0.0/0" + next_hop_type = "Internet" + } + ] + } + ] +} + +##----------------------------------------------------------------------------- +## Log Analytic Module Call. +## Log Analytic workspace for firerwall diagnostic setting. +##----------------------------------------------------------------------------- +module "log-analytics" { + source = "terraform-az-modules/log-analytics/azure" + version = "1.0.0" + name = local.name + environment = local.environment + label_order = ["name", "environment", "location"] + log_analytics_workspace_sku = "PerGB2018" + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location +} + + + +##----------------------------------------------------------------------------- +## Firewall module call. +## All firewall related resources will be deployed from this module, i.e. including firewall and firewall rules. +##----------------------------------------------------------------------------- +module "firewall" { + depends_on = [module.name_specific_subnet] + source = "../.." + name = local.name + environment = local.environment + resource_group_name = module.resource_group.resource_group_name + location = module.resource_group.resource_group_location + subnet_id = module.name_specific_subnet.subnet_ids["AzureFirewallSubnet"] + firewall_enable = true + policy_rule_enabled = true + public_ip_names = ["ingress", "vnet", "app", "app-2"] + enable_diagnostic = true + eventhub_name = local.name + public_ip_prefix_enable = true + log_analytics_workspace_id = module.log-analytics.workspace_id + + application_rule_collection = [ + { + name = "example_app_policy" + priority = 200 + action = "Allow" + rules = [ + { + name = "app_test" + source_addresses = ["*"] // ["X.X.X.X"] + destination_fqdns = ["*"] // ["X.X.X.X"] + protocols = [ + { + port = "443" + type = "Https" + }, + { + port = "80" + type = "Http" + } + ] + } + ] + } + ] + network_rule_collection = [ + { + name = "example_network_policy" + priority = "100" + action = "Allow" + rules = [ + { + name = "ssh" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["22"] + } + + ] + }, + { + name = "example_network_policy-2" + priority = "101" + action = "Allow" + rules = [ + { + name = "smtp" + protocols = ["TCP"] + source_addresses = ["*"] // ["X.X.X.X"] + destination_addresses = ["*"] // ["X.X.X.X"] + destination_ports = ["587"] + } + ] + } + ] + + nat_rule_collection = [ + { + name = "example_nat_policy-1" + priority = "101" + rules = [ + { + name = "nat_rule_collection1_rule1" + protocols = ["TCP", "UDP"] + source_addresses = ["10.0.0.1", "10.0.0.2"] + destination_ports = ["80"] + destination_address = module.firewall.public_ip_addresses["vnet"] + translated_address = "192.168.0.1" + translated_port = "8080" + }, + ] + }, + ] +} diff --git a/examples/firewall-with-public-ip-prefix/outputs.tf b/examples/firewall-with-public-ip-prefix/outputs.tf new file mode 100644 index 0000000..7d3d57f --- /dev/null +++ b/examples/firewall-with-public-ip-prefix/outputs.tf @@ -0,0 +1,23 @@ +output "firewall_id" { + description = "Firewall generated id" + value = module.firewall.firewall_id + +} + +output "firewall_name" { + value = module.firewall.firewall_name + description = "The name of the Firewall" + + +} + +output "public_ip_id" { + value = module.firewall.public_ip_ids + description = "value of public IP IDs" +} + +output "public_ip_address" { + value = module.firewall.public_ip_addresses + description = "value of public IP Addresses" +} + diff --git a/examples/firewall-with-public-ip-prefix/version.tf b/examples/firewall-with-public-ip-prefix/version.tf new file mode 100644 index 0000000..e56b7ac --- /dev/null +++ b/examples/firewall-with-public-ip-prefix/version.tf @@ -0,0 +1,16 @@ +##----------------------------------------------------------------------------- +## Versions +##----------------------------------------------------------------------------- +# Terraform version +terraform { + required_version = ">= 1.6.6" +} + +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = ">=3.116.0" + } + } +} \ No newline at end of file diff --git a/locals.tf b/locals.tf index 691b39e..699a012 100644 --- a/locals.tf +++ b/locals.tf @@ -1,6 +1,7 @@ + ##----------------------------------------------------------------------------- -## Locals +## Locals declaration for determining the local variables ##----------------------------------------------------------------------------- locals { - label_order = var.label_order -} \ No newline at end of file + name = var.custom_name != null ? var.custom_name : module.labels.id +} diff --git a/main.tf b/main.tf index 22e570c..1a58273 100644 --- a/main.tf +++ b/main.tf @@ -1,3 +1,234 @@ ##----------------------------------------------------------------------------- -## Resources +# Standard Tagging Module – Applies standard tags to all resources for traceability ##----------------------------------------------------------------------------- +module "labels" { + source = "terraform-az-modules/labels/azure" + version = "1.0.0" + name = var.custom_name == null ? var.name : var.custom_name + location = var.location + environment = var.environment + managedby = var.managedby + label_order = var.label_order + repository = var.repository + deployment_mode = var.deployment_mode + extra_tags = var.extra_tags +} + +##----------------------------------------------------------------------------- +# Firewall Public IP Prefix – optional +##----------------------------------------------------------------------------- +resource "azurerm_public_ip_prefix" "pip_prefix" { + count = var.enabled && var.firewall_enable && var.public_ip_prefix_enable ? 1 : 0 + name = format(var.resource_position_prefix ? "public-ip-prefix-%s" : "%s-public-ip-prefix", local.name) + location = var.location + resource_group_name = var.resource_group_name + sku = var.public_ip_prefix_sku + ip_version = var.public_ip_prefix_ip_version + prefix_length = var.public_ip_prefix_length + tags = module.labels.tags +} + +##----------------------------------------------------------------------------- +# Public IPs – one resource, creates all PIPs +##----------------------------------------------------------------------------- +resource "azurerm_public_ip" "public_ip" { + for_each = toset(var.public_ip_names) + name = var.public_ip_prefix_enable ? format(var.resource_position_prefix ? "pip-%s-%s" : "%s-%s-pip", local.name, each.value) : format(var.resource_position_prefix ? "ip-%s-%s" : "%s-%s-ip", local.name, each.value) + location = var.location + resource_group_name = var.resource_group_name + allocation_method = var.public_ip_allocation_method + sku = var.public_ip_sku + public_ip_prefix_id = var.public_ip_prefix_enable ? azurerm_public_ip_prefix.pip_prefix[0].id : null + ddos_protection_mode = "VirtualNetworkInherited" + tags = module.labels.tags + + lifecycle { + create_before_destroy = true + } +} + +##----------------------------------------------------------------------------- +# Azure Firewall +##----------------------------------------------------------------------------- +resource "azurerm_firewall" "firewall" { + depends_on = [azurerm_public_ip.public_ip, azurerm_public_ip_prefix.pip_prefix] + count = var.enabled && var.firewall_enable ? 1 : 0 + name = format(var.resource_position_prefix ? "firewall-%s" : "%s-firewall", local.name) + location = var.location + resource_group_name = var.resource_group_name + threat_intel_mode = var.threat_intel_mode + sku_tier = var.sku_tier + sku_name = var.sku_name + firewall_policy_id = join("", azurerm_firewall_policy.policy.*.id) + tags = module.labels.tags + private_ip_ranges = var.firewall_private_ip_ranges + dns_servers = var.dns_servers + + # Primary ip_configuration (first in the list) + ip_configuration { + name = format(var.resource_position_prefix ? "ipconfig-%s-%s" : "%s-%s-ipconfig", local.name, var.public_ip_names[0]) + subnet_id = var.subnet_id + public_ip_address_id = azurerm_public_ip.public_ip[var.public_ip_names[0]].id + } + + # Additional ip_configurations (skip first one) + dynamic "ip_configuration" { + for_each = length(var.public_ip_names) > 1 ? toset(slice(var.public_ip_names, 1, length(var.public_ip_names))) : [] + content { + name = format(var.resource_position_prefix ? "ipconfig-%s-%s" : "%s-%s-ipconfig", local.name, ip_configuration.value) + public_ip_address_id = azurerm_public_ip.public_ip[ip_configuration.value].id + # subnet_id omitted here! + } + } + + lifecycle { + ignore_changes = [tags] + } +} + +##----------------------------------------------------------------------------- +# Firewall Policy +##----------------------------------------------------------------------------- +resource "azurerm_firewall_policy" "policy" { + count = var.enabled && var.firewall_enable ? 1 : 0 + name = format(var.resource_position_prefix ? "firewall-policy-%s" : "%s-firewall-policy", local.name) + resource_group_name = var.resource_group_name + location = var.location + sku = var.sku_policy + + dynamic "identity" { + for_each = var.identity_type != null && var.sku_policy == "Premium" && var.sku_tier == "Premium" ? [1] : [] + content { + type = var.identity_type + identity_ids = var.identity_type == "UserAssigned" ? [join("", azurerm_user_assigned_identity.identity.*.id)] : null + } + } +} + +##----------------------------------------------------------------------------- +# Firewall Policy Rule Collection Groups +##----------------------------------------------------------------------------- +resource "azurerm_firewall_policy_rule_collection_group" "app_policy" { + depends_on = [azurerm_firewall_policy.policy] + count = var.enabled && var.policy_rule_enabled ? 1 : 0 + name = var.app_policy_collection_group + firewall_policy_id = var.firewall_policy_id == null ? join("", azurerm_firewall_policy.policy.*.id) : var.firewall_policy_id + priority = 300 + + dynamic "application_rule_collection" { + for_each = var.application_rule_collection + content { + name = application_rule_collection.value.name + priority = application_rule_collection.value.priority + action = application_rule_collection.value.action + + dynamic "rule" { + for_each = application_rule_collection.value.rules + content { + name = lookup(rule.value, "name", null) + source_addresses = lookup(rule.value, "source_addresses", null) + source_ip_groups = lookup(rule.value, "source_ip_groups", null) + destination_fqdns = lookup(rule.value, "destination_fqdns", null) + + dynamic "protocols" { + for_each = rule.value.protocols + content { + port = lookup(protocols.value, "port", null) + type = lookup(protocols.value, "type", null) + } + } + } + } + } + } +} + +resource "azurerm_firewall_policy_rule_collection_group" "network_policy" { + depends_on = [azurerm_firewall_policy.policy] + count = var.enabled && var.policy_rule_enabled ? 1 : 0 + name = var.net_policy_collection_group + firewall_policy_id = var.firewall_policy_id == null ? join("", azurerm_firewall_policy.policy.*.id) : var.firewall_policy_id + priority = 200 + + dynamic "network_rule_collection" { + for_each = var.network_rule_collection + content { + name = network_rule_collection.value.name + priority = network_rule_collection.value.priority + action = network_rule_collection.value.action + + dynamic "rule" { + for_each = network_rule_collection.value.rules + content { + name = rule.value.name + protocols = rule.value.protocols + destination_ports = rule.value.destination_ports + source_addresses = lookup(rule.value, "source_addresses", null) + source_ip_groups = lookup(rule.value, "source_ip_groups", null) + destination_addresses = lookup(rule.value, "destination_addresses", null) + destination_ip_groups = lookup(rule.value, "destination_ip_groups", null) + destination_fqdns = lookup(rule.value, "destination_fqdns", null) + } + } + } + } +} + +resource "azurerm_firewall_policy_rule_collection_group" "nat_policy" { + depends_on = [azurerm_firewall_policy.policy] + count = var.enabled && var.policy_rule_enabled ? 1 : 0 + name = var.nat_policy_collection_group + firewall_policy_id = var.firewall_policy_id == null ? join("", azurerm_firewall_policy.policy.*.id) : var.firewall_policy_id + priority = 100 + + dynamic "nat_rule_collection" { + for_each = var.nat_rule_collection + content { + name = nat_rule_collection.value.name + priority = nat_rule_collection.value.priority + action = "Dnat" + + dynamic "rule" { + for_each = nat_rule_collection.value.rules + content { + name = rule.value.name + protocols = rule.value.protocols + destination_ports = rule.value.destination_ports + source_addresses = lookup(rule.value, "source_addresses", null) + destination_address = lookup(rule.value, "destination_address", null) + translated_address = lookup(rule.value, "translated_address", null) + translated_port = lookup(rule.value, "translated_port", null) + } + } + } + } +} + +##----------------------------------------------------------------------------- +# Diagnostics +##----------------------------------------------------------------------------- +resource "azurerm_monitor_diagnostic_setting" "firewall_diagnostic" { + count = var.enabled && var.enable_diagnostic && var.firewall_enable ? 1 : 0 + name = format(var.resource_position_prefix ? "firewall-diagnostic-%s" : "%s-firewall-diagnostic", local.name) + target_resource_id = azurerm_firewall.firewall[0].id + storage_account_id = var.storage_account_id + eventhub_name = var.eventhub_name + eventhub_authorization_rule_id = var.eventhub_authorization_rule_id + log_analytics_workspace_id = var.log_analytics_workspace_id + log_analytics_destination_type = var.log_analytics_destination_type + + dynamic "enabled_metric" { + for_each = var.metric_enabled ? ["AllMetrics"] : [] + content { + category = enabled_metric.value + } + } + + dynamic "enabled_log" { + for_each = var.logs + content { + category_group = lookup(enabled_log.value, "category_group", null) + category = lookup(enabled_log.value, "category", null) + } + } +} diff --git a/outputs.tf b/outputs.tf index e0af9ca..6537015 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,7 +1,30 @@ -##----------------------------------------------------------------------------- -## Outputs -##----------------------------------------------------------------------------- -output "label_order" { - value = local.label_order - description = "Label order." -} \ No newline at end of file +output "firewall_id" { + description = "Firewall ID" + value = join("", azurerm_firewall.firewall.*.id) +} + +output "firewall_name" { + value = join("", azurerm_firewall.firewall.*.name) + description = "Firewall name" +} + +output "public_ip_ids" { + description = "The IDs of all public IPs" + value = { for k, v in azurerm_public_ip.public_ip : k => v.id } +} + +output "public_ip_addresses" { + description = "value of public IP addresses" + value = { for k, v in azurerm_public_ip.public_ip : k => v.ip_address } +} + +output "firewall_policy_id" { + description = "value of firewall policy ID" + value = join("", azurerm_firewall_policy.policy.*.id) +} + +output "public_ip_prefix_id" { + description = "value of public IP prefix ID" + value = join("", azurerm_public_ip_prefix.pip_prefix.*.id) +} + diff --git a/permissions.tf b/permissions.tf index 0220f63..3bc2652 100644 --- a/permissions.tf +++ b/permissions.tf @@ -1,3 +1,10 @@ -##----------------------------------------------------------------------------- -## Permissions, Roles, and Policies -##----------------------------------------------------------------------------- \ No newline at end of file + +##------------------------------------------------------------------------------------ +# User Assigned Identity – Creates a user assigned identity for Azure Firewall Policy +##------------------------------------------------------------------------------------ +resource "azurerm_user_assigned_identity" "identity" { + count = var.enabled && var.firewall_enable ? 1 : 0 + location = var.location + name = format(var.resource_position_prefix ? "fw-policy-mid-%s" : "%s-fw-policy-mid", local.name) + resource_group_name = var.resource_group_name +} diff --git a/variables.tf b/variables.tf index 5e40885..c76a7bc 100644 --- a/variables.tf +++ b/variables.tf @@ -1,8 +1,376 @@ ##----------------------------------------------------------------------------- -## Variables +## Naming convention ##----------------------------------------------------------------------------- +variable "custom_name" { + type = string + default = null + description = "Override default naming convention" +} + +variable "resource_position_prefix" { + type = bool + default = true + description = <= 1 + error_message = "At least one public_ip_names entry is required when firewall_enable = true." + } +} + +variable "public_ip_prefix_enable" { + type = bool + default = false + description = "Flag to control creation of public ip prefix resource." + +} + +# variable "primary_public_ip_name" { +# description = "One of public_ip_names. Used for the subneted ip_configuration." +# type = string +# } + +variable "public_ip_allocation_method" { + type = string + description = "Defines the allocation method for this IP address. Possible values are Static or Dynamic" + default = "Static" +} + + +variable "public_ip_sku" { + description = "The SKU of the Public IP. Accepted values are Basic and Standard. Defaults to Standard" + default = "Standard" + type = string +} + +variable "public_ip_prefix_sku" { + type = string + default = "Standard" + description = "SKU for public ip prefix. Default to standard." +} + +variable "public_ip_prefix_ip_version" { + type = string + default = "IPv4" + description = "The IP Version to use, IPv6 or IPv4. Changing this forces a new resource to be created. Default is IPv4" +} + +variable "public_ip_prefix_length" { + type = number + default = 28 + description = "Specifies the number of bits of the prefix. The value can be set between 0 (4,294,967,296 addresses) and 31 (2 addresses). Defaults to 28(16 addresses). Changing this forces a new resource to be created." +} + +#----------------------------------------------------------------------------- +# Firewall configuration +#----------------------------------------------------------------------------- +variable "threat_intel_mode" { + description = "(Optional) The operation mode for threat intelligence-based filtering. Possible values are: Off, Alert, Deny. Defaults to Alert." + default = "Alert" + type = string + + validation { + condition = contains(["Off", "Alert", "Deny"], var.threat_intel_mode) + error_message = "The threat intel mode is invalid." + } +} + +variable "sku_tier" { + description = "Specifies the firewall sku tier" + default = "Standard" + type = string +} + +variable "sku_name" { + type = string + default = "AZFW_VNet" + description = "SKU name of the Firewall. Possible values are `AZFW_VNet` and `AZFW_Hub`." +} + +variable "firewall_private_ip_ranges" { + description = "A list of SNAT private CIDR IP ranges, or the special string `IANAPrivateRanges`, which indicates Azure Firewall does not SNAT when the destination IP address is a private range per IANA RFC 1918." + type = list(string) + default = null +} + +variable "dns_servers" { + description = "DNS Servers to use with Azure Firewall. Using this also activate DNS Proxy." + type = list(string) + default = null +} + +variable "subnet_id" { + type = string + default = "" + description = "The ID of the subnet to attach the firewall to. If not specified, the module will create a new subnet named 'AzureFirewallSubnet' in the specified virtual network." +} + +#----------------------------------------------------------------------------- +# Firewall Policy configuration +#----------------------------------------------------------------------------- +variable "sku_policy" { + description = "Specifies the firewall-policy sku" + default = "Standard" + type = string +} + +variable "identity_type" { + description = "Specifies the type of Managed Service Identity that should be configured on this Storage Account. Possible values are `SystemAssigned`, `UserAssigned`, `SystemAssigned, UserAssigned` (to enable both)." + type = string + default = "UserAssigned" +} + +#----------------------------------------------------------------------------- +# Policy Rule Collection Group configuration +#----------------------------------------------------------------------------- +variable "policy_rule_enabled" { + type = bool + default = false + description = "Flag used to control creation of policy rules." +} + +variable "app_policy_collection_group" { + type = string + default = "DefaultApplicationRuleCollectionGroup" + description = "(optional) Name of app policy group" +} + +variable "firewall_policy_id" { + type = string + default = null + description = "The ID of the Firewall Policy." +} + +variable "application_rule_collection" { + type = list(object({ + name = string + priority = number + action = string + rules = list(object({ + name = string + source_addresses = optional(list(string), []) # Optional: List of source IP addresses + source_ip_groups = optional(list(string), []) # Optional: List of source IP groups + destination_fqdns = optional(list(string), []) # Optional: List of destination FQDNs + destination_ip_groups = optional(list(string), []) # Optional: List of destination IP groups + protocols = list(object({ + port = optional(number, null) # Optional: Port number + type = optional(string, null) # Optional: Protocol type (e.g., TCP, UDP) + })) + })) + })) + default = [] + description = "List of application rule collections for the firewall policy." +} + +#----------------------------------------------------------------------------- +# Network Rule configuration +#----------------------------------------------------------------------------- +variable "net_policy_collection_group" { + type = string + description = "(optional) Name of network policy group" + default = "DefaultNetworkRuleCollectionGroup" +} + +variable "network_rule_collection" { + type = list(object({ + name = string + priority = number + action = string + description = optional(string, null) # Optional: Description of the rule collection + rules = list(object({ + name = string + protocols = list(string) # List of protocols (e.g., TCP, UDP, ICMP) + source_addresses = optional(list(string), []) # Optional: List of source IP addresses + source_ip_groups = optional(list(string), []) # Optional: List of source IP groups + destination_addresses = optional(list(string), []) # Optional: List of destination IP addresses + destination_ip_groups = optional(list(string), []) # Optional: List of destination IP groups + destination_ports = optional(list(string), []) # Optional: List of destination ports + destination_fqdns = optional(list(string), []) # Optional: List of destination FQDNs + })) + })) + default = [] + description = "List of network rule collections for the firewall policy." +} + +#----------------------------------------------------------------------------- +# Nat Rule configuration +#----------------------------------------------------------------------------- +variable "dnat_destination_ip" { + description = "Variable to specify that you have destination ip to attach to policy or not.(Destination ip is public ip that is attached to firewall)" + type = bool + default = true +} + +variable "nat_policy_collection_group" { + type = string + default = "DefaultDnatRuleCollectionGroup" + description = "(optional) Name of nat policy group" +} + +variable "nat_rule_collection" { + description = "List of NAT rule collections for the firewall policy." + type = list(object({ + name = string + priority = number + description = optional(string, null) # Optional: Description of the NAT rule collection + rules = list(object({ + name = string + protocols = list(string) # List of protocols (e.g., TCP, UDP) + source_addresses = optional(list(string), []) # Optional: List of source IP addresses + destination_address = optional(string, null) # Optional: List of destination IP addresses + destination_ports = optional(list(string), []) # Optional: List of destination ports + translated_address = string # Required: Internal IP to which traffic is forwarded + translated_port = optional(string, null) # Optional: Internal port to which traffic is forwarded + source_ip_groups = optional(list(string), []) # Optional: List of source IP groups + destination_ip_groups = optional(list(string), []) # Optional: List of destination IP groups + })) + })) + default = [] +} + +#----------------------------------------------------------------------------- +# Diagnostic settings configuration +#----------------------------------------------------------------------------- +variable "enable_diagnostic" { + type = bool + default = false + description = "Set to false to prevent the module from creating the diagnosys setting for the firewall Resource.." +} + +variable "storage_account_id" { + type = string + default = null + description = "Storage account id to pass it to destination details of diagnosys setting of firewall." +} + +variable "eventhub_name" { + type = string + default = null + description = "Eventhub Name to pass it to destination details of diagnosys setting of firewall." +} + +variable "eventhub_authorization_rule_id" { + type = string + default = null + description = "Eventhub authorization rule id to pass it to destination details of diagnosys setting of firewall." +} + +variable "log_analytics_workspace_id" { + type = string + default = null + description = "log analytics workspace id to pass it to destination details of diagnosys setting of firewall." +} + +variable "log_analytics_destination_type" { + type = string + default = "AzureDiagnostics" + description = "Possible values are AzureDiagnostics and Dedicated, default to AzureDiagnostics. When set to Dedicated, logs sent to a Log Analytics workspace will go into resource specific tables, instead of the legacy AzureDiagnostics table." +} + +variable "metric_enabled" { + type = bool + default = false + description = "Set to true to enable metrics for the diagnosys setting." +} + +variable "logs" { + type = list(object({ + category_group = optional(string) + category = optional(string) + })) + default = [] + description = "List of logs to enable for the diagnosys setting." }