Skip to content

Commit 25275d9

Browse files
feat(iac): add nat-gateway module (#284)
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
1 parent db7269a commit 25275d9

5 files changed

Lines changed: 259 additions & 0 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# nat-gateway
2+
3+
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.
4+
5+
## Terraform documentation
6+
7+
For the list of inputs, outputs, resources... check the [terraform module documentation](tfdocs.md).
8+
9+
## Prerequisites
10+
11+
The subnet must already exist. Use the [subnet module](../subnet/) to create it and pass the resulting subnet ID to this module.
12+
13+
> **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.
14+
15+
## Usage
16+
17+
```hcl
18+
module "nat_gateway" {
19+
source = "../../../dtos-devops-templates/infrastructure/modules/nat-gateway"
20+
21+
name = "nat-myapp-dev-uks"
22+
public_ip_name = "pip-nat-myapp-dev-uks"
23+
resource_group_name = azurerm_resource_group.main.name
24+
location = "uksouth"
25+
subnet_id = module.subnet_servers.id
26+
}
27+
```
28+
29+
## Zones
30+
31+
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:
32+
33+
```hcl
34+
module "nat_gateway" {
35+
...
36+
zones = ["1"]
37+
}
38+
```
39+
40+
An empty list (the default) deploys the NAT gateway without zone pinning.
41+
42+
## Idle timeout
43+
44+
The TCP idle timeout defaults to 4 minutes. Increase it for workloads with long-lived connections:
45+
46+
```hcl
47+
module "nat_gateway" {
48+
...
49+
idle_timeout_in_minutes = 10
50+
}
51+
```
52+
53+
Valid range is 4–120 minutes.
54+
55+
## Monitoring
56+
57+
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.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module "pip" {
2+
source = "../public-ip"
3+
4+
name = var.public_ip_name
5+
resource_group_name = var.resource_group_name
6+
location = var.location
7+
allocation_method = "Static"
8+
sku = "Standard"
9+
zones = var.zones
10+
11+
tags = var.tags
12+
}
13+
14+
resource "azurerm_nat_gateway" "nat_gateway" {
15+
name = var.name
16+
location = var.location
17+
resource_group_name = var.resource_group_name
18+
sku_name = "Standard"
19+
idle_timeout_in_minutes = var.idle_timeout_in_minutes
20+
zones = var.zones
21+
22+
tags = var.tags
23+
}
24+
25+
resource "azurerm_nat_gateway_public_ip_association" "nat_gateway_pip" {
26+
nat_gateway_id = azurerm_nat_gateway.nat_gateway.id
27+
public_ip_address_id = module.pip.id
28+
}
29+
30+
resource "azurerm_subnet_nat_gateway_association" "nat_gateway_subnet" {
31+
subnet_id = var.subnet_id
32+
nat_gateway_id = azurerm_nat_gateway.nat_gateway.id
33+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
output "id" {
2+
description = "The ID of the NAT gateway."
3+
value = azurerm_nat_gateway.nat_gateway.id
4+
}
5+
6+
output "name" {
7+
description = "The name of the NAT gateway."
8+
value = azurerm_nat_gateway.nat_gateway.name
9+
}
10+
11+
output "public_ip_address" {
12+
description = "The public IP address associated with the NAT gateway."
13+
value = module.pip.ip_address
14+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Module documentation
2+
3+
## Required Inputs
4+
5+
The following input variables are required:
6+
7+
### <a name="input_name"></a> [name](#input\_name)
8+
9+
Description: The name of the NAT gateway.
10+
11+
Type: `string`
12+
13+
### <a name="input_public_ip_name"></a> [public\_ip\_name](#input\_public\_ip\_name)
14+
15+
Description: The name of the public IP address resource created for the NAT gateway.
16+
17+
Type: `string`
18+
19+
### <a name="input_resource_group_name"></a> [resource\_group\_name](#input\_resource\_group\_name)
20+
21+
Description: The name of the resource group in which to create the NAT gateway.
22+
23+
Type: `string`
24+
25+
### <a name="input_subnet_id"></a> [subnet\_id](#input\_subnet\_id)
26+
27+
Description: The ID of the subnet to associate with the NAT gateway.
28+
29+
Type: `string`
30+
31+
## Optional Inputs
32+
33+
The following input variables are optional (have default values):
34+
35+
### <a name="input_idle_timeout_in_minutes"></a> [idle\_timeout\_in\_minutes](#input\_idle\_timeout\_in\_minutes)
36+
37+
Description: The idle timeout in minutes for the NAT gateway. Must be between 4 and 120.
38+
39+
Type: `number`
40+
41+
Default: `4`
42+
43+
### <a name="input_location"></a> [location](#input\_location)
44+
45+
Description: The location/region where the NAT gateway will be created.
46+
47+
Type: `string`
48+
49+
Default: `"uksouth"`
50+
51+
### <a name="input_tags"></a> [tags](#input\_tags)
52+
53+
Description: A mapping of tags to assign to the resource.
54+
55+
Type: `map(string)`
56+
57+
Default: `{}`
58+
59+
### <a name="input_zones"></a> [zones](#input\_zones)
60+
61+
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.
62+
63+
Type: `list(string)`
64+
65+
Default: `[]`
66+
## Modules
67+
68+
The following Modules are called:
69+
70+
### <a name="module_pip"></a> [pip](#module\_pip)
71+
72+
Source: ../public-ip
73+
74+
Version:
75+
## Outputs
76+
77+
The following outputs are exported:
78+
79+
### <a name="output_id"></a> [id](#output\_id)
80+
81+
Description: The ID of the NAT gateway.
82+
83+
### <a name="output_name"></a> [name](#output\_name)
84+
85+
Description: The name of the NAT gateway.
86+
87+
### <a name="output_public_ip_address"></a> [public\_ip\_address](#output\_public\_ip\_address)
88+
89+
Description: The public IP address associated with the NAT gateway.
90+
## Resources
91+
92+
The following resources are used by this module:
93+
94+
- [azurerm_nat_gateway.nat_gateway](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/nat_gateway) (resource)
95+
- [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)
96+
- [azurerm_subnet_nat_gateway_association.nat_gateway_subnet](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet_nat_gateway_association) (resource)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
variable "name" {
2+
description = "The name of the NAT gateway."
3+
type = string
4+
validation {
5+
condition = can(regex("^[a-zA-Z0-9][a-zA-Z0-9.-]{0,78}[a-zA-Z0-9]$", var.name))
6+
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."
7+
}
8+
}
9+
10+
variable "resource_group_name" {
11+
description = "The name of the resource group in which to create the NAT gateway."
12+
type = string
13+
}
14+
15+
variable "location" {
16+
description = "The location/region where the NAT gateway will be created."
17+
type = string
18+
default = "uksouth"
19+
validation {
20+
condition = contains(["uksouth", "ukwest"], var.location)
21+
error_message = "The location must be either uksouth or ukwest."
22+
}
23+
}
24+
25+
variable "public_ip_name" {
26+
description = "The name of the public IP address resource created for the NAT gateway."
27+
type = string
28+
}
29+
30+
variable "subnet_id" {
31+
description = "The ID of the subnet to associate with the NAT gateway."
32+
type = string
33+
}
34+
35+
variable "idle_timeout_in_minutes" {
36+
description = "The idle timeout in minutes for the NAT gateway. Must be between 4 and 120."
37+
type = number
38+
default = 4
39+
validation {
40+
condition = var.idle_timeout_in_minutes >= 4 && var.idle_timeout_in_minutes <= 120
41+
error_message = "idle_timeout_in_minutes must be between 4 and 120."
42+
}
43+
}
44+
45+
variable "zones" {
46+
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."
47+
type = list(string)
48+
default = []
49+
validation {
50+
condition = length(var.zones) <= 1
51+
error_message = "NAT gateway supports at most one availability zone."
52+
}
53+
}
54+
55+
variable "tags" {
56+
description = "A mapping of tags to assign to the resource."
57+
type = map(string)
58+
default = {}
59+
}

0 commit comments

Comments
 (0)