Files
automations/deployments/openbao/config.hcl
T
57_WolveandClaude Opus 5 dc9761a668 feat(openbao): serve the web UI, so init/unseal can happen in a browser
config.hcl shipped `ui = false`, so there was no UI to reach even with working
connectivity. That forces init and unseal through `docker compose exec`, which
puts the unseal keys and root token into a server shell's history and
scrollback -- the one place they should never be.

Make it a substitution point driven by OPENBAO_UI, default on. This adds no
exposure: the UI is served on the same listener, and anything that can reach
:8200 can already do everything through the API. What it changes is where the
unseal material is displayed -- the operator's browser instead of the host.

Rendering config.hcl rather than copying it needed three things to be right:

- Precedence. OPENBAO_UI passed to a run wins; otherwise the value .env already
  deploys applies; otherwise the default. Without that, `OPENBAO_UI=0 bash
  deploy.sh` against an existing node would report success and change nothing.
  Env-presence is captured before the ":=" default, so an explicit 0 is
  distinguishable from "not mentioned" and a re-run cannot silently re-enable
  the UI. A value passed this run is written back to .env.
- Reload. A bind-mounted file's CONTENTS are not part of the compose config
  hash, so `up -d` leaves a changed config.hcl unloaded -- the same trap fixed
  in copyparty (76d2a09) and simplex (0f22735). Restart only on an actual
  change, and say loudly that a restart RE-SEALS the vault, because that is not
  a free action on a running one.
- `cmp -s` stays inside an `if` condition; as `cmp -s A B && CONFIG_CHANGED=1`
  it would trip set -e whenever the files matched.

Verified: default renders `ui = true` and seeds .env; an identical re-run
reports no change; .env's 0 is honoured when nothing is passed; a passed 1 beats
.env's 0 and is written through; a non-boolean value dies.

Note for LAN-isolated hosts: the listener publishes on OPENBAO_BIND only, so a
browser on another subnet still cannot reach it. The generated cert already
carries DNS:localhost + IP:127.0.0.1 in its SANs, so
`ssh -L 8200:<bind-addr>:8200` and https://localhost:8200 works against the
existing cert without widening the publish.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 15:01:56 -05:00

57 lines
2.6 KiB
HCL

# OpenBao — hardened, same-LAN tape-encryption key store for Kanrisha.
#
# Native TLS on the listener (no reverse proxy in front), integrated raft
# storage (clean snapshot-based DR), and mlock enabled. This vault is the SOLE
# recovery path for encrypted tapes — losing the OpenBao data AND the unseal
# material loses every encrypted tape. Back it up (raft snapshots) and store the
# unseal keys/root token out of band (see the README + DR runbook).
storage "raft" {
path = "/openbao/data"
node_id = "openbao-1"
}
# TLS-terminating listener. tls.crt/tls.key are self-signed by deploy.sh on first
# run, or a CA-signed pair you drop in ./tls (e.g. issued by your Smallstep CA
# over ACME — see the README). The Kanrisha daemon trusts this via
# [encryption.openbao].ca_cert.
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/openbao/tls/tls.crt"
tls_key_file = "/openbao/tls/tls.key"
# Optional mTLS — require + verify a client cert from the tape host:
# tls_require_and_verify_client_cert = true
# tls_client_ca_file = "/openbao/tls/client-ca.crt"
}
# mlock keeps key material off swap — REQUIRED. Needs cap_add: [IPC_LOCK] +
# ulimits memlock unlimited (docker-compose.yml) and swap disabled on the host.
# Do NOT add `disable_mlock = true`.
# Single-node raft: advertising 127.0.0.1 is fine because a lone node is always
# ACTIVE and never redirects clients (they connect straight to the LAN address).
# For HA / multiple nodes, set BAO_API_ADDR + BAO_CLUSTER_ADDR to each node's
# reachable LAN address in the compose env instead.
api_addr = "https://127.0.0.1:8200"
cluster_addr = "https://127.0.0.1:8201"
# The built-in web UI. Substituted by deploy.sh from OPENBAO_UI. It adds NO new
# exposure: it is served on this same listener, and anything reachable on :8200
# can already do everything through the API. Turning it on is what lets an
# operator initialise/unseal and log in from a browser, so the unseal keys are
# shown in the browser instead of a server shell's scrollback.
ui = @UI@
# ── Auto-unseal (optional) — DEFAULT IS MANUAL UNSEAL ────────────────────────
# With no seal stanza OpenBao starts SEALED and needs `bao operator unseal` (x3)
# after every restart. To auto-unseal against a PKCS#11 HSM/token, uncomment and
# fill this in, mount the PKCS#11 module + device into the container, and set
# OPENBAO_HSM_PIN in .env:
#
# seal "pkcs11" {
# lib = "/usr/lib/softhsm/libsofthsm2.so"
# slot = "0"
# pin = "env://BAO_HSM_PIN"
# key_label = "kanrisha-unseal"
# mechanism = "0x1087" # CKM_AES_GCM
# }