Creating a Fortinet VM for Libvirt/vagrant
- 6 minutes read - 1096 wordsI needed to replicate a production Fortinet firewall environment to do some testing. I could have done this in GNS3/EVE-NG or some other virtual enviornment, but I find that Vagrant/Libvirt gives me the most usability when trying to reuse a setup over time, such as for automated testing.
So with that goal in mind, I needed to create a vagrant box of Fortinet’s FortiVM firewall, but specifically for use with libvirt. Multiple times I’ve drawn from Brad’s blog which has great Arista vEOS and Juniper vMX examples. So I’ll take a similar approach.
Tools
- Vagrant: Vagrant from Hashicorp. Vagrant is a tool for building and managing virtual machine environments in a single workflow.
- FortiVM: The Fortinet Fortigate VM images. Note I’m using a KVM image, and that a support login is required to download the images.
Workflow
To setup the Fortinet VM, I’m essentially following Brad’s Arista walkthrough and swapping out relevant parts for Fortinet:
- Download a KVM version of the Fortigate VM
I am using version 6.4.3 for this example. The filename I downloaded was named FGT_VM64_KVM-v6-build1778-FORTINET.out.kvm.zip
. After unzipping, you’re left with a file ending in .qcow2. I’ve renamed the file to fortios-6.4.3.qcow2
to keep track of different OS versions.
- Boot VM manually once to bootstrap configure it to run in a vagrant environment:
virt-install \
--connect=qemu:///system \
--name=fortios \
--os-type=generic \
--arch=x86_64 \
--cpu host \
--vcpus=1 \
--hvm \
--ram=1024 \
--disk path=fortios-6.4.3.qcow2,bus=ide,format=qcow2 \
--network=network:vagrant-libvirt,model=virtio \
--graphics none \
--import
You will see the VM boot:
Starting install...
Connected to domain fortios
Escape character is ^]
System is starting...
Formatting shared data partition ... done!
Starting system maintenance...
Serial number is FGVMEVZUBZH0BD9B
FortiGate-VM64-KVM login:
Note that you’re forced to set an admin password on first login. I’ve set my password to admin
for the bootstrap config. I can enforce more secure policy via further specific config.
- Configure the VM for a vagrant / libvirt environment
The following changes need to be made to support vagrant:
- Configure port1 to learn it’s IP address via DHCP.
- Configure a vagrant admin account, and allow login via the vagrant insecure SSH key
- Configure a DNS server
In addition, in my vagrant enviornment I’m using the second ethernet port of each network device to connect to a simulated out-of-band network for configuration via ansible. To accomplish this, the following additional changes will be made to the boostrap config:
- Configure port2 to learn it’s IP via DHCP
- Allow ping and ssh (and all management services) on port2
The following is the complete config snippet to paste in to the VM:
config system admin
edit "vagrant"
set accprofile "super_admin"
set ssh-public-key1 "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key"
set password ENC SH28SLSP20eURl8us/aceUFwjdJOggVKBfSQSP8eZi2dyoNferE+lgfmTIitbE=
next
end
config system interface
edit "port1"
set mode dhcp
set allowaccess ping https ssh http fgfm
next
edit "port2"
set mode dhcp
set allowaccess ping https ssh http fgfm
next
end
config system dns
set primary 1.1.1.1
end
- Shut down the VM
FortiGate-VM64-KVM # execute shutdown
You’ll see the following shutdown messages, and you’ll be back to a shell prompt when shutdown is complete:
This operation will shutdown the system !
Do you want to continue? (y/n)y
System is shutting down...
The system is going down NOW !!
FortiGate-VM64-KVM #
The system is halted.
Power down.
Domain creation completed.
You can restart your domain by running:
virsh --connect qemu:///system start fortios
- Create a metadata file that will be used to create a vagrant box
In the same directory, create a file called metadata.json
with the following contents:
{"provider":"libvirt","format":"qcow2","virtual_size":1}
- Download the box creation script
The good folks who maintain the vagrant-libvirt plugin have a script that can be used to convert qcow2 images to a vagrant box. Download the libvirt conversion script:
curl -O https://raw.githubusercontent.com/vagrant-libvirt/vagrant-libvirt/master/tools/create_box.sh
- Use the previously downloaded script to create a vagrant box
bash create_box.sh fortios-6.4.3.qcow2
You’ll see the following output as the script runs:
{2}
==> Creating box, tarring and gzipping
./metadata.json
./Vagrantfile
./box.img
Total bytes written: 169881600 (163MiB, 686MiB/s)
==> fortios-6.4.3.box created
==> You can now add the box:
==> 'vagrant box add fortios-6.4.3.box --name fortios-6.4.3'
- (Optional) Move the vagrant box to a different location
My linux server has it’s M2 SSD storage for the /var
partition, specifically to support VMs. I’ve moved the final vagrant box to that partition:
sudo cp fortios-6.4.3.box /var/lib/libvirt/images/
sudo chown libvirt-qemu:kvm /var/lib/libvirt/images/fortios-6.4.3.box
- Create a metadata file called
fortios.json
so that the vagrant box is added with the correct version number
Note the file path should be updated to the full path name of either your current working directory, or where you copied the box file to.
{
"name": "fortinet/fortios",
"description": "Fortinet FortiOS VM",
"versions": [
{
"version": "6.4.3",
"providers": [
{
"name": "libvirt",
"url": "file:///var/lib/libvirt/images/fortios-6.4.3.box"
}
]
}
]
}
- Add the box to Vagrant
vagrant box add fortios.json
The following shows a successful addition of your new vagrant box:
==> box: Loading metadata for box 'fortios.json'
box: URL: file:///home/pete/images/fortinet/fortios.json
==> box: Adding box 'fortinet/fortios' (v6.4.3) for provider: libvirt
box: Unpacking necessary files from: file:///var/lib/libvirt/images/fortios-6.4.3.box
==> box: Successfully added box 'fortinet/fortios' (v6.4.3) for 'libvirt'!
You can verify the box is loaded:
vagrant box list
CumulusCommunity/cumulus-vx (libvirt, 3.7.12)
CumulusCommunity/cumulus-vx (libvirt, 4.2.1)
arista/veos (libvirt, 4.25.0F)
fortinet/fortios (libvirt, 6.0.6)
fortinet/fortios (libvirt, 6.4.3)
generic/ubuntu1804 (libvirt, 3.0.34)
generic/ubuntu2004 (libvirt, 3.0.32)
- Modify your Vagrantfile
Finally, to use your newly-created fortios vagrant box, add something similar to the following to an existing Vagrantfile
. Note this is one of my working examples, and it’s beyond the scope of this blog post to explain the complexity that exists in vagrant configuration. However feel free to reach out to me if you have questions.
##### DEFINE VM for fw-primary #####
config.vm.define "fw-primary" do |device|
device.vm.hostname = "fw-primary"
device.vm.box = "fortinet/fortios"
device.vm.box_version = "6.4.3"
device.vm.provider :libvirt do |v|
v.disk_bus = 'ide'
v.cpus = 1
v.memory = 1024
end
config.ssh.insert_key = false
device.vm.synced_folder ".", "/vagrant", disabled: true
# NETWORK INTERFACES
# link for port2 --> oob-mgmt-switch:swp5
device.vm.network "private_network",
:mac => "44:38:39:00:00:7f",
:libvirt__tunnel_type => 'udp',
:libvirt__tunnel_local_ip => '127.0.0.1',
:libvirt__tunnel_local_port => "#{ 8064 + offset }",
:libvirt__tunnel_ip => '127.0.0.1',
:libvirt__tunnel_port => "#{ 9064 + offset }",
:libvirt__iface_name => 'port2',
auto_config: false
# link for port3 --> dummy:port3
device.vm.network "private_network",
:mac => "44:38:39:00:00:05",
:libvirt__tunnel_type => 'udp',
:libvirt__tunnel_local_ip => '127.0.0.1',
:libvirt__tunnel_local_port => "#{ 8003 + offset }",
:libvirt__tunnel_ip => '127.0.0.1',
:libvirt__tunnel_port => "#{ 9003 + offset }",
:libvirt__iface_name => 'port3',
auto_config: false
# link for port4 --> dummy:port4
device.vm.network "private_network",
:mac => "44:38:39:00:00:07",
:libvirt__tunnel_type => 'udp',
:libvirt__tunnel_local_ip => '127.0.0.1',
:libvirt__tunnel_local_port => "#{ 8004 + offset }",
:libvirt__tunnel_ip => '127.0.0.1',
:libvirt__tunnel_port => "#{ 9004 + offset }",
:libvirt__iface_name => 'port4',
auto_config: false
end
- Profit!
vagrant up