AWS Site-to-Site VPN with Azure

AWS and Azure Configuration with Terraform

Introduction

Silos cannot function in the modern world. Interoperability is crucial for successful businesses and as much as one may want, a truly homogenous environment is hard to come by. In our everyday demands, all of us experience the urgent necessity to quickly establish communication across various technological environments from various companies. With that, I’ve attempted to demonstrate how easily we can set up a Site-to-Site VPN connection between AWS and Azure, and even further ease the need of a rinse-and-repeat option with Terraform.

Prerequisites

As this will be a Infrastructure as Code demonstration, you should have:

  1. Understanding of Terraform
  2. An AWS Account with the correct privileges to administer a VPC, EC2, and Site to Site VPN Connections and related objects
  3. An Azure Subscription with the correct privileges to administer a Resource Group, VNet and subnets, VPN Connections and related objects

Logical Diagram of Final Output

Terraform

Because each vendor’s Terraform file will have dependencies of the other, you will have to make the choice of which one to run first and use the output of that run to input into the other vendor’s Terraform file. Of course you can run everything at once with the dependency clauses; however for demonstration purposes, I have these vendor files separated.

AWS

#use the aws provider and find the default profile in the aws credentials file
provider "aws" {
  region = "us-east-1"
  profile                 = "default"
}

#find the latest AWS Linux AMI
data "aws_ssm_parameter" "linux" {
  name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}

#we will use the default vpc (172.31.0.0/16)
data "aws_vpc" "default" {
    default = true
}

#defining a subnet in the default vpc to place my Linux EC2
data "aws_subnet" "default" {
    vpc_id = data.aws_vpc.default.id
    availability_zone = "us-east-1a"
    default_for_az = true
}

#create a security group to allow all inbound from local AWS CIDR and Azure Vnet of 192.168.0.0/16 and all outbound to anywhere
resource "aws_security_group" "allowIn" {
  name        = "allow_inbound"
  vpc_id      = data.aws_vpc.default.id

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [data.aws_vpc.default.cidr_block, "192.168.0.0/16"]
  }
    egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

#Linux creation in the default VPC and subnet defined earlier, tied to security group created earlier, and outputting the private IP address of the EC2 after creation
resource "aws_instance" "awslinux01" {
    ami = "${data.aws_ssm_parameter.linux.value}"
    instance_type = "t3.medium"
    tags = {
        Name = "awslinux"
    }
    subnet_id = data.aws_subnet.default.id
    vpc_security_group_ids = [aws_security_group.allowIn.id]

}

output "AWSLinuxPrivateIP" {
    value = aws_instance.awslinux01.private_ip
}

#This IP address will either have to be put in now or after the Azure Terraform is run
resource "aws_customer_gateway" "azureVNG" {
  bgp_asn    = 65000
  ip_address = "20.163.133.4"
  type       = "ipsec.1"

  tags = {
    Name = "AWS-Virtual Network Gateway"
  }
}

#Virtual Private Gateway creation and attachment to AWS VPC; Route propagation enabled
resource "aws_vpn_gateway" "vpngw" {
  vpc_id = data.aws_vpc.default.id

  tags = {
    Name = "vpngw"
  }
}

resource "aws_vpn_gateway_attachment" "vpngw_attachment" {
  vpc_id         = data.aws_vpc.default.id
  vpn_gateway_id = aws_vpn_gateway.vpngw.id
}

resource "aws_vpn_gateway_route_propagation" "routepropagation" {
  vpn_gateway_id = aws_vpn_gateway.vpngw.id
  route_table_id = data.aws_vpc.default.main_route_table_id
}

#Creation of site to site VPN in AWS using the AWS Virtual Private Gateway, the Customer Gateway of the Azure Virtual Network Gateway, and a predefined pre-shared key for the tunnel
resource "aws_vpn_connection" "vpn" {
  vpn_gateway_id      = aws_vpn_gateway.vpngw.id
  customer_gateway_id = aws_customer_gateway.cgw.id
  type                = "ipsec.1"
  static_routes_only  = true
  tunnel1_preshared_key = "abc123xyz987"
}

#create static route to Azure Vnet on the AWS VPN side
resource "aws_vpn_connection_route" "AzureNetwork" {
  destination_cidr_block = "192.168.0.0/16"
  vpn_connection_id      = aws_vpn_connection.vpn.id
}

#output of Tunnel 1 IP address to be used in Azure Terraform
output "AWStunnel1IP" {
  value = aws_vpn_connection.vpn.tunnel1_address
}

Azure

#Use the Azure provider with the Subscription ID
provider "azurerm" {
  features {
    resource_group {
       prevent_deletion_if_contains_resources = false
    }
 
}
 subscription_id="12a3b45c-6d78-9012-e3f4-5648g90hi123"
}

#Create resource group to hold all objects
resource "azurerm_resource_group" "myResourceGroup" {
  name     = "myrg"
  location = "East US"
}

#Create a VNet with a CIDR that doesn't overlap with the AWS CIDR
resource "azurerm_virtual_network" "vnet" {
  name                = "vnet1"
  location            = azurerm_resource_group.myResourceGroup.location
  resource_group_name = azurerm_resource_group.myResourceGroup.name
  address_space       = ["192.168.0.0/16"]
    tags = {
    environment = "Testing"
  }
}

#Create a subnet to hold my Azure Virtual Machine
resource "azurerm_subnet" "privateSubnet" {
  name                 = "defaultsubnet"
  resource_group_name  = azurerm_resource_group.myResourceGroup.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["192.168.1.0/24"]
}

#Create a gateway subnet to hold the Virtual Network Gateway
resource "azurerm_subnet" "gatewaysubnet" {
  name                 = "GatewaySubnet"
  resource_group_name  = azurerm_resource_group.myResourceGroup.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["192.168.10.0/27"]
}

#Create Virtual Network Gateway Public IP and output value to use in AWS section of the Customer Gateway
resource "azurerm_public_ip" "ipVNG" {
  name                = "vngPubIP"
  location            = azurerm_resource_group.myResourceGroup.location
  resource_group_name = azurerm_resource_group.myResourceGroup.name
  allocation_method = "Dynamic"
}

output "toEnterInAWScgw" {
    value = azurerm_public_ip.ipVNG.ip_address
}

#Create Virtual Network Gateway and tie the public IP address from above
resource "azurerm_virtual_network_gateway" "vng" {
  name                = "vng"
  location            = azurerm_resource_group.myResourceGroup.location
  resource_group_name = azurerm_resource_group.myResourceGroup.name
  type     = "Vpn"
  vpn_type = "RouteBased"
  active_active = false
  enable_bgp    = false
  sku           = "VpnGw1"
  ip_configuration {
    name                          = "vnetGatewayConfig"
    public_ip_address_id          = azurerm_public_ip.ipVNG.id
    private_ip_address_allocation = "Dynamic"
    subnet_id                     = azurerm_subnet.gatewaysubnet.id
  }
}

#Create network security group to tie to Azure VM
resource "azurerm_network_security_group" "nsg" {
  name                = "myNetworkSecurityGroup"
  location            = azurerm_resource_group.myResourceGroup.location
  resource_group_name = azurerm_resource_group.myResourceGroup.name

  security_rule {
    name                       = "fromAWS"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "*"
    source_address_prefix      = "172.16.0.0/16"
    destination_address_prefix = "192.168.0.0/16"
  }
}

#Create NIC for Azure VM assignment
resource "azurerm_network_interface" "nicAzureVM" {
  name                = "nic01"
  location            = azurerm_resource_group.myResourceGroup.location
  resource_group_name = azurerm_resource_group.myResourceGroup.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.privateSubnet.id
    private_ip_address_allocation = "Dynamic"
  }

#Associate NIC with previously created Network Security Group
}
resource "azurerm_network_interface_security_group_association" "nicAssoc" {
  network_interface_id      = azurerm_network_interface.nicAzureVM.id
  network_security_group_id = azurerm_network_security_group.nsg.id
}

#Create Azure Virtual Machine and output private IP address after creation
resource "azurerm_windows_virtual_machine" "azureVM" {
  name                = "VM01"
  resource_group_name = azurerm_resource_group.myResourceGroup.name
  location            = azurerm_resource_group.myResourceGroup.location
  size                = "Standard_F2"
  admin_username      = "adminuser"
  admin_password = "someP@ssw0rd"
  network_interface_ids = [
    azurerm_network_interface.nicAzureVM.id,
  ]

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2019-Datacenter"
    version   = "latest"
  }
  
}
output "AzureWindowsPrivateIP" {
    value = azurerm_windows_virtual_machine.azureVM.private_ip_addresses
}

Cleaning Up

Once you are done with the above and/or no longer need the resources created, you should manually delete all resources or via Terraform run terraform destroy (optionally with the -auto-approve to bypass conformational messages).

Leave a Comment

Your email address will not be published. Required fields are marked *