Qubes OS DevOps automation toolkit: Basic Ansible example
This is an example of Ansible automation that leverages this toolkit to let you manage Qubes VMs. With it, you'll be managing dozens of VMs (as well as any SSH-based remote host) in a fraction of the time it would take to do so by hand or with shell scripts.
Hitting the ground running
Get yourself ready to test this example:
- Create or designate an AppVM where you'll run this example. We'll assume
it will be called
manager
in this text. - Install the
git
and theansible
programs on the TemplateVM of that designatedmanager
VM. In the latest Qubes OS release,sudo dnf install git ansible
from a terminal window would do the trick. - Power off both the
manager
and its Template VM. - Start the
manager
VM. - Open a terminal window on it.
git clone
this project into/home/user/ansible-qubes
.
You're ready to continue with this tutorial. If you would like to get an introduction on Ansible concepts parallel to reading this document, a compare-and-contrast exercise with the Ansible introduction would probably work very well.
Now, let's dive into this example Ansible setup.
Ansible configuration
The starting point is the file ansible.cfg
. This file tells Ansible where
to find the requisite components to make the Qubes automation work. As
you can see, it's composed mostly of paths, and it points you to the
hosts
file.
Importantly, because Ansible will look for the ansible.cfg
file
on your current directory first, that means you will be running your
Ansible commands on the directory containing ansible.cfg
. Later, you can
deploy aliases, symlinks or helpers to help you work around that.
Inventory
The hosts
file is your machine inventory -- the VMs (and also physical
machines) that you own, and how they group together. The included inventory
is almost certainly guaranteed not to match the machines you have, nor how
you've grouped them conceptually, so feel free to edit it how you see fit. To
learn more about managing the inventory, check out the relevant Ansible
documentation.
Two key things to note about the inventory:
- You denote which machines are Qubes VMs by setting the
ansible_connection
property toqubes
on the configuration line specifying the inventory entry. Instead of a host name, you use the VM name to refer to the machine. - You can (of course, if the VM holding these files has network connectivity) mix and match hosts you can manage via SSH into your inventory.
How Ansible knows to connect to your Qubes VMs
Importantly, nothing about this is magic. qubes
in the ansible_connection
parameter merely tells Ansible to use the
Qubes connection plugin
as pointed to by the ansible.cfg
file. That file automatically enlists
the bombshell-client
technology to connect you to your VMs via Ansible.
When bombshell-client
starts, it attempts to connect to the target VM's
qubes.VMShell
service. This is a small Qubes RPC stub that allows a calling
VM to execute any command on the target VM. Of course, all of this is
always subject to the Qubes authorization mechanism, so it's secure.
Time to test drive it!
Add some of your VMs to your inventory now. Let's assume you've added your
work
VM to the inventory, and that you are running this from a manager
VM you've created for the purpose.
Ready?
OK, let's try the following. On the terminal window, type:
ansible work -m shell -a whoami
Qubes OS will ask you for permission to run several shell commands from the
manager
VM to the work
VM. Accept those prompts, and you'll see
something like this:
[user@manager ansible]$ ansible work -m shell -a whoami
work | success | rc=0 >>
user
That's your work
VM responding with I am logged in as user
, which
happens to be the user that Ansible logged into your VM as. The above
command line makes the shell
module (specified in the command line as
-m shell
) execute its arguments (specified with -a
). Ansible has a ton
of execution modules, and they are all documented here:
Connecting to dom0
If you're running a bit ahead of these instructions, you may have noticed
that running ansible dom0 -m shell whoami
actually results in an error.
This is by design from the Qubes team -- the qubes.VMShell
service does
not ship in the dom0
VM at all. If you'd like to be able to manage dom0
from your configuration management system, all you need to do is deploy
the necessary configuration to dom0
.
See the heading Enabling bombshell-client access to dom0 in the top-level
README.md
file of this toolkit for instructions on how to do that.
Running modules as root
You can, however, demand to execute commands as root
wih the parameter -s
:
[user@manager ansible]$ ansible work -s -m shell -a whoami
work | success | rc=0 >>
root
-s
makes the shell
module execute its arguments as root. In fact, it
makes any Ansible module run as root. Ansible has a ton of execution modules, and they
are all documented here:
Note that running as root may require the sudo
package to be installed
first. If you encounter problems getting to root, then please install
the sudo
package on the VM you're targeting (or, more appropriately,
in its template, if it is a template-based VM).
Targeting multiple machines
So far, we've targeted only the work
VM, but technically you can target any
machine or group of machines in your inventory. Based on the (entirely
fictional) inventory shipped in this example:
ansible work:netvm ...
targets two VMsansible appvms ...
targets all VMs in a groupappvms
.ansible 'all:!nonqubes' ...
targets all inventory machines minus the non-Qubes ones.
Playbooks
Everything you've seen so far applies to simple ansible
runs. But the real
worth of Ansible is the possibility to weave repeatable, idempotent scripts
that involve multiple machines, so you're not constantly repeating yourself.
Enter ansible-playbook
,
generously documented there, and exemplified here.
For a quick primer on how to run playbooks, here are the essentials:
ansible-playbook -v <playbook YML file> [-l <only these hosts>]
This would tell ansible-playbook
to run every step of the playbook file
in order, on all the hosts the playbook targets. Should you
want to limit your run to a subset of the hosts, you can use the -l
argument
and pass those hosts, which are in the exact same format as the one
taken by the ansible
command on its hosts specification list.
We ship several different sample playbooks:
test-nofacts.yml
: logs into the specified machines and retrieves variables, but without the fact gathering process, leaving the collected environment in a/tmp/
directory.test-facts.yml
: does the same thing, but collects facts about the targeted hosts before dumping the variables.editors.yml
: deploys some text editors on your template VMs (assumed to be Fedora).qubes-service.yml
: deploys a Qubes service to your template VM, which can later be turned on via the Services tab of the properties window of VMs based on the template. In the example, the service is namedqubes-helloworld
, so that would be the name of the service to add and enable on the Services tab.
More will come as time goes by. For now, that's all. Happy hacking!