dc9761a66867dcf54dffe6ef863a2cad8cfaad51
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 ( |
||
|
|
a02524ae6e |
fix(openbao): detect the account the entrypoint drops to, not the probe's
The image's entrypoint starts as root and then runs `su-exec openbao "$@"` before exec'ing the server. deploy.sh probed with `--entrypoint id -u`, which BYPASSES the entrypoint, so it reported UID 0 and chowned config.hcl, tls/ and the raft volume to root -- while the server ran as uid 100 and could write none of it: error initializing storage of type raft: failed to create fsm: failed to open bolt file: open /openbao/data/vault.db: permission denied The container crash-looped on that, and tls.key (0600 root:root) would have failed the listener straight afterwards. Confirmed on the affected host: the probe reports 0, `id -u openbao` reports 100, and the entrypoint's line 92 is `set -- su-exec openbao "$@"`. Ask for the account the entrypoint switches to, falling back to the old probe when the image has no such account (then the server really does run as whatever the entrypoint started as). Preferring the service account is also the safe direction to be wrong in: root ignores file permissions, so chowning to the unprivileged uid still works if the server turns out to run as root, whereas the reverse is fatal. Gate the raft chown on the volume's ACTUAL ownership rather than on a first-run flag. The flag was false forever after the first deploy, so a volume left root-owned by an earlier run -- exactly the state this bug created -- could never be repaired by re-running; the operator had to chown it by hand. Reading the owner costs one container start and still keeps the recursive chown off a healthy live raft dir. FIRST_RUN is now unused, so it is gone. Also from the same run, two reporting failures: - `docker compose ps` printed "Restarting (1)" and the script went on to print an unqualified DEPLOYED banner. It now inspects the container state and, when it is not `running`, says plainly that this is a crash loop rather than the expected sealed-and-unhealthy state -- before and after the banner, with the logs command to run. - Every `docker compose` command in that banner assumed the project directory. deploy.sh runs them from $STACK_DIR itself, so the omission only bit the operator afterwards, with "no configuration file provided: not found". The banner now says to cd there first and quotes that error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
bf52426299 |
fix(openbao): keep a failing address probe from aborting the deploy
host_addrs() ends in a pipeline, so under `set -o pipefail` a probe that
exits non-zero -- even after printing perfectly usable addresses, or
because awk is missing -- made `addrs="$(host_addrs)"` non-zero, and
`set -e` killed deploy.sh at that line.
Nothing was printed when it did: the 2>/dev/null had already swallowed the
tool's own error and the fail-open guard on the next lines was never
reached, so the operator got a bare exit 1 mid-deploy with nothing to
diagnose. Worst case it aborted a deploy whose bind address was CORRECT --
reproduced with an `ip` stub that prints the matching address, then exits 1.
Capture with `|| true` so the emptiness test actually drives the fail-open
the comment beside it already promised. Neutralising inside host_addrs
instead would not cover a missing awk, since pipefail takes the rightmost
non-zero status.
Re-verified the check is not weakened: a healthy probe with the address
genuinely absent still lists the host's addresses and dies with the full
message, and all seven .env/environment precedence cases are unchanged.
Found by adversarial review of
|
||
|
|
920edc50b3 |
fix(openbao): validate the publish bind before writing any state
deploy.sh narrows the Docker publish bind to OPENBAO_ADDR when that is an IP, but never checked that the host actually holds that address. A typo'd IP therefore failed late, inside `docker compose up`: failed to bind host port 192.160.100.50:8200/tcp: cannot assign requested address ...by which point .env had been seeded and the TLS cert generated with the bad address in its SAN. Neither is rewritten on a re-run (.env is never overwritten, gen-tls.sh never regenerates over an existing pair), so re-running with a corrected OPENBAO_ADDR silently changed nothing. Add host_addrs() + check_bind_addr(), run before anything is written: - lists the host's addresses from plain `ip addr show` -- no -o/scope filters, since busybox ip supports neither -- falling back to ifconfig, and skipping the check when neither exists rather than blocking; - skips 0.0.0.0 / :: / *, and unwraps an [IPv6] publish literal; - SKIP_BIND_CHECK=1 overrides for an address that only comes up later. Resolve the bind compose will really interpolate, which follows compose's own precedence -- shell environment before .env: - exported (automations.sh passes answers via `env VAR=...`, or a standalone OPENBAO_BIND=... run): the environment wins, so warn when .env disagrees, because a later bare `docker compose up` would not; - derived here: that assignment is not exported, so .env wins; - in neither: compose falls back to 0.0.0.0 and publishes the API on every interface -- warn, since that is a silent exposure. Also warn when .env's OPENBAO_ADDR differs from this run's, naming the cert that has to be deleted for the SAN to be regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b027de2182 |
fix(openbao): follow-ups from adversarial fix-verification
An adversarial re-verification of
|
||
|
|
0812f345a8 |
fix(openbao): sanity-audit fixes — 4 blockers + hardening
A multi-agent sanity audit of the freshly-merged deployment found four end-to-end blockers (and several smaller issues); all fixed here. HIGH (were blocking): - deploy.sh never called install_docker(), so `docker compose pull` hit command-not-found on any host without Docker. Now called before the compose steps. - The container's server process runs as the image's own (often non-root) user but the mounted config/TLS were root-owned 0640/0600 and the raft volume root-owned -> vault crash-looped, never binding :8200. deploy.sh now detects the image UID after pull and aligns ownership of config.hcl, ./tls and the data volume (a no-op when the image runs as root); config.hcl is installed 0644 (holds no secrets). - Docs told operators to set the daemon key `openbao_ca_cert`, but the Kanrisha daemon's key is `ca_cert` (config.go, mapstructure:"ca_cert"). The wrong key is fatal on strict unmarshal / leaves TLS unverified. Renamed in all 5 places (config.hcl, gen-tls.sh, deploy.sh x2, README). - DR backup used `docker compose cp openbao:… -`, which emits a TAR stream, so the age-encrypted snapshot was tar-wrapped and would not restore. Switched to `docker compose exec -T openbao cat` for the raw bytes, wrote the snapshot to a scratch path (not the live raft dir), and documented the matching restore. MEDIUM: - Swap detection used `swapon --show` (absent on BusyBox) and `\s` (GNU-only) -> silently no-op on Alpine, leaving swap on. Now uses /proc/swaps and [[:space:]] so mlock hardening actually holds on musl. - A Docker-published port bypasses the host INPUT firewall, so the source rule was illusory. deploy.sh now narrows OPENBAO_BIND to OPENBAO_ADDR when it is an IP, the compose/README/.env comments state the reality, and a new Exposure section + an init-immediately warning were added. - Fixed broken ../kanrisha/ and deployments/kanrisha/ links (separate repo). LOW: - OPENBAO_TLS_SANS is now honored (folded into the SAN list from the env). - .gitignore excludes *.snap / *.snap.age. - Bootstrap note clarifies bootstrap.sh needs the `bao` CLI (run it from the Kanrisha host/workstation, not this Docker-only vault host). - README multi-OS count corrected (eight stacks) + automations.sh header lists openbao. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
2efc9dbffb |
feat(openbao): hardened same-LAN tape-encryption key store for Kanrisha
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> |