Wandering Vagrant: A Crash Course in Vagrant Virtual Machines

A crash course of all the disparate knowledge I wish I had when I first delved into Vagrant

Published: December 15, 2022

Reading Time: 19 minutes

What is Vagrant

When working in security there are often times when you need to test an idea that requires some sort of infrastructure, this can be testing how a self-hosted software works, inspecting what traffic looks like coming from a different machine, or any other scenario where having a dedicated virtual machine could be helpful. Vagrant is a tool that allows you to hook into a virtual machine provider and rapidly deploy virtual machines. Vagrant can tie into almost every virtual machine provider (vmware, virtualbox, vsphere, etc) to manage virtual machines very easily.

The reason I’m writing this post is to capture all the disparate knowledge I collected when I first started delving into Vagrant. It started out as just my notes but in an effort to make them useful to others I am posting them here. If you work with a technology, consider giving back to the community by getting your knowledge out there for free. Without further ado, here is a little crash course on Vagrant.

1. Vagrant Crash Course

This section will teach you the bare-bones of what you need to do to get a Vagrant VM up and running.

To get started with vagrant you need surprisingly little. I will be demonstrating how to get vagrant up and running using a Ubuntu Linux host machine but it should be relatively the same on any Linux host. I will be using Virtualbox for this but I will cover how to use Vagrant with VMware workstation later (as well as some words of warning). This section will go over how to :

  • Creating a Vagrantfile
  • Creating a Booting Kali Linux virtual machine
  • Creating a shared folder for each
  • Accessing the virtual machine

The first thing we need to do is install both Vagrant and Virtualbox

1# Installs vagrant and virtualbox
2sudo apt install vagrant virtualbox 

Next, we need to create our project directory. Vagrant utilizes your current working directory to know which Virtual Machine you’re interacting with. I will create a directory under ~/Documents/vagrant/ called securityTesting and move into that directory.

1# Makes the directory. -p creates the parent directories if they don't exist
2mkdir -p ~/Documents/vagrant/securityTesting
3# Move into the directory we just created. You could also use cd $_ ($_ represents the last argument of the previous command. 
4cd ~/Documents/vagrant/securityTesting 

Next we need to initialize this directory with Vagrant which is similar to initializing a directory as a git repository. This will create a directory inside our securityTesting folder called .vagrant which will hold our Vagrant information as well as create a boilerplate Vagrantfile.

1# You probably don't need to run this unless you've been messing with Vagrant before. 
2# export VAGRANT_DEFAULT_PROVIDER=virtualbox 
3
4#Creates boilerplate vagrant file
5vagrant init 
6
7# Open up our newly create VagrantFile
8vim Vagrantfile 

The next requirement for Vagrant is a box that defines what virtual environment our virtual machine will boot into. You can search for boxes on Vagrant’s Site. For this demonstration we will be using a Kali Linux box.

Let’s take a look at the boilerplate Vagrantfile that is created by running vagrant init. By default there are only three lines that are un-commented, lets go line by line and talk about what they do.

1Vagrant.configure("2") do |config|
2  config.vm.box = "kalilinux/rolling"
3end

Now that you’re in the Vagrantfile, go to line 14 and change config.vm.box = "base" to config.vm.base = "kalilinux/rolling".

  1. Vagrant.configure("2")do |config|: Defines the version of vagrant we will be using. config defines the name of the object we are creating which is used in line 2 of our configuration file. You can name your object blocks anything.

  2. config.vm.box = "kalilinux/rolling": Tells vagrant which Vagrant Box we will be using. Note that the syntax is <objectname>.vm.box = "<username/boxname>" where <objectname> will be dependent on what is defined in the previous line. You can browse various Vagrant boxes here

  3. end: This just simply closes out the block defined on the first line.

Anything between the opening of the block and the end line will be the configuration of your virtual machines. We will be adding to this configuration later to define new properties for our virtual machines.

Next all we need to do to get our Kali VM up and running is open the Virtualbox GUI and run the command following command from the vagrant directory (in this case the securityTesting directory):

1# Starts the vagrant VM. Make sure you're in the correct directory
2vagrant up 

If you get any errors, make sure you’ve opened Virtualbox so it can configure itself.

After Vagrant gets our VM created, we now have access to a Kali Linux Virtual machine via the GUI provided by Virtualbox using the credentials vagrant:vagrant as well as through SSH access using the vagrant ssh command.

Before moving forward, lets go through some of the important output from out vagrant up command.

  • Importaing base box 'kalilinux/rolling': Lets you know which box you’re importing as a virtual environment.

  • 22 (guest) => 2222 (host) (adapter 1): Changes the SSH port you use to access your guest from 22 to 2222 on your host.

  • SSH auth method: Private key: By default, this vagrant box generates an SSH key used for authentication to your VM. If you used a different box, it is likely the credentials will be vagrant:vagrant.

  • /vagrant => /home/<hostname>/Documents/vagrant/securityTesting: Shares your Vagrant folder with your VM. Any file/folder you place in ~/Documents/securityTesting will be available to your VM under /Vagrant by default. I will discuss this in more detail later.

Now lets access our machine. As stated previously, you can access your Vagrant Virtual machine in two ways by default (we will discuss other access methods later). First, you can open your Virtualbox GUI and click show to get console access to your virtual machine. The default login credentials for Kali (at the time of writing) is vagrant:vagrant. These are the credentials for most vagrant machines. Lets access our machine via SSH and look around. From your project directory execute the following commands to view our vm and SSH into it.

1# Displays the current status of our Vagrant VM. It should say "running" and the provider
2vagrant status 
3# SSH into our machine.
4vagrant ssh 

As you can see, upon executing vagrant ssh, we are dropped into a Kali Linux Virtual machine. Sweet! Notice that this is functionally the same as using any other Kali machine. By default, Vagrant shares our project directory folder (for me,~/Documents/vagrant/securityTesting). To access files in our default shared folder from inside our Kali virtual machine, navigate to /vagrant and you will see our Vagrantfile that we used to create this virtual machine.

1# From inside our Kali Linux machine
2# Change into our shared folder
3cd /vagrant 
4# See the files
5ls
6# Create a text file from within our kali VM in the shared vagrant folder
7echo "Hello from the upside down" >> readme.txt 
8# Leave the vagrant virtual machine
9exit

Now that we created this readme.txt file, exit the Kali virtual machine by simply typing exit. Now that you’re back into your host machine, you can see that our readme.txt file was created. This works from guest to host and vice-versa.

Awesome, now that we have the basics done, lets configure some of the extra functionality Vagrant offers. First, lets stop our current virtual machine and edit some of the configuration files:

1# This attempts a graceful shutdown of our virtual machine 
2vagrant halt 
3# View the status of our VM
4vagrant status 
5# Optinally, you can run `vagrant destroy` to destroy the machine.
6# It will be recreated upon executing vagrant up but files created in the VM will not persist

Open up the Vagrantfile configuration file in our project directory and lets add some new lines to it.

 1Vagrant.configure("2") do |config|
 2  # Defines the box we will be using
 3    config.vm.box = "kalilinux/rolling"
 4  # Changes the hostname of the virtual machine
 5	config.vm.hostname = "attacker"
 6  # Vagrent displays a message aftter running `vagrant up`
 7	config.vm.post_up_message = "Attacker machine has booted up!"
 8  # Maps a directory on your host machine to a directory in your virtual machine. 
 9  # The `~/tools` is now going to be accessible in `/home/vagrant/tools`
10  # Note that the guest location must be an absolute path
11	  config.vm.synced_folder "~/tools", "/home/vagrant/tools"
12  # This forwards the guest port to the host port which will allow you to connect
13  # to services hosted in your virtual environment from your host machine.
14	  config.vm.network "forwarded_port", guest: 1337, host:1337
15end

Let’s boot up our Kali machine again and verify our changes have been made. To do so, simply save your Vagrantfile and execute Vagrant up. Notice that new information is provided upon boot.

After connecting back to out machine, we can see that hostname has changed and our shared tools directory is now in our home folder. Let’s verify that our port forwarding worked by hosting a simple python webserver on port 1337 and connecting to it from Firefox on our host machine.

1# Inside of Kali Linux VM:
2python3 -m http.server 1337

After navigating to localhost:1337/tools in firefox on our host machine, we see that we have gotten a directory listing from our simple webserver inside of our virtual machine.

That is the a very basic high level overview of Vagrant. It really is that simple. I would be lying if I said I didn’t run into some bugs but for the most part they were due to typos in my Vagrantfile or from not understanding how Vagrant works. To be honest, this is mostly what I use vagrant for, but there are more complexities you can explore if you have a need. One such complexity is a Multi-VM environments which we will cover next.

2. Multi-VM Environments

Creating a small multi-VM environment is fairly straightforward but it will take a little bit more configuration file editing. It should be noted that you can create very complex environments if you need. Check out Game Of Active Directory for a great example of some more complex stuff you can do with Vagrant (and other technologies). Luckily for us, a small multi-VM environment can all be done using one Vagrantfile configuration file.

For this example we are going to add another virtual machine to our environment that will be a webserver. To accomplish this, we’re going to make some changes to our Vagrantfile configuration file:

 1Vagrant.configure("2") do |config|
 2	# Define our Kali VM with the keyword attacker.
 3    config.vm.define "attacker" do |attacker|  	                      
 4		# Same as before but with the "attacker" object keyword
 5        attacker.vm.box = "kalilinux/rolling"                         
 6		# Changes hostname
 7        attacker.vm.hostname = "attacker"                             
 8		# Adds our synced folder
 9        attacker.vm.synced_folder "~/tools", "/home/vagrant/tools"    
10		# Forwards port 1337 on the VM to 1337 on the host
11        attacker.vm.network "forwarded_port", guest: 1337, host: 1337 
12		# Requests an ip address from DHCP 
13        attacker.vm.network "private_network", type: "dhcp"           
14	# End of our "attacker" object block
15    end  
16
17	# Define our webserver vm with the webserver keyword.
18    config.vm.define "webserver" do |webserver|                       
19		# Tells vagrant to grab this VM from https://app.vagrantup.com.
20        webserver.vm.box = "ncaro/php7-debian8-apache-nginx-mysql"    
21		# Forwards port 80 on the VM to port 8080 on the host.
22        webserver.vm.network "forwarded_port", guest: 80, host: 8080  
23		# Requests an IP address from DHCP.
24        webserver.vm.network "private_network", type: "dhcp"          
25	# End of our "webserver" object block
26    end
27end

Now that we have our new Vagrantfile, lets destroy our old environment just to demonstrate how it’s done and boot up our new Multi-VM environment.

1# Destorys the VM(s). All files on the machines will be removed.
2vagrant destroy 
3# This will bring our new VMs up and download the new webserver box from vagrantup.com.
4vagrant up 

Note, if you get an error that you can’t create the virtual machine because the specified ports are already in use, you might have to kill the process listening on that port. To do so run sudo lsof -i:1337 and then kill <PID returned by previous command> This sometimes happens with virtualbox.

As you can see, our output from vagrant up is now quite a bit longer but if you take a closer look, you can see that each machine booted. Lets take a look at our current environment using vagrant status

We can see that both of our VMs have a status of running. Great, now we can connect to each of them individually to inspect them. Notice that our vagrant ssh syntax has now changed. We now have to state which virtual machine we wish to connect to:

1# Connect to our webserver via ssh. Credentials are vagrant:vagrant
2vagrant ssh webserver 

Once connecting to the webserver virtual machine, we can quickly verify that our machine has gotten a DHCP IP address and has nginx running.

To verify that our portforwarding of port 80 on our VM to 8080 on our host machine worked correctly, lets try connecting to the webserver using our new localhost:8080. Note that since we now have a DHCP address, we can also access the webserver using that address from our host. In my case my IP assigned by DHCP is 192.168.56.8. We can verify that it worked by navigating to 192.168.56.8 on our host machine displays the default nginx PHP page.

Great, now lets verify that we can communicate from our attacker VM to our webserver by running a common tool such as dirb on it. To do so, all we have to do is exit our webserver virtual machine and connect to our attacker virtual machine using the same vagrant ssh attacker syntax as before. After running dirb we can see that the tool is being run as it finds our index.html and index.php files which verifies it can see our webserver!

That is about it for a simple Multi-VM environment. The sky (or more likely your hardware) is the limit with how many machines you deploy. Once again, I typically use Vagrant for quick testing but there are some incredibly complex things being done with vagrant. Up next I wanted to quickly discuss using Vagrant with VMware Workstation.

3. Vagrant and VMware Workstation

When I first started using Vagrant I was really opposed to using Virtualbox as my provider. Most of the experiences I’ve had with Virtualbox have been poor and since I already had access to VMware Workstation, I figured it was only logical to use Workstation with Vagrant. With that being said, I have found that overtime I much prefer using Virtualbox. There are two things that drive this decision.

  1. Virtualbox works better: I’ve had far less issues using Virtualbox and the issues I have encountered are typically easy to solve because most people who are using Vagrant are using it in conjunction with Virtualbox. Additionally, when using Virtualbox as a provider, you have easy access to your box’s GUI via clicking show on your VM in the Virtualbox application. Workstation does not even show your VMs within the application which is odd. Not a deal breaker, but worth noting.
  2. Virtualbox has more boxes available: This is pretty straightforward. Virtualbox is used more by people in the Vagrant community. When selecting a box from VagrantUp, you must choose one that supports your provider. Finding a generic Linux box for VMware is not difficult, but finding more obscure boxes can be tedious if you’re trying to find them for VMware Workstation.

With all that being said, if you still want to use Vagrant with VMware, you can do so fairly easily. This assumes you have a valid VMware workstation license.

First, you must download the Vagrant VMware utility from the Hashicorp site. To do so,

  1. Visit The Vagrant VMware Download Page.
  2. Assuming you’re on a Debian system, click the Debian section.
  3. Select Download.
  4. Install the Vagrant VMware utility with the following commands.
1# Move to your downloads folder
2cd ~/Downloads  
3# Install the Vagrant VMware Utility
4sudo dpkg -i vagrant-vmware-utility*
5
6# Or if you want to install it by going goblin mode (totally not necessary)
7# ls -ltr ~/Downloads | tail -n1 | awk {"print \$9"} | xargs -I{} sudo dpkg -i ~/Downloads/{}  
8# ^^ It takes the last file placed in your downloads folder and runs sudo dpkg -i on it 

Next you must install the Vagrant VMware plugin:

1# Install the VMware Vagrant plugin
2vagrant plugin install vagrant-vmware-desktop 

That is all you need to do to get it working with VMware workstation. It should be noted that if you have Virtualbox and VMware Workstation installed, you can specify which provider you would like to use by appending the provider flag on your Vagrant up command. For example:

1# Bring vagrant up with a specific provider
2vagrant up --provider=virtualbox
3# or 
4vagrant up --provider=vmware_desktop

4. Vagrant With Windows Machines

Working with Windows virtual machines is possible with Vagrant but is a little more complicated due to Windows being less user friendly when it comes to this kind of quick bootstrapping of development environments. Don’t let this dissuade you though! While it requires a little bit more work, it is much easier than spinning up a normal windows machine from scratch! Lets discuss a few differences between working with Linux and Windows VMs.

  1. Windows does not use SSH: Vagrant windows machines do not use ssh by default. By default, you interact with Windows virtual machines with Winrm, powershell, or RDP.
  2. Windows opens up RDP by default: Most Vagrant boxes that use windows open up RDP by default. This is typically the easiest way to interact with a windows machine.

Creating windows machines in Vagrant is done just like any other machine, by editing the Vagrantfile. Like with any Vagrant VM, you need to find a box that is supported by your provider on VagrantUp. I will be using the box gusztavvargadr/windows-10.

First, create a new Vagrant directory and initialize it:

1# Make base Vagrant directory and move into it
2mkdir windows && cd windows 
3# Initilize the directory as a Vagrant directory and pre-fill our vagrant file with
4# the gusztavvargadr/windows-10 box.
5vagrant init gusztavvargadr/windows-10 
6# Start Vagrant with virtualbox
7vagrant up --provider=virtualbox

After a couple of minutes our windows VM should be up. We now have a few connection options. First, if you’re using Virtualbox, you can simply view the console to access your machine. By default, this image also opens up RDP access to the machine. If you notice, when the VM was booting up, Vagrant told us that port 3389 (RDP) was being mapped to port 53389 on our host. To RDP into our machine, you can use your favorite RDP client and connect to 127.0.0.1:53389. Finally, you can type vagrant rdp to gain RDP access to the VM although I’ve found this doesn’t work well.

If you’re looking for command line access to the VM, you can also access it via winrm or powershell. To do so, you can simply run the following commands vagrant winrm --command <command> or vagrant powershell --command <command>.

That is it for a very basic windows installation. After working with some windows VMs in vagrant, I’ve found that there are still some kinks to be worked out such as sometimes winrm does not work as expected. I hope as vagrant becomes more popular these kinks will be ironed out. It should also be noted that if you want to build your own custom VMs to deploy you can do so using another tool provided by Hashicorp known as Packer,however, that is beyond the scope of this crash course.

5. Vagrant VS Ansible VS Terraform

When delving into Vagrant, I was curious about the difference between other technologies such as Terraform and Ansible. Each technology has some overlap but has some different use cases, here is a brief overview of each.

Terraform is also a technology provided by Hasicorp that is used to help automate infrastructure creation. Terraform is more focused on creating infrastructure as code in cloud environments. Contrast this with Vagrant which is more focused on creating local development environments. An analogy you can use to distinguish the two from each other is Terraform is to the C programming language as Vagrant is to python. The C language (terraform) gives you an incredible amount of control and can scale with large programs where as python (vagrant) is much easier to write and is often used for quick testing.

So where does Ansible come into the mix? Where Ansible really shines is in the configuration of environments via it’s agent-less SSH features. Ansible playbooks can be created to automatically update/install/manage software through the use of SSH. Think of Ansible as a technology that can push out commands via SSH (or WinRM for windows) much like a GPO. This can be incredibly useful for configuration management of environments. While Vagrant does allow you to execute commands on the machines you’re creating, it does not have an easy to use playbook system or the ability to manage non-vagrant machines like Ansible does. Ansible is a really awesome technology that I will be diving into for my next deep dive.

6. My Vagrant Cheat sheet

Here is a collection of useful commands, boiler plate documents, and configurations I find myself coming back to

Starting Boxes

  • vagrant init bento/ubuntu-22.04: Start a VM with the supplied box.
  • vagrant up: Starts vagrant VM (must be in the vagrant base folder).
  • vagrant up --provider=Virtualbox: Starts VM with specified provider.
  • vagrant status: Displays the status of the vagrant VMs in your current directory.
  • vagrant global-status: Displays the status of all vagrant VMs no matter what directory you’re in.
  • Boxes can be found here

Finding Boxes

  • vagrant box list: Lists all downloaded boxes.

It would be sweet for someone to make a command line tool that allows you to find boxes, just fyi in case anyone is looking for a project idea :)

Useful Boxes

  • kalilinux/rolling: Kali Linux.
  • StefanScherer/windows_2019: Windows server 2019.
  • gusztavvargadr/windows-10: Windows 10.
  • ncaro/php7-debian8-apache-nginx-mysql: Nginx webserver with PHP and a few other utilities.
  • bento/ubuntu-22.04: Base ubuntu 22.04.

Vagrantfile

  • vagrant init bento/ubuntu-22.04: Creates file and adds box into init file.
  • config.vm.box = "bento/ubuntu-22.04": Define the Vagrant box.
  • config.vm.base_mac = "AB:AB:AB:AB:AB:AB": Sets the VM MAC.
  • config.vm.hostname = "grahamiscool": Sets the VM hostname.
  • config.vm.post_up_message = "Second breakfast": Displays a message on machine creation.
  • config.vm.network "private_network", ip: "192.168.33.10": Creates a private network.
  • config.vm.synced_folder "/home/graham/data", "/vagrant_data": Shares a folder with the guest VM.
  • Configure Memory and CPUs. (This is provider specific):
1config.vm.provider "virtualbox" do |v|
2  v.memory = 2048
3  v.cpus = 2
4end
  • Boilerplate Vagrantfile to create a Kali machine, webserver, and Windows machine:
 1Vagrant.configure("2") do |config|
 2	# Define our Kali VM with the keyword attacker.
 3    config.vm.define "attacker" do |attacker|  	                      
 4		# Same as before but with the "attacker" object keyword
 5        attacker.vm.box = "kalilinux/rolling"                         
 6		# Changes hostname
 7        attacker.vm.hostname = "attacker"                             
 8		# Adds our synced folder
 9        attacker.vm.synced_folder "~/tools", "/home/vagrant/tools"    
10		# Forwards port 1337 on the VM to 1337 on the host
11        attacker.vm.network "forwarded_port", guest: 1337, host: 1337 
12		# Requests an ip address from DHCP 
13        attacker.vm.network "private_network", type: "dhcp"           
14	# End of our "attacker" object block
15    end  
16
17	# Define our webserver vm with the webserver keyword.
18    config.vm.define "webserver" do |webserver|                       
19		# Tells vagrant to grab this VM from https://app.vagrantup.com.
20        webserver.vm.box = "ncaro/php7-debian8-apache-nginx-mysql"    
21		# Forwards port 80 on the VM to port 8080 on the host.
22        webserver.vm.network "forwarded_port", guest: 80, host: 8080  
23		# Requests an IP address from DHCP.
24        webserver.vm.network "private_network", type: "dhcp"          
25	# End of our "webserver" object block
26    end
27end

7. Wrapping Up

Vagrant is a powerful tool that can allow you to easily spin up virtual environments in a way that allows you to rapidly experiment with different operating systems. The amount of things you can do with it is virtually endless (no pun intended). In the future I would like to do an additional blog on Vagrant that dives deep into creating custom virtual machines using Packer.

Have any questions?

Do you have any questions or comments? Feel free to reach out to me on twitter, Mastodon, or Linkedin.