This is a follow-on from Disposable Cloud Environments With Vagrant and Tailscale. The summary is that I've worked out how to get new boxes up and integrated with Tailscale with a small bootstrap Ansible playbook and some 1Password integration.
This is going to be short and direct, with the goal of showing how to repeat this and never have to think about how to manage secrets. Credit to kaushikchandrashekar/developer-vagrant for shortcutting much of this process with a github project showing Vagrant leveraging Ansible Galaxy.
The source code is available at https://github.com/wsargent/vagrant-tailscale-example.
The Problem
The previous post used Vagrant's inline script to set up Tailscale and everything else:
Vagrant.configure("2") do |config|
config.env.enable
# vm parameters
config.vm.provision "tailscale-install", type: "shell" do |s|
s.inline = "curl -fsSL https://tailscale.com/install.sh | sh"
end
config.vm.provision "tailscale-up", type: "shell" do |s|
s.inline = "tailscale up --ssh --operator=vagrant --authkey #{ENV['TAILSCALE_AUTHKEY']}"
end
# ...yet more script...
end
Inline scripts in Vagrant aren't great. Every Vagrantfile is different, and the failure behavior is unpredictable. In addition, doing the work of op run -- vagrant up
to integrate with 1Password CLI was awkward.
Let's take the opposite approach. Bootstrap Vagrant into a Tailscale host with as little manual work as possible, then provision using Ansible through Tailscale, bypassing Vagrant.
Vagrant with Ansible
The first step was to use the Ansible Provisioner in Vagrant, and put as few things into the Vagrant file as possible.
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/jammy64"
config.vm.hostname = "vagrant-docker"
config.vm.provision :ansible do |ansible|
ansible.compatibility_mode = "2.0"
ansible.playbook = "playbook.yml"
ansible.galaxy_role_file = "requirements.yml"
ansible.galaxy_roles_path = "/etc/ansible/roles"
ansible.galaxy_command = "sudo ansible-galaxy install --role-file=%{role_file} --roles-path=%{roles_path} --force"
end
config.trigger.before :destroy do |trigger|
trigger.run_remote = {inline: "tailscale logout"}
trigger.on_error = :continue
end
end
Python package management is slightly terrifying, so I opted for apt
to install Ansible:
$ sudo add-apt-repository --yes --update ppa:ansible/ansible
$ sudo apt install ansible
Using Ansible Galaxy
There are two Ansible packages I needed to get Tailscale set up: artis3n.tailscale
and community.general.onepassword
.
These can be set up in requirements.yml
:
---
roles:
- name: artis3n.tailscale
collections:
- name: community.general
It's easiest to install these pre-emptively rather than have it pop up in the middle of the install:
$ ansible-galaxy install artis3n.tailscale
$ ansible-galaxy collection install community.general
Running the Playbook
After the packages are installed, the only thing needed in the playbook is to set up the tailscale
role and look up the authkey from 1Password using community.general.onepassword
:
---
- hosts: all
become: true
tasks:
- name: install-tailscale
import_role:
name: artis3n.tailscale
vars:
tailscale_authkey: "{{ lookup('community.general.onepassword', 'vagrant-tailscale', field='credential', vault='will-connect-vault') }}"
tailscale_args: "--ssh"
I did have to go into Tailscale and change the SSH Check mode from action: check
to action: accept
so it didn't keep asking me to click on URLs.
The only thing I need to do is make sure I'm signed into 1Password, and then after that the host will pop up:
$ eval $(op signin) # sign into 1p CLI
$ vagrant up
# ...tailscale status shows new host on tailnet...
Integrating Tailscale with Ansible
From there, it's now a question of how to install software other than Tailscale on the box. Ansible can do it, but first Ansible has to know about it.
The first thing to do is set up dynamic inventory with Tailscale using the Tailscale Inventory Plugin:
$ ansible-galaxy collection install freeformz.ansible
And then the configuration in ansible.cfg
:
[inventory]
enable_plugins = freeformz.ansible.tailscale
[defaults]
inventory = $HOME/tailscale.yaml
remote_user = vagrant
host_key_checking = False
[ssh_connection]
pipelining=true
retries=10
And now the vagrant boxes can get installs the same way that any other host would. For example, to install Docker:
$ ansible-playbook playbooks/docker.yml
Where docker.yml
contains:
- name: Configure with Docker
hosts: vagrant-docker
become: true
tasks:
- name: apt-update
apt:
update_cache: yes
- name: install-docker
import_role:
name: geerlingguy.docker
vars:
docker_edition: 'ce'
docker_package: "docker-"
docker_package_state: present
docker_install_compose: true
docker_users:
- vagrant
Next Steps
I am aware of Ansible Vault but want to use 1Password in part because the next step is to extend it to 1Password Connect for use with Kubernetes and Terraform. With any luck, this should make working with API keys and tokens much easier – copy and paste them into 1Password and be done. And then, just maybe, never think about secrets management again.
Comments