Skip to content

feat: add openbao role #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
exclude:
- python_version: "3.9"
ansible_version: "2.18"
type:
- openbao
- vault
steps:
- name: Github Checkout 🛎
uses: actions/checkout@v4
Expand All @@ -35,11 +38,11 @@
run: |
pipx uninstall ansible-core
python3 -m pip install --upgrade pip
python3 -m pip install ansible-core==${{ matrix.ansible_version }}.* docker git+https://github.com/stackhpc/ansible-modules-hashivault@main

Check warning on line 41 in .github/workflows/pull_request.yml

View workflow job for this annotation

GitHub Actions / lint / Ansible 2.14 lint

yaml[line-length]

Line too long (186 > 160 characters)
ansible-galaxy collection build
ansible-galaxy collection install *.tar.gz
ansible-galaxy collection install community.general

- name: Run integration tests 🧪
run: |
ansible-playbook -i tests/inventory -v tests/*.yml -e ansible_python_interpreter=$(which python3)
ansible-playbook -i tests/inventory -v tests/test_${{ matrix.type }}.yml -e ansible_python_interpreter=$(which python3)
4 changes: 3 additions & 1 deletion galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace: stackhpc
name: hashicorp
description: >
Hashicorp Vault/Consul deployment and configuration
version: "2.5.1"
version: "2.6.0"
readme: "README.md"
authors:
- "Michał Nasiadka"
Expand All @@ -11,6 +11,7 @@ authors:
- "Pierre Riteau"
- "Bartosz Bezak"
- "Kyle Dean"
- "Jack Hodgkiss"
dependencies:
"community.docker": "*"
license:
Expand All @@ -21,4 +22,5 @@ tags:
- infrastructure
- security
- vault
- openbao
repository: "https://github.com/stackhpc/ansible-collection-hashicorp"
129 changes: 129 additions & 0 deletions roles/openbao/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
This role deploys and initializes OpenBao with a Raft backend.

Requirements
------------

`ansible-modules-hashivault` Python package installed on the Ansible control host
`hvac` Python package installed on the remote hosts

Note that since version `4.6.4`, `ansible-modules-hashivault` requires
`ansible>4`.

Role variables
--------------

* Common variables
* Optional
* `openbao_registry_url`: Address of the Docker registry used to authenticate (default: "")
* `openbao_registry_username`: Username used to authenticate with the Docker registry (default: "")
* `openbao_registry_password`: Password used to authenticate with the Docker registry (default: "")

* OpenBao
* Mandatory
* `openbao_cluster_name`: OpenBao cluster name (e.g. "prod_cluster")
* `openbao_config_dir`: Directory into which to bind mount OpenBao configuration
* Optional
* `openbao_bind_address`: Which IP address should OpenBao bind to (default: "127.0.0.1")
* `openbao_api_addr`: OpenBao [API addr](https://openbao.org/docs/configuration/#high-availability-parameters) - Full URL including protocol and port (default: "http://127.0.0.1:8200")
* `openbao_init_addr`: OpenBao init addr (used only for initialisation purposes) - full URL including protocol and port (default: "http://127.0.0.1:8200")
* `openbao_docker_name`: Docker - under which name to run the OpenBao image (default: "bao")
* `openbao_docker_image`: Docker image for OpenBao (default: "openbao/openbao")
* `openbao_docker_tag`: Docker image tag for OpenBao (default: "latest")
* `openbao_extra_volumes`: List of `"<host_location>:<container_mountpoint>"`
* `openbao_ca_cert`: Path to CA certificate used to verify OpenBao server TLS cert
* `openbao_tls_key`: Path to TLS key to use by OpenBao
* `openbao_tls_cert`: Path to TLS cert to use by OpenBao
* `openbao_log_keys`: Whether to log the root token and unseal keys in the Ansible output. Default `false`
* `openbao_set_keys_fact`: Whether to set a `openbao_keys` fact containing the root token and unseal keys. Default `false`
* `openbao_write_keys_file`: Whether to write the root token and unseal keys to a file. Default `false`
* `openbao_write_keys_file_host`: Host on which to write root token and unseal keys. Default `localhost`
* `openbao_write_keys_file_path`: Path of file to write root token and unseal keys. Default `bao-keys.json`

Root and unseal keys
--------------------

After OpenBao has been initialised, a root token and a set of unseal keys are emitted.
It is very important to store these keys safely and securely.
This role provides several mechanisms for extracting the root token and unseal keys:

1. Print to Ansible log output (`openbao_log_keys`)
1. Set a `openbao_keys` fact (`openbao_set_keys_fact`)
1. Write to a file (`openbao_write_keys_file`)

In each case, the output will contain the following:

```json
{
"keys": [
"...",
"..."
],
"keys_base64": [
"...",
"..."
],
"root_token": "..."
}
```

Example playbook (used with OpenStack Kayobe)
---------------------------------------------

```
---
- name: Prepare for OpenBao role
any_errors_fatal: True
gather_facts: True
hosts: consul
tasks:
- name: Ensure /opt/kayobe/bao exists
file:
path: /opt/kayobe/bao
state: directory

- name: Template out tls key and cert
vars:
tls_files:
- content: "{{ secrets_external_tls_cert }}"
dest: "tls.cert"
- content: "{{ secrets_external_tls_key }}"
dest: "tls.key"
copy:
content: "{{ item.content }}"
dest: "/opt/kayobe/bao/{{ item.dest }}"
owner: 100
group: 1001
mode: 0600
loop: "{{ tls_files }}"
no_log: True
become: true

- name: Run OpenBao role
any_errors_fatal: True
gather_facts: True
hosts: consul
roles:
- role: stackhpc.hashicorp.openbao
openbao_bind_address: "{{ external_net_ips[inventory_hostname] }}"
openbao_api_addr: "https://{{ external_net_fqdn }}:8200"
openbao_config_dir: "/opt/kayobe/bao"
```

Example post-config playbook to enable secrets engines:
```
---
- name: OpenBao post deployment config
any_errors_fatal: True
gather_facts: True
hosts: bao
tasks:
- name: Enable bao secrets engines
hashivault_secret_engine:
url: "https://vault.example.com:8200"
token: "{{ secrets_openbao_keys.root_token }}"
name: pki
backend: pki
run_once: True
```

NOTE: secrets_external_tls_cert/key are variables in Kayobe's secrets.yml
86 changes: 86 additions & 0 deletions roles/openbao/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
openbao_registry_url: ""
openbao_registry_username: ""
openbao_registry_password: ""

openbao_docker_name: "bao"
openbao_docker_image: "openbao/openbao"
openbao_docker_tag: "latest"

openbao_cluster_name: ""
openbao_protocol: "{{ 'https' if openbao_tls_key and openbao_tls_cert else 'http' }}"
# Allow openbao_vip_url and openbao_vip_address for backwards compatibility.
openbao_vip_address: "{{ openbao_vip_url | default(openbao_bind_address) }}"
openbao_api_addr: "{{ openbao_protocol ~ '://' ~ openbao_vip_address ~ ':8200' }}"
openbao_bind_address: "127.0.0.1"
openbao_init_addr: "http://127.0.0.1:8200"
openbao_tls_key: ""
openbao_tls_cert: ""

openbao_config_dir: ""

openbao_config: >
{
"cluster_name": "{{ openbao_cluster_name }}",
"ui": false,
"api_addr": "{{ openbao_api_addr }}",
"cluster_addr": "http://127.0.0.1:8201",
"listener": [{
"tcp": {
"address": "{{ openbao_bind_address }}:8200",
{% if openbao_tls_key and openbao_tls_cert %}
"tls_min_version": "tls12",
"tls_key_file": "/bao/config/{{ openbao_tls_key }}",
"tls_cert_file": "/bao/config/{{ openbao_tls_cert }}"
{% else %}
"tls_disable": "true"
{% endif %}
}{% if openbao_bind_address != '127.0.0.1' %},
},
{
"tcp": {
"address": "127.0.0.1:8200",
"tls_disable": "true"
}
{% endif %}
}],
"storage": {
"raft": {
"node_id": "raft_{{ ansible_facts.nodename }}",
"path": "/openbao/file"
}
},
"telemetry": {
"prometheus_retention_time": "30s",
"disable_hostname": true
}
}

# Docker options
openbao_container: {}

# Docker volumes
# Default volume mapping
_openbao_default_volumes:
- "{{ openbao_config_dir | default('openbao_config', true) }}:/openbao/config"
- "openbao_file:/openbao/file"
- "openbao_logs:/openbao/logs"

# Exposed for playbooks to access later
openbao_extra_volumes: []

# Combined volume lists
_openbao_volumes: "{{ _openbao_default_volumes + openbao_extra_volumes }}"

# Whether to log the root token and unseal keys in the Ansible output.
openbao_log_keys: false

# Whether to set a openbao_keys fact containing the root token and unseal keys.
openbao_set_keys_fact: false

# Whether to write the root token and unseal keys to a file.
openbao_write_keys_file: false
# Host on which to write root token and unseal keys.
openbao_write_keys_file_host: localhost
# Path of file to write root token and unseal keys.
openbao_write_keys_file_path: bao-keys.json
6 changes: 6 additions & 0 deletions roles/openbao/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
# WORKAROUND: Without this, we see the following in some setups:
# ERROR! couldn't resolve module/action 'hashivault_unseal'. This often indicates a misspelling, missing collection, or incorrect module path.
# Seen using Kayobe on Ansible 2.10.17, running modules on a remote host.
collections:
- community.docker
6 changes: 6 additions & 0 deletions roles/openbao/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- name: "Vault Prerequisites"
import_tasks: prereqs.yml

- name: "Deploy OpenBao"
import_tasks: openbao.yml
59 changes: 59 additions & 0 deletions roles/openbao/tasks/openbao.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
- name: Ensure OpenBao container is running
docker_container:
name: "{{ openbao_docker_name }}"
image: "{{ openbao_docker_image }}:{{ openbao_docker_tag }}"
network_mode: host
etc_hosts: "{{ openbao_container.etc_hosts | default(omit) }}"
capabilities: IPC_LOCK
volumes: "{{ _openbao_volumes }}"
comparisons:
'*': strict
restart_policy: "always"
env:
BAO_LOCAL_CONFIG: "{{ openbao_config | to_json }}"
command: >
server
become: true

- name: Check if OpenBao is initialized
uri:
url: "{{ openbao_init_addr }}/v1/sys/init"
register: openbao_init_status
retries: 50
delay: 1
run_once: true
until: openbao_init_status.status == 200

- name: "Initialize OpenBao"
run_once: true
when:
- not openbao_init_status.json.initialized
block:
- name: Initialize OpenBao
hashivault_init:
url: "{{ openbao_init_addr }}"
ca_cert: "{{ openbao_ca_cert | default(omit) }}"
no_log: true
register: openbao_keys_result

- name: Print OpenBao keys
debug:
var: openbao_keys_result
when:
- openbao_log_keys | bool

- name: Set openbao_keys fact
set_fact:
openbao_keys: "{{ openbao_keys_result }}"
when:
- openbao_set_keys_fact | bool

- name: Write OpenBao keys to a file
copy:
content: "{{ openbao_keys_result | to_nice_json }}"
dest: "{{ openbao_write_keys_file_path }}"
mode: "0600"
delegate_to: "{{ openbao_write_keys_file_host }}"
when:
- openbao_write_keys_file | bool
8 changes: 8 additions & 0 deletions roles/openbao/tasks/prereqs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
- name: Log into Docker registry
docker_login:
registry: "{{ openbao_registry_url }}"
username: "{{ openbao_registry_username }}"
password: "{{ openbao_registry_password }}"
when: openbao_registry_username | length > 0
become: true
3 changes: 3 additions & 0 deletions tests/inventory
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[consul]
localhost ansible_connection=local

[openbao]
localhost ansible_connection=local
Loading