Having spend way too much time perfecting my personal development setup, I've reached a point, where it annoys me to not be able to use the muscle memory, keyboard shortcuts and tools I have gotten used to. Like probably most Linux users I started out with a ready-made Linux distribution, I think it was Debian in my case. Over the years I have tried countless distributions, window managers, editors, themes, shells and tools. Some of them stuck with me, others proved not to be what I was looking for.
This "natural selection" has stabilized in some instances, but I still try to optimize workflows where I can and look into new stuff on a regular basis. While I would like to think differently, critics might say I could have spent all the time I tried to fine-tune vim plugins to save a few keystrokes on writing actual code and would have been more productive. This might be true, but let's face it: I had and still have a lot of fun on the way, have learned about things I would never have discovered.
This endless rabbit hole even led me to build my own keyboard and program it with a modified firmware along with learning the colemak keyboard layout, just to name a few.
The plan
While Kali Linux is a great tool for security research and pentesting, I have trying to avoid using it for the reasons above. It's great, but it seemed easier in most cases to just install an AUR package on arch Linux, than to have to work in an uncustomized VM.
This approach is starting to reach its limits. My OS is starting to get full of one-use tools I installed for a particular use-case and forgot to get rid of afterwards. Also, quite a lot of them need root privileges to work properly and I don't feel like running everything on my main installation.
My dotfiles have been managed with Ansible for some time now. In conjunction with Vagrant this seemed like a good solution to create Kali VMs with one command, use them and dispose them to create them fresh if something goes sideways.
I adapted them and created a Vagrantfile
with an Ansible playbook that allows
me to generate and start a fresh Kali VM with one command, including all my
personal customizations. And here is how I did it.
Ingredients
Ansible: Wikipedia says: Ansible is an open-source software provisioning, configuration management, and application-deployment tool. I will be using it to automate all the setup steps I would normally have to do manually on a fresh install to get it in the state I want it.
Vagrant: Wikipedia (again): Vagrant is an open-source software product for building and maintaining portable virtual software development environments.
The setup will be based around the i3 tiling window manager and my dotfiles.
Vagrantfile
Kali has announced officially maintained Images on Vagrant Cloud.
The so called Vagrantfile
defines a VM based on some Image. If you wanted to
just use Kali in its default configuration, you could just start the Vagrant
file provided and be done.
To get it, just create an empty directory and initialize it with vagrant init
This will create a Vagrantfile
that can be used to spawn new VMs with ease.
Vagrant itself doesn't do the virtualization, it only manages it. While multiple
virtualization backends are supported, I opted for the default: VirtualBox.
There are a few basic settings I changed to add a shared folder to the VM, set the amount of RAM it can use and enable the GUI.
# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder.
config.vm.synced_folder ,
# VirtualBox settings
config.vm.provider do
# Display the VirtualBox GUI when booting the machine
vb.gui = true
# Customize the amount of memory on the VM:
vb.memory =
end
Later on, I encountered an error that occurs from time to time and prevents
Ansible from finding the playbook we will add. There is an
issue on their GitHub
page about it and I'm still not sure if it's a proper bug. The solution was to
add this line just before the final end
in the Vagrantfile
.
# Workaround to make Ansible find the playbook
vagrant_synced_folder_default_type =
Now comes the most important customization of the file, adding Ansible
provisioning. The Vagrantfile
is a plain ruby file, but even without knowing
the language it should be mostly self-explanatory.
We use the :ansible_local
build-in provisioning. This allows for Ansible to be
executed inside the VM. That way, you don't have to even have Ansible
installed on your system. The ansible.install = true
takes care of magically
installing it when needed in the VM.
config.vm.provision :ansible_local do
ansible.playbook =
ansible.verbose = true
ansible.install = true
ansible.limit =
ansible.galaxy_role_file =
ansible.galaxy_roles_path =
ansible.become = true
end
If you have worked with Ansible before, you will know what the playbook.yml
and requirements.yml
files are. Here we just instruct Ansible to look for them
in the current directory. Internally Vagrant creates a shared folder and copies
those files to the VM so Ansible can run on it.
And that's it for the Vagrantfile
. We now only have to create our Ansible
project to set everything up. One note about Ansible's inventory: Vagrant will
generate that for us dynamically. The ansible.limit = "all"
directive takes
care of everything else.
Ansible
Let's start with what I already had. To manage my dotfiles on my main OS I have
created already roles that take care of setting up different applications. This
can be added to the roles
list in the playbook and will be executed. If you
want to look into what each of them does, look at my
dotfiles repository.
roles:
- ansible-xresources
- ansible-i3
- ansible-vim
- ansible-xfce4-terminal
- ansible-tmux
- ansible-zsh
- ansible-rofi
These roles only set up the configuration files for my personal user and do not actually install the tools. I kept them that way to make them compatible with different distributions and being able to deploy them on systems where I don't have root privileges to install stuff. For this reason, we need to install the packages that are not in Kali per default.
I added a task with a list of packages to be installed via apt
. It's easy to
add or remove entries here. The list is not that long so putting them in a
separate file didn't make sense at this point.
tasks:
- name: Install missing packages
apt:
pkg:
- i3-wm
- i3lock
- i3status
- zsh
- neovim
- apt-file
- htop
- rofi
- nodejs
- nitrogen
state: latest
update_cache: yes
Since my font of choice, Roboto
Mono is not available in the
repositories I also added a straight forward task to pull it from google and
place it in the correct location. It includes a creates
directive that checks
if the fonts are already present and skips downloading if true.
- name: Install fonts and update font cache
script: install_fonts.sh
args:
creates: /usr/share/fonts/truetype/robotomono
The script just downloads the font files to the correct folder
and updates the font cache. The --content-disposition
enables the use of
"Content-Disposition" headers to describe what the name of a downloaded file
should be.
#!/usr/bin/env bash
# Download the files
# Update font cache
I also set the shell of root
to zsh in a separate task.
- name: Set shell of user root to zsh
user:
name: root
shell: /bin/zsh
The last two tasks are not absolutely necessary but make the experience a bit more enjoyable. I guess you can guess what they do.
- name: Copy wallpaper
copy:
src: wallpaper.png
dest: /usr/share/backgrounds/kali/kali-i3.png
- name: Set wallpaper
copy:
src: nitrogen.cfg
dest: "{{ansible_env.HOME}}/.config/nitrogen/bg-saved.cfg"
While talking about eye-candy I should add, that all my dotfiles are based on
the great base16 color schemes by
chriskemptson. You can find all the supported applications in this
list.
Each color scheme is defined is made up of 16 colors and has it's own repository.
More specifically, the color schemes definitions are defined in yaml
syntax,
which is great because Ansible can use it in its templates directly.
To use a different color scheme just pick one that you like and copy it to the
group_vars/all
file in the Ansible project. I chose onedark,
you can see the result in the screenshot above.
# group_vars/all
---
scheme: "OneDark"
scheme: "base16-onedark"
author: "Lalit Magant (http://github.com/tilal6991)"
base00: "282c34"
base01: "353b45"
...
base0F: "be5046"
Finally there are a few differences between the configuration files I use on my
main installation and the ones inside the VM. For this purpose I added a few
control variables to the vars:
section of the playbook. Setting these values
will change the outcome of the rendered templates.
As an example, I use the i3-gaps fork of i3
on my system, which has some patches applied to it. While there is a Linux Arch
package for it, there is none in the repositories of Kali. The configuration is
mostly compatible, except for the gaps
directives, which are
provided by one of the patches.
In this case the i3_use_gaps
variable is set to false
in the playbook
(default value being true
) explicitly like this:
vars:
- i3_use_gaps: false
- i3_terminal: xfce4-terminal
- i3_use_polybar: false
- i3_optional: false
The relevant parts of my i3 configuration template are fenced with special jinja2 directives, which evaluate those and only include the output between them if the value is true.
gaps inner 6
gaps outer 0
smart_gaps on
smart_borders no_gaps
A note about Ansible Galaxy
Ansible Galaxy provides prepackaged units of
work known to Ansible as roles. Think of it like a sharing platform for Ansible
roles, directly integrated with GitHub. This allows us to specify a
requirements.yml
file with all the roles needed for the playbook. Vagrant also
passes this file to Ansible which in turn automatically downloads them, so they
can be used.
The syntax is pretty straightforward and just defines where to get them from, the name and the protocol to use. Here is an example for the i3 role:
- src: https://github.com/pinpox/ansible-i3.git
name: ansible-i3
scm: git
Give me the VM already!
And that's it. If all that sounded complicated to just create a virtual machine, be relived to know that you now just need one single command to generate the VM:
This command will automatically download the Kali Image, create a VirtualBox machine, download the roles, copy them over, start the machine, execute the Ansible project and greet you with your finished box to log in.
In case you make changes to the playbook after the VM has already been created,
you can run the modified playbook again without the need to create a new machine
with vagrant provision
... Profit?
You can find the final result here with a bit of additional documentation. Be warned that this was created for personal purposes and you might have to adapt it. If you find any bugs or want something else added be free to drop me an issue or send me a pull request.