Files
automations/deployments/openbao/deploy.sh
T
57_WolveandClaude Opus 4.8 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>
2026-07-08 19:11:37 -05:00

370 lines
19 KiB
Bash

#!/usr/bin/env bash
#
# deploy.sh -- deploy the hardened OpenBao tape-encryption key store on a
# same-LAN host (Alpine / Debian / Alma). Single-node, runs as root.
#
# What this does:
# 1. Installs docker + compose if missing.
# 2. Lays down docker-compose.yml, config.hcl, gen-tls.sh in $STACK_DIR.
# 3. Seeds .env on first run (OPENBAO_ADDR into the cert SAN); never
# overwrites an existing .env.
# 4. Generates a self-signed TLS cert (if ./tls is empty) -- drop a CA-signed
# pair there instead to use your Smallstep CA.
# 5. Disables swap (so mlock is meaningful) and opens 8200/tcp.
# 6. Pulls images and brings the stack up. OpenBao starts SEALED -- initialise
# + unseal once afterwards (printed at the end).
#
# Idempotent: re-run to apply config changes / pull new images.
#
# Self-contained: docker-compose.yml, config.hcl, gen-tls.sh, .env.example are
# embedded as a base64 tar.gz at the bottom. Rebuild with build.sh after edits.
#
# Usage:
# OPENBAO_ADDR=10.0.0.10 bash deploy.sh # interactive prompt for the rest
# OPENBAO_ADDR=vault.lan SKIP_PROMPTS=1 bash deploy.sh
# STACK_DIR=/opt/openbao bash deploy.sh
set -euo pipefail
: "${STACK_DIR:=/srv/openbao}"
: "${SKIP_DOCKER_INSTALL:=0}"
: "${FORCE:=0}"
: "${SKIP_PROMPTS:=0}" # non-interactive: require values via env, no prompts
[[ "$SKIP_PROMPTS" == "1" ]] && FORCE=1
: "${OPENBAO_ADDR:=}"
: "${OPENBAO_BIND:=0.0.0.0}"
: "${DISABLE_SWAP:=1}" # set 0 to skip swapoff (mlock then only best-effort)
log() { printf '\033[1;32m[+]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[!]\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; exit 1; }
[[ $EUID -eq 0 ]] || die "Run as root."
# ---------------------------------------------------------------------------
# OS detection + Docker install (Alpine / Debian / Alma). Inlined so this
# deploy.sh stays self-contained when scp'd standalone.
# ---------------------------------------------------------------------------
osfam() {
local id="" like=""
if [[ -r /etc/os-release ]]; then
id="$(. /etc/os-release 2>/dev/null && echo "${ID:-}")"
like="$(. /etc/os-release 2>/dev/null && echo "${ID_LIKE:-}")"
fi
case " $id $like " in
*" alpine "*) echo alpine ;;
*" debian "*|*" ubuntu "*) echo debian ;;
*" rhel "*|*" fedora "*|*" centos "*) echo rhel ;;
*) echo "${id:-unknown}" ;;
esac
}
install_docker() {
[[ "$SKIP_DOCKER_INSTALL" == "1" ]] && { log "Skipping Docker install."; return; }
if command -v docker >/dev/null 2>&1; then
log "Docker already installed: $(docker --version)"
else
log "Installing Docker (OS: $(osfam))..."
case "$(osfam)" in
alpine) apk add -q docker docker-cli-compose openrc ;;
debian|rhel) command -v curl >/dev/null 2>&1 || \
{ command -v apt-get >/dev/null 2>&1 && apt-get install -y -qq curl; } || \
{ command -v dnf >/dev/null 2>&1 && dnf install -y -q curl; }
curl -fsSL https://get.docker.com | sh ;;
*) die "Unsupported OS for auto Docker install. Set SKIP_DOCKER_INSTALL=1 and install Docker yourself." ;;
esac
fi
if command -v rc-update >/dev/null 2>&1; then
rc-update add docker default >/dev/null 2>&1 || true
rc-service docker status >/dev/null 2>&1 || rc-service docker start
elif command -v systemctl >/dev/null 2>&1; then
systemctl enable --now docker >/dev/null 2>&1 || systemctl start docker || true
fi
}
open_bao_port() {
# Register 8200/tcp (the vault API). Prefer the host firewall when present;
# else ufw/firewalld if active. Restrict the source to the tape host where
# you can -- this is a secrets store, not a public service.
if [[ -d /etc/firewall/ports.d && -x /usr/local/sbin/firewall-apply ]]; then
log "Registering 8200/tcp with host firewall..."
printf '8200/tcp\n' > /etc/firewall/ports.d/openbao.rule
/usr/local/sbin/firewall-apply
elif command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
log "ufw active -- allowing 8200/tcp..."
ufw allow 8200/tcp >/dev/null
elif command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then
log "firewalld active -- allowing 8200/tcp..."
firewall-cmd -q --add-port=8200/tcp --permanent
firewall-cmd -q --reload
fi
}
disable_swap() {
[[ "$DISABLE_SWAP" == "1" ]] || { warn "DISABLE_SWAP=0 -- mlock will be best-effort."; return; }
# Detect active swap via /proc/swaps (a header line + one line per device) so
# this works on musl/BusyBox too, where `swapon --show` does not exist.
if [[ -r /proc/swaps ]] && [[ "$(wc -l < /proc/swaps)" -gt 1 ]]; then
log "Disabling swap (mlock keeps key material off disk)..."
swapoff -a || warn "swapoff failed -- disable swap manually."
else
log "No active swap."
fi
# Persist: comment any swap lines in fstab so it stays off across reboots.
# [[:space:]] (not \s) so the match works under musl/BusyBox grep/sed.
if [[ -f /etc/fstab ]] && grep -qE '^[^#].*[[:space:]]swap[[:space:]]' /etc/fstab; then
sed -i.bak -E 's|^([^#].*[[:space:]]swap[[:space:]].*)$|# \1 # disabled for OpenBao mlock|' /etc/fstab
log "Commented swap entries in /etc/fstab (backup: /etc/fstab.bak)."
fi
}
# ----------------------------------------------------------------------------
# Extract embedded archive (docker-compose.yml, config.hcl, gen-tls.sh, .env.example)
# ----------------------------------------------------------------------------
SCRIPT_DIR=$(mktemp -d -t openbao-deploy.XXXXXX)
trap 'rm -rf "$SCRIPT_DIR"' EXIT
extract_archive() {
grep -a -A 9999999 '^__ARCHIVE_BELOW__$' "$0" \
| tail -n +2 \
| base64 -d \
| tar -xz -C "$SCRIPT_DIR" 2>/dev/null || true
}
extract_archive
# Fallback: run straight from the source dir (before build.sh embeds a payload).
if [[ ! -f "$SCRIPT_DIR/docker-compose.yml" ]]; then
SRC=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
for f in docker-compose.yml config.hcl gen-tls.sh .env.example; do
[[ -f "$SRC/$f" ]] || die "Missing $f (no embedded payload and not in $SRC -- run build.sh)."
cp "$SRC/$f" "$SCRIPT_DIR/$f"
done
fi
# ----------------------------------------------------------------------------
# Prompt for required values
# ----------------------------------------------------------------------------
prompt() {
local var="$1" msg="$2" cur="${!1}"
[[ -n "$cur" ]] && return
[[ "$SKIP_PROMPTS" == "1" ]] && die "$var required (set it in the environment; running with SKIP_PROMPTS=1)."
read -r -p "$msg: " "$var"
}
prompt OPENBAO_ADDR "LAN address the Kanrisha tape host reaches this vault at (IP or DNS)"
[[ -n "$OPENBAO_ADDR" ]] || die "OPENBAO_ADDR is required."
# Build the cert SAN list: loopback + whatever OPENBAO_ADDR is (IP vs DNS) +
# any extra SANs the operator exported in OPENBAO_TLS_SANS.
ADDR_IS_IP=0
if [[ "$OPENBAO_ADDR" =~ ^[0-9.]+$ || "$OPENBAO_ADDR" == *:* ]]; then
ADDR_SAN="IP:${OPENBAO_ADDR}"
ADDR_IS_IP=1
else
ADDR_SAN="DNS:${OPENBAO_ADDR}"
fi
SANS="DNS:localhost,IP:127.0.0.1,${ADDR_SAN}"
[[ -n "${OPENBAO_TLS_SANS:-}" ]] && SANS="${SANS},${OPENBAO_TLS_SANS}"
# A Docker-published port bypasses the host INPUT firewall, so the interface bind
# is the real restriction. If the operator left OPENBAO_BIND at the all-interfaces
# default and OPENBAO_ADDR is an IP, narrow the publish to just that LAN IP.
if [[ "$OPENBAO_BIND" == "0.0.0.0" && "$ADDR_IS_IP" == "1" ]]; then
OPENBAO_BIND="$OPENBAO_ADDR"
log "Binding the API to ${OPENBAO_BIND} only (set OPENBAO_BIND to override)."
elif [[ "$OPENBAO_BIND" == "0.0.0.0" ]]; then
warn "OPENBAO_BIND=0.0.0.0 and OPENBAO_ADDR is a DNS name -- API publishes on ALL interfaces. Set OPENBAO_BIND to a LAN IP to narrow it."
fi
# ----------------------------------------------------------------------------
# Lay down the stack
# ----------------------------------------------------------------------------
log "Setting up $STACK_DIR..."
install -d -m 0750 "$STACK_DIR"
install -m 0640 "$SCRIPT_DIR/docker-compose.yml" "$STACK_DIR/docker-compose.yml"
# config.hcl holds no secrets and is read by the in-container server process
# (which may be a non-root user) over a read-only mount -- keep it world-readable.
install -m 0644 "$SCRIPT_DIR/config.hcl" "$STACK_DIR/config.hcl"
install -m 0750 "$SCRIPT_DIR/gen-tls.sh" "$STACK_DIR/gen-tls.sh"
ENV_FILE="$STACK_DIR/.env"
if [[ ! -f "$ENV_FILE" ]]; then
log "Seeding $ENV_FILE..."
install -m 0600 "$SCRIPT_DIR/.env.example" "$ENV_FILE"
sed -i \
-e "s|^OPENBAO_ADDR=.*|OPENBAO_ADDR=${OPENBAO_ADDR}|" \
-e "s|^OPENBAO_BIND=.*|OPENBAO_BIND=${OPENBAO_BIND}|" \
-e "s|^OPENBAO_TLS_SANS=.*|OPENBAO_TLS_SANS=${SANS}|" \
"$ENV_FILE"
else
log ".env exists; leaving it alone."
fi
# ----------------------------------------------------------------------------
# TLS: self-signed unless a cert is already present (drop in a CA-signed pair
# to use your Smallstep CA -- see the README).
# ----------------------------------------------------------------------------
install -d -m 0750 "$STACK_DIR/tls"
OPENBAO_TLS_SANS="$SANS" bash "$STACK_DIR/gen-tls.sh" "$STACK_DIR/tls"
disable_swap
open_bao_port
# ----------------------------------------------------------------------------
# Bring up the stack
# ----------------------------------------------------------------------------
if [[ "$FORCE" != "1" ]]; then
printf '\nAbout to start OpenBao from %s (TLS on https://%s:8200). Continue? [y/N] ' "$STACK_DIR" "$OPENBAO_ADDR"
read -r ans
[[ "${ans,,}" == "y" || "${ans,,}" == "yes" ]] || { warn "Aborted."; exit 0; }
fi
install_docker
cd "$STACK_DIR"
log "Pulling image..."
docker compose pull
# The OpenBao server process runs as the image's own user -- root in some image
# variants, a non-root service user in others -- and reads its config + TLS over
# read-only bind mounts and writes the raft data volume. So those must be owned by
# that UID. Detect it from the pulled image and align ownership; all of this is a
# harmless no-op when the image runs as root.
BAO_UID=$(docker compose run --rm --no-deps --entrypoint id openbao -u 2>/dev/null | tr -dc '0-9' || true)
BAO_UID=${BAO_UID:-0}
log "OpenBao container runs as UID ${BAO_UID}; aligning file/volume ownership."
chown -R "${BAO_UID}:${BAO_UID}" "$STACK_DIR/config.hcl" "$STACK_DIR/tls"
# tls.key stays private to that UID; config.hcl + tls.crt are non-secret.
chmod 0600 "$STACK_DIR/tls/tls.key" 2>/dev/null || true
# Raft data volume is created root-owned on first mount; make it writable by the
# server UID (a one-shot root container chowns the mounted volume in-place).
docker compose run --rm --no-deps --user 0:0 --entrypoint chown openbao \
-R "${BAO_UID}:${BAO_UID}" /openbao/data 2>/dev/null || \
warn "Could not pre-chown the raft data volume; if OpenBao fails to write storage, chown the openbao-data volume to UID ${BAO_UID}."
log "Starting OpenBao..."
docker compose up -d --remove-orphans
# OpenBao starts SEALED (and, first time, uninitialised), so it reports unhealthy
# until you init + unseal -- that is expected. Give it a moment to bind.
sleep 5
docker compose ps
cat <<EOF
================================================================
DEPLOYED (OpenBao starts SEALED -- finish setup below)
Address: https://${OPENBAO_ADDR}:8200
Stack dir: ${STACK_DIR}
TLS: ${STACK_DIR}/tls/tls.crt (give this to the Kanrisha daemon as
[encryption.openbao].ca_cert)
>> Initialise NOW: an uninitialised vault reachable on the LAN can be init'd by
anyone who connects, capturing the root token + unseal keys. Do step 1 before
walking away.
1. Initialise + unseal (ONCE). Store the unseal keys + root token OUT OF BAND:
docker compose exec -e BAO_ADDR=https://127.0.0.1:8200 openbao \\
bao operator init -tls-skip-verify
docker compose exec -e BAO_ADDR=https://127.0.0.1:8200 openbao \\
bao operator unseal -tls-skip-verify <key> # x3, three different keys
2. Bootstrap for Kanrisha. Run the Kanrisha repo's deploy/openbao/bootstrap.sh
from a host that HAS the 'bao' CLI (the Kanrisha host or your workstation --
this vault host only ships Docker), pointed at this vault. Copy tls.crt there
first as the BAO_CACERT:
BAO_ADDR=https://${OPENBAO_ADDR}:8200 BAO_CACERT=/path/to/openbao-ca.crt \\
BAO_TOKEN=<root> bash bootstrap.sh
Then point the Kanrisha daemon at:
[encryption.openbao]
address = "https://${OPENBAO_ADDR}:8200"
ca_cert = "/etc/kanrisha/openbao-ca.crt" # = tls.crt above
3. Back it up (this vault is the sole recovery path for encrypted tapes):
docker compose exec openbao \\
bao operator raft snapshot save -address=https://127.0.0.1:8200 -tls-skip-verify /tmp/openbao.snap
docker compose exec -T openbao cat /tmp/openbao.snap > ${STACK_DIR}/openbao.snap
...then age-encrypt + copy it off-box. See the README for the full DR flow.
Manage:
docker compose logs -f
docker compose pull && docker compose up -d # update
docker compose down # stop, keep the vault data
docker compose down -v # stop, WIPE the vault (DESTROYS keys)
Re-running this script is idempotent (it won't re-init or touch .env / tls).
================================================================
EOF
# IMPORTANT: nothing executable below this line. Everything after
# __ARCHIVE_BELOW__ is the embedded tar.gz payload (base64), added by build.sh.
exit 0
__ARCHIVE_BELOW__
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
Oym9JwC6CAgptd9aab0Tha4Q6paSMpeYkMkVlXfH2hmyLkOxXcbr0NmC+q152GrZ1xLk3EmFUuuI
fugh2vaSJULz3g/fDOhPf6L3jzZQDmYjmSNuB2364Zu9F4b04AUJph4fn0xHdOHsHTx/XNGS/If1
RV4qhAkwF+CnARcllttIEECww1s9PjvtioEVqa0eV5Vpyd7wC0xnDknqEPqeFARY9tptlRtHikHO
w0H/f8lJ1D1blPIhKXwBUMP3WeblXozzzvxlfqeGWR7GCge5G5AAbE2/BycdHo/sd0tyzmafiNgR
zUUnFKRtJuvl9R8AOo4nRzY7wSgcXT1gMj8DpB5FxcSL1dHX71kfWaf8BVARPe/3txYtH+zvb63c
am0L76X1ksGGVEpvKJ7K6lD70Vximekh2Q3VrJP7+HAfgsezXnK9pb6rMkPYToA9/bElfykJhp8y
r3vquXPp0bvQ93936GraEzQG8q568GIkjf+qNT5b/x08X+M/xomDwcHzgwb/fYkGKJVmK/YarAmC
cCR5DBOXTEWlyvizkK9UyLBlJrkpIiWiQBStuAaxrvxpyXhloC1HwXOU5UAuBr7gdzoVwJOBjAs5
RtnyHoNAOJoSF3I0DU0Km6XZ0rxiFtVdVwW4MHBpKhdhxKDmqTL+L/bHlSEboDv8Aj4noCTiKw9d
49NHk8vT6atR7d7MUl6Rr+N1Vf9Ok9ZOWSpyaSoSMe8yty6eMFJA1syvw82llvuFV0hhNkuBhT9V
KS2RBMZ2fun9Yddt1TuPKpzKgjstLwkYyVb3AkYXp+ZuANKSQM5XL9vRmeJLEtdAvrc9aJ+teaz1
z7ze1xK7PvZqu+ATcj2OX2q/kBoZhqqHLAr9sIjYGvjyDkI1sp1M+Yx17K0Ilzoj2rrA8LmrCmWV
2grE1u+lM7XpRGhYb9A21q1uT9RvNrjdVv2l+5GtP29agJSJ/92K/LQ2Mf6/q8uRoKMqwdzW4l2o
8CoFEzeE/R1FaZpd83uYzv0i9BcbWmKqsSVA1l37+shc6wrWhfTx5Denl+eT1+PJFWu7ISDvazmt
NWkp14/ZTrVchslFn67VnF/wwN0l7OWqhU0KJ4nZdlp09Mm0bmCyyooIY/b1Ufj1Vm4NfB7y64Iy
XyQ5vc1pw9Ipp1xxnqschq3cLl2xupt69VZeXSa+Jr9eJ9TrbHp3K5Mu82jO+HlV81anSpz57yYB
X4KPvLtZu4qKIa3tFPAIGeCmxp/ybTK4xJv/Yr3HgS74fagIJsvTYOnz/QYrb11jzOjlkVyM2+TB
RwviL8rrPsx1Www3Y7pPwyouDfNtGb7c8tnbMh+/JWMyT7kHN+WKqylM98qytDa1a3OfbKtCDYM9
L+/TSDpuQ4IoW+1VbbJkhFLW2DlMliX21lZh+6hJaZrWtKY1rWlNa1rTmta0pjWtaU1rWtOa1rSm
Na1pTWta05rWtKY1rWlNa1rTmvZf1v4G3yMW1ABQAAA=