Skip to content

Instantly share code, notes, and snippets.

@mikesparr
Last active April 21, 2024 13:49
Show Gist options
  • Save mikesparr/2580819fdbe96b108335e7080f5c8832 to your computer and use it in GitHub Desktop.
Save mikesparr/2580819fdbe96b108335e7080f5c8832 to your computer and use it in GitHub Desktop.
Example site to site VPN between Google Cloud Platform (GCP) and Amazon Web Services (AWS)
#!/usr/bin/env bash
#####################################################################
# REFERENCES
# - https://cloud.google.com/architecture/build-ha-vpn-connections-google-cloud-aws
# - https://cloud.google.com/vpc/docs/private-service-connect
#####################################################################
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_USER=$(gcloud config get-value core/account) # set current user
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
export IDNS=${PROJECT_ID}.svc.id.goog # workload identity domain
export GCP_REGION="us-west1" # CHANGEME (OPT)
export GCP_ZONE="us-west1-b" # CHANGEME (OPT)
# enable apis
gcloud services enable compute.googleapis.com \
servicenetworking.googleapis.com
# configure gcloud sdk
gcloud config set compute/region $GCP_REGION
gcloud config set compute/zone $GCP_ZONE
############################################################################
# GCP NETWORK + VPN GATEWAY (GCP: 10.1.0.0/16) (us-west1: Oregon)
############################################################################
export NETWORK_NAME="gc-vpc"
export SUBNET_NAME="subnet-10-1-1-0"
export VPN_GATEWAY_NAME="vpn-gw-1"
export ROUTER_NAME="router-1"
export ASN_GCP="65510"
export GCP_RANGE="10.1.1.0/24"
# create custom vpc network
gcloud compute networks create $NETWORK_NAME \
--subnet-mode custom \
--bgp-routing-mode global
# create a test subnet
gcloud compute networks subnets create $SUBNET_NAME \
--network $NETWORK_NAME \
--region $GCP_REGION \
--range $GCP_RANGE
# create VPN gateway
gcloud compute vpn-gateways create $VPN_GATEWAY_NAME \
--network $NETWORK_NAME \
--region $GCP_REGION
# create cloud router
gcloud compute routers create $ROUTER_NAME \
--region $GCP_REGION \
--network $NETWORK_NAME \
--asn $ASN_GCP \
--advertisement-mode custom \
--set-advertisement-groups all_subnets
# get public IP addresses from above (for use with AWS gateway setup)
export INTERFACE_0_IP_ADDRESS=$(gcloud compute vpn-gateways describe $VPN_GATEWAY_NAME --format="value(vpnInterfaces[0].ipAddress)")
export INTERFACE_1_IP_ADDRESS=$(gcloud compute vpn-gateways describe $VPN_GATEWAY_NAME --format="value(vpnInterfaces[1].ipAddress)")
############################################################################
# AWS NETWORK + VPN (AWS: 10.0.0.0/16) (us-west-1: N. California)
# - https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html
# - https://docs.aws.amazon.com/vpc/latest/userguide/vpc-subnets-commands-example.html
############################################################################
export ASN_AWS="65520"
export AWS_RANGE="10.0.0.0/16"
export AWS_SUBNET_RANGE="10.0.0.0/24"
export TRANSIT_GATEWAY_DESCRIPTION="gateway-to-gcp"
export SHARED_SECRET="i_bowl_cookies_rocket_2022" # CHANGE ME (simplicity just using 1 instead of 4)
export AWS_T1_IP="169.254.200" # cidr add 0./30
export AWS_T2_IP="169.254.201" # bgp IP add .2 ; peer IP add .1
export AWS_T3_IP="169.254.202"
export AWS_T4_IP="169.254.203"
# create custom vpc network
export AWS_VPC_ID=$(aws ec2 create-vpc --cidr-block $AWS_RANGE --query Vpc.VpcId --output text)
# create private route table
export PRIVATE_ROUTE_TABLE_ID=$(aws ec2 create-route-table \
--vpc-id $AWS_VPC_ID \
--query RouteTable.RouteTableId --output text)
# create a test subnet
export AWS_PRIVATE_SUBNET_ID=$(aws ec2 create-subnet --vpc-id $AWS_VPC_ID --cidr-block $AWS_SUBNET_RANGE \
--query Subnet.SubnetId --output text)
# associate subnet to route table
aws ec2 associate-route-table \
--route-table-id $PRIVATE_ROUTE_TABLE_ID \
--subnet-id $AWS_PRIVATE_SUBNET_ID
# verify subnet created
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$AWS_VPC_ID" \
--query "Subnets[*].{ID:SubnetId,CIDR:CidrBlock}"
# create customer gateways
export AWS_CUSTOMER_GATEWAY_1=$(aws ec2 create-customer-gateway \
--type ipsec.1 \
--public-ip $INTERFACE_0_IP_ADDRESS \
--bgp-asn $ASN_GCP \
--query CustomerGateway.CustomerGatewayId --output text)
export AWS_CUSTOMER_GATEWAY_2=$(aws ec2 create-customer-gateway \
--type ipsec.1 \
--public-ip $INTERFACE_1_IP_ADDRESS \
--bgp-asn $ASN_GCP \
--query CustomerGateway.CustomerGatewayId --output text)
# create transit gateway
# (TIP: virtual private gateways cheaper option if don't need transit)
export AWS_TRANSIT_GATEWAY_ID=$(aws ec2 create-transit-gateway --description $TRANSIT_GATEWAY_DESCRIPTION \
--options=AmazonSideAsn=$ASN_AWS,AutoAcceptSharedAttachments=enable,DefaultRouteTableAssociation=enable,DefaultRouteTablePropagation=enable,VpnEcmpSupport=enable,DnsSupport=enable \
--query TransitGateway.TransitGatewayId --output text)
# attach transit gateway
aws ec2 create-transit-gateway-vpc-attachment \
--transit-gateway-id $AWS_TRANSIT_GATEWAY_ID \
--vpc-id $AWS_VPC_ID \
--subnet-id $AWS_PRIVATE_SUBNET_ID
# create VPN connections
export AWS_VPN_CONNECTION_1=$(aws ec2 create-vpn-connection \
--type ipsec.1 \
--customer-gateway-id $AWS_CUSTOMER_GATEWAY_1 \
--transit-gateway-id $AWS_TRANSIT_GATEWAY_ID \
--options TunnelOptions="[{TunnelInsideCidr=$AWS_T1_IP.0/30,PreSharedKey=$SHARED_SECRET},{TunnelInsideCidr=$AWS_T2_IP.0/30,PreSharedKey=$SHARED_SECRET}]" \
--query "" --output text)
export AWS_VPN_CONNECTION_2=$(aws ec2 create-vpn-connection \
--type ipsec.1 \
--customer-gateway-id $AWS_CUSTOMER_GATEWAY_2 \
--transit-gateway-id $AWS_TRANSIT_GATEWAY_ID \
--options TunnelOptions="[{TunnelInsideCidr=$AWS_T3_IP.0/30,PreSharedKey=$SHARED_SECRET},{TunnelInsideCidr=$AWS_T4_IP.0/30,PreSharedKey=$SHARED_SECRET}]" \
--query "" --output text)
# retrieve configuration of two VPN connections (Generic Vendor + ikev2)
export AWS_GW_IP_1=$(aws ec2 describe-vpn-connections --query "VpnConnections[0].Options.TunnelOptions[0].OutsideIpAddress" --output text)
export AWS_GW_IP_2=$(aws ec2 describe-vpn-connections --query "VpnConnections[0].Options.TunnelOptions[1].OutsideIpAddress" --output text)
export AWS_GW_IP_3=$(aws ec2 describe-vpn-connections --query "VpnConnections[1].Options.TunnelOptions[0].OutsideIpAddress" --output text)
export AWS_GW_IP_4=$(aws ec2 describe-vpn-connections --query "VpnConnections[1].Options.TunnelOptions[1].OutsideIpAddress" --output text)
export AWS_INT_IP_1=$(aws ec2 describe-vpn-connections --query "VpnConnections[0].Options.TunnelOptions[0].TunnelInsideCidr" --output text)
export AWS_INT_IP_2=$(aws ec2 describe-vpn-connections --query "VpnConnections[0].Options.TunnelOptions[1].TunnelInsideCidr" --output text)
export AWS_INT_IP_3=$(aws ec2 describe-vpn-connections --query "VpnConnections[1].Options.TunnelOptions[0].TunnelInsideCidr" --output text)
export AWS_INT_IP_4=$(aws ec2 describe-vpn-connections --query "VpnConnections[1].Options.TunnelOptions[1].TunnelInsideCidr" --output text)
# print out configs for sanity checking later
cat << EOF
----------------------------------------------------------------
VPN Gateway Configs:
Gateway 1:
- Customer GW IP: $INTERFACE_0_IP_ADDRESS
- Tunnel 1: External: $AWS_GW_IP_1 / Internal: $AWS_INT_IP_1
- Tunnel 2: External: $AWS_GW_IP_2 / Internal: $AWS_INT_IP_2
Gateway 2:
- Customer GW IP: $INTERFACE_1_IP_ADDRESS
- Tunnel 1: $AWS_GW_IP_3 / Internal: $AWS_INT_IP_3
- Tunnel 2: $AWS_GW_IP_4 / Internal: $AWS_INT_IP_4
-----------------------------------------------------------------
EOF
############################################################################
# EC2 TEST INSTANCE
# - https://www.geeksforgeeks.org/launching-an-ec2-instance-using-aws-cli/
############################################################################
export AWS_KEY_NAME="aws-keypair"
export AWS_KEY_FILE="aws-keypair.pem"
export SECURITY_GROUP_NAME="vpn-access"
export AMI_ID="ami-01f87c43e618bf8f0" # Ubuntu 20.04 LTS amd64
# create keypair
aws ec2 create-key-pair --key-name $AWS_KEY_NAME --query "KeyMaterial" \
--output text > $AWS_KEY_FILE
# create security group
export AWS_SECURITY_GROUP_ID=$(aws ec2 create-security-group --group-name $SECURITY_GROUP_NAME \
--description $SECURITY_GROUP_NAME \
--vpc-id $AWS_VPC_ID \
--query GroupId --output text)
# authorize access for security group (from anywhere for simplicity)
aws ec2 authorize-security-group-ingress --group-id $AWS_SECURITY_GROUP_ID \
--protocol tcp --port 22 --cidr $GCP_RANGE # SSH only from my GCP project
aws ec2 authorize-security-group-ingress --group-id $AWS_SECURITY_GROUP_ID \
--protocol tcp --port 80 --cidr 0.0.0.0/0 # access HTTP server from anywhere
aws ec2 authorize-security-group-ingress --group-id $AWS_SECURITY_GROUP_ID \
--protocol icmp --port all --cidr 0.0.0.0/0 # ping server from anywhere
# launch instance
export AWS_EC2_INSTANCE_ID=$(aws ec2 run-instances --image-id $AMI_ID \
--count 1 \
--instance-type t2.micro \
--key-name $AWS_KEY_NAME \
--security-group-ids $AWS_SECURITY_GROUP_ID \
--subnet-id $AWS_PRIVATE_SUBNET_ID \
--query "Instances[0].InstanceId" --output text)
# get instance IP address
export AWS_EC2_INSTANCE_IP=$(aws ec2 describe-instances --instance-id $AWS_EC2_INSTANCE_ID \
--query "Reservations[].Instances[].PrivateIpAddress" --output text)
############################################################################
# GCP TUNNELS + ROUTER
# (TIP) verify matching IPs in both GCP and AWS consoles if BGP session issues
# ----------------------------------------------------------------
# VPN Gateway Configs:
# Gateway 1:
# - Customer GW IP: 35.242.55.180
# - Tunnel 1: External: 13.52.40.49 / Internal: 169.254.202.0/30
# - Tunnel 2: External: 52.52.225.196 / Internal: 169.254.203.0/30
# Gateway 2:
# - Customer GW IP: 35.220.54.143
# - Tunnel 1: 52.9.126.13 / Internal: 169.254.200.0/30
# - Tunnel 2: 54.219.92.171 / Internal: 169.254.201.0/30
# -----------------------------------------------------------------
############################################################################
export PEER_GATEWAY_NAME="aws-vpn-gateway"
export IKE_VERSION="2"
# create external VPN gateway
gcloud compute external-vpn-gateways create $PEER_GATEWAY_NAME \
--interfaces 0=$AWS_GW_IP_1,1=$AWS_GW_IP_2,2=$AWS_GW_IP_3,3=$AWS_GW_IP_4
# create four VPN tunnels
gcloud compute vpn-tunnels create tunnel-1 \
--peer-external-gateway $PEER_GATEWAY_NAME \
--peer-external-gateway-interface 0 \
--region $GCP_REGION \
--ike-version $IKE_VERSION \
--shared-secret $SHARED_SECRET \
--router $ROUTER_NAME \
--vpn-gateway $VPN_GATEWAY_NAME \
--interface 0
gcloud compute vpn-tunnels create tunnel-2 \
--peer-external-gateway $PEER_GATEWAY_NAME \
--peer-external-gateway-interface 1 \
--region $GCP_REGION \
--ike-version $IKE_VERSION \
--shared-secret $SHARED_SECRET \
--router $ROUTER_NAME \
--vpn-gateway $VPN_GATEWAY_NAME \
--interface 0
gcloud compute vpn-tunnels create tunnel-3 \
--peer-external-gateway $PEER_GATEWAY_NAME \
--peer-external-gateway-interface 2 \
--region $GCP_REGION \
--ike-version $IKE_VERSION \
--shared-secret $SHARED_SECRET \
--router $ROUTER_NAME \
--vpn-gateway $VPN_GATEWAY_NAME \
--interface 1
gcloud compute vpn-tunnels create tunnel-4 \
--peer-external-gateway $PEER_GATEWAY_NAME \
--peer-external-gateway-interface 3 \
--region $GCP_REGION \
--ike-version $IKE_VERSION \
--shared-secret $SHARED_SECRET \
--router $ROUTER_NAME \
--vpn-gateway $VPN_GATEWAY_NAME \
--interface 1
# add four cloud router interfaces (use string parameter substitution with .2 for router)
gcloud compute routers add-interface $ROUTER_NAME \
--interface-name int-1 \
--vpn-tunnel tunnel-1 \
--ip-address "${AWS_INT_IP_1/.0\/30/.2}" \
--mask-length 30 \
--region $GCP_REGION
gcloud compute routers add-interface $ROUTER_NAME \
--interface-name int-2 \
--vpn-tunnel tunnel-2 \
--ip-address "${AWS_INT_IP_2/.0\/30/.2}" \
--mask-length 30 \
--region $GCP_REGION
gcloud compute routers add-interface $ROUTER_NAME \
--interface-name int-3 \
--vpn-tunnel tunnel-3 \
--ip-address "${AWS_INT_IP_3/.0\/30/.2}" \
--mask-length 30 \
--region $GCP_REGION
gcloud compute routers add-interface $ROUTER_NAME \
--interface-name int-4 \
--vpn-tunnel tunnel-4 \
--ip-address "${AWS_INT_IP_4/.0\/30/.2}" \
--mask-length 30 \
--region $GCP_REGION
# add four BGP peers (use string parameter substitution with .1 for peer)
gcloud compute routers add-bgp-peer $ROUTER_NAME \
--peer-name aws-conn1-tunn1 \
--peer-asn $ASN_AWS \
--interface int-1 \
--peer-ip-address "${AWS_INT_IP_1/.0\/30/.1}" \
--region $GCP_REGION
gcloud compute routers add-bgp-peer $ROUTER_NAME \
--peer-name aws-conn1-tunn2 \
--peer-asn $ASN_AWS \
--interface int-2 \
--peer-ip-address "${AWS_INT_IP_2/.0\/30/.1}" \
--region $GCP_REGION
gcloud compute routers add-bgp-peer $ROUTER_NAME \
--peer-name aws-conn1-tunn3 \
--peer-asn $ASN_AWS \
--interface int-3 \
--peer-ip-address "${AWS_INT_IP_3/.0\/30/.1}" \
--region $GCP_REGION
gcloud compute routers add-bgp-peer $ROUTER_NAME \
--peer-name aws-conn1-tunn4 \
--peer-asn $ASN_AWS \
--interface int-4 \
--peer-ip-address "${AWS_INT_IP_4/.0\/30/.1}" \
--region $GCP_REGION
# verify
gcloud compute routers get-status $ROUTER_NAME \
--region $GCP_REGION \
--format='flattened(result.bgpPeerStatus[].name, result.bgpPeerStatus[].ipAddress, result.bgpPeerStatus[].peerIpAddress)'
############################################################################
# GCP COMPUTE ENGINE TEST INSTANCE
############################################################################
export GCP_INSTANCE_ID="gcp-test-instance"
# create compute engine instance (with no external IP)
gcloud compute instances create $GCP_INSTANCE_ID \
--project=$PROJECT_ID \
--zone=$GCP_ZONE \
--machine-type=e2-micro \
--network-interface=subnet=$SUBNET_NAME,no-address \
--metadata=enable-oslogin=TRUE \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--image-project debian-cloud \
--image-family debian-11
# enable firewall access on port 22
gcloud compute firewall-rules create --network=$NETWORK_NAME test-allow-ssh --allow=tcp:22
# copy AWS keypair file to instance
gcloud compute scp ./$AWS_KEY_FILE $GCP_INSTANCE_ID:~
# test a ping of EC2 instance
gcloud compute ssh --zone $GCP_ZONE $GCP_INSTANCE_ID \
--tunnel-through-iap \
--command "ping $AWS_EC2_INSTANCE_IP"
@mikesparr
Copy link
Author

Overview

A common need for hybrid or multi-cloud connectivity is to establish a high-availability (HA) VPN tunnel between clouds. This example simplifies the online tutorials for establish a VPN and eliminates a lot of manual steps (click ops) in the browser console.

Experiment

The following steps were followed:

  • GCP project:

    • create VPC
    • create Subnet
    • create VPN Gateway
    • create Cloud Router
  • AWS account:

    • create VPC
    • create Subnet
    • attach VPC to Subnet
    • create Customer Gateways (2: associate to GCP VPN Gateway external IPs)
    • create Transit Gateway
    • attach Transit Gateway to VPC
    • create VPN Connections using Transit Gateway (2)
    • create Keypair
    • create Security Group
    • add Security Group rules (ssh, http, icmp)
    • run EC2 instance
  • GCP project:

    • create external VPN Gateway with interfaces to AWS VPN tunnels
    • create VPN tunnels (4)
    • add router interfaces (4)
    • add BGP peers (4)
    • create GCS instance
    • add firewall rule
    • transfer (scp) AWS keypair file to GCS instance
    • test ping from GCE instance to EC2 instance via internal IP

Results

Successful BGP session and tunnels

Screen Shot 2022-04-19 at 9 06 07 PM
Screen Shot 2022-04-19 at 9 05 20 PM

Successful ping test from GCE to EC2

Screen Shot 2022-04-19 at 8 54 03 PM

@mikesparr
Copy link
Author

mikesparr commented Apr 20, 2022

NOTICE

Remember to tear down resources when done to avoid extra cloud costs.

GCP

Google makes this easy so just delete your test projects with gcloud projects delete <project-id> command respectively.

AWS

If this is a dev / sandbox environment then the quickest way is to use cloud-nuke:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment