TLDR: We set up a small windows lab with Vagrant.
In the last article we spoke about Infrastructure as Code and DevOps practices to simplify the creation of virtual machines and automate the provisioning process. In this article we’ll actually get our hands dirty and create a test environment using Vagrant, Ansible, and VirtualBox.
So the first thing we need to do is download the required software:
- (Insert your favorite text editor here; I’ll be using Atom)
Installing Vagrant isn’t too difficult, just follow the wizard and it’ll add it to your path by default. Same goes for VirtualBox, it should all work right out of the box.
Once everything is installed we can get started. First lets create a folder for our files, I usually just name it the name of the lab. Then we’re going to go into that folder and run the following command:
vagrant init hashicorp/bionic64
This will create a file that contains the configuration for a basic Ubuntu virtual machine. If you look in the folder now, you should see the Vagrantfile.
Once you’ve done this then you already have a VM ready to boot. You can actually start the VM by running:
This will tell Vagrant to download the virtual machine image, boot it, and provision it based on what’s in the Vagrantfile.
But we’re hoping to do a bit more than just a single VM, so if you turned on the VM we can get rid of it off by running:
vagrant destroy -f
This will power off and delete that VM along with all of its files. Now with that VM gone let’s open up the Vagrantfile in the editor of your choice and take a look. At first glance there’s a lot of commented lines, these are examples of the different things you can configure here. However we can pretty much get rid of all of it since we’ll be configuring everything from scratch.
So with a blank configuration we can start by defining the virtual machines that we’re going to create within the main loop. We can do this with the following syntax:
config.vm.define "vm-name" do |var|
For this example, we’ll be creating three virtual machines:
- An Ubuntu instance (for Ansible use)
- A Windows 10 instance
- A Windows Server 2019 instance (to create a domain)
So here’s what the Vagrantfile looks like after defining those three virtual machines.
Now we’re ready to start defining properties for each specific virtual machine. I’ll only be briefly describing what each property does but Vagrant’s documentation has much better explanations if you’d like to know more or what other properties you can set.
We’ll be using the following:
var.vm.box = <the image that you'll be using>
var.vm.network = <internal or bridged network, ip address>
var.vm.hostname = <hostname for the machine>var.vm.provider "virtualbox" do |x|
x.gui = <show vm or run in background>
x.name = <name of vm in virtualbox gui>
x.cpus = <number of cpus>
x.memory = <memory allocated to vm in megabytes>var.vm.synced_foler <folder to share from local machine>, <location on vm>
var.vm.provision "shell", path: <path to a script on local machine>
Starting with the Ubuntu virtual machine, we’ll be using all of the above. We’ll use the “hashicorp/bionic64” image, add an internal private network with a static ip, set its hostname, then give it 1 CPU and 1GB of memory.
Now you’ll notice we also used the vm.synced_folder and vm.provision parameters with this virtual machine. This is because there is a folder that we need to share to the VM as well as a script that we need to run in order to install/setup Ansible. So lets start by creating the folders.
We’ll need to create three folders within the working folder:
- Ansible (to hold the Ansible config and hosts file)
- Scripts (to hold simple provisioning scripts)
- Playbooks (to hold Ansible plays)
Now that we have a “./Playbooks” folder, when the VM boots up it will make a link between that folder in the host machine to the path we defined in the config. The idea is that any plays that we create in that folder will also be accessible from the VM so that we can execute them.
Next comes the script. We defined “./Scripts/Setup-Ansible.sh” so let’s go ahead and create it. This script will be run when the VM boots so it should do some basic stuff. In this case we’ll have it install Ansible and some dependencies as well as copying over the Ansible config files from the “./Ansible” folder.
All that’s left for this machine is to add the Ansible configuration files and create some plays. In this case I just used a default “ansible.cfg” file and “hosts” file. In the cfg file we can set the following:
host_key_checking = False
Then in the hosts file, we define the machines that we’re going to manage with Ansible. The hosts are defined with the following syntax:
<name of machine> ansible_host=<ip address of machine>[group-name:vars]
<variables for group>
Since we’re going to be setting static IP addresses for the VMs here, we need to remember to define those IPs in the Vagrantfile.
As you can see, we defined other variables in the hosts file such as:
This is because to manage Windows hosts with Ansible, we’re going to connect over WinRM instead of the usual SSH.
By default WinRM isn’t configured properly so we’re also going to create a script to enabled it when the Windows VMs boot. Luckily Ansible provides us with an easy way to set it up with their own script from it’s documentation. We just need to download it and run it. Since the script will run on Windows we’ll write it in powershell and call it “Setup-WinRM.ps1".
Now we just need to call this script when provisioning the Windows virtual machines with “vm.provision” argument. Let’s go ahead and configure those VMs in the Vagrantfile.
We’ll create two virtual machines, one using a Windows 10 image from “gusztavvargadr/windows-10", and one using a Windows Server 2019 image from “gusztavvargadr/windows-server”. We’ll give them the same IP addresses that we defined in the hosts file and give each 2 CPUs and 4GB of memory.
And now with all three virtual machines defined and configured in the Vagrant file we should be able to bring up this environment. Let’s run
and watch vagrant provision these machines for us.
Note: The first time you run vagrant up it might take a while since it has to download the images from the internet. The images get cached however, so the next time you bring the environment it wont take as long.
Give it a few minutes and once it’s done we’ll get our prompt back. To verify that the machines were provisioned you can run the command:
This will show us the status of the machines defined in the Vagrantfile. Another easy way would be to just open Virtualbox and see if the machines were created.
Now from here we can connect to the machines using vagrant. We can connect to linux instances with SSH or for the windows instances we can connect using Powershell or RDP. The syntax for this is:
vagrant <connection method> <machine name>examples:
vagrant ssh ansible-controller vagrant rdp windows-box
Lets connect to the Ubuntu instance via SSH and check out how the machine was provisioned.
First let’s see if Ansible was installed. We can run the following command to check its version:
Then we can check if the Playbooks folder is being shared correctly. If we create a file in that folder on our host machine, we should see it come up in the Playbooks directory inside the Ubuntu instance.
In this case, I’ve created a play to install Active Directory called “Install-AD.yml”. This play will enable and set a new password for the Administrator account, install the AD and DNS roles, and create a new domain called “lab.exmaple.local”.
So we can see that Ansible 2.5.1 was installed and when we list the files in the Playbooks directory you can see the “Install-AD.yml” file that we have open in our host machine.
Let’s run this play and check the windows server afterwards to see if Active Directory was actually installed.
We can see that the play came back successful and if we open the Windows Server instance we can log in with the Administrator credentials we defined in the play. We can also see the server has the AD DS and DNS roles installed and is part of the “lab.example.local” domain.
From here we have the basic infrastructure set up. We can create plays on our host machine to do various things such as install software or configure the guest OS and we can run them from the Ubuntu instance without having to reboot or re-provision. Then once we’re done we can easily turn off the environment by running:
This will shutdown all the virtual machines in the Vagrantfile. We can also choose to just destroy the entire environment and provision fresh VMs by running:
vagrant destroy -f
Pretty easy no? There’s definitely a lot more to explore with Vagrant and Ansible but trying to cover all their features in one article would be ludicrous.
Instead, I recommend you try this out yourself and see what other things you can add on to make custom environments. There’s plenty of already made boxes that you can use with Vagrant on their site.
I’ve uploaded all the code used in this article on my Github:
And as a bonus I also created a security lab with a kali instance to practice web security and metasploit at this repo.
Happy hacking and thanks for reading.