OpenBao - With Forgejo and Ansible Automation Platform
OpenBao System Architecture and Operations Reference
1. Core Architecture
OpenBao functions as an API-based request router. All interactions are HTTP API calls to specific paths.
- Prefix Routing: The first path segment defines the handling engine. Unmounted prefixes drop requests (404).
- KV-v2 Data Split: Applications access secrets at
secret/data/foo. Administrators manage configuration/versions atsecret/metadata/foo. - Engines & Mounts: Engines are pre-compiled plugins (
kv,database,approle). Mounts are active deployments of engines to API paths. - Built-in Paths: *
sys/: Runtime configuration.auth/: Authentication endpoints.identity/: Entity mapping.cubbyhole/: Ephemeral, token-scoped storage.
Setting up OpenBao (the community-maintained fork of HashiCorp Vault) is very similar to the original Vault setup, but with a few key “Baoish” quirks regarding binary names and environment variables.
Here is a guide to getting a basic instance running.
Installation
1. Package installation (CentOS/RHEL)
# Add EPEL for dependencies
sudo dnf install -y epel-release
# Install OpenBao (replace with actual repo/package name)
sudo dnf install -y openbao
2. Setup (Linux)
For a persistent installation, you’ll want to use a configuration file (.hcl) and a storage backend (we use the local filesystem for simplicity). The service will run as openbao user.
The Configuration (/etc/openbao.d/openbao.hcl)
# /etc/openbao.d/openbao.hcl
# Full configuration options can be found at https://openbao.org/docs/configuration
ui = true
# use the local host filesystem as its database backend, rather than a distributed system like Raft, Consul, or PostgreSQL
# no HA needed for my setup
storage "file" {
path = "/var/lib/openbao/data"
}
# enable auditing
audit "file" "file" {
description = "Local file audit log"
options = {
file_path = "/var/log/openbao_audit.log"
}
}
# HTTPS listener
# self-signed
# don't forget to use export BAO_SKIP_VERIFY=true
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/etc/openbao.d/tls/tls.crt"
tls_key_file = "/etc/openbao.d/tls/tls.key"
}
3. Initialization & Unsealing
Once the service is running, OpenBao starts in a Sealed state (encrypted). You must initialize it once to get your “Unseal Keys.”
Initialize:
export BAO_ADDR='http://127.0.0.1:8200' bao operator initCrucial: Save the Unseal Keys and the Initial Root Token somewhere extremely secure. If you lose these, your data is gone forever.
Unseal: Run this command x (defined by the
initial_sharesvalue from the initialization output) times (using x different keys from the list you just generated):bao operator unsealLogin:
bao login [YOUR_ROOT_TOKEN]
Key Differences from Vault
- The Command: Use
baoinstead ofvault. (Though many distributions include avaultalias for compatibility). - The Environment Variables: Usually prefixed with
BAO_(e.g.,BAO_ADDR,BAO_TOKEN), though it often respectsVAULT_vars for migration ease. - Open Source: Unlike Vault, features like Namespaces (multi-tenancy) are included for free in OpenBao.
OpenBao Configuration & Usage
1. System Configuration & Audit
Here we set up a file-based audit log and verify that API calls are being logged correctly.
# Prepare log file
sudo touch /var/log/openbao_audit.log
sudo chown openbao:openbao /var/log/openbao_audit.log
# Append to /etc/openbao.d/openbao.hcl
audit "file" "file" {
options = { file_path = "/var/log/openbao_audit.log" }
}
# Apply and unseal (I have 2 initial_shares, so I run this twice)
sudo systemctl restart openbao
bao operator unseal
bao operator unseal
# Verify audit log
bao token lookup
tail -n 1 /var/log/openbao_audit.log | jq
2. Access Control (Policies)
We create an admin user because we don’t work with root all the time. Policies map capabilities to paths. Default is deny.
# Create admin policy
cat <<EOF > foo-admin.hcl
path "auth/*" { capabilities = ["create", "read", "update", "delete", "list", "sudo"] }
path "sys/auth/*" { capabilities = ["create", "update", "delete", "sudo"] }
path "sys/auth" { capabilities = ["read"] }
path "sys/policies/acl/*" { capabilities = ["create", "read", "update", "delete", "list", "sudo"] }
path "sys/policies/acl" { capabilities = ["list"] }
path "sys/mounts/*" { capabilities = ["create", "read", "update", "delete", "list", "sudo"] }
path "sys/mounts" { capabilities = ["read"] }
path "secret/*" { capabilities = ["create", "read", "update", "delete", "list", "sudo"] }
EOF
bao policy write foo-admin foo-admin.hcl
# Create machine least-privilege policy (KV-v2 data path)
cat <<EOF > foo-app.hcl
path "secret/data/foo/*" { capabilities = ["read"] }
EOF
bao policy write foo-app foo-app.hcl
3. Authentication (Human & Machine)
Human (Userpass):
bao auth enable userpass
bao write auth/userpass/users/example-user password="bar-password" policies="foo-admin"
bao login -method=userpass username=example-user
Machine (AppRole):
bao auth enable approle
# Create role mapped to policy
bao write auth/approle/role/foo-role token_policies="foo-app" token_ttl=1h token_max_ttl=4h
# Retrieve credentials for CI/CD injection
bao read auth/approle/role/foo-role/role-id
bao write -f auth/approle/role/foo-role/secret-id
4. Secrets Management (KV-v2)
Write to standard path, read from /data/ path.
bao kv put secret/foo/bar db_user="example-admin" db_pass="bar-password"
5. Ansible Integration
Local Requirements & Execution:
sudo dnf install python3-hvac
ansible-galaxy collection install community.hashi_vault
export VAULT_ADDR="https://127.0.0.1:8200"
export VAULT_AUTH_METHOD="approle"
export VAULT_ROLE_ID="<foo-role-id>"
export VAULT_SECRET_ID="<foo-secret-id>"
ansible-playbook foo-playbook.yml
Ansible Automation Platform (AAP) Configuration: Create a Custom Credential Type in the AAP UI.
Input Configuration (YAML):
fields:
- id: bao_url
type: string
label: OpenBao Server URL
- id: role_id
type: string
label: AppRole Role ID
- id: secret_id
type: string
label: AppRole Secret ID
secret: true
Injector Configuration (YAML):
env:
VAULT_ADDR: '{{ bao_url }}'
VAULT_AUTH_METHOD: approle
VAULT_ROLE_ID: '{{ role_id }}'
VAULT_SECRET_ID: '{{ secret_id }}'
Playbook Syntax:
tasks:
- name: Fetch secret
ansible.builtin.set_fact:
db_pass: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/data/foo/bar:db_pass') }}"
6. Forgejo / GitHub Actions Integration
Use repository secrets for VAULT_ROLE_ID and VAULT_SECRET_ID. If the runner executes in a Docker container, the Vault URL must point to the host via the Docker bridge gateway (172.17.0.1).
steps:
- name: Fetch Secrets
uses: hashicorp/vault-action@v3
with:
url: https://127.0.0.1:8200
method: approle
roleId: ${{ secrets.VAULT_ROLE_ID }}
secretId: ${{ secrets.VAULT_SECRET_ID }}
secrets: |
secret/data/foo/bar db_pass | DB_PASS_VAR ;