Use Case — Site-to-Site VPN: US Office ↔ AWS VPC

On-prem side US HQ office, UniFi UDM Pro Max — 10.12.0.0/16
AWS side US AWS prod VPC, us-west-2 — 10.1.0.0/20
Tunnel IPsec site-to-site (IKEv2), static routing
DNS UDM built-in DNS → conditional forward → Route 53 Resolver inbound endpoint

1. Architecture

        Office (UDM Pro Max)                         AWS us-west-2 prod VPC (10.1.0.0/20)
        10.12.0.0/16                                 ┌───────────────────────────────────┐
   ┌─────────────────────┐      IPsec tunnel         │  Virtual Private Gateway (VGW)     │
   │ VLANs 20/30/40/80 ──┼──── (IKEv2, 2 tunnels) ───┼──▶ attached to VPC                 │
   │ UDM built-in DNS    │      over WAN             │                                   │
   │  (dnsmasq)          │                           │  Route 53 Resolver INBOUND endpoint│
   └─────────┬───────────┘                           │   10.1.4.10 (app1, AZ-a)          │
             │ forward aws.corp.example.com          │   10.1.5.10 (app2, AZ-b)          │
             └──────────── DNS/53 over tunnel ──────▶│  ▲ Private Hosted Zone            │
                                                     └──┼────────────────────────────────┘
                                                        aws.corp.example.com

Two flows ride the tunnel: data (office subnets ↔ VPC subnets) and DNS (office clients querying AWS private names, forwarded by the UDM to the Route 53 Resolver inbound endpoint inside the VPC).

This terminates the tunnel on a Virtual Private Gateway attached directly to the VPC (simplest for one office ↔ one VPC). In the full hub-and-spoke design you would instead terminate on the regional Transit Gateway and reach all VPCs behind it — same UDM config, different AWS endpoint.

2. Addressing and routing

Office prefix advertised to AWS 10.12.0.0/16 (Guest/IoT VLANs excluded by firewall policy)
AWS prefix advertised to office 10.1.0.0/20 (the VPC)
Resolver inbound endpoint IPs 10.1.4.10, 10.1.5.10 (two AZs)
Private hosted zone aws.corp.example.com

On AWS, route propagation from the VGW adds 10.12.0.0/16 to the VPC route tables. On the UDM, the tunnel's remote subnet (10.1.0.0/20) is installed as a route automatically. Because both sides sit under the US 10.0.0.0/12, the route tables stay compact.


3. Part A — AWS side (Terraform)

Extends the existing us-west-2 prod VPC (aws_vpc.main, subnets keyed app1/app2, route tables private/data).

###############################################################################
# Site-to-Site VPN: US office (UDM Pro Max) -> AWS us-west-2 prod VPC
###############################################################################

variable "udm_public_ip" {
  type        = string
  description = "Public (WAN) IP of the US office UDM Pro Max"
}

variable "office_cidr" {
  type    = string
  default = "10.12.0.0/16"
}

# --- Customer Gateway = the UDM ------------------------------------------------
resource "aws_customer_gateway" "us_office" {
  bgp_asn    = 65000 # required by the resource even for static routing
  ip_address = var.udm_public_ip
  type       = "ipsec.1"
  tags       = { Name = "cgw-us-office-udm" }
}

# --- Virtual Private Gateway attached to the VPC -------------------------------
resource "aws_vpn_gateway" "main" {
  vpc_id          = aws_vpc.main.id
  amazon_side_asn = 64512
  tags            = { Name = "vgw-us-aws-usw2-prod" }
}

# --- The VPN connection (static routing, IKEv2, hardened tunnel params) --------
resource "aws_vpn_connection" "us_office" {
  customer_gateway_id = aws_customer_gateway.us_office.id
  vpn_gateway_id      = aws_vpn_gateway.main.id
  type                = "ipsec.1"
  static_routes_only  = true

  # Tunnel 1 (mirror these onto tunnel2_* for the second, redundant tunnel)
  tunnel1_ike_versions                  = ["ikev2"]
  tunnel1_phase1_encryption_algorithms  = ["AES256"]
  tunnel1_phase2_encryption_algorithms  = ["AES256"]
  tunnel1_phase1_integrity_algorithms   = ["SHA2-256"]
  tunnel1_phase2_integrity_algorithms   = ["SHA2-256"]
  tunnel1_phase1_dh_group_numbers       = [14]
  tunnel1_phase2_dh_group_numbers       = [14]

  tunnel2_ike_versions                  = ["ikev2"]
  tunnel2_phase1_encryption_algorithms  = ["AES256"]
  tunnel2_phase2_encryption_algorithms  = ["AES256"]
  tunnel2_phase1_integrity_algorithms   = ["SHA2-256"]
  tunnel2_phase2_integrity_algorithms   = ["SHA2-256"]
  tunnel2_phase1_dh_group_numbers       = [14]
  tunnel2_phase2_dh_group_numbers       = [14]

  tags = { Name = "vpn-us-office-to-usw2" }
}

# Static route telling AWS the office prefix is reachable over this tunnel.
resource "aws_vpn_connection_route" "office" {
  destination_cidr_block = var.office_cidr
  vpn_connection_id      = aws_vpn_connection.us_office.id
}

# Propagate VGW routes into the tiers that should reach the office.
resource "aws_vpn_gateway_route_propagation" "private" {
  for_each       = toset(local.azs)
  vpn_gateway_id = aws_vpn_gateway.main.id
  route_table_id = aws_route_table.private[each.key].id
}

resource "aws_vpn_gateway_route_propagation" "data" {
  vpn_gateway_id = aws_vpn_gateway.main.id
  route_table_id = aws_route_table.data.id
}

# Allow the office prefix toward in-VPC workloads (example SG).
resource "aws_security_group" "from_office" {
  name        = "sg-allow-from-us-office"
  description = "Allow traffic from the US office over the VPN"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "All from office (tighten per app)"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [var.office_cidr]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = { Name = "sg-allow-from-us-office" }
}

###############################################################################
# Route 53 Resolver INBOUND endpoint (office -> AWS private DNS)
###############################################################################

resource "aws_security_group" "r53_inbound" {
  name        = "sg-r53-resolver-inbound"
  description = "DNS from the US office to the Route 53 Resolver inbound endpoint"
  vpc_id      = aws_vpc.main.id

  ingress {
    description = "DNS UDP from office"
    from_port   = 53
    to_port     = 53
    protocol    = "udp"
    cidr_blocks = [var.office_cidr]
  }
  ingress {
    description = "DNS TCP from office"
    from_port   = 53
    to_port     = 53
    protocol    = "tcp"
    cidr_blocks = [var.office_cidr]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = { Name = "sg-r53-resolver-inbound" }
}

resource "aws_route53_resolver_endpoint" "inbound" {
  name               = "r53-inbound-usw2"
  direction          = "INBOUND"
  security_group_ids = [aws_security_group.r53_inbound.id]

  ip_address {
    subnet_id = aws_subnet.this["app1"].id # 10.1.4.0/24, AZ-a
    ip        = "10.1.4.10"
  }
  ip_address {
    subnet_id = aws_subnet.this["app2"].id # 10.1.5.0/24, AZ-b
    ip        = "10.1.5.10"
  }
  tags = { Name = "r53-inbound-usw2" }
}

# Example private hosted zone the office will resolve through the endpoint.
resource "aws_route53_zone" "private" {
  name = "aws.corp.example.com"
  vpc {
    vpc_id = aws_vpc.main.id
  }
}

###############################################################################
# Outputs needed to configure the UDM
###############################################################################

output "vpn_tunnel1_address" { value = aws_vpn_connection.us_office.tunnel1_address }
output "vpn_tunnel2_address" { value = aws_vpn_connection.us_office.tunnel2_address }

output "vpn_tunnel1_psk" {
  value     = aws_vpn_connection.us_office.tunnel1_preshared_key
  sensitive = true
}
output "vpn_tunnel2_psk" {
  value     = aws_vpn_connection.us_office.tunnel2_preshared_key
  sensitive = true
}

output "resolver_inbound_ips" {
  value = [for a in aws_route53_resolver_endpoint.inbound.ip_address : a.ip]
}

4. Part B — UDM Pro Max side (UniFi Network)

4.1 The IPsec tunnel

Settings → VPN → Site-to-Site VPN → Manual IPsec:

Field Value
Remote Gateway vpn_tunnel1_address (AWS tunnel 1 outside IP)
Pre-Shared Key vpn_tunnel1_psk
Local subnets 10.12.0.0/16 (or list Corporate/App/Data/Cloud-Transit VLANs only)
Remote subnets 10.1.0.0/20
Key Exchange IKEv2
Encryption AES-256
Hash SHA-256
DH / PFS group 14

The tunnel parameters must match the AWS side exactly (the Terraform above sets AES-256 / SHA2-256 / DH14, IKEv2). AWS provisions two tunnel endpoints for HA; configure tunnel 1 as primary and add tunnel 2 as a second site-to-site entry for failover. Seamless dual-tunnel failover is cleanest with BGP (see Notes).

The office firewall already permits Corporate/Application/Data → Cloud Transit; that zone policy now routes across this tunnel. Guest and IoT remain blocked, so they never traverse the VPN even though 10.12.0.0/16 is the encryption domain.

4.2 Split DNS → Route 53

Goal: an office client asking for db.aws.corp.example.com is answered with the record's private VPC IP, resolved by Route 53 inside AWS.

  1. Point clients at the UDM. In each VLAN's DHCP settings, hand out the UDM as the DNS server (its per-VLAN gateway IP, e.g. 10.12.20.1). All client lookups now hit the UDM's built-in DNS.

Fallback if your UniFi version lacks Forward Domain in the UI. The built-in DNS is dnsmasq, so the same result comes from a server=/<domain>/<resolver-ip> directive, applied via a udm-utilities on-boot script (SSH access required):

# /mnt/data/on_boot.d/10-aws-dns.sh
cat > /run/dnsmasq.conf.d/aws-dns.conf <<'EOF'
server=/aws.corp.example.com/10.1.4.10
server=/aws.corp.example.com/10.1.5.10
EOF
kill -9 "$(cat /run/dnsmasq.pid)"
On-boot scripts are a community mechanism, not an Ubiquiti-supported feature — prefer the native Forward Domain UI where available; use the script only as a fallback, and re-test after firmware upgrades.

Add a Forward Domain (Settings → DNS → Forward Domain, in current UniFi Network):

Domain Forward to
aws.corp.example.com 10.1.4.10, 10.1.5.10

Add the reverse zone too if you need PTR lookups for VPC IPs (e.g. 1.10.in-addr.arpa → same resolver IPs). Everything else continues to your normal upstream resolver.


5. Verification

  1. Tunnel up: AWS console → Site-to-Site VPN shows both tunnels UP; UDM → VPN shows the peer connected.
  2. Data path: from an office Application-VLAN host, ping/curl a private IP in 10.1.x.x; from an EC2 instance, reach an office host in 10.12.x.x.
  3. DNS: from an office client, nslookup db.aws.corp.example.com returns a 10.1.x.x private IP (proves the query reached the Route 53 inbound endpoint). A public name like example.com still resolves via the normal upstream (proves only the AWS domain is conditionally forwarded).

6. Notes

  • Reverse resolution (AWS → office). For EC2 to resolve on-prem names, add a Route 53 Resolver OUTBOUND endpoint plus a forwarding rule sending corp.example.com to the UDM's DNS IP (10.12.10.1). That's the mirror of the inbound flow.
  • BGP option. Set static_routes_only = false, give the UDM and CGW real ASNs, and enable BGP on the UDM. AWS then advertises 10.1.0.0/20 and learns 10.12.0.0/16 dynamically, and dual-tunnel failover becomes automatic — preferable for production.
  • Transit Gateway. To reach every US AWS VPC (not just this one), attach the VPN to a Transit Gateway instead of the VGW and let the office learn the summarized 10.0.0.0/14. The UDM config is unchanged except the remote subnet.
  • Tighten the encryption domain. Instead of 10.12.0.0/16, list only the VLANs that need AWS (10.12.20.0/24, 10.12.30.0/24, 10.12.40.0/24, 10.12.80.0/24) so Guest/IoT are excluded at the IPsec layer as well as the firewall.
  • Secrets. The tunnel PSKs are sensitive Terraform outputs — retrieve them with terraform output -raw vpn_tunnel1_psk and enter them in the UniFi UI; don't commit them.
  • DNS resolution of VPC interface endpoints. If you use PrivateLink/interface endpoints with private DNS, forwarding <region>.amazonaws.com-style zones (or the specific service domains) to the resolver makes those resolve to private IPs from the office too.

Read more

SOP — Create and Manage Microsoft Azure for Enterprise

Version 1.0 Date 2026-06-22 Domain mptwork.com Model Identity rooted in the existing Microsoft Entra tenant; hierarchy of Management Groups → Subscriptions → Resource Groups → Resources Consoles Azure portal portal.azure.com · Entra admin center entra.microsoft.com Companion SOPs M365/Entra — sop-subscribe-manage-m365-mptwork.md, sop-entra-id-authentication-sso-mptwork.md · Network/IPAM — enterprise-ipam-reference-architecture.md · AWS

By admin

SOP — Create and Manage a Google Cloud (GCP) Account

Version 1.0 Date 2026-06-22 Domain mptwork.com Model GCP Organization rooted in Cloud Identity / Google Workspace for mptwork.com; hierarchy of Folders → Projects; workforce access via Google Groups, optionally federated to Microsoft Entra Companion SOPs Google Workspace — sop-subscribe-manage-google-workspace-mptwork.md · Entra auth & SSO — sop-entra-id-authentication-sso-mptwork.md · Network/IPAM — enterprise-ipam-reference-architecture.md

By admin