Deploying vault via docker and ansible
Adding to my in-promptu series on automating docker containers with ansible, this time I'm looking at Hashicorp's Vault. This is slightly different, in that it required a binary to be installed on the ansible controller (a raspberry pi). Once vault is deployed, we need to unseal the containter.
As usual, we start by setting up the variables. If this is the first time you have set up vault, the unseal key and root token will not be generated yet. They can be found in the container's logs, once it has been initially deployed.
---
- name: "Vault on Docker Playbook"
hosts: docker.host
become: yes
become_method: sudo
vars:
- container: vault
- domain: 'docker.host'
- ports: "8200"
- unseal_key: UNSEALKEY
- root_token: ROOTTOKEN
- vault_token: ROOTTOKEN
- vault_addr: "http://{{ container }}.{{ domain }}:{{ ports }}"
- openldap_url: "freeipa.host:389"
tasks:
The next stage is to set up the volumes we require. For this, we need to set up three volumes - config, logs and files. This is similar to creating volumes that we have done before, except this time we are using an items loop to create all three.
- name: Create the homepage configuration volume
docker_volume:
name: "{{ container }}-{{ item }}"
with_items:
- config
- logs
- file
tags: volumecreate
Again, as we have done before, we set up the labels to use traefik.
- name: Create Traefik labels's dictionary
set_fact:
my_labels: "{{ my_labels | default({}) | combine ({ item.key : item.value }) }}"
with_items:
- { 'key': 'traefik.enable' , 'value': 'true'}
- { 'key': 'traefik.docker.network', 'value': "traefik-public"}
- { 'key': "traefik.http.middlewares.{{ container }}-https-redirect.redirectscheme.scheme", 'value': "https"}
- { 'key': "traefik.http.routers.{{ container }}-secure.entrypoints",'value': "https"}
- { 'key': "traefik.http.routers.{{ container }}-secure.rule",'value': "Host(`{{ container }}.{{ domain }}`)"}
- { 'key': "traefik.http.routers.{{ container }}-secure.service",'value': "{{ container }}"}
- { 'key': "traefik.http.routers.{{ container }}-secure.tls",'value': "true"}
- { 'key': "traefik.http.routers.{{ container }}.entrypoints",'value': "http"}
- { 'key': "traefik.http.routers.{{ container }}.middlewares",'value': "{{ container }}-https-redirect"}
- { 'key': "traefik.http.routers.{{ container }}.rule",'value': "Host(`{{ container }}.{{ domain }}`)"}
# - { 'key': "traefik.http.services.{{ container }}.loadbalancer.server.port", 'value': "8200"}
- { 'key': "traefik.http.services.{{ container }}.loadbalancer.server.port", 'value': "{{ ports }}"}
Now the labels are set up, next we deploy the container.
At this stage, the container can be deployed, get the root token and unseal token, to populate the playbook. The next stages check require the vault binary. I followed this guide: https://austineosuide.medium.com/installing-hashicorp-vault-on-a-raspberry-pi-4-1b1716f3cf51 These are the steps that were followed:
# Download Hashicorp Vault
wget https://releases.hashicorp.com/vault/1.6.2/vault_1.6.2_linux_arm.zip
# Install unzip & Unzip
unzip vault_1.6.2_linux_arm.zip
# Move vault to /usr/bin
sudo mv vault /usr/bin
# Confirm all good
vault -v
Now that the binary is installed, the next stage is to check if vault is actually running. This is done slightly differently to normal as it is a local_action. This means that it runs on the controller, rather than the controlled device.
- name: Check vault is running
local_action:
module: ansible.builtin.uri
url: "{{ vault_addr }}" # Check Vault URL
return_content: yes # Return Http Response content
validate_certs: no # Not recommended, but a simple hack to avoid SSL checks
status_code: # List of HTTP status code considered to be as a successful request
- 200
Once we know vault is actually running, we need to check if it is actually sealed. This is done by running vault status, again on the controller. This is done in a slightly different way, since I was trying out the ways this could work. This also marks the task as changed when the return code (rc) is set to 2 (as per the docs for the vault binary).
- name: check if vault is sealed
shell: |
vault status
register: vault_status
environment:
VAULT_ADDR: "{{ vault_addr }}"
ignore_errors: True
changed_when:
- vault_status.rc == 2
delegate_to: 127.0.0.1
Lastly, we unseal the vault, if it is actually sealed.
- name: Unseal vault with unseal keys
shell: |
vault operator unseal {{ unseal_key }}
environment:
VAULT_ADDR: "{{ vault_addr }}"
when: vault_status.rc == 2
delegate_to: 127.0.0.1
For completeness, the full code is here:
---
- name: "Vault on Docker Playbook"
hosts: docker.host
become: yes
become_method: sudo
vars:
- container: vault
- domain: 'docker.host'
- ports: "8200"
- unseal_key: UNSEALKEY
- root_token: ROOTTOKEN
- vault_token: ROOTTOKEN
- vault_addr: "http://{{ container }}.{{ domain }}:{{ ports }}"
- openldap_url: "freeipa.host:389"
tasks:
- name: Create the homepage configuration volume
docker_volume:
name: "{{ container }}-{{ item }}"
with_items:
- config
- logs
- file
tags: volumecreate
- name: Create Traefik labels's dictionary
set_fact:
my_labels: "{{ my_labels | default({}) | combine ({ item.key : item.value }) }}"
with_items:
- { 'key': 'traefik.enable' , 'value': 'true'}
- { 'key': 'traefik.docker.network', 'value': "traefik-public"}
- { 'key': "traefik.http.middlewares.{{ container }}-https-redirect.redirectscheme.scheme", 'value': "https"}
- { 'key': "traefik.http.routers.{{ container }}-secure.entrypoints",'value': "https"}
- { 'key': "traefik.http.routers.{{ container }}-secure.rule",'value': "Host(`{{ container }}.{{ domain }}`)"}
- { 'key': "traefik.http.routers.{{ container }}-secure.service",'value': "{{ container }}"}
- { 'key': "traefik.http.routers.{{ container }}-secure.tls",'value': "true"}
- { 'key': "traefik.http.routers.{{ container }}.entrypoints",'value': "http"}
- { 'key': "traefik.http.routers.{{ container }}.middlewares",'value': "{{ container }}-https-redirect"}
- { 'key': "traefik.http.routers.{{ container }}.rule",'value': "Host(`{{ container }}.{{ domain }}`)"}
# - { 'key': "traefik.http.services.{{ container }}.loadbalancer.server.port", 'value': "8200"}
- { 'key': "traefik.http.services.{{ container }}.loadbalancer.server.port", 'value': "{{ ports }}"}
- name: Start Vault and apply labels
docker_container:
name: "{{ container }}"
state: started
# networks:
# - name: traefik-public
image: hashicorp/vault:latest
env:
PUID: "1000"
PGID: "1000"
TZ: "Etc/UTC"
VERSION: "docker"
ports:
- "{{ ports }}:{{ ports }}"
volumes:
- "{{ container }}-config:/vault/config.d"
- "{{ container }}-file:/vault/file"
- "{{ container }}-logs:/vault/logs"
labels: "{{ my_labels }}"
tags: deploycontainer
- name: Check vault is running
local_action:
module: ansible.builtin.uri
url: "{{ vault_addr }}" # Check Vault URL
return_content: yes # Return Http Response content
validate_certs: no # Not recommended, but a simple hack to avoid SSL checks
status_code: # List of HTTP status code considered to be as a successful request
- 200
- name: check if vault is sealed
shell: |
vault status
register: vault_status
environment:
VAULT_ADDR: "{{ vault_addr }}"
ignore_errors: True
changed_when:
- vault_status.rc == 2
delegate_to: 127.0.0.1
- name: Unseal vault with unseal keys
shell: |
vault operator unseal {{ unseal_key }}
environment:
VAULT_ADDR: "{{ vault_addr }}"
when: vault_status.rc == 2
delegate_to: 127.0.0.1
Now you should have a playbook that you can run if you have to restart your vault instance, as well as unseal it remotely.