Managing Azure VPN Gateway with Terraform

Learn how to set up and manage Azure VPN Gateway using Terraform, including site-to-site and point-to-site configurations

Managing Azure VPN Gateway with Terraform

Azure VPN Gateway provides secure cross-premises connectivity between your virtual network and on-premises networks. This guide demonstrates how to set up and manage VPN Gateway using Terraform.

Video Tutorial

Learn more about managing Azure VPN Gateway with Terraform in this comprehensive video tutorial:

Prerequisites

  • Azure CLI configured with appropriate permissions
  • Terraform installed (version 1.0.0 or later)
  • Resource group created
  • Understanding of networking concepts

Project Structure

terraform-azure-vpngateway/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── vpngateway/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── configs/
    └── vpn.json

VPN Gateway Configuration

Create modules/vpngateway/main.tf:

# Virtual Network
resource "azurerm_virtual_network" "main" {
  name                = "${var.project_name}-vnet"
  location            = var.location
  resource_group_name = var.resource_group_name
  address_space       = ["10.0.0.0/16"]

  tags = var.tags
}

# Gateway Subnet
resource "azurerm_subnet" "gateway" {
  name                 = "GatewaySubnet"
  resource_group_name  = var.resource_group_name
  virtual_network_name = azurerm_virtual_network.main.name
  address_prefixes     = ["10.0.255.0/24"]
}

# Public IP
resource "azurerm_public_ip" "vpn" {
  name                = "${var.project_name}-vpn-pip"
  location            = var.location
  resource_group_name = var.resource_group_name
  allocation_method   = "Static"
  sku                 = "Standard"
  zones               = ["1", "2", "3"]

  tags = var.tags
}

# VPN Gateway
resource "azurerm_virtual_network_gateway" "main" {
  name                = "${var.project_name}-vpn"
  location            = var.location
  resource_group_name = var.resource_group_name
  type                = "Vpn"
  vpn_type            = "RouteBased"
  sku                 = "VpnGw2AZ"
  active_active       = true
  enable_bgp          = true
  generation          = "Generation2"

  ip_configuration {
    name                          = "vnetGatewayConfig1"
    public_ip_address_id          = azurerm_public_ip.vpn.id
    private_ip_address_allocation = "Dynamic"
    subnet_id                     = azurerm_subnet.gateway.id
  }

  ip_configuration {
    name                          = "vnetGatewayConfig2"
    public_ip_address_id          = azurerm_public_ip.vpn_secondary.id
    private_ip_address_allocation = "Dynamic"
    subnet_id                     = azurerm_subnet.gateway.id
  }

  bgp_settings {
    asn = 65515
  }

  vpn_client_configuration {
    address_space = ["172.16.0.0/24"]

    root_certificate {
      name             = "VPNRoot"
      public_cert_data = file("${path.module}/certs/root.cer")
    }

    revoked_certificate {
      name       = "Revoked"
      thumbprint = "ABC123..."
    }

    aad_tenant = "https://login.microsoftonline.com/${data.azurerm_client_config.current.tenant_id}"
    aad_audience = "41b23e61-6c1e-4545-b367-cd054e0ed4b4"
    aad_issuer   = "https://sts.windows.net/${data.azurerm_client_config.current.tenant_id}/"

    radius_server_address = var.radius_server_address
    radius_server_secret  = var.radius_server_secret

    vpn_client_protocols = ["OpenVPN", "IkeV2"]
  }

  custom_route {
    address_prefixes = ["10.0.0.0/8"]
  }

  tags = var.tags
}

# Local Network Gateway
resource "azurerm_local_network_gateway" "onprem" {
  name                = "${var.project_name}-lng"
  location            = var.location
  resource_group_name = var.resource_group_name
  gateway_address     = var.onprem_gateway_address
  address_space       = ["192.168.0.0/16"]

  bgp_settings {
    asn                 = 65516
    bgp_peering_address = "192.168.1.1"
  }

  tags = var.tags
}

# Connection
resource "azurerm_virtual_network_gateway_connection" "onprem" {
  name                = "${var.project_name}-connection"
  location            = var.location
  resource_group_name = var.resource_group_name

  type                           = "IPsec"
  virtual_network_gateway_id     = azurerm_virtual_network_gateway.main.id
  local_network_gateway_id       = azurerm_local_network_gateway.onprem.id
  connection_protocol            = "IKEv2"
  routing_weight                = 10
  enable_bgp                    = true
  express_route_gateway_bypass  = true

  shared_key = var.vpn_shared_key

  ipsec_policy {
    dh_group         = "DHGroup14"
    ike_encryption   = "AES256"
    ike_integrity    = "SHA256"
    ipsec_encryption = "AES256"
    ipsec_integrity  = "SHA256"
    pfs_group        = "PFS14"
    sa_datasize      = 102400000
    sa_lifetime      = 27000
  }

  traffic_selector_policy {
    local_address_cidrs  = ["10.0.0.0/24"]
    remote_address_cidrs = ["192.168.0.0/24"]
  }

  tags = var.tags
}

# NAT Rules
resource "azurerm_virtual_network_gateway_nat_rule" "main" {
  name                = "${var.project_name}-nat-rule"
  resource_group_name = var.resource_group_name
  gateway_name        = azurerm_virtual_network_gateway.main.name
  mode                = "EgressSnat"
  type                = "Static"
  
  internal_mapping {
    address_space = "10.0.0.0/24"
  }

  external_mapping {
    address_space = "192.168.0.0/24"
  }
}

## Network Configuration

1. **Network Security Groups**
```hcl
resource "azurerm_network_security_group" "gateway" {
  name                = "${var.project_name}-gateway-nsg"
  location            = var.location
  resource_group_name = var.resource_group_name

  security_rule {
    name                       = "AllowVPNGateway"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "*"
    source_port_range          = "*"
    destination_port_range     = "*"
    source_address_prefix      = "GatewayManager"
    destination_address_prefix = "*"
  }

  tags = var.tags
}

resource "azurerm_subnet_network_security_group_association" "gateway" {
  subnet_id                 = azurerm_subnet.gateway.id
  network_security_group_id = azurerm_network_security_group.gateway.id
}

Route Configuration

  1. Route Table
resource "azurerm_route_table" "main" {
  name                = "${var.project_name}-rt"
  location            = var.location
  resource_group_name = var.resource_group_name

  route {
    name                   = "ToOnPremise"
    address_prefix         = "192.168.0.0/16"
    next_hop_type         = "VirtualNetworkGateway"
  }

  tags = var.tags
}

resource "azurerm_subnet_route_table_association" "main" {
  subnet_id      = azurerm_subnet.main.id
  route_table_id = azurerm_route_table.main.id
}

Monitoring Configuration

  1. Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "vpn" {
  name                       = "${var.project_name}-diag"
  target_resource_id        = azurerm_virtual_network_gateway.main.id
  log_analytics_workspace_id = var.log_analytics_workspace_id

  log {
    category = "GatewayDiagnosticLog"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }

  metric {
    category = "AllMetrics"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 30
    }
  }
}

resource "azurerm_monitor_metric_alert" "vpn" {
  name                = "${var.project_name}-tunnel-alert"
  resource_group_name = var.resource_group_name
  scopes              = [azurerm_virtual_network_gateway.main.id]
  description         = "Alert when VPN tunnel is down"

  criteria {
    metric_namespace = "Microsoft.Network/virtualNetworkGateways"
    metric_name      = "TunnelAverageBandwidth"
    aggregation      = "Average"
    operator         = "LessThan"
    threshold        = 1
  }

  action {
    action_group_id = var.action_group_id
  }
}

Advanced Features

  1. Custom IPSec/IKE Policy
resource "azurerm_virtual_network_gateway_connection" "custom_policy" {
  name                = "${var.project_name}-custom-policy"
  location            = var.location
  resource_group_name = var.resource_group_name

  type                       = "IPsec"
  virtual_network_gateway_id = azurerm_virtual_network_gateway.main.id
  local_network_gateway_id   = azurerm_local_network_gateway.onprem.id

  ipsec_policy {
    dh_group         = "DHGroup14"
    ike_encryption   = "AES256"
    ike_integrity    = "SHA256"
    ipsec_encryption = "AES256"
    ipsec_integrity  = "SHA256"
    pfs_group        = "PFS2048"
    sa_datasize      = 102400000
    sa_lifetime      = 27000
  }

  shared_key = var.vpn_shared_key

  tags = var.tags
}

## Best Practices

1. **Performance**
   - Use appropriate SKU
   - Enable active-active
   - Configure BGP
   - Optimize routing

2. **High Availability**
   - Use zone-redundant gateways
   - Configure active-active
   - Implement failover
   - Monitor connections

3. **Security**
   - Use strong IPSec policies
   - Implement encryption
   - Configure authentication
   - Monitor traffic

4. **Cost Optimization**
   - Choose appropriate SKU
   - Monitor bandwidth usage
   - Optimize connections
   - Use appropriate zones

## Conclusion

You've learned how to set up and manage Azure VPN Gateway using Terraform. This setup provides:
- Secure cross-premises connectivity
- Site-to-site VPN
- Point-to-site VPN
- High availability

Remember to:
- Monitor tunnel status
- Review security policies
- Update certificates
- Maintain connections