Source Code
In the last post we covered configuring a single FortiGate named Core-DC
. In this post we need to configure two FortiGates that will act as hubs in the SDWAN lab. The current design of the lab looks like this.
LAB Design
Using Modules
Hub1 and Hub1 are what we are going to focus on in this post. The majority of the configuration is the same on the two devices, the main differences are the IP addresses. In order to avoid cut and pasting the resources, we are going to use a terraform module. Hashicorp has this to say about modules: Modules are the main way to package and reuse resource configurations with Terraform.
The current directory structure looks like this.
.
├── hub1
│ ├── main.tf
│ ├── outputs.tf
│ ├── terraform.tfvars
│ └── variables.tf
├── hub2
│ ├── main.tf
│ ├── outputs.tf
│ ├── terraform.tfvars
│ └── variables.tf
└── modules
└── advpn_hub
├── firewall.tf
├── main.tf
├── outputs.tf
├── router.tf
├── system.tf
├── variables.tf
└── vpn.tf
Here are the main.tf files for Hub1 and Hub2.
Hub1 - main.tf
terraform {
required_providers {
fortios = {
source = "fortinetdev/fortios"
}
}
}
provider "fortios" {
hostname = var.management
token = var.token
insecure = "true"
}
module "advpn_hub" {
source = "../modules/advpn_hub"
hostname = "Hub1"
loopback_ip = "172.28.255.200 255.255.255.255"
wan1_ip = "198.51.100.200 255.255.255.0"
wan2_ip = "192.88.99.200 255.255.255.0"
port3_itr p = "172.28.0.1 255.255.255.252"
vpn1 = {
prefix = "192.168.100.0 255.255.255.0"
ip = "192.168.100.253 255.255.255.255"
remote_ip = "192.168.100.254 255.255.255.0"
ipv4_start_ip = "192.168.100.1"
ipv4_end_ip = "192.168.100.252"
ipv4_netmask = "255.255.255.0"
network_id = "1"
aspath = "65000"
}
vpn2 = {
prefix = "192.168.99.0 255.255.255.0"
ip = "192.168.99.253 255.255.255.255"
remote_ip = "192.168.99.254 255.255.255.0"
ipv4_start_ip = "192.168.99.1"
ipv4_end_ip = "192.168.99.252"
ipv4_netmask = "255.255.255.0"
network_id = "2"
aspath = "65000 65000"
}
bgp = {
neighbor = "172.28.0.2"
neighbor_range1 = "192.168.100.0 255.255.255.0"
neighbor_range2 = "192.168.99.0 255.255.255.0"
}
}
Hub2 - main.tf
terraform {
required_providers {
fortios = {
source = "fortinetdev/fortios"
source = "fortios.billgrant.io/local/fortios"
}
}
}
provider "fortios" {
hostname = var.management
token = var.token
insecure = "true"
}
module "advpn_hub" {
source = "../modules/advpn_hub"
hostname = "Hub2"
loopback_ip = "172.28.255.201 255.255.255.255"
wan1_ip = "198.51.100.201 255.255.255.0"
wan2_ip = "192.88.99.201 255.255.255.0"
port3_ip = "172.28.0.5 255.255.255.252"
vpn1 = {
prefix = "192.168.98.0 255.255.255.0"
ip = "192.168.98.253 255.255.255.255"
remote_ip = "192.168.98.254 255.255.255.0"
ipv4_start_ip = "192.168.98.1"
ipv4_end_ip = "192.168.98.252"
ipv4_netmask = "255.255.255.0"
network_id = "1"
aspath = "65000"
}
vpn2 = {
prefix = "192.168.97.0 255.255.255.0"
ip = "192.168.97.253 255.255.255.255"
remote_ip = "192.168.97.254 255.255.255.0"
ipv4_start_ip = "192.168.97.1"
ipv4_end_ip = "192.168.97.252"
ipv4_netmask = "255.255.255.0"
network_id = "2"
aspath = "65000 65000"
}
bgp = {
neighbor = "172.28.0.6"
neighbor_range1 = "192.168.98.0 255.255.255.0"
neighbor_range2 = "192.168.97.0 255.255.255.0"
}
}
The important part here is this
module "advpn_hub" {
source = "../modules/advpn_hub"
This tells terraform to use the module in the advpn_hub
. We can also pass variables onto the module. So lets take a look at the modules code.
advpn_hub module - main.tf
terraform {
required_providers {
fortios = {
source = "fortinetdev/fortios"
}
}
}
Not much here because the provider configuration get passed onto the module from the respective hubs main.tf
The other important part of the modules is the variables.tf
.
advpn_hub module - variables.tf
variable "hostname" {
description = "FortiGate system hostname"
type = string
}
variable "loopback_ip" {
description = "Loopback (lo0) interface IP address"
type = string
}
variable "wan1_ip" {
description = "WAN1 interface IP address"
type = string
}
variable "wan2_ip" {
description = "WAN2 interface IP address"
type = string
}
variable "port3_ip" {
description = "port3 interface IP address"
type = string
}
variable "vpn1" {
description = "VPN1 specific parameters"
type = object({
prefix = string
ip = string
remote_ip = string
ipv4_start_ip = string
ipv4_end_ip = string
ipv4_netmask = string
network_id = string
aspath = string
})
}
variable "vpn2" {
description = "VPN2 specific parameters"
type = object({
prefix = string
ip = string
remote_ip = string
ipv4_start_ip = string
ipv4_end_ip = string
ipv4_netmask = string
network_id = string
aspath = string
})
}
variable "bgp" {
type = object({
neighbor = string
neighbor_range1 = string
neighbor_range2 = string
})
}
When we run terraform apply
on each of the hubs it will pass the variables from hub’s main.tf
to the module.
Resource configuration
We have 33 total resources so I am not going to cover all of them. We will focus on the vpn settings.
advpn_hub module - vpn.tf
resource "fortios_vpnipsec_phase1interface" "VPN1_P1" {
add_route = "disable"
auto_discovery_sender = "enable"
dpd = "on-idle"
dpd_retryinterval = "60"
ike_version = "2"
interface = "WAN1"
ipv4_end_ip = var.vpn1.ipv4_end_ip
ipv4_netmask = var.vpn1.ipv4_netmask
ipv4_start_ip = var.vpn1.ipv4_start_ip
mode_cfg = "enable"
name = "VPN1"
net_device = "disable"
peertype = "any"
psksecret = "fortinet"
proposal = "aes256-sha256"
network_overlay = "enable"
network_id = var.vpn1.network_id
type = "dynamic"
fec_ingress = "enable"
fec_egress = "enable"
depends_on = [fortios_system_interface.WAN1]
}
resource "fortios_vpnipsec_phase2interface" "VPN1_P2" {
phase1name = fortios_vpnipsec_phase1interface.VPN1_P1.name
proposal = "aes256-sha256"
name = "VPN1"
depends_on = [fortios_vpnipsec_phase1interface.VPN1_P1]
}
resource "fortios_vpnipsec_phase1interface" "VPN2_P1" {
add_route = "disable"
auto_discovery_sender = "enable"
dpd = "on-idle"
dpd_retryinterval = "60"
ike_version = "2"
interface = "WAN2"
ipv4_end_ip = var.vpn2.ipv4_end_ip
ipv4_netmask = var.vpn2.ipv4_netmask
ipv4_start_ip = var.vpn2.ipv4_start_ip
mode_cfg = "enable"
name = "VPN2"
net_device = "disable"
peertype = "any"
psksecret = "fortinet"
proposal = "aes256-sha256"
network_overlay = "enable"
network_id = var.vpn2.network_id
type = "dynamic"
fec_ingress = "enable"
fec_egress = "enable"
depends_on = [fortios_system_interface.WAN2]
}
resource "fortios_vpnipsec_phase2interface" "VPN2_P2" {
phase1name = fortios_vpnipsec_phase1interface.VPN2_P1.name
proposal = "aes256-sha256"
name = "VPN2"
depends_on = [fortios_vpnipsec_phase1interface.VPN2_P1]
}
Something to not here. You cannot create the Phase2 interface on FortiGate unless the referenced Phase1 interface exists. That is where depends_on
comes in. If we look at the code for "fortios_vpnipsec_phase2interface" "VPN2_P2"
.
resource "fortios_vpnipsec_phase2interface" "VPN2_P2" {
phase1name = fortios_vpnipsec_phase1interface.VPN2_P1.name
proposal = "aes256-sha256"
name = "VPN2"
depends_on = [fortios_vpnipsec_phase1interface.VPN2_P1]
}
The depends_on
in the above code tells terraform to create VPN2_P1
before creating VPN2_P2
. This helps ensure the command will not fail.
Running Terraform apply
The two hubs are only configured with a management IP and an API key. Lets take a look at the hubs current IPSec tunnels.
Hub1 Tunnels
Hub2 Tunnels
As we can see they are both blank. Okay lets run terraform apply
Hub1
Terraform will perform the following actions:
...
# module.advpn_hub.fortios_vpnipsec_phase1interface.VPN1_P1 will be created
+ resource "fortios_vpnipsec_phase1interface" "VPN1_P1" {
+ add_route = "disable"
+ auto_discovery_sender = "enable"
+ dpd = "on-idle"
+ dpd_retryinterval = "60"
+ dynamic_sort_subtable = "false"
+ fec_egress = "enable"
+ fec_ingress = "enable"
+ ike_version = "2"
+ interface = "WAN1"
+ ipv4_end_ip = "192.168.100.252"
+ ipv4_netmask = "255.255.255.0"
+ ipv4_start_ip = "192.168.100.1"
+ mode_cfg = "enable"
+ name = "VPN1"
+ net_device = "disable"
+ network_id = 1
+ network_overlay = "enable"
+ peertype = "any"
+ proposal = "aes256-sha256"
+ psksecret = (sensitive value)
+ type = "dynamic"
}
# module.advpn_hub.fortios_vpnipsec_phase1interface.VPN2_P1 will be created
+ resource "fortios_vpnipsec_phase1interface" "VPN2_P1" {
+ add_route = "disable"
+ auto_discovery_sender = "enable"
+ dpd = "on-idle"
+ dpd_retryinterval = "60"
+ dynamic_sort_subtable = "false"
+ fec_egress = "enable"
+ fec_ingress = "enable"
+ ike_version = "2"
+ interface = "WAN2"
+ ipv4_end_ip = "192.168.99.252"
+ ipv4_netmask = "255.255.255.0"
+ ipv4_start_ip = "192.168.99.1"
+ mode_cfg = "enable"
+ name = "VPN2"
+ net_device = "disable"
+ network_id = 2
+ network_overlay = "enable"
+ peertype = "any"
+ proposal = "aes256-sha256"
+ psksecret = (sensitive value)
+ type = "dynamic"
}
# module.advpn_hub.fortios_vpnipsec_phase2interface.VPN1_P2 will be created
+ resource "fortios_vpnipsec_phase2interface" "VPN1_P2" {
+ name = "VPN1"
+ phase1name = "VPN1"
+ proposal = "aes256-sha256"
}
# module.advpn_hub.fortios_vpnipsec_phase2interface.VPN2_P2 will be created
+ resource "fortios_vpnipsec_phase2interface" "VPN2_P2" {
+ name = "VPN2"
+ phase1name = "VPN2"
+ proposal = "aes256-sha256"
}
Plan: 33 to add, 0 to change, 0 to destroy.
...
module.advpn_hub.fortios_vpnipsec_phase1interface.VPN2_P1: Creation complete after 0s [id=VPN2]
module.advpn_hub.fortios_vpnipsec_phase1interface.VPN1_P1: Creation complete after 0s [id=VPN1]
module.advpn_hub.fortios_vpnipsec_phase2interface.VPN2_P2: Creation complete after 0s [id=VPN2]
module.advpn_hub.fortios_vpnipsec_phase2interface.VPN1_P2: Creation complete after 0s [id=VPN1]
...
Apply complete! Resources: 33 added, 0 changed, 0 destroyed.
Hub2
Terraform will perform the following actions:
...
# module.advpn_hub.fortios_vpnipsec_phase1interface.VPN1_P1 will be created
+ resource "fortios_vpnipsec_phase1interface" "VPN1_P1" {
+ add_route = "disable"
+ auto_discovery_sender = "enable"
+ dpd = "on-idle"
+ dpd_retryinterval = "60"
+ dynamic_sort_subtable = "false"
+ fec_egress = "enable"
+ fec_ingress = "enable"
+ ike_version = "2"
+ interface = "WAN1"
+ ipv4_end_ip = "192.168.98.252"
+ ipv4_netmask = "255.255.255.0"
+ ipv4_start_ip = "192.168.98.1"
+ mode_cfg = "enable"
+ name = "VPN1"
+ net_device = "disable"
+ network_id = 1
+ network_overlay = "enable"
+ peertype = "any"
+ proposal = "aes256-sha256"
+ type = "dynamic"
}
# module.advpn_hub.fortios_vpnipsec_phase1interface.VPN2_P1 will be created
+ resource "fortios_vpnipsec_phase1interface" "VPN2_P1" {
+ add_route = "disable"
+ auto_discovery_sender = "enable"
+ dpd = "on-idle"
+ dpd_retryinterval = "60"
+ dynamic_sort_subtable = "false"
+ fec_egress = "enable"
+ fec_ingress = "enable"
+ ike_version = "2"
+ interface = "WAN2"
+ ipv4_end_ip = "192.168.97.252"
+ ipv4_netmask = "255.255.255.0"
+ ipv4_start_ip = "192.168.97.1"
+ mode_cfg = "enable"
+ name = "VPN2"
+ net_device = "disable"
+ network_id = 2
+ network_overlay = "enable"
+ peertype = "any"
+ proposal = "aes256-sha256"
+ type = "dynamic"
}
# module.advpn_hub.fortios_vpnipsec_phase2interface.VPN1_P2 will be created
+ resource "fortios_vpnipsec_phase2interface" "VPN1_P2" {
+ name = "VPN1"
+ phase1name = "VPN1"
+ proposal = "aes256-sha256"
}
# module.advpn_hub.fortios_vpnipsec_phase2interface.VPN2_P2 will be created
+ resource "fortios_vpnipsec_phase2interface" "VPN2_P2" {
+ name = "VPN2"
+ phase1name = "VPN2"
+ proposal = "aes256-sha256"
}
Plan: 33 to add, 0 to change, 0 to destroy.
...
module.advpn_hub.fortios_vpnipsec_phase1interface.VPN2_P1: Creation complete after 0s [id=VPN2]
module.advpn_hub.fortios_vpnipsec_phase1interface.VPN1_P1: Creation complete after 0s [id=VPN1]
module.advpn_hub.fortios_vpnipsec_phase2interface.VPN1_P2: Creation complete after 1s [id=VPN1]
module.advpn_hub.fortios_vpnipsec_phase2interface.VPN2_P2: Creation complete after 1s [id=VPN2]
...
Apply complete! Resources: 33 added, 0 changed, 0 destroyed.
Lets take a look at the IPsec tunnels
Hub1 Configured
Hub2 Configured
Everything looks good. If some day I needed to add a third hub, it would be easy as creating a few commands and the variables. In the next post I am going to configure the branches and add them to FortiManager. Here is the source code used in this post.