From 0095998eae467d63c529ccc85f303d853512f615 Mon Sep 17 00:00:00 2001 From: Josiel Souza Date: Mon, 2 Mar 2026 11:54:38 +0000 Subject: [PATCH] feat(iac): add nat-gateway module This commit adds a new Terraform module for deploying an Azure NAT gateway with a dedicated public IP address. The module includes comprehensive documentation, input validation, and outputs. Refs: DTOSS-12318 --- infrastructure/modules/nat-gateway/README.md | 57 +++++++++++ infrastructure/modules/nat-gateway/main.tf | 33 +++++++ infrastructure/modules/nat-gateway/outputs.tf | 14 +++ infrastructure/modules/nat-gateway/tfdocs.md | 96 +++++++++++++++++++ .../modules/nat-gateway/variables.tf | 59 ++++++++++++ 5 files changed, 259 insertions(+) create mode 100644 infrastructure/modules/nat-gateway/README.md create mode 100644 infrastructure/modules/nat-gateway/main.tf create mode 100644 infrastructure/modules/nat-gateway/outputs.tf create mode 100644 infrastructure/modules/nat-gateway/tfdocs.md create mode 100644 infrastructure/modules/nat-gateway/variables.tf diff --git a/infrastructure/modules/nat-gateway/README.md b/infrastructure/modules/nat-gateway/README.md new file mode 100644 index 00000000..dbc2995e --- /dev/null +++ b/infrastructure/modules/nat-gateway/README.md @@ -0,0 +1,57 @@ +# nat-gateway + +Deploy an [Azure NAT gateway](https://learn.microsoft.com/en-us/azure/nat-gateway/nat-overview) with a dedicated public IP address. Provides explicit outbound connectivity for subnets whose VMs have no public IP addresses. + +## Terraform documentation + +For the list of inputs, outputs, resources... check the [terraform module documentation](tfdocs.md). + +## Prerequisites + +The subnet must already exist. Use the [subnet module](../subnet/) to create it and pass the resulting subnet ID to this module. + +> **Note:** Default outbound access for VMs was retired by Microsoft in September 2025. A NAT gateway (or equivalent) is required for any subnet that needs outbound internet connectivity. + +## Usage + +```hcl +module "nat_gateway" { + source = "../../../dtos-devops-templates/infrastructure/modules/nat-gateway" + + name = "nat-myapp-dev-uks" + public_ip_name = "pip-nat-myapp-dev-uks" + resource_group_name = azurerm_resource_group.main.name + location = "uksouth" + subnet_id = module.subnet_servers.id +} +``` + +## Zones + +Azure NAT gateway supports a single availability zone (zonal) or no zone pinning (regional). It does not support zone-redundant deployment across multiple zones. Pass a single zone for a zonal deployment: + +```hcl +module "nat_gateway" { + ... + zones = ["1"] +} +``` + +An empty list (the default) deploys the NAT gateway without zone pinning. + +## Idle timeout + +The TCP idle timeout defaults to 4 minutes. Increase it for workloads with long-lived connections: + +```hcl +module "nat_gateway" { + ... + idle_timeout_in_minutes = 10 +} +``` + +Valid range is 4–120 minutes. + +## Monitoring + +NAT gateway does not support Azure Monitor diagnostic settings. Metrics such as SNAT connection count, dropped packets, and throughput are available natively in the Azure portal under the resource's Metrics blade. diff --git a/infrastructure/modules/nat-gateway/main.tf b/infrastructure/modules/nat-gateway/main.tf new file mode 100644 index 00000000..6c6cb035 --- /dev/null +++ b/infrastructure/modules/nat-gateway/main.tf @@ -0,0 +1,33 @@ +module "pip" { + source = "../public-ip" + + name = var.public_ip_name + resource_group_name = var.resource_group_name + location = var.location + allocation_method = "Static" + sku = "Standard" + zones = var.zones + + tags = var.tags +} + +resource "azurerm_nat_gateway" "nat_gateway" { + name = var.name + location = var.location + resource_group_name = var.resource_group_name + sku_name = "Standard" + idle_timeout_in_minutes = var.idle_timeout_in_minutes + zones = var.zones + + tags = var.tags +} + +resource "azurerm_nat_gateway_public_ip_association" "nat_gateway_pip" { + nat_gateway_id = azurerm_nat_gateway.nat_gateway.id + public_ip_address_id = module.pip.id +} + +resource "azurerm_subnet_nat_gateway_association" "nat_gateway_subnet" { + subnet_id = var.subnet_id + nat_gateway_id = azurerm_nat_gateway.nat_gateway.id +} diff --git a/infrastructure/modules/nat-gateway/outputs.tf b/infrastructure/modules/nat-gateway/outputs.tf new file mode 100644 index 00000000..df9d88ec --- /dev/null +++ b/infrastructure/modules/nat-gateway/outputs.tf @@ -0,0 +1,14 @@ +output "id" { + description = "The ID of the NAT gateway." + value = azurerm_nat_gateway.nat_gateway.id +} + +output "name" { + description = "The name of the NAT gateway." + value = azurerm_nat_gateway.nat_gateway.name +} + +output "public_ip_address" { + description = "The public IP address associated with the NAT gateway." + value = module.pip.ip_address +} diff --git a/infrastructure/modules/nat-gateway/tfdocs.md b/infrastructure/modules/nat-gateway/tfdocs.md new file mode 100644 index 00000000..a60f7e9c --- /dev/null +++ b/infrastructure/modules/nat-gateway/tfdocs.md @@ -0,0 +1,96 @@ +# Module documentation + +## Required Inputs + +The following input variables are required: + +### [name](#input\_name) + +Description: The name of the NAT gateway. + +Type: `string` + +### [public\_ip\_name](#input\_public\_ip\_name) + +Description: The name of the public IP address resource created for the NAT gateway. + +Type: `string` + +### [resource\_group\_name](#input\_resource\_group\_name) + +Description: The name of the resource group in which to create the NAT gateway. + +Type: `string` + +### [subnet\_id](#input\_subnet\_id) + +Description: The ID of the subnet to associate with the NAT gateway. + +Type: `string` + +## Optional Inputs + +The following input variables are optional (have default values): + +### [idle\_timeout\_in\_minutes](#input\_idle\_timeout\_in\_minutes) + +Description: The idle timeout in minutes for the NAT gateway. Must be between 4 and 120. + +Type: `number` + +Default: `4` + +### [location](#input\_location) + +Description: The location/region where the NAT gateway will be created. + +Type: `string` + +Default: `"uksouth"` + +### [tags](#input\_tags) + +Description: A mapping of tags to assign to the resource. + +Type: `map(string)` + +Default: `{}` + +### [zones](#input\_zones) + +Description: Availability zones for the NAT gateway and its public IP. Use ["1"] for a zonal deployment. An empty list deploys with no zone redundancy. + +Type: `list(string)` + +Default: `[]` +## Modules + +The following Modules are called: + +### [pip](#module\_pip) + +Source: ../public-ip + +Version: +## Outputs + +The following outputs are exported: + +### [id](#output\_id) + +Description: The ID of the NAT gateway. + +### [name](#output\_name) + +Description: The name of the NAT gateway. + +### [public\_ip\_address](#output\_public\_ip\_address) + +Description: The public IP address associated with the NAT gateway. +## Resources + +The following resources are used by this module: + +- [azurerm_nat_gateway.nat_gateway](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway) (resource) +- [azurerm_nat_gateway_public_ip_association.nat_gateway_pip](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway_public_ip_association) (resource) +- [azurerm_subnet_nat_gateway_association.nat_gateway_subnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_nat_gateway_association) (resource) diff --git a/infrastructure/modules/nat-gateway/variables.tf b/infrastructure/modules/nat-gateway/variables.tf new file mode 100644 index 00000000..b7a50312 --- /dev/null +++ b/infrastructure/modules/nat-gateway/variables.tf @@ -0,0 +1,59 @@ +variable "name" { + description = "The name of the NAT gateway." + type = string + validation { + condition = can(regex("^[a-zA-Z0-9][a-zA-Z0-9.-]{0,78}[a-zA-Z0-9]$", var.name)) + error_message = "The NAT gateway name must be between 2 and 80 characters, start and end with an alphanumeric character, and can contain alphanumeric characters, hyphens, and periods." + } +} + +variable "resource_group_name" { + description = "The name of the resource group in which to create the NAT gateway." + type = string +} + +variable "location" { + description = "The location/region where the NAT gateway will be created." + type = string + default = "uksouth" + validation { + condition = contains(["uksouth", "ukwest"], var.location) + error_message = "The location must be either uksouth or ukwest." + } +} + +variable "public_ip_name" { + description = "The name of the public IP address resource created for the NAT gateway." + type = string +} + +variable "subnet_id" { + description = "The ID of the subnet to associate with the NAT gateway." + type = string +} + +variable "idle_timeout_in_minutes" { + description = "The idle timeout in minutes for the NAT gateway. Must be between 4 and 120." + type = number + default = 4 + validation { + condition = var.idle_timeout_in_minutes >= 4 && var.idle_timeout_in_minutes <= 120 + error_message = "idle_timeout_in_minutes must be between 4 and 120." + } +} + +variable "zones" { + description = "Availability zones for the NAT gateway and its public IP. Use [\"1\"] for a zonal deployment. An empty list deploys with no zone redundancy." + type = list(string) + default = [] + validation { + condition = length(var.zones) <= 1 + error_message = "NAT gateway supports at most one availability zone." + } +} + +variable "tags" { + description = "A mapping of tags to assign to the resource." + type = map(string) + default = {} +}