Le VPN, que vous connaissez sûrement, permet de relier deux réseaux distants de manière chiffrée. Lors de cette configuration, en plus de définir vos adresses IP privées ou votre ASN (Autonomous System Number), il vous sera demandé de spécifier les réseaux internes que vous souhaitez connecter entre eux.
Vous pouvez configurer manuellement ces routes en spécifiant les sous-réseaux à interconnecter, mais cela peut devenir long et sujet à erreurs si vous avez beaucoup de réseaux à gérer. Alternativement, vous pouvez automatiser cette configuration en utilisant le protocole de routage dynamique avec BGP.
Ce protocole permet à vos routeurs d'échanger automatiquement les informations de routage et d'adapter les chemins empruntés en fonction des changements dans votre réseau ou ceux de vos partenaires.
Lors de ce déploiement, nous aurons 4 liens. GCP nous fournit 2 interfaces comprenant 2 IPs publiques. Coté AWS, nous allons créer 2 Customer Gateway qui vont chacune nous fournir 2 IPs publiques.
Pour déployer notre VPN, nous utilisons Terraform en nous nous concentrons uniquement sur la création du VPN. Ni l’initialisation du projet sur Terraform ni la création du VPC côté AWS et GCP ne sont abordés. De plus, si vous souhaitez déployer plusieurs VPN, nous suggérons l’utilisation de Terragrunt afin de réduire la répétition du code et d’améliorer sa lisibilité.
Commençons par créer un module qui contient les fichiers suivants
├── modules
│ └── aws-to-gcp-vpn
│ ├── aws.tf
│ ├── gcp.tf
│ ├── providers.tf
│ └── variables.tf
variable "project_id" {
type = string
description = "Le projet GCP sur lequel nous voulons déployer le VPN"
}
variable "gcp_vpn_gwy_region" {
type = string
default = "europe-west1"
description = "La region sur GPC sur laquelle nous voulons déployer le VPN"
}
variable "gcp_router_asn" {
type = string
default = "65000"
description = "L'identifiant ASN qu'on va utiliser pour le routage BGP côté GCP"
}
variable "aws_router_asn" {
type = string
default = "65001"
description = "L'identifiant ASN qu'on va utiliser pour le routage BGP côté AWS"
}
variable "aws_vpc_id" {
type = string
default = "AWS VPC id"
description = "L'ID VPC auquel on veut se connecter côté AWS"
}
variable "gcp_network" {
type = string
description = "Le nom du VPC auquel on veut se connecter côté GCP"
}
variable "aws_vpc_cidr" {
type = string
description = "Le CIDR du VPC côté AWS, sous la forme x.x.x.x/x"
}
variable "prefix" {
type = string
description = "Un préfix pour le naming des ressources"
}
variable "tunnel_preshared_key" {
type = string
description = "Notre preshared_key utilisée pour la création du tunnel"
default = "thisismysecret"
}
variable "num_tunnels" {
type = number
validation {
condition = var.num_tunnels % 2 == 0
error_message = "number of tunnels needs to be in multiples of 2."
}
validation {
condition = var.num_tunnels >= 4
error_message = "min 4 tunnels required for high availability."
}
description = < < EOF
Nombre total de tunnels VPN. Cela doit être un multiple de 2..
EOF
}
variable "route_table_id" {
type = list(string)
description = "Nos tables de routage coté AWS."
}
variable "destination_cidr_block" {
type = list(string)
description = "Le CIDR de notre VPC GCP pour creer les routes"
}
variable "gcp_cidr" {
type = string
description = "Le CIDR de notre VPC GCP pour la création du VPN"
}
provider "google" {
project = var.project_id
}
provider "aws" {
region = "AWS_REGION"
profile = "AWS_PROFILE"
}
Sur le fichier providers.tf, nous spécifions les providers GCP et AWS afin de pouvoir interagir avec nos 2 clouds.
locals {
default_num_ha_vpn_interfaces = 2
}
resource "aws_customer_gateway" "gwy" {
count = local.default_num_ha_vpn_interfaces
device_name = "${var.prefix}-gwy-${count.index}"
bgp_asn = var.gcp_router_asn
type = "ipsec.1"
ip_address = google_compute_ha_vpn_gateway.gwy.vpn_interfaces[count.index]["ip_address"]
}
resource "aws_vpn_connection" "vpn_conn" {
count = var.num_tunnels / 2
customer_gateway_id = aws_customer_gateway.gwy[count.index % 2].id
type = "ipsec.1"
vpn_gateway_id = aws_vpn_gateway.this.id
tunnel1_preshared_key = var.tunnel_preshared_key
tunnel2_preshared_key = var.tunnel_preshared_key
tags = {
Name = "${var.prefix}-vpn-connn"
}
}
resource "aws_vpn_gateway" "this" {
vpc_id = var.aws_vpc_id
amazon_side_asn = var.aws_router_asn
}
module "vpn_gateway" {
count = local.default_num_ha_vpn_interfaces
source = "terraform-aws-modules/vpn-gateway/aws"
version = "~> 3.0"
vpn_gateway_id = aws_vpn_gateway.this.id
customer_gateway_id = aws_customer_gateway.gwy[count.index].id
vpc_id = var.aws_vpc_id
create_vpn_connection = false
local_ipv4_network_cidr = var.aws_vpc_cidr
remote_ipv4_network_cidr = var.gcp_cidr
}
resource "aws_vpn_gateway_route_propagation" "private_subnets_vpn_routing" {
count = length(var.route_table_id)
vpn_gateway_id = aws_vpn_gateway.this.id
route_table_id = element(var.route_table_id, count.index)
}
Ce fichier va créer tous les services sur AWS nécessaire au bon fonctionnement du VPN.
Nous avons tout d’abord le service aws_vpn_gateway
qui crée une passerelle VPN (VPN Gateway) sur AWS dans notre VPC.
Vient ensuite le service aws_customer_gateway
qui va créer une passerelle cliente (Customer Gateway) pour la connexion VPN entre AWS et GCP. Coté GCP, nous avons 2 interfaces, donc nous créons 2 Customer Gateway.
Afin de nous simplifier la vie, nous utilisons le module vpn_gateway
qui va se charger de configurer la passerelle VPN.
Pour finir sur la configuration du VPN coté AWS, nous créons via la ressource aws_vpn_connection
qui va créer la connexion avec le VPN GCP. Par simplicité, nous avons mis la preshared_key en clair dans cet exemple. Nous vous conseillons fortement l’utilisation d’un secret manager.
En dernier, nous allons activer la propagation des routes afin que nos réseaux sachent comment communiquer avec GCP. Nous faisons cela avec la ressource aws_vpn_gateway_route_propagation
. Grâce au protocole BGP, tous nos sous réseaux GCP seront automatiquement renseignés dans notre table de routage AWS.
locals {
four_interface_ext_gwys = [for i in range(floor(var.num_tunnels / 4)) :
{ key : i, redundancy_type = "FOUR_IPS_REDUNDANCY" }
]
two_interface_ext_gwys = [for i in range(ceil(var.num_tunnels / 4) - length(local.four_interface_ext_gwys)) :
{
key : i + length(local.four_interface_ext_gwys),
redundancy_type = "TWO_IPS_REDUNDANCY"
} if var.num_tunnels % 4 != 0
]
num_ext_gwys = concat(local.four_interface_ext_gwys, local.two_interface_ext_gwys)
aws_vpn_conn_addresses = {
for k, v in chunklist([
for k, v in flatten([
for k, v in aws_vpn_connection.vpn_conn :
[v.tunnel1_address, v.tunnel2_address]
]) : v
], 4) :
k => v
}
tunnels = chunklist(flatten([
for i in range(length(local.num_ext_gwys)) : [
for k, v in setproduct(range(2), chunklist(range(4), 2)) :
{
ext_gwy : i,
peer_gwy_interface : k,
vpn_gwy_interface : v[0] % 2
}
]
]), var.num_tunnels)[0]
bgp_sessions = {
for k, v in flatten([
for k, v in aws_vpn_connection.vpn_conn :
[
{
ip_address : v.tunnel1_cgw_inside_address,
peer_ip_address : v.tunnel1_vgw_inside_address
},
{
ip_address : v.tunnel2_cgw_inside_address,
peer_ip_address : v.tunnel2_vgw_inside_address
}
]
]) : k => v
}
}
resource "google_compute_ha_vpn_gateway" "gwy" {
name = "${var.prefix}-ha-vpn-gwy"
network = var.gcp_network
region = var.gcp_vpn_gwy_region
}
resource "google_compute_external_vpn_gateway" "ext_gwy" {
for_each = { for k, v in local.num_ext_gwys : k => v }
name = "${var.prefix}-ext-vpn-gwy-${each.key}"
redundancy_type = each.value["redundancy_type"]
dynamic "interface" {
for_each = local.aws_vpn_conn_addresses[each.key]
content {
id = interface.key
ip_address = interface.value
}
}
}
resource "google_compute_router" "router" {
name = "vpn-router"
network = var.gcp_network
region = var.gcp_vpn_gwy_region
bgp {
asn = var.gcp_router_asn
advertise_mode = "CUSTOM"
advertised_groups = [
"ALL_SUBNETS"
]
}
}
resource "google_compute_vpn_tunnel" "tunnel" {
for_each = { for k, v in local.tunnels : k => v }
name = "${var.prefix}-tunnel-${each.key}"
shared_secret = var.tunnel_preshared_key
peer_external_gateway = google_compute_external_vpn_gateway.ext_gwy[each.value["ext_gwy"]].name
peer_external_gateway_interface = each.value["peer_gwy_interface"]
region = var.gcp_vpn_gwy_region
router = google_compute_router.router.name
ike_version = "2"
vpn_gateway = google_compute_ha_vpn_gateway.gwy.id
vpn_gateway_interface = each.value["vpn_gwy_interface"]
}
resource "google_compute_router_interface" "interface" {
for_each = local.bgp_sessions
name = "${var.prefix}-interface-${each.key}"
router = google_compute_router.router.name
region = var.gcp_vpn_gwy_region
ip_range = "${each.value["ip_address"]}/30"
vpn_tunnel = google_compute_vpn_tunnel.tunnel[each.key].name
}
resource "google_compute_router_peer" "peer" {
for_each = local.bgp_sessions
name = "${var.prefix}-peer-${each.key}"
interface = "${var.prefix}-interface-${each.key}"
peer_asn = var.aws_router_asn
ip_address = each.value["ip_address"]
peer_ip_address = each.value["peer_ip_address"]
router = google_compute_router.router.name
region = var.gcp_vpn_gwy_region
}
Coté GCP, nous avons une configuration similaire.
Le bloc locals
, sans rentrer dans les détails, va créer entre autres des listes ou alors transformer des valeurs afin de les réutiliser dans nos ressources GCP. Cela va permettre l’utilisation d’une boucle “for_each” afin d’éviter une répétition du code due au fait que nous devons connecter nos 2 interfaces GCP à 4 IPs publiques AWS.
La ressource google_compute_ha_vpn_gateway
crée une passerelle VPN HA (High Availability).
La ressource google_compute_router
crée un routeur GCP pour gérer le trafic BGP associé aux tunnels VPN.
Ensuite, nous avons la ressource google_compute_external_vpn_gateway
qui crée des passerelles VPN externes pour GCP avec différentes configurations de redondance.
Avec la ressource google_compute_vpn_tunnel
, nous créons des tunnels VPN HA en utilisant les passerelles VPN externes (peer_external_gateway
) et les interfaces associées.
La ressource google_compute_router_interface
permet la création des interfaces sur le routeur GCP pour chaque session BGP.
Pour finir, nous avons la ressource google_compute_router_peer
qui crée des pairs BGP sur le routeur GCP, permettant la communication entre les sous-réseaux GCP et AWS.
Maintenant que nous avons notre code Terraform, nous allons le déployer.
À la racine de votre projet, créez un dossier Terraform. Dans celui-ci créez un fichier main.tf
├── modules
│ └── aws-to-gcp-vpn
│ ├── aws.tf
│ ├── gcp.tf
│ ├── providers.tf
│ └── variables.tf
└── terraform
└── main.tf
Votre main.tf devra contenir les différentes variables selon les ressources que vous avez sur GCP et AWS, voici les miennes.
module "aws_to_gcp_vpn" {
source = "../modules/aws-to-gcp-vpn"
project_id = "theodo-cloud-lab"
gcp_router_asn = "65000"
aws_router_asn = "65001"
aws_vpc_id = "vpc-0ab08ef3d971e565d"
gcp_network = "damienj"
aws_vpc_cidr = "10.0.0.0/16"
prefix = "test"
num_tunnels = 4
route_table_id = ["rtb-0a2ee2e1a5f8ccc63"]
destination_cidr_block = [ "192.168.0.0/24" ]
gcp_cidr = "192.168.0.0/24"
}
Après apply, voilà ce que vous devriez avoir sur AWS et GCP :
Nos 4 tunnels sont UP coté AWS
Notre route pour communiquer avec notre réseau GCP renseigné automatiquement grâce au protocole BGP
Nos 4 tunnels sont UP sur GCP
Vous pouvez tester que votre connexion fonctionne en créant une machine sur GCP et sur AWS et essayer de les ping entre elles via leur adresse IP.
Nous venons de voir comment facilement et rapidement déployer un VPN entre AWS et GCP avec du routage BGP. Vous pouvez aller plus loin dans la configuration et créer une pre shared key aléatoire avec la ressource random. Vous pouvez également réduire la plage d’IP GCP à laquelle AWS peut se connecter.