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:

  1. Create or designate an AppVM where you'll run this example. We'll assume it will be called manager in this text.
  2. Install the git and the ansible programs on the TemplateVM of that designated manager VM. In the latest Qubes OS release, sudo dnf install git ansible from a terminal window would do the trick.
  3. Power off both the manager and its Template VM.
  4. Start the manager VM.
  5. Open a terminal window on it.
  6. 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:

  1. You denote which machines are Qubes VMs by setting the ansible_connection property to qubes on the configuration line specifying the inventory entry. Instead of a host name, you use the VM name to refer to the machine.
  2. 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 VMs
  • ansible appvms ... targets all VMs in a group appvms.
  • 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 named qubes-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!