Skip to content

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

☁️ Azure Virtual Wan Terraform Module

Create an Azure Virtual WAN — the global network backbone for virtual hubs, VPN/ExpressRoute gateways, and branch connectivity — with secure, full-featured defaults, on hashicorp/azurerm ~> 4.0.

Terraform azurerm module type resources

🧩 Overview

  • 🌐 Creates one azurerm_virtual_wan — the top-level backbone that virtual hubs and their gateways attach to.
  • 🛡️ Secure by default: the full-featured Standard tier, VPN encryption enabled, and no Office 365 local breakout.
  • 🧭 Exposes the WAN tier, VPN encryption toggle, branch-to-branch transit, and Office 365 local breakout — all typed and validated.
  • 🔗 Emits id, name, type, and location for downstream hub and gateway wiring.

💡 Why it matters: the Virtual WAN is the root of a hub-and-spoke global network. Choosing the Standard tier and keeping VPN encryption on at creation avoids a forced replacement later — the tier is immutable, and everything attached to the WAN would be replaced with it. The empty call gives you a production-grade backbone that hubs and gateways can build on immediately.

❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!

🗺️ Where this fits in the family

flowchart TD
  RG["terraform-azurerm-resource-group"]
  VWAN["terraform-azurerm-virtual-wan"]
  AVWAN["azurerm_virtual_wan"]
  HUB["terraform-azurerm-virtual-hub"]
  VPNGW["terraform-azurerm-vpn-gateway"]
  ERGW["terraform-azurerm-express-route-gateway"]
  P2S["terraform-azurerm-point-to-site-vpn-gateway"]
  CONN["VPN sites / ExpressRoute / P2S connections"]

  RG -->|"resource_group_name + location"| VWAN
  VWAN --> AVWAN
  VWAN -->|"virtual_wan_id"| HUB
  HUB -->|"virtual_hub_id"| VPNGW
  HUB -->|"virtual_hub_id"| ERGW
  HUB -->|"virtual_hub_id"| P2S
  VPNGW -->|"branch / site-to-site"| CONN
  ERGW -->|"private circuit"| CONN
  P2S -->|"remote users"| CONN

  classDef this fill:#0078D4,color:#ffffff,stroke:#004578,stroke-width:2px;
  classDef key fill:#004578,color:#ffffff,stroke:#004578;
  class VWAN this;
  class AVWAN key;
Loading

🧬 What this module builds

flowchart LR
  I1["name / location / resource_group_name"]
  I2["type = Standard (default)"]
  I3["disable_vpn_encryption = false (default)"]
  I4["allow_branch_to_branch_traffic = true (default)"]
  I5["office365_local_breakout_category = None (default)"]

  VWAN["azurerm_virtual_wan.this"]

  O1["id"]
  O2["name"]
  O3["type"]
  O4["location"]

  I1 --> VWAN
  I2 --> VWAN
  I3 --> VWAN
  I4 --> VWAN
  I5 --> VWAN
  VWAN --> O1
  VWAN --> O2
  VWAN --> O3
  VWAN --> O4

  classDef this fill:#0078D4,color:#ffffff,stroke:#004578,stroke-width:2px;
  class VWAN this;
Loading

Resource inventory

Resource Cardinality Role
azurerm_virtual_wan.this 1 (keystone) The Virtual WAN backbone.

✅ Provider / Versions

Requirement Value
Terraform >= 1.12.0
Provider hashicorp/azurerm ~> 4.0
Provider block None in this module — the caller configures provider "azurerm" { features {} }, auth, and subscription.

Schema notes that bite

  • name, resource_group_name, and location are immutable — changing any of them forces replacement of the Virtual WAN.
  • type is NOT force-new, and the trap is the other way round. A tier change is an ordinary in-place update, so both directions plan identically — but Microsoft supports only Basic → Standard. A Standard → Basic edit produces a clean one-line plan that Azure refuses at apply. The module defaults to Standard.
  • Basic is feature-limited. A Basic WAN supports site-to-site VPN only — no hub-to-hub transit, no ExpressRoute gateways in its hubs, and no custom routing. Standard unlocks the full secured-hub feature set.
  • allow_branch_to_branch_traffic defaults to true. This is the normal WAN behavior and permits lateral traffic between connected branches; set it to false to isolate branches.
  • The Virtual WAN itself has no public data plane to disable — exposure is governed by the gateways and hubs attached to it, each of which is a separate module.

🔑 Required Azure RBAC Roles / Permissions

  • Network Contributor on the target resource group (or a custom role with Microsoft.Network/virtualWans/*), scoped as narrowly as the deployment allows.

Azure Prerequisites

  • The Microsoft.Network resource provider registered on the subscription.
  • An existing resource group in a supported US Azure region.
  • The caller configures the provider "azurerm" { features {} } block, auth, and subscription; the module declares none of these.

📁 Module Structure

terraform-azurerm-virtual-wan/
├── providers.tf     # required_version + azurerm ~> 4.0; no provider block
├── variables.tf     # name, rg, location, type, encryption, branch transit, O365 breakout, tags, timeouts
├── main.tf          # azurerm_virtual_wan.this
├── outputs.tf       # id, name, type, location
├── README.md        # this document
├── SCOPE.md         # cross-module contract
├── LICENSE          # MIT
└── .gitignore       # canonical Terraform ignore set

⚙️ Quick Start

provider "azurerm" {
  features {}
}

module "vwan" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                = "vwan-hub-prod-eastus2"
  resource_group_name = "rg-network-prod-eastus2"
  location            = "eastus2"
}

ℹ️ The empty call is a Standard WAN with VPN encryption enabled and no Office 365 local breakout. Pin the module by tag (?ref=v1.0.0), never a branch. The caller configures the provider, auth, and features {}.

🔌 Cross-Module Contract

Consumes

Input Type From
resource_group_name string terraform-azurerm-resource-group (name)
location string caller / resource group (location)

Emits

Output Description Consumed by
id Virtual WAN Resource ID virtual hubs (virtual_wan_id), vpn-site, downstream modules
name Virtual WAN name diagnostics, tagging
type WAN tier in effect (Basic / Standard) topology validation
location Region the WAN was created in region-aware wiring

📚 Example Library

1 · Minimal (Standard, encryption on)
module "vwan" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                = "vwan-min-eastus2"
  resource_group_name = "rg-network-eastus2"
  location            = "eastus2"
}

🔒 Standard tier, VPN encryption enabled, no Office 365 local breakout — the secure baseline out of the box.

2 · Basic tier (feature-limited)
module "vwan" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                = "vwan-basic-eastus2"
  resource_group_name = "rg-network-eastus2"
  location            = "eastus2"
  type                = "Basic"
}

⚠️ A Basic WAN supports site-to-site VPN only — no hub-to-hub transit, no ExpressRoute gateways, no custom routing. Upgrading to Standard later is a supported in-place change that converts every hub in the WAN; it is the reverse that cannot be done.

3 · Branch-to-branch traffic disabled (branch isolation)
module "vwan" {
  source                         = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                           = "vwan-isolated-eastus2"
  resource_group_name            = "rg-network-eastus2"
  location                       = "eastus2"
  allow_branch_to_branch_traffic = false
}

🔒 Set allow_branch_to_branch_traffic = false to stop connected branches from reaching one another through the hub — a tighter posture for segmented topologies.

4 · Office 365 local breakout — Optimize
module "vwan" {
  source                            = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                              = "vwan-o365-opt-eastus2"
  resource_group_name               = "rg-network-eastus2"
  location                          = "eastus2"
  office365_local_breakout_category = "Optimize"
}

ℹ️ Breaks out only the Office 365 Optimize category locally, keeping latency-sensitive Microsoft 365 traffic off the backbone path.

5 · Office 365 local breakout — OptimizeAndAllow
module "vwan" {
  source                            = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                              = "vwan-o365-oa-eastus2"
  resource_group_name               = "rg-network-eastus2"
  location                          = "eastus2"
  office365_local_breakout_category = "OptimizeAndAllow"
}
6 · Office 365 local breakout — All
module "vwan" {
  source                            = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                              = "vwan-o365-all-eastus2"
  resource_group_name               = "rg-network-eastus2"
  location                          = "eastus2"
  office365_local_breakout_category = "All"
}

⚠️ All breaks out every Office 365 category locally — confirm this matches your egress and inspection policy before enabling.

7 · VPN encryption disabled (explicit opt-out)
module "vwan" {
  source                 = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                   = "vwan-noenc-eastus2"
  resource_group_name    = "rg-network-eastus2"
  location               = "eastus2"
  disable_vpn_encryption = true
}

⚠️ disable_vpn_encryption = true turns off VPN encryption across the WAN. The module defaults this to false (encryption on); set it only for a deliberate, reviewed exception.

8 · With tags
module "vwan" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                = "vwan-tagged-eastus2"
  resource_group_name = "rg-network-eastus2"
  location            = "eastus2"
  tags = {
    environment = "prod"
    owner       = "network-platform"
    costCenter  = "1234"
  }
}
9 · Custom timeouts
module "vwan" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                = "vwan-timeouts-eastus2"
  resource_group_name = "rg-network-eastus2"
  location            = "eastus2"
  timeouts = {
    create = "30m"
    delete = "30m"
  }
}
10 · Standard WAN ready for a secured hub
module "vwan" {
  source                         = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                           = "vwan-secured-eastus2"
  resource_group_name            = "rg-network-eastus2"
  location                       = "eastus2"
  type                           = "Standard"
  disable_vpn_encryption         = false
  allow_branch_to_branch_traffic = true
}

💡 A Standard WAN is the prerequisite for virtual hubs that host ExpressRoute gateways, hub-to-hub transit, and custom routing. Create the WAN here, then attach a terraform-azurerm-virtual-hub by module.vwan.id.

11 · for_each — a WAN per region
module "vwan" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  for_each            = toset(["eastus2", "westus2", "centralus"])
  name                = "vwan-${each.key}"
  resource_group_name = "rg-network-${each.key}"
  location            = each.key
}

ℹ️ A stable for_each key (the region) means adding or removing a region never re-indexes the others.

12 · Consuming a resource group's outputs
module "rg" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
  name     = "rg-network-prod-eastus2"
  location = "eastus2"
}

module "vwan" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                = "vwan-prod-eastus2"
  resource_group_name = module.rg.name
  location            = module.rg.location
}

💡 Wire resource_group_name and location from the resource-group module so the WAN follows the group's placement.

13 · 🏗️ End-to-end composition

A resource group, a Standard Virtual WAN, and a virtual hub (owned by its sibling module) attached to the WAN by id.

provider "azurerm" {
  features {}
}

module "rg" {
  source   = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
  name     = "rg-network-prod-eastus2"
  location = "eastus2"
}

module "vwan" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-wan.git?ref=v1.0.0"
  name                = "vwan-prod-eastus2"
  resource_group_name = module.rg.name
  location            = module.rg.location
  type                = "Standard"
}

# Hubs are a SEPARATE catalog module — this module stays a clean single-resource backbone.
module "hub" {
  source              = "git::https://github.com/microsoftexpert/terraform-azurerm-virtual-hub.git?ref=v1.0.0"
  name                = "vhub-prod-eastus2"
  resource_group_name = module.rg.name
  location            = module.rg.location
  virtual_wan_id      = module.vwan.id
  address_prefix      = "10.100.0.0/23"
}

💡 The WAN flows into the virtual hub by module.vwan.id. VPN/ExpressRoute gateways and connections then attach to the hub — each its own sibling module — so no single module owns the whole backbone.

📥 Inputs

Name Type Required Default Description
name string ✅ — Virtual WAN name. Immutable.
resource_group_name string ✅ — Containing resource group. Immutable.
location string ✅ — Azure region. Immutable.
type string — "Standard" Standard / Basic. Updatable in place; the downgrade to Basic is refused by Azure.
disable_vpn_encryption bool — false Disable VPN encryption across the WAN.
allow_branch_to_branch_traffic bool — true Allow branch-to-branch transit through the hub.
office365_local_breakout_category string — "None" None / Optimize / OptimizeAndAllow / All.
tags map(string) — {} Resource tags.
timeouts object — null Optional create/read/update/delete timeouts.
Full variable schemas
variable "type"                              { type = string, default = "Standard" } # Basic | Standard; in-place, upgrade-only
variable "disable_vpn_encryption"            { type = bool,   default = false }      # encryption ON by default
variable "allow_branch_to_branch_traffic"   { type = bool,   default = true }        # branch transit on (WAN default)
variable "office365_local_breakout_category" { type = string, default = "None" }     # None | Optimize | OptimizeAndAllow | All (validated)

variable "timeouts" {
  type = object({
    create = optional(string)
    read   = optional(string)
    update = optional(string)
    delete = optional(string)
  })
  default = null
}

🧾 Outputs

Output Description Notes
id Virtual WAN Resource ID Emitted first; consumed as virtual_wan_id by hubs.
name Virtual WAN name —
type WAN tier in effect Basic or Standard.
location Region the WAN was created in —

🧠 Architecture Notes

  • Single-resource ownership. This module owns exactly one azurerm_virtual_wan.this. Virtual hubs, VPN and ExpressRoute gateways, VPN sites, and connections are separate catalog modules that consume this WAN's id — keeping the backbone a clean, independently versioned boundary.
  • Immutable identity, but a MUTABLE tier with a one-way door. name, resource_group_name and location are force-new. type is not: the provider declares no ForceNew on it and sends it on update, so a tier change plans as an ordinary in-place edit. Basic → Standard works and converts every hub in the WAN. Standard → Basic produces an identical-looking plan and is refused by Azure at apply — the plan gives no warning, which is why the module defaults to Standard.
  • Encryption on by default. disable_vpn_encryption defaults to false; VPN traffic across the WAN is encrypted unless a caller explicitly opts out.
  • Branch transit is a security surface. allow_branch_to_branch_traffic defaults to true (standard WAN behavior). Where branches must not reach one another, set it to false.
  • No secrets. The module accepts and emits identifiers and configuration only.
  • features {} dependence. No provider {} block here; the caller configures provider "azurerm" { features {} }.

🧱 Design Principles

Concern Secure/full-featured default Opt-out
WAN tier Standard (full secured-hub feature set) Basic (VPN-only, limited)
VPN encryption disable_vpn_encryption = false (encryption on) true
Office 365 local breakout None (no local breakout) Optimize / OptimizeAndAllow / All
Branch-to-branch transit true (standard WAN behavior) false (branch isolation)

🚀 Runbook

cd terraform-azurerm-virtual-wan
terraform init -backend=false
terraform validate
terraform fmt -check
Remove-Item -Recurse -Force .terraform -ErrorAction SilentlyContinue

Pin the module by tag (?ref=v1.0.0), never a branch. Plan-only during authoring; a human runs plan/apply from CI.

🧪 Testing

The offline proof gate — terraform init -backend=false, terraform validate, terraform fmt -check — proves the configuration is type-correct against the pinned azurerm ~> 4.0 schema (including the type and office365_local_breakout_category validations) and canonically formatted, with no cloud calls. What it does not exercise: resource-provider registration, region availability, and the downstream compatibility of a Basic tier with hubs that need Standard features — those surface only under terraform plan/apply against real credentials from CI.

💬 Example Output

$ terraform output
id       = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-network-prod-eastus2/providers/Microsoft.Network/virtualWans/vwan-prod-eastus2"
location = "eastus2"
name     = "vwan-prod-eastus2"
type     = "Standard"

🔍 Troubleshooting

Symptom Cause Fix
Standard → Basic rejected at apply The downgrade is not supported by Azure, and type is not force-new so the plan looks ordinary Keep the WAN at Standard. There is no in-place path back, and no replacement path either without destroying every hub attached.
Hub cannot host an ExpressRoute gateway WAN is Basic Set type = "Standard". This is a supported in-place upgrade that converts every hub in the WAN — recreating the WAN is unnecessary and destroys everything attached to it.
Branches unexpectedly reach one another allow_branch_to_branch_traffic defaults to true Set it to false to isolate branches.
VPN traffic not encrypted disable_vpn_encryption = true was set Remove it (default false) to keep encryption on.
AuthorizationFailed on apply Identity lacks Network Contributor Grant Network Contributor on the resource group.
Create fails: resource provider not registered Microsoft.Network not registered Register the provider on the subscription.

🔗 Related Docs

  • Provider resource: azurerm_virtual_wan
  • Sibling modules: terraform-azurerm-virtual-hub, terraform-azurerm-vpn-gateway, terraform-azurerm-express-route-gateway, terraform-azurerm-point-to-site-vpn-gateway, terraform-azurerm-resource-group
  • This module's cross-module contract: SCOPE.md

💙 "Infrastructure as Code should be standardized, consistent, and secure."