A dedicated OpenBao deployment, kept OFF the Kanrisha tape host so a compromise of the tape node can't reach the vault. - Native TLS on the listener (self-signed by default via gen-tls.sh, or a CA-signed cert from a Smallstep CA over ACME) — no Caddy/Let's Encrypt; reached over the LAN, not the public internet. - Integrated raft storage (clean snapshot-based DR). - mlock on (cap_add IPC_LOCK + memlock unlimited + host swapoff in deploy.sh). - Manual unseal by default; optional PKCS#11 HSM auto-unseal. - deploy.sh: Docker install (Alpine/Debian/Alma), self-signed cert, .env seed, swapoff, firewall 8200/tcp, compose up; then prints init/unseal + the KV-v2 + AppRole bootstrap for Kanrisha + the raft-snapshot DR flow. Self-contained (config payload embedded by build.sh). - Registered in automations.sh + the README deployment table. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
openbao
Hardened OpenBao — the tape-encryption key store for Kanrisha. Deliberately separate from the tape host: a compromise of the tape node must not reach the vault, and OpenBao's mlock/TLS/unseal lifecycle is cleaner on its own box.
Unlike the other stacks here there is no Caddy / Let's Encrypt — a secrets store terminates TLS itself and is reached over the LAN, not the public internet.
- Native TLS on the listener — self-signed by default, or a CA-signed cert from your Smallstep CA over ACME.
- Integrated raft storage — snapshot-based DR.
- mlock on — key material never hits swap.
- Manual unseal by default, or PKCS#11 HSM auto-unseal.
⚠️ This vault is the sole recovery path for encrypted tapes. Losing the OpenBao data and the unseal keys/root token loses every encrypted tape. Take raft snapshots and store the unseal material out of band (below).
Required .env values
| Variable | Notes |
|---|---|
OPENBAO_ADDR |
IP/DNS the Kanrisha tape host uses to reach this vault (goes in the cert SAN; you point the daemon at https://$OPENBAO_ADDR:8200). |
OPENBAO_BIND |
Host interface the API binds to. Default all; set the LAN IP to narrow it. |
OPENBAO_TLS_SANS |
Extra SANs for the self-signed cert (deploy.sh adds OPENBAO_ADDR + loopback). |
OPENBAO_TAG |
OpenBao image tag (pin it). |
OPENBAO_HSM_PIN |
Only for PKCS#11 auto-unseal. Leave blank for manual unseal. |
See .env.example for the full list.
Deploy
./automations.sh # Deploy on this host → deploy: openbao
# or, non-interactive:
OPENBAO_ADDR=10.0.0.10 SKIP_PROMPTS=1 bash deployments/openbao/deploy.sh
deploy.sh installs Docker, generates a self-signed TLS cert (if none present),
seeds .env, disables swap (for mlock), firewalls 8200/tcp, and brings the
stack up. OpenBao starts sealed — initialise + unseal once:
docker compose exec -e BAO_ADDR=https://127.0.0.1:8200 openbao \
bao operator init -tls-skip-verify # prints 5 unseal keys + root token
docker compose exec -e BAO_ADDR=https://127.0.0.1:8200 openbao \
bao operator unseal -tls-skip-verify <key> # x3, three different keys
Store the unseal keys + root token out of band — ideally age-encrypted with
your backup recipient (globals/age-pubkey.txt), never on this host.
TLS
-
Self-signed (default):
deploy.shrunsgen-tls.shto create./tls/tls.{crt,key}withOPENBAO_ADDRin the SAN. Handtls.crtto the Kanrisha daemon as[encryption.openbao].openbao_ca_cert. -
Smallstep CA over ACME (option): issue a cert from your
step-caand drop it in./tlsinstead —gen-tls.shthen no-ops. e.g. with thestepclient:step ca certificate "$OPENBAO_ADDR" ./tls/tls.crt ./tls/tls.key \ --provisioner acme --acme https://ca.lan/acme/acme/directory # renew on a timer: step ca renew --daemon ./tls/tls.crt ./tls/tls.keyGive the Kanrisha daemon your Smallstep root as
openbao_ca_cert(then it trusts the vault without-tls-skip-verify).
Auto-unseal (optional)
Default is manual unseal after each restart. For hands-off restarts, enable the
seal "pkcs11" stanza in config.hcl, mount the PKCS#11 module +
device into the openbao service, and set OPENBAO_HSM_PIN in .env.
Bootstrap for Kanrisha
Once unsealed, enable KV v2 + AppRole and seed the domain key with the Kanrisha
bootstrap script (from the Kanrisha repo, deploy/openbao/bootstrap.sh):
BAO_ADDR=https://$OPENBAO_ADDR:8200 BAO_CACERT=./tls/tls.crt \
BAO_TOKEN=<root> bash bootstrap.sh
It enables the kanrisha-tape KV-v2 mount (with effectively-unlimited
max_versions so a key rotation never orphans old tapes), creates the AppRole +
policy, seeds the domain key, and prints the [encryption.openbao] block for the
Kanrisha config. Point the daemon at address = "https://$OPENBAO_ADDR:8200".
Backup / DR
The vault is the sole recovery path for encrypted tapes — back it up:
# Consistent raft snapshot (safe while running):
docker compose exec -e BAO_ADDR=https://127.0.0.1:8200 openbao \
bao operator raft snapshot save -tls-skip-verify /openbao/data/snapshot.snap
# then copy it off-box, age-encrypted:
docker compose cp openbao:/openbao/data/snapshot.snap - | \
age -r "$(cat ../../globals/age-pubkey.txt)" > openbao-$(date +%F).snap.age
Restore: bao operator raft snapshot restore. Snapshots do not contain the
unseal keys — you still need those (and the root token) to unseal a restored
vault, which is why they are stored separately.