Title here
Summary here
todo
vpc_id=$( aws ec2 create-vpc --cidr-block 10.0.0.0/16 --query 'Vpc.VpcId' --output text )
aws ec2 create-tags --resource "${vpc_id}" --tags Key=Name,Value=vpc-AzureAWSVPNsubnet_id=$( aws ec2 create-subnet --vpc-id "${vpc_id}" --cidr-block 10.0.1.0/24 --availability-zone ca-central-1 --query 'Subnet.SubnetId' --output text )
aws ec2 create-tags --resource "${subnet_id}" --tags Key=Name,Value=subnet-AzureAWSVPNvgw_id=$( aws ec2 create-vpn-gateway --type ipsec.1 --amazon-side-asn 64512 )
aws ec2 create-tags --resource "${vgw_id}" --tags Key=Name,Value=vgw-AzureAWSVPNaws ec2 attach-vpn-gatway --vpn-gateway-id $vgw_id --vpc-id $vpc_idcgw_id=$( aws ec2 create-customer-gateway --type ipsec.1 --public-ip [ azure_gw_ip ] --bgp-asn 65000)
aws ec2 create-tags --resource "${cgw_id}" --tags Key=Name,Value=cgw-AzureAWSVPNvpn_id=$( aws ec2 create-vpn-connection --type ipsec.1 --customer-gateway-id ="${cgw_id}" --vpn-gateway-id "${vgw_id}" --options StaticRoutesOnly=true
aws ec2 create-tags --resource "${vpn_id}" --tags Key=Name,Value=vpn-AzureAWSVPNtodo
todo
todo
todo
todo
To allow SSH & ICMP
todo
todo
Project details:
Instance details:
Leave everything else as-is => Review + create => Create
rg_name="RG-AzureAWSVPN"
vnet_name="VNet-AzureAWSVPN"
ip_name="IP-AzureAWSVPN"
gw_name="GW-AzureAWSVPN"az group create --name $rg_name --location canadacentralaz network vnet create --name $vnet_name --resource-group $rg_name --address-prefix 172.16.0.0/16 --subnet-name Subnet-AzureAWSVPN --subnet-prefix 172.16.1.0/24az network vnet subnet create --resource-group $rg_name --vnet-name $vnet_name --name GatewaySubnet --address-prefixes 172.16.254.0/27az network public-ip create --name $ip_name --resource-group $rg_name --allocation-method Staticaz network vnet-gateway create --name $gw_name --resource-group $rg_name --public-ip-address $ip_name --vnet $vnet_name --gateway-type Vpn --vpn-type RouteBased --sku VpnGw1this may take awhile, start the AWS stuff while the wheels are turning here
todo
GitLab Terraform repository incoming in the near future
But for now:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.110"
}
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.6.0"
}
# -------------------------------
# Providers
# -------------------------------
provider "azurerm" {
features {}
subscription_id = var.azure_subscription_id
}
provider "aws" {
region = var.aws_region
}
# -------------------------------
# Azure side
# -------------------------------
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/resource_group
resource "azurerm_resource_group" "azure" {
name = "RG-AzureAWSVPN"
location = var.azure_location
}
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network
resource "azurerm_virtual_network" "azure" {
name = "VNet-AzureAWSVPN"
address_space = ["172.16.0.0/16"]
location = azurerm_resource_group.azure.location
resource_group_name = azurerm_resource_group.azure.name
}
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet
resource "azurerm_subnet" "internal" {
name = "Subnet-AzureAWSVPN"
resource_group_name = azurerm_resource_group.azure.name
virtual_network_name = azurerm_virtual_network.azure.name
address_prefixes = ["172.16.1.0/24"]
}
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subnet
resource "azurerm_subnet" "gateway" {
name = "GatewaySubnet"
resource_group_name = azurerm_resource_group.azure.name
virtual_network_name = azurerm_virtual_network.azure.name
address_prefixes = ["172.16.254.0/27"]
}
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/public_ip
resource "azurerm_public_ip" "vpn" {
name = "IP-AzureAWSVPN"
location = azurerm_resource_group.azure.location
resource_group_name = azurerm_resource_group.azure.name
allocation_method = "Static"
sku = "Standard"
}
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_gateway
resource "azurerm_virtual_network_gateway" "vpn" {
name = "GW-AzureAWSVPN"
location = azurerm_resource_group.azure.location
resource_group_name = azurerm_resource_group.azure.name
type = "Vpn"
vpn_type = "RouteBased"
active_active = false
enable_bgp = false
sku = "VpnGw2"
ip_configuration {
name = "gw-ipconfig"
public_ip_address_id = azurerm_public_ip.vpn.id
subnet_id = azurerm_subnet.gateway.id
}
}
# AWS will create its gateway IP; Azure needs that for the LNG
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/local_network_gateway
resource "azurerm_local_network_gateway" "aws" {
name = "LNG-AzureAWSVPN"
location = azurerm_resource_group.azure.location
resource_group_name = azurerm_resource_group.azure.name
#gateway_address = aws_vpn_gateway.aws_vgw.id != "" ? aws_vpn_gateway.aws_vgw.tags["PublicIp"] : null
# We'll override this below properly
#gateway_address = aws_vpn_connection.aws.connection_static_ip
gateway_address = aws_vpn_connection.aws.tunnel1_address
#address_space = ["10.0.1.0/24"]
address_space = ["10.0.0.0/16"]
}
# VPN connection (Azure → AWS)
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_gateway_connection
resource "azurerm_virtual_network_gateway_connection" "vpn" {
name = "CON-AzureAWSVPN"
location = azurerm_resource_group.azure.location
resource_group_name = azurerm_resource_group.azure.name
type = "IPsec"
virtual_network_gateway_id = azurerm_virtual_network_gateway.vpn.id
local_network_gateway_id = azurerm_local_network_gateway.aws.id
shared_key = var.shared_key
}
# Security group allowing SSH + ICMP
resource "azurerm_network_security_group" "nsg" {
name = "NSG-AzureAWSVPN"
location = azurerm_resource_group.azure.location
resource_group_name = azurerm_resource_group.azure.name
security_rule {
name = "AllowSSHFromAnywhere"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "AllowICMPInternal"
priority = 200
direction = "Inbound"
access = "Allow"
protocol = "Icmp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "172.16.1.0/24"
destination_address_prefix = "172.16.1.0/24"
}
}
# -------------------------------
# AWS side
# -------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc
resource "aws_vpc" "aws" {
cidr_block = "10.0.0.0/16"
tags = { Name = "VPC-AzureAWSVPN" }
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet
resource "aws_subnet" "aws" {
vpc_id = aws_vpc.aws.id
cidr_block = "10.0.1.0/24"
availability_zone = "${var.aws_region}a"
tags = { Name = "Subnet-AzureAWSVPN" }
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpn_gateway
resource "aws_vpn_gateway" "aws_vgw" {
vpc_id = aws_vpc.aws.id
amazon_side_asn = 64512
tags = { Name = "VGW-AzureAWSVPN" }
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpn_gateway_attachment
resource "aws_vpn_gateway_attachment" "attach" {
vpc_id = aws_vpc.aws.id
vpn_gateway_id = aws_vpn_gateway.aws_vgw.id
}
# Customer gateway (uses Azure VPN gateway public IP)
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/customer_gateway
resource "aws_customer_gateway" "azure_cgw" {
bgp_asn = 65000
ip_address = azurerm_public_ip.vpn.ip_address
type = "ipsec.1"
tags = { Name = "CGW-AzureAWSVPN" }
}
# VPN connection (AWS → Azure)
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpn_connection
resource "aws_vpn_connection" "aws" {
customer_gateway_id = aws_customer_gateway.azure_cgw.id
#customer_gateway_id = aws_customer_gateway.aws_cgw.id
vpn_gateway_id = aws_vpn_gateway.aws_vgw.id
type = "ipsec.1"
tunnel1_preshared_key = var.shared_key
tunnel2_preshared_key = var.shared_key
static_routes_only = true
# Deprecated apparently
#Provider Version Inline routes block aws_vpn_connection_route resource
#≤ v4.0 ❌ Not supported ✅ Required (only method)
#4.1 – 4.63 ✅ Supported ✅ Still supported (both worked)
#≥ v5.0 (current) ❌ Removed (read-only) ✅ Only supported method
#routes = [
# {
# destination_cidr_block = "172.16.1.0/24"
# source = "static"
# state = "active"
# }
#]
tags = { Name = "AWS-VPN-To-Azure" }
}
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpn_connection_route
# Use this instead of the inline routes parameter for vpn_connection apparently
resource "aws_vpn_connection_route" "azure_subnet" {
vpn_connection_id = aws_vpn_connection.aws.id
destination_cidr_block = "172.16.1.0/24"
}
# Enable route propagation
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpn_gateway_route_propagation
resource "aws_vpn_gateway_route_propagation" "prop" {
route_table_id = aws_vpc.aws.main_route_table_id
vpn_gateway_id = aws_vpn_gateway.aws_vgw.id
}
# -------------------------------
# Example Azure VM (depends implicitly on network resources)
# -------------------------------
resource "azurerm_public_ip" "vm_ip" {
name = "vm-public-ip"
location = azurerm_resource_group.azure.location
resource_group_name = azurerm_resource_group.azure.name
allocation_method = "Static"
sku = "Standard"
}
resource "azurerm_network_interface" "vmnic" {
name = "nic-azureaws"
location = azurerm_resource_group.azure.location
resource_group_name = azurerm_resource_group.azure.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.internal.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.vm_ip.id
}
}
resource "azurerm_linux_virtual_machine" "vm" {
name = "VM-AzureAWSVPN"
resource_group_name = azurerm_resource_group.azure.name
location = azurerm_resource_group.azure.location
size = "Standard_B1s"
admin_username = "azureuser"
network_interface_ids = [azurerm_network_interface.vmnic.id]
admin_ssh_key {
username = "azureuser"
public_key = file("~/.ssh/id_rsa.pub")
}
os_disk {
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
version = "latest"
}
}
# Associate NSG with subnet
resource "azurerm_subnet_network_security_group_association" "assoc" {
subnet_id = azurerm_subnet.internal.id
network_security_group_id = azurerm_network_security_group.nsg.id
}
# ---------------------------------------
# AWS Internet Gateway + Route Table
# ---------------------------------------
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.aws.id
tags = { Name = "IGW-AzureAWSVPN" }
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.aws.id
tags = { Name = "RT-Public-AzureAWSVPN" }
}
# Default route through Internet Gateway
resource "aws_route" "default_internet_access" {
route_table_id = aws_route_table.public.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
# Associate route table with subnet
resource "aws_route_table_association" "public_assoc" {
subnet_id = aws_subnet.aws.id
route_table_id = aws_route_table.public.id
}
# Enable VPN route propagation as well
resource "aws_vpn_gateway_route_propagation" "prop_public" {
route_table_id = aws_route_table.public.id
vpn_gateway_id = aws_vpn_gateway.aws_vgw.id
}
# ---------------------------------------
# AWS Security Group for VM
# ---------------------------------------
resource "aws_security_group" "vm_sg" {
name = "SG-AzureAWSVPN"
description = "Allow SSH from anywhere, ICMP from Azure internal subnet"
vpc_id = aws_vpc.aws.id
ingress {
description = "Allow SSH from anywhere"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "Allow ICMP from Azure subnet"
from_port = -1
to_port = -1
protocol = "icmp"
cidr_blocks = ["172.16.1.0/24"]
}
egress {
description = "Allow all outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "SG-AzureAWSVPN" }
}
# ---------------------------------------
# AWS EC2 Instance
# ---------------------------------------
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance
resource "aws_instance" "vm" {
ami = data.aws_ami.amazon_linux.id
instance_type = "t2.micro"
subnet_id = aws_subnet.aws.id
vpc_security_group_ids = [aws_security_group.vm_sg.id]
associate_public_ip_address = true
key_name = var.aws_key_pair
tags = { Name = "VM-AzureAWSVPN" }
}
# Fetch latest Amazon Linux 2 AMI automatically
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
variable "aws_region" {
type = string
default = "ca-central-1"
}
variable "azure_subscription_id" {
type = string
default = "[ subscription_id ]"
}
variable "azure_location" {
type = string
default = "canadacentral"
}
variable "aws_key_pair" {
type = string
default = "[ keypair_name ]"
}
variable "shared_key" {
type = string
sensitive = true
default = "[ shared_key ]"
}
# ---------------------------------------
# Output: Azure & AWS VPN Connection Info
# ---------------------------------------
output "azure_vpn_gateway_public_ip" {
description = "Public IP address of the Azure VPN Gateway (connect here from AWS)"
value = azurerm_public_ip.vpn.ip_address
}
output "aws_customer_gateway_public_ip" {
description = "Public IP address of the AWS Customer Gateway (connect here from Azure)"
value = aws_vpn_connection.aws.tunnel1_address
}
output "aws_vpn_tunnel1_external_ip" {
description = "AWS tunnel 1 external IP (connects to Azure VPN Gateway)"
value = aws_vpn_connection.aws.tunnel1_address
}
output "aws_vpn_tunnel2_external_ip" {
description = "AWS tunnel 2 external IP (secondary tunnel)"
value = aws_vpn_connection.aws.tunnel2_address
}
output "aws_vpn_tunnel1_inside_cidr" {
description = "AWS tunnel 1 inside CIDR"
value = aws_vpn_connection.aws.tunnel1_inside_cidr
}
output "aws_vpn_tunnel2_inside_cidr" {
description = "AWS tunnel 2 inside CIDR"
value = aws_vpn_connection.aws.tunnel2_inside_cidr
}
output "aws_vpn_connection_shared_key_tunnel1" {
description = "Preshared key for tunnel 1"
value = aws_vpn_connection.aws.tunnel1_preshared_key
sensitive = true
}
output "aws_vpn_connection_shared_key_tunnel2" {
description = "Preshared key for tunnel 2"
value = aws_vpn_connection.aws.tunnel2_preshared_key
sensitive = true
}
output "aws_vpn_static_routes" {
description = "Static routes configured on the AWS VPN Connection"
value = aws_vpn_connection.aws.routes
}
output "azure_to_aws_static_routes" {
description = "Azure local network gateway address space(s)"
value = azurerm_local_network_gateway.aws.address_space
}
# ---------------------------------------
# Optional: Render a simple configuration template for each side
# ---------------------------------------
output "azure_vpn_device_config_template" {
description = "Configuration summary for Azure side (for manual validation)"
value = <<EOT
Azure VPN Gateway: ${azurerm_virtual_network_gateway.vpn.name}
Public IP: ${azurerm_public_ip.vpn.ip_address}
Connects to AWS VPN Gateway:
- AWS Tunnel 1 IP: ${aws_vpn_connection.aws.tunnel1_address}
- AWS Tunnel 2 IP: ${aws_vpn_connection.aws.tunnel2_address}
Shared Key Tunnel 1: ${aws_vpn_connection.aws.tunnel1_preshared_key}
Shared Key Tunnel 2: ${aws_vpn_connection.aws.tunnel2_preshared_key}
Address Spaces:
Azure VNet: 172.16.1.0/24
AWS Subnet: 10.0.1.0/24
EOT
sensitive = true
}
output "aws_vpn_device_config_template" {
description = "Configuration summary for AWS side (for manual validation)"
value = <<EOT
AWS VPN Connection: ${aws_vpn_connection.aws.id}
Tunnels:
- Tunnel 1: ${aws_vpn_connection.aws.tunnel1_address}
- Tunnel 2: ${aws_vpn_connection.aws.tunnel2_address}
Tunnel Inside CIDRs:
- ${aws_vpn_connection.aws.tunnel1_inside_cidr}
- ${aws_vpn_connection.aws.tunnel2_inside_cidr}
Connects to Azure Gateway:
- IP: ${azurerm_public_ip.vpn.ip_address}
Static Route(s): ${join(", ", [for r in aws_vpn_connection.aws.routes : r.destination_cidr_block])}
Shared Key Tunnel 1: ${aws_vpn_connection.aws.tunnel1_preshared_key}
Shared Key Tunnel 2: ${aws_vpn_connection.aws.tunnel2_preshared_key}
EOT
sensitive = true
}todo