Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
cfff63c
new role init
Alex-Welsh Oct 23, 2025
4daa520
WIP: single node migration working
Alex-Welsh Oct 24, 2025
88f8b4a
Add cluster_addr to vault_raft_config
seunghun1ee Nov 13, 2025
e3943f9
Comment out WIP task
seunghun1ee Nov 13, 2025
854ba14
Fix playbooks attempting to unseal too fast
seunghun1ee Nov 13, 2025
1e3f715
Query leader&followers for HA
seunghun1ee Nov 13, 2025
a6b4bcd
Make Raft migration compatible with HA
seunghun1ee Nov 13, 2025
6b50f46
Fix single node migration
seunghun1ee Dec 3, 2025
0479784
WIP: HA migration to openbao
seunghun1ee Dec 4, 2025
a0eedf2
Simplify vault to openbao steps
seunghun1ee Jan 6, 2026
4e5dd1e
Get new leader from different host
seunghun1ee Jan 6, 2026
6f6ab70
clean up
seunghun1ee Jan 8, 2026
1dd0ba2
Refactor prereq
seunghun1ee Jan 8, 2026
8c90a8f
Clean up default variables
seunghun1ee Jan 9, 2026
91cb4ac
Fix broken owner/group for openbao certs
seunghun1ee Jan 13, 2026
b01c29d
Fix variable on ha migration playbook
seunghun1ee Jan 13, 2026
1ccb9a7
Test serial include_tasks
seunghun1ee Jan 13, 2026
27fb5ea
rename single node playbook
seunghun1ee Jan 13, 2026
35ad1cf
Try using looping task on HA migration
seunghun1ee Jan 13, 2026
1e314df
Test delgation
seunghun1ee Jan 13, 2026
7aea677
Move ansible_host to apply for include_role tasks
seunghun1ee Jan 13, 2026
33a9562
Use hostvar for api address
seunghun1ee Jan 13, 2026
303f51e
Try putting all vars in apply for roles
seunghun1ee Jan 13, 2026
cbe7ca4
Try using playbook wrapper instead of role
seunghun1ee Jan 14, 2026
276895d
Clean up
seunghun1ee Jan 14, 2026
9dfbd23
Set default values for migration_* vars
seunghun1ee Jan 14, 2026
93f5299
Mark mandatory variables
seunghun1ee Jan 14, 2026
edf7b12
Add readme
seunghun1ee Jan 15, 2026
f320f8d
Add single node migration CI
seunghun1ee Jan 16, 2026
184de94
Replace inventory_hostname to ansible_facts.hostname
seunghun1ee Jan 16, 2026
7220f52
Temp: display vault logs at the end
seunghun1ee Jan 16, 2026
e5b653f
Add disable_mlock option for Vault Raft deployment
seunghun1ee Jan 16, 2026
ae8df67
Temp: Display root token and unseal keys
seunghun1ee Jan 16, 2026
d2baca2
Pin Docker tags of Vault and OpenBao
seunghun1ee Jan 16, 2026
684b132
Revert "Add disable_mlock option for Vault Raft deployment"
seunghun1ee Jan 16, 2026
109dd99
Test waiting Vault before unsealing
seunghun1ee Jan 16, 2026
0fa4fba
Temp: get curl result
seunghun1ee Jan 16, 2026
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
11 changes: 11 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
- openbao
- openbao_ha
- vault
- vault_bao_migration
steps:
- name: Github Checkout 🛎
uses: actions/checkout@v4
Expand All @@ -39,7 +40,7 @@
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/TerryHowe/ansible-modules-hashivault@c22434d887f0b8a5ac3ebda710664a027291e71c

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

View workflow job for this annotation

GitHub Actions / lint / Ansible 2.18 lint

yaml[line-length]

Line too long (186 > 160 characters)

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

View workflow job for this annotation

GitHub Actions / lint / Ansible 2.20 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
Expand All @@ -47,3 +48,13 @@
- name: Run integration tests 🧪
run: |
ansible-playbook -i tests/inventory -v tests/test_${{ matrix.type }}.yml -e ansible_python_interpreter=$(which python3)

- name: Display vault logs
run: |
docker logs vault
if: ${{ always() }}

- name: Try curl to Vault
run: |
curl http://localhost:8200/v1/sys/seal-status
if: ${{ always() }}
30 changes: 30 additions & 0 deletions playbooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Vault to OpenBao migration playbook
-----------------------------------
`vault_bao_migration.yml` provides full play of Vault to OpenBao migration route for both single node and HA clustered Vault.

Playbook Variables
------------------

* Mandatory
* `vault_hosts_group`: Name of ansible inventory group that runs Vault

`vault_bao_migration` Role Variables
------------------------------------

This playbook also requires to be used role variables of the role `vault_bao_migration`. Please refer to the README of the role for more information about its variables.

Example usage
-------------
```
- name: Migrate Vault to Openbao
import_playbook: stackhpc.hashicorp.vault_bao_migration
vars:
vault_hosts_group: vault
# These are vault_bao_migration role variables
migration_common_root_token: "{{ secret_store_keys.root_token }}"
migration_common_unseal_keys: "{{ secret_store_keys.keys_base64 }}"
migration_common_cluster_name: prod
migration_vault_config_dir: /opt/kayobe/vault
migration_openbao_config_dir: /opt/kayobe/openbao

```
79 changes: 79 additions & 0 deletions playbooks/ha_migration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
- name: HA Vault to OpenBao migration
hosts: ordered_vault_hosts
serial: 1
any_errors_fatal: true
vars:
# Variable defaults from vault_bao_migration role
migration_vault_docker_name: vault
migration_openbao_docker_name: openbao
migration_openbao_docker_image: "openbao/openbao"
migration_openbao_docker_tag: "2.2.0"
migration_common_bind_addr: "127.0.0.1"
migration_common_api_port: 8200
migration_common_api_addr: "{{ migration_common_bind_addr ~ ':' ~ migration_common_api_port }}"
migration_openbao_registry_url: ""
migration_openbao_registry_username: ""
migration_openbao_registry_password: ""
migration_common_tls_key: ""
migration_common_tls_cert: ""
migration_common_tls_ca: ""
tasks:
- name: Step down from leader is current host is a Raft leader
when: ansible_facts.hostname == _vault_leader
block:
- name: Step down from leader if current host is Vault Raft leader
community.docker.docker_container_exec:
command: |
vault operator step-down
container: "{{ migration_vault_docker_name }}"
env:
VAULT_ADDR: "{{ migration_common_api_addr }}"
VAULT_TOKEN: "{{ migration_common_root_token }}"
VAULT_CAPATH: "{{ '/vault/config/' + migration_common_tls_ca if (migration_common_tls_ca | default(false)) else omit }}"
user: root

- name: Wait for the cluster to set a new leader
ansible.builtin.wait_for:
timeout: 10

- name: Get new leader API address from OpenBao running host
ansible.builtin.uri:
url: "http://127.0.0.1:8200/v1/sys/leader"
register: new_leader_api_addr_query
delegate_to: "{{ groups['ordered_vault_hosts'][0] }}"
vars:
ansible_host: "{{ hostvars[groups['ordered_vault_hosts'][0]].ansible_host }}"

- name: Stop Vault container at current host
community.docker.docker_container:
name: "{{ migration_vault_docker_name }}"
state: stopped
become: true

- name: Apply OpenBao role for current host
ansible.builtin.include_role:
name: stackhpc.hashicorp.openbao
vars:
openbao_cluster_name: "{{ migration_common_cluster_name }}"
openbao_config_dir: "{{ migration_openbao_config_dir }}"
openbao_raft_leaders:
- "{{ (new_leader_api_addr_query.json.leader_address if ansible_facts.hostname == _vault_leader else _vault_leader_api_addr) | urlsplit('hostname') }}"

Check warning on line 61 in playbooks/ha_migration.yml

View workflow job for this annotation

GitHub Actions / lint / Ansible 2.18 lint

yaml[line-length]

Line too long (161 > 160 characters)

Check warning on line 61 in playbooks/ha_migration.yml

View workflow job for this annotation

GitHub Actions / lint / Ansible 2.20 lint

yaml[line-length]

Line too long (161 > 160 characters)
openbao_bind_addr: "{{ migration_common_bind_addr }}"
openbao_api_addr: "{{ migration_common_api_addr }}"
openbao_registry_url: "{{ migration_openbao_registry_url }}"
openbao_registry_username: "{{ migration_openbao_registry_username }}"
openbao_registry_password: "{{ migration_openbao_registry_password }}"
openbao_docker_name: "{{ migration_openbao_docker_name }}"
openbao_docker_image: "{{ migration_openbao_docker_image }}"
openbao_docker_tag: "{{ migration_openbao_docker_tag }}"
openbao_tls_cert: "{{ migration_common_tls_cert }}"
openbao_tls_key: "{{ migration_common_tls_key }}"
openbao_tls_ca: "{{ migration_common_tls_ca }}"

- name: Unseal OpenBao at current host
ansible.builtin.include_role:
name: stackhpc.hashicorp.vault_unseal
vars:
vault_api_addr: "{{ migration_common_api_addr }}"
vault_unseal_keys: "{{ migration_common_unseal_keys }}"
38 changes: 38 additions & 0 deletions playbooks/vault_bao_migration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
- name: Hashicorp Vault to OpenBao migration

Check failure on line 2 in playbooks/vault_bao_migration.yml

View workflow job for this annotation

GitHub Actions / lint / Ansible 2.18 lint

syntax-check[specific]

The field 'hosts' has an invalid value, which includes an undefined variable.. 'vault_hosts_group' is undefined
gather_facts: true
hosts: "{{ groups[vault_hosts_group] }}"

Check failure on line 4 in playbooks/vault_bao_migration.yml

View workflow job for this annotation

GitHub Actions / lint / Ansible 2.20 lint

syntax-check[specific]

Error processing keyword 'hosts': 'vault_hosts_group' is undefined
tasks:
- name: test
debug:
var: migration_common_unseal_keys

- name: test2
debug:
var: migration_common_root_token

- name: Prerequesites
ansible.builtin.include_role:
name: vault_bao_migration
tasks_from: prereqs

- name: Migrate storage to Raft
ansible.builtin.include_role:
name: vault_bao_migration
tasks_from: raft

- name: Single Node Migration
ansible.builtin.include_role:
name: vault_bao_migration
tasks_from: single_node_migration
when: _vault_followers | length == 0

- name: HA migration prerequesites
ansible.builtin.include_role:
name: vault_bao_migration
tasks_from: ha_prereqs
when: _vault_followers | length > 0

- name: HA cluster migration
ansible.builtin.import_playbook: ha_migration.yml
when: _vault_followers | length > 0
78 changes: 78 additions & 0 deletions roles/vault_bao_migration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
This role provides set of tasks that can be used when migrating Hashicorp Vault to OpenBao.
A playbook that provides ready-to-use migration path can be found at `playbooks/vault_bao_migration.yml`
or `stackhpc.hashicorp.vault_bao_migration` with import_playbook task.

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`.

Migration Constraints
---------------------

* Vault version 1.14.1 (OSS)
* OpenBao version 2.2.0
* Shamir unseal (no auto-unseal)

These contraints are based on OpenBao's guide [In-Place Migration from Vault CE](https://openbao.org/docs/guides/migration/)

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

* Common variables
* Mandatory
* `migration_common_root_token`: Root token string of current Vault cluster
* `migration_common_unseal_keys`: List of unseal key shards.
* Optional
* `migration_common_cluster_name`: Name of the current Vault cluster e.g. "prod_cluater" (default: "")
* `migration_common_bind_addr`: Which IP address should OpenBao bind to (default: "127.0.0.1")
* `migration_common_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")
* `migration_common_init_addr`: OpenBao init addr (used only for initialisation purposes) - full URL including protocol and port (default: "http://127.0.0.1:8200")
* `migration_common_tls_key`: Path to TLS key to use by Vault and OpenBao
* `migration_common_tls_cert`: Path to TLS cert to use by Vault and OpenBao
* `migration_common_tls_ca`: Path to TLS CA certificate that can be used by peers to validate the leaders TLS

* Hashicorp Vault
* Mandatory
* `migration_vault_config_dir`: Directory where current Vault configuration is mounted
* Optional
* `migration_consul_docker_name`: The name of Consul container that is running (default: "consul")
* `migration_consul_bind_port`: The port that is currently used by Consul (default: "8500")
* `migration_vault_docker_name`: The name of Vault container that is running (default: "vault")
* `migration_vault_docker_image`: Docker image for Vault (default: "hashicorp/vault")
* `migration_vault_docker_tag`: Docker image tag for Vault (default: "1.14.1)
* `migration_vault_extra_volumes`: List of `"<host_location>:<container_mountpoint>"` that were configured to current Vault cluster
* `migration_vault_container_options.etc_hosts`: Dict; `{<hostname>:<ip_address>}` to be added to container /etc/host

* OpenBao
* Mandatory
* `migration_openbao_config_dir`: Directory into which to bind mount OpenBao configuration
* Optional
* `migration_openbao_registry_url`: Address of the Docker registry used to authenticate (default: "")
* `migration_openbao_registry_username`: Username used to authenticate with the Docker registry (default: "")
* `migration_openbao_registry_password`: Password used to authenticate with the Docker registry (default: "")
* `migration_openbao_docker_name`: Docker - under which name to run the OpenBao image (default: "bao")
* `migration_openbao_docker_image`: Docker image for OpenBao (default: "openbao/openbao")
* `migration_openbao_docker_tag`: Docker image tag for OpenBao (default: "2.2.0")
* `migration_openbao_write_keys_file_host`: Host on which to write root token and unseal keys. (default: `localhost`)
* `migration_openbao_write_keys_file_path`: Path of file to write root token and unseal keys. (default: `bao-keys.json`)
* `migration_openbao_enable_ui`: Whether to enable user interface that could be accessed from the `openbao_api_addr`. Default `false`

Example use with `vault_bao_migration` playbook
-----------------------------------------------
```
- name: Migrate Vault to Openbao
import_playbook: stackhpc.hashicorp.vault_bao_migration
vars:
vault_hosts_group: vault
migration_common_root_token: "{{ secret_store_keys.root_token }}"
migration_common_unseal_keys: "{{ secret_store_keys.keys_base64 }}"
migration_common_cluster_name: prod
migration_vault_config_dir: /opt/kayobe/vault
migration_openbao_config_dir: /opt/kayobe/openbao

```
115 changes: 115 additions & 0 deletions roles/vault_bao_migration/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
### Common variables for shared configuration between Vault and OpenBao
migration_common_cluster_name: "" # required
migration_common_tls_key: ""
migration_common_tls_cert: ""
migration_common_tls_ca: ""

migration_common_protocol: "{{ 'https' if migration_common_tls_key and migration_common_tls_cert else 'http' }}"

migration_common_api_addr: "{{ migration_common_bind_addr ~ ':' ~ migration_common_api_port }}"
migration_common_bind_addr: "127.0.0.1"
migration_common_init_addr: "{{ migration_common_api_addr }}"
migration_common_cluster_addr: "{{ migration_common_bind_addr ~ ':' ~ migration_common_cluster_port }}"

migration_common_api_port: 8200
migration_common_cluster_port: 8201

migration_common_root_token: "" # required
migration_common_unseal_keys: [] # required

### Vault specific variables that are needed during Raft migration
migration_consul_docker_name: "consul"
migration_consul_bind_port: 8500

migration_vault_docker_name: "vault"
migration_vault_docker_image: "hashicorp/vault"
migration_vault_docker_tag: "1.14.1"

migration_vault_config_dir: "" # required

# Staging directory for Vault migration
migration_vault_staging_dir_path: /var/lib/vault-migration

# Docker volumes
# Default volume mapping
_vault_default_volumes:
- "{{ migration_vault_config_dir }}:/vault/config"
- "vault_file:/vault/file"
- "vault_logs:/vault/logs"

# Exposed for playbooks to access later
migration_vault_extra_volumes: []

# Combined volume lists
_vault_volumes: "{{ _vault_default_volumes + migration_vault_extra_volumes }}"

# For checking leader host and follower hosts
# It is recommened not to override these variables
_vault_leader: ""
_vault_leader_api_addr: ""
_vault_followers: []

migration_vault_raft_config: >
{
"cluster_name": "{{ migration_common_cluster_name }}",
"ui": true,
"api_addr": "{{ migration_common_api_addr }}",
"cluster_addr": "{{ migration_common_protocol }}://{{ migration_common_cluster_addr }}",
"listener": [{
"tcp": {
"address": "{{ migration_common_bind_addr }}:8200",
{% if migration_common_tls_key and migration_common_tls_cert %}
"tls_min_version": "tls12",
"tls_key_file": "/vault/config/{{ migration_common_tls_key }}",
"tls_cert_file": "/vault/config/{{ migration_common_tls_cert }}"
{% else %}
"tls_disable": "true"
{% endif %}
}{% if migration_common_bind_addr != '127.0.0.1' %},
},
{
"tcp": {
"address": "127.0.0.1:8200",
"tls_disable": "true"
}
{% endif %}
}],
"storage": {
"raft": {
"path": "/vault/file/",
"node_id": "raft_{{ ansible_facts.hostname }}"{% if _vault_followers | length > 0 %},
"retry_join": {
"leader_api_addr": "{{ _vault_leader_api_addr }}"{% if migration_common_tls_ca %},
"leader_ca_cert_file": "/vault/config/{{ migration_common_tls_ca }}"
{% endif %}
}
{% endif %}
}
},
"telemetry": {
"prometheus_retention_time": "30s",
"disable_hostname": true
}
}

# Vault Docker options for when restarting Vault container with Raft
migration_vault_container_options: {}

### OpenBao specific variables that are needed when migrating to OpenBao
migration_openbao_registry_url: ""
migration_openbao_registry_username: ""
migration_openbao_registry_password: ""

migration_openbao_docker_name: "openbao"
migration_openbao_docker_image: "openbao/openbao"
migration_openbao_docker_tag: "2.2.0"

migration_openbao_config_dir: "" # required

# Host on which to write root token and unseal keys.
migration_openbao_write_keys_file_host: localhost
# Path of file to write root token and unseal keys.
migration_openbao_write_keys_file_path: bao-keys.json

migration_openbao_enable_ui: false
6 changes: 6 additions & 0 deletions roles/vault_bao_migration/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
Loading
Loading