Like Ansible, but for provisioning Virtual Machines, such as VMware, VirtualBox, qemu, or Hyper-V.
Useful links to get started:
- Vagrant Docs: Quick Start Tutorials
- Kali Docs: Kali Vagrant Guest
- Vagrant Docs: Intro to Ansible
- Ansible Docs: Vagrant Guide
Validate your install with one of these public keys:
- hashicorp public keys
C874 011F 0AB4 0511 0D02 1055 3436 5D94 72D7 468F
- hashicorp public keys on keybase.io
C874 011F 0AB4 0511 0D02 1055 3436 5D94 72D7 468F
- hashicorp gpg apt key
798A EC65 4E5C 1542 8C8E 42EE AA16 FCBC A621 E701
- How to Verify a Hashicorp Binary
Download the binaries for Windows and macOS.
Ubuntu / Debian install:
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install vagrant
Kali provides the most straight forward examples for getting started and what Vagrant can achieve.
The commands below do the following:
- Obtain the Vagrantfile from Kali's listings on Vagrant Cloud and initialize this directory for a new VM
- Downloads the
.box
file from Vagrant Cloud if it's missing or a new version is available, and starts the VM using the default provider installed (usually VirtualBox, VMware or Hyper-V)- If you already have the box specified in the Vagrantfile as
config.vm.box
available locally,vagrant up
will provision, register, and start the VM without downloading anything - Basically with the
.box
available, and a Vagrantfile, you can skipvagrant init
- If you already have the box specified in the Vagrantfile as
- Gracefully powers off the VM
mkdir -p ~/vagrant/kali; cd ~/vagrant/kali
vagrant init kalilinux/rolling
vagrant up
vagrant halt
The .box
file is a template for creating separate VM's. Create a new directory and Vagrantfile for each Virtual Machine.
When you run vagrant up
- If a
.box
file wasn't previously imported manually, and no URL is provided, Vagrant will attempt to search the public Vagrant Cloud and download it - Vagrant checks if a new version of the
.box
file is available in Vagrant Cloud and if so, downloads it (this is skipped if you imported a box locally or from a URL)- *NOTE: if Vagrant tries to download a
.box
file, you'll see this on the command line
- *NOTE: if Vagrant tries to download a
- Calculates and compares the box checksum to the metadata files unless you specified a checksum
- Forwards
localhost:2222/tcp
of the host tolocalhost:22/tcp
of the guest VM for ssh access and provisioning access - Automatically mounts the cwd
.
containing the Vagrantfile and any subdirectories, in the guest at/vagrant
with read / write access (you should disable write access) - Starts the provider's GUI window of the VM
- You can then run
vagrant ssh
to open an ssh session into the guest
Vagrant SSH
On the first ssh connection, by default vagrant
will replace the default SSH keys with a random pair generated by vagrant:
<SNIP>
default: Vagrant insecure key detected. Vagrant will automatically replace
default: this with a newly generated keypair for better security.
default:
default: Inserting generated public key within guest...
default: Removing insecure key from the guest if it's present...
default: Key inserted! Disconnecting and reconnecting using new SSH key...
<SNIP>
TIP: You can also safely change the default vagrant user's (insecure) password of "vagrant" or even enforce private key authentication, and the vagrant ssh
command will still be able to manage the machine with the new, random SSH key.
Each vagrant machine's private key is saved in the same directory as the Vagrantfile
, under .vagrant/machines/default/<provider>/private_key
.
How Vagrant itself works
- The
.box
file is an archive that contains the.vmdk
and metadata files used as a template for every VM you create using that box as a base. - All invocation of the
vagrant
command should be done from the directory of the Vagrantfile. Otherwise you must specifiy a machine ID.
Obtain the status (includes machine ID) of all known Vagrant machines on the system:
vagrant global-status
Boxes are the .box
files used as templates to build VM's. They're tarballs containing four components including the .vmdk
virtual disk file itself.
Boxes are stored per-user at the default location of ~/.vagrant.d/boxes/
on both Linux and Windows.
This is a condensed overview of the lifecycle of commands you might run affecting boxes and VM provisioning. Most importantly:
- Boxes are not VM's, they're templates to build VM's from locally
- Deleting (destroying) a VM does not harm or remove the configuration files
vagrant box add hashicorp/bionic64 # Downloads the box from Vagrant Cloud if a local .box file is not specified
vagrant up # Start the VM
vagrant ssh # SSH into the VM
vagrant halt # Shutdown the VM
vagrant destroy # Delete the VM from your hypervisor (based on a Vagrantfile in cwd, or specify an ID)
vagrant box list # List all boxes (remember, these are templates, not the VM, "vagrant destroy" deletes the VM
vagrant box remove hashicorp/bionic64 # Remove a box (aka a VM template) from your local Vagrant environment
When using Vagrant's Cloud, keep in mind anyone can upload a box. The users bento
and ubuntu
are generally trusted. It's important to ensure the username space on Vagrant's Cloud is owned by who you expect it to be. Similar to sourcing OS ISO files, check each distro's official releases for Vagrant boxes or documentation. Similarly if there's a trusted user (like bento
) uploading boxes, double check any documentation to ensure the username matches.
The best way to demonstrate this is with Ubuntu cloud's Vagrant images. Download the following files just like you would an Ubuntu ISO.
See these pages for more detail on the following box add
command:
vagrant box add --name 'my-local-box' file://home/dev/vagrant/boxes/custom.box
Snapshots can be managed in the following ways:
vagrant snapshot list
vagrant snapshot save [<vm-name>] <snapshot-name>
vagrant snapshot restore [<vm-name>] <snapshot-name> [--no-start]
vagrant snapshot delete [<vm-name>] <snapshot-name>
If you're in a directory with a Vagrantfile, you do not need to specify the VM name or ID.
There's also snapshot push|pop
which are more for temporary usage, as you need to include --no-delete
when pop
ing (restoring) a previous snapshot. Otherwise it restores the previous state and deletes the latest snapshot.
Automation is at the heart of Vagrant with the following provisioning options:
- Shell commands
- Ansible / Chef / Puppet / Salt / Docker integration
Provisioning normally only happens during the first boot of the VM. However, as demonstrated at the bottom of the Kali docs on Vagrant usage, provisioning can occur in the following ways manually.
vagrant provision # (re)provision the powered on VM after making configuration changes
vagrant up --provision # when VM is powered off, power it on then provision
vagrant reload --provision # reboot the VM then provision
The configuration section example below shows how to include shell commands and ansible plays in a Vagrantfile.
- The Vagrantfile directory and all subdirectories are shared with the guest and writable by default, but can be disabled with
config.vm.synced_folder ".", "/vagrant", disabled: true
- You can reload a modified configuration file into a running VM with:
vagrant reload
- Vagrant Docs: Shell Provisioner
- Vagrant Docs: Ansible Provisioner
- Vagrant Docs: Provider Configuration
You could script updating a VM, taking a new snapshot, and deleting the oldest snapshot, to occur on a scheduled cycle via cron.
The Vagrantfile could include the following lines in the provisioning section:
config.vm.provision "shell", inline: <<-SHELL
export PATH=$PATH:/usr/bin
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get full-upgrade -yq
apt-get autoremove --purge -yq
SHELL
The script could look like:
vagrant up --provision
vagrant halt
vagrant snapshot save vm-name "snapshot-$(date +%F)"
vagrant snapshot delete vm-name "$(vagrant snapshot list vm-name | tail -n 2 | head -n 1)"
Here's an example snippet from a Vagrantfile for Kali Linux using VirtualBox as the provider:
...
# VirtualBox provider configuration:
config.vm.provider "virtualbox" do |vb|
vb.name = "kali-vagrant"
vb.memory = "8192"
vb.cpus = "6"
vb.customize ["modifyvm", :id, "--clipboard-mode", "hosttoguest"]
vb.customize ["modifyvm", :id, "--drag-and-drop", "hosttoguest"]
#vb.customize ["modifyvm", :id, "--cable-connected1", "off"]
end
...
The vb.customize
lines allow you to make adjustements specific to the VirtualBox hyperviser. Each hyperviser (provider) has customization options like this.
Providers basically means hypervisors.
This is the most common provider, most boxes you find on Vagrant's public cloud will (only) support VirtualBox.
Working with Hyper-V boxes has a few differences:
- You need to run
vagrant.exe
from an Administrator shell - You'll be prompted to select a network adapter on first boot (change this later in the Hyper-V manager)
- Vagrantfile network configurations are ignored
- Vagrant cannot enforce a static IP
- You can however, do this with the provisioner section of the Vagrantfile
- Hyper-V's built in Default Switch (NAT network) changes its subnet range and gateway on each reboot
- Vagrant will report the VM's IP on each boot if it can reach it
- Vagrant should be able to ssh into the VM, even without an IPv4 address, using the IPv6 link-local address range of fe80::/10
If you have multiple virtual network adapters in Hyper-V, choose the Default Network switch, or any internal switch the host can reach, during the initial boot and provisioning of the guest. Just don't use an external adapter (not safe considering vagrant:vagrant
are the default ssh credentials) or private adapter (only VM's can communicate on private network adapters, the host can't).
- This allows Vagrant to
vagrant ssh
into the guest (likely using IPv6 link-local scope fe80::/10 addressing if DHCP is not working on the Default Switch) - You can connect a second adapter with additional network access to the guest in the Hyper-V manager once Vagrant finishes creating the VM
In case you plan to move a Vagrant guest to a network where vagrant ssh
cannot reach it:
- Provision the guest initally in a network the host can reach
- Move the provisioned guest to the desired network adapter after provisioning
- You can still start the guest, manage snapshots, and other things using Vagrant in these cases
- Shut the guest down manually instead of using
vagrant halt
ifvagrant ssh
cannot reach the guest
Example Hyper-V Provider section in a Vagrantfile:
# Hyper-V provider configuration:
config.vm.provider "hyperv" do |hv|
hv.vmname = "kali-rolling-vagrant"
hv.cpus = "4"
hv.memory = "8192"
#hv.ip_address_timeout = 240
hv.enable_enhanced_session_mode = true
hv.enable_virtualization_extensions = false
hv.vm_integration_services = {
guest_service_interface: false,
heartbeat: false,
key_value_pair_exchange: false,
shutdown: false,
time_synchronization: false,
vss: false
}
end
Hyper-V uses SMB as it's default shared folder mechanism.
- Creates an SMB share in the specified directory as admin
- Requires your host's local admin account credentials be passed to Vagrant
- You may need to specify an
smb_host
IP address
Example line using smb_host
:
config.vm.synced_folder "./provisioning", "/vagrant/provisioning", :mount_options => ["ro"], smb_host: "10.55.55.1"
To clean up any leftover Vagrant SMB shares:
Get-SmbShare | where {$_.Name -imatch "vgt-*"} | Remove-SmbShare
Ansible support is limited to WSL on a Windows host.
An easy work around is to use the shell provisioner to install ansible within the guest, clone or retrieve any playbooks, and run them from there.
This assumes your Windows host's firewall is set to Set-NetFirewallProfile -All -AllowInboundRules Block
, which blocks ALL inbound traffic, even if a rule exists.
- Create a static NAT network, this way you have a predictable subnet that does not change on reboot how Hyper-V's Default Switch does
- Set the IP, route, and interface information using the provisioner section of the Vagrantfile based on that custom NAT network's information
- This is an easy way to automate and avoid networking issues in Hyper-V
If you require special firewall rules, keep in mind this adapter can be mentioned in rules specifically, by providing the name of the adapter as the argument to -InterfaceAlias
when executing the Get-NetAdapater
, New-NetFirewallRule
cmdlets.
This helps resolve issues with the Default Switch changing it's subnet range and gateway on each reboot of the host.
As long as Get-NetNat
returns no existing NAT interfaces, you can create a custom NAT switch and network using the following:
- 10.55.55.1/24 is chosen since it's relatively obscure and unique, meaning it won't collide with common home wifi, office, or Hyper-V subnets
- SOHO routers will often default to the range of 192.168.0.0/16
- Hyper-V tends to use the subnet range of 172.16.0.0/12
- VPNs and corporate networks will use the range of 10.0.0.0/8, be sure to review any existing network configurations or requirements before using 10.55.55.1/24
New-VMSwitch -SwitchName "CustomNATSwitch" -SwitchType Internal
$ifindex = (Get-NetAdapter -IncludeHidden | where { $_.Name -eq "vEthernet (CustomNATSwitch)" }).ifIndex
New-NetIPAddress -IPAddress 10.55.55.1 -PrefixLength 24 -InterfaceIndex $ifindex
New-NetNat -Name CustomNATNetwork -InternalIPInterfaceAddressPrefix 10.55.55.0/24
To remove the NAT network and switch:
Get-NetNat | where { $_.Name -eq "CustomNATNetwork" } | Remove-NetNat
Get-VMSwitch | where { $_.SwitchName -eq "CustomNATSwitch" | Remove-VMSwitch
If you want to be able to reach VM's on this switch from the host or WSL, you may need to enable forwarding. Just remember, the Windows host is likely filtering ping
packets. Try ssh
, Test-NetConnection
, nmap
, or naabu
to verify connectivity.
Forwarding can be enabled or disabled per interface with the following:
# Apply
Get-NetIPInterface | where {$_.InterfaceAlias -eq 'vEthernet (CustomNATSwitch)'} | Set-NetIPInterface -Forwarding Enabled
# Remove
Get-NetIPInterface | where {$_.InterfaceAlias -eq 'vEthernet (CustomNATSwitch)'} | Set-NetIPInterface -Forwarding Disabled
I've needed to do the following during the hashicorp/bionic64
tutorial:
- Set an internal switch (it can be any internal switch the host can reach) as the initial network adapter
- This is so
vagrant ssh
can reach the guest over link-local IPv6 (fe80::/10) if DHCP fails on the Default Switch - Add a secondary network adapter that has desired network access (e.g., to the public internet or to another internal network)
- Run
sudo ip link set eth1 up; sudo dhclient -v
for the second adapter to pick up an IP address via DHCP
In some cases Vagrant could never find the link-local IPv6 address. In the event this happens, you can log in manually to the VM once and set a static configuration with either netplan
or NetworkManager using nmcli
.
On hosts without nmcli
you can use netplan if it's available. Edit an existing file under /etc/netplan/
:
sudo nano /etc/netplan/01-some-file.yml
Replace contents with the following:
network:
version: 2
ethernets:
eth0:
dhcp4: false
dhcp6: false
addresses:
- 10.55.55.28/24
nameservers:
addresses: [1.1.1.1, 9.9.9.9]
routes:
- to: default # Use `to: 0.0.0.0/0` on Ubuntu 18.04
via: 10.55.55.1
See this stack exchange post regarding use of 0.0.0.0/0
instead of default
. This appears to be an issue with Ubuntu 18.04.
With this method, the guest will still obtain DHCP leases on networks that provide it. Otherwise it will always have the static IP configuration available on it's eth0
interface for use with the static NAT network.
To enable and disable an interface:
nmcli device status # Get the status of all network devices
nmcli con up id eth0 # Enable the profile named "eth0"
nmcli dev disconnect eth0 # Disable the profile named "eth0"
To configure an interface profile from scratch with nmcli
(this follows Red Hat's example, swapping in our own information):
nmcli con add type ethernet con-name test-lab ifname eth0 ip4 10.55.55.11/24 gw4 10.55.55.1 #[ip6 abbe::cafe gw6 2001:db8::1]
nmcli con mod test-lab ipv4.dns "1.1.1.1 1.0.0.1"
nmcli con mod test-lab ipv6.dns "2606:4700:4700::1111 2606:4700:4700::1001"
nmcli con up test-lab ifname eth0
nmcli device status
nmcli -p con show test-lab
If you get the error that "device is strictly unmanaged" check /etc/NetworkManager/
and /etc/NetworkManager/conf.d
.
Look for any lines under [keyfile]
or [ifupdown]
sections in files that might be setting interfaces as unmanaged or strictly managed.
In Kali, you need to change managed=false
to managed=true
in /etc/NetworkManager.conf
to use nmcli
.
To delete a network configuration:
nmcli show
nmcli con delete id <profile-name> # Alternatively, use `uuid <profile-uuid>` instead of `id <profile-name>`
The GOAD (Game of AD) project uses Windows boxes prepared by StefanScherer/packer-windows, which are referenced in the Microsoft documentation for deploying Vagrant images locally. The Vagrantfile is available on Microsoft's azure_arc GitHub.
Windows versions from 2016 / 10 to 2022 / 11 are available on Stefan Scherer's Vagrant Cloud.