From 5dd5ea0e693e378ef3ecb5806df21a156532a662 Mon Sep 17 00:00:00 2001 From: "Manuel Amador (Rudd-O)" Date: Mon, 21 Dec 2015 01:45:20 +0000 Subject: [PATCH] added sample Ansible playbooks and documentation --- README.md | 18 +- examples/README.md | 8 + examples/ansible/README.md | 175 ++++++++++++++++++ examples/ansible/ansible.cfg | 9 + examples/ansible/editors.yml | 9 + .../qubes-service/qubes-service.service.j2 | 11 ++ examples/ansible/hosts | 29 +++ examples/ansible/qubes-service.yml | 13 ++ examples/ansible/tasks/dumpenv.yml | 8 + examples/ansible/test-facts.yml | 6 + examples/ansible/test-nofacts.yml | 5 + 11 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/ansible/README.md create mode 100644 examples/ansible/ansible.cfg create mode 100644 examples/ansible/editors.yml create mode 100644 examples/ansible/files/qubes-service/qubes-service.service.j2 create mode 100644 examples/ansible/hosts create mode 100644 examples/ansible/qubes-service.yml create mode 100644 examples/ansible/tasks/dumpenv.yml create mode 100644 examples/ansible/test-facts.yml create mode 100644 examples/ansible/test-nofacts.yml diff --git a/README.md b/README.md index 9eb8a21..d95250e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@ Qubes OS DevOps automation toolkit ================================== -This is a kit of several tools to help you automate your Qubes OS -operations: +This software helps you automate development operations on Qubes OS through +industry-standard configuration management solutions. + +**Do you learn better by example?** Then jump to the directory +[examples/ansible/](examples/ansible/) to get started right away. + +The software in this kit includes the following: 1. A computer program `bombshell-client` that can run in dom0 or in any domU, which uses the `qubes.VMShell` Qubes RPC service @@ -71,6 +76,15 @@ The rsync manpage documents the use of a special form of rsh to connect to remote hosts -- this option can be used with `bombshell-client` to run rsync against other VMs as if they were normal SSH hosts. +Enabling bombshell-client access to dom0 +---------------------------------------- + +`dom0` needs its `qubes.VMShell` service activated. As `root` in `dom0`, +create a file `/etc/qubes-rpc/qubes.VMshell` with mode `0644` and make +sure its contents say `/bin/bash`. + +That's it -- `bombshell-client` should work against dom0 now. + How to use this with automation tools like Ansible and SaltStack ---------------------------------------------------------------- diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..d7eb2c4 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +Qubes OS DevOps automation toolkit: examples +============================================ + +This is the examples directory. Each example within this directory has a +`README.md` file that explains it further. Feel free to use these examples +as templates to get your own automation started. + +* [ansible/](ansible/) contains an example Ansible setup. diff --git a/examples/ansible/README.md b/examples/ansible/README.md new file mode 100644 index 0000000..15f452d --- /dev/null +++ b/examples/ansible/README.md @@ -0,0 +1,175 @@ +Qubes OS DevOps automation toolkit: Ansible example +================================================== + +This is an example of Ansible automation that leverages this toolkit to +let you manage Qubes VMs. These instructions assume that you have already: + +1. cloned this entire project into directory `/home/user/ansible-qubes` +2. installed Ansible (`yum install ansible`) in the TemplateVM of that AppVM +3. opened a terminal window and changed to the directory containing this file + +They also assume a bit of familiarity with Ansible as well. If you would like +to get an introduction on Ansible concepts, this examples directory is ripe +for a compare-and-contrast exercise with the +[Ansible introduction](https://docs.ansible.com/ansible/intro_getting_started.html). + +Now, let's do a quick walkthrough of these files. + +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](https://docs.ansible.com/ansible/intro_inventory.html). + +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 `ansible/connection_plugins/qubes.py` +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: + +* [Intro to modules](https://docs.ansible.com/ansible/modules.html) +* [Module index](https://docs.ansible.com/ansible/modules_by_category.html) + +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 lik 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: + +* [Intro to modules](https://docs.ansible.com/ansible/modules.html) +* [Module index](https://docs.ansible.com/ansible/modules_by_category.html) + +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 possiblity to weave repeatable, idempotent scripts +that involve multiple machines, so you're not constantly repeating yourself. +Enter [`ansible-playbook`](https://docs.ansible.com/ansible/playbooks.html), +generously documented there, and exemplified here. + +For a quick primer on how to run playbooks, here are the essentials: + + ansible-playbook -v [-l ] + +This would tell `ansible-playbook` to run every step of the playbook file +in order, on all the hosts it targets on each `hosts` stanza. 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! diff --git a/examples/ansible/ansible.cfg b/examples/ansible/ansible.cfg new file mode 100644 index 0000000..e97755f --- /dev/null +++ b/examples/ansible/ansible.cfg @@ -0,0 +1,9 @@ +[defaults] +connection_plugins = ../../ansible/connection_plugins +callback_plugins = ./callback_plugins +action_plugins = ./action_plugins +hostfile = ./hosts +hash_behaviour = merge + +[ssh_connection] +pipelining = True diff --git a/examples/ansible/editors.yml b/examples/ansible/editors.yml new file mode 100644 index 0000000..74aa6cd --- /dev/null +++ b/examples/ansible/editors.yml @@ -0,0 +1,9 @@ +- hosts: templatevms + sudo: True + tasks: + - name: deploy some editors + dnf: pkg={{ item }} state=present + with_items: + - emacs + - nano + - vim diff --git a/examples/ansible/files/qubes-service/qubes-service.service.j2 b/examples/ansible/files/qubes-service/qubes-service.service.j2 new file mode 100644 index 0000000..2d06995 --- /dev/null +++ b/examples/ansible/files/qubes-service/qubes-service.service.j2 @@ -0,0 +1,11 @@ +[Unit] +Description={{ description }} +ConditionPathExists=/var/run/qubes-service/qubes-{{ name }} +After=qubes-gui-agent.service + +[Service] +ExecStart=/bin/echo Hello world +StandardOutput=syslog + +[Install] +WantedBy=multi-user.target diff --git a/examples/ansible/hosts b/examples/ansible/hosts new file mode 100644 index 0000000..dcffa76 --- /dev/null +++ b/examples/ansible/hosts @@ -0,0 +1,29 @@ +# Here is where you define your VMs. +fedora-23 ansible_connection=qubes +netvm ansible_connection=qubes +firewallvm ansible_connection=qubes +work ansible_connection=qubes + +# Playbooks involving dom0 will fail unless you set up shell access for it. +# See the README.md file for instructions on how to do that. +dom0 ansible_connection=qubes + +# Provided the VM where you are running Ansible has network connectivity, +# you could mix SSH hosts in here just as well. +pluto ansible_host=example.com ansible_user=root + +[appvms] +work + +[servicevms] +netvm +firewallvm + +[templatevms] +fedora-23 + +[dom0s] +dom0 + +[nonqubes] +pluto diff --git a/examples/ansible/qubes-service.yml b/examples/ansible/qubes-service.yml new file mode 100644 index 0000000..3cef88c --- /dev/null +++ b/examples/ansible/qubes-service.yml @@ -0,0 +1,13 @@ +- hosts: templatevms + sudo: True + vars: + description: Qubes hello world + name: helloworld + tasks: + - name: deploy service file + template: + src: files/qubes-service/qubes-service.service.j2 + dest: /etc/systemd/system/qubes-{{ name }}.service + register: unitfile + - name: enable service file + service: name=qubes-{{ name }}.service enabled=true diff --git a/examples/ansible/tasks/dumpenv.yml b/examples/ansible/tasks/dumpenv.yml new file mode 100644 index 0000000..cb243e6 --- /dev/null +++ b/examples/ansible/tasks/dumpenv.yml @@ -0,0 +1,8 @@ +--- + +- name: Remove previous directory + local_action: file dest=/tmp/dump state=absent +- name: Create directory + local_action: shell mkdir -p /tmp/dump || true +- name: Dump all vars + local_action: template src=files/dumpenv/dumpenv.j2 dest=/tmp/dump/{{ inventory_hostname }} diff --git a/examples/ansible/test-facts.yml b/examples/ansible/test-facts.yml new file mode 100644 index 0000000..6be8ad6 --- /dev/null +++ b/examples/ansible/test-facts.yml @@ -0,0 +1,6 @@ +--- +- hosts: all:!nonqubes + tasks: + - name: cat the contents of file /etc/passwd + shell: head /etc/passwd + - include: tasks/dumpenv.yml diff --git a/examples/ansible/test-nofacts.yml b/examples/ansible/test-nofacts.yml new file mode 100644 index 0000000..6b0a9a2 --- /dev/null +++ b/examples/ansible/test-nofacts.yml @@ -0,0 +1,5 @@ +--- +- hosts: all:!nonqubes + gather_facts: False + tasks: + - include: tasks/dumpenv.yml