An adversarial re-verification of 0812f34 found a couple of the fixes were
incomplete or regressed; addressed here.
- DR was an INCOMPLETE fix: `raft snapshot save`/`restore` are token-gated
(sys/storage/raft/snapshot is sudo-capable) and the container carries no
ambient token, so the previously-"fixed" backup returned "missing client
token" and streamed a zero-byte snapshot. Both the deploy.sh runbook and the
README DR flow now pass `-e BAO_TOKEN=<token>` on save and restore.
- IPv6 bind REGRESSION (introduced by the bind-narrowing): a bare IPv6 literal
in the compose port map (`fd00::10:8200:8200`) is invalid and aborts at
`docker compose pull`. Now IPv4 vs IPv6 are classified separately and IPv6 is
bracketed (`[fd00::10]:8200:8200`).
- Docker readiness race (now reachable since install_docker is actually
called): openrc backgrounds dockerd and returns before the socket is up, so
the next `docker compose pull` raced it under set -e. install_docker now polls
`docker info` for up to 30s.
- UID detection hardened: added `-T` to the one-off `docker compose run`, and an
empty result is now a loud warning (with the manual-chown remedy) instead of a
silent fall-through to root that would re-create the crash-loop on a non-root
image.
- The raft-volume chown is gated to first run (captured before any compose-run
instantiates the volume), so idempotent re-deploys don't recursively re-chown
a live raft dir.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
399 lines
20 KiB
Bash
399 lines
20 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
|
|
# dockerd is often started in the background (esp. openrc) and returns before
|
|
# the socket is listening -- poll so the first `docker compose` call doesn't
|
|
# race it and abort under set -e.
|
|
local i
|
|
for i in $(seq 1 30); do
|
|
docker info >/dev/null 2>&1 && return
|
|
sleep 1
|
|
done
|
|
warn "Docker daemon not ready after 30s; continuing (compose may fail -- check 'docker info')."
|
|
}
|
|
|
|
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_KIND=dns
|
|
if [[ "$OPENBAO_ADDR" =~ ^[0-9.]+$ ]]; then
|
|
ADDR_SAN="IP:${OPENBAO_ADDR}"; ADDR_KIND=ipv4
|
|
elif [[ "$OPENBAO_ADDR" == *:* ]]; then
|
|
ADDR_SAN="IP:${OPENBAO_ADDR}"; ADDR_KIND=ipv6
|
|
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. IPv6
|
|
# literals must be bracketed in the compose port mapping ([addr]:8200:8200).
|
|
if [[ "$OPENBAO_BIND" == "0.0.0.0" ]]; then
|
|
case "$ADDR_KIND" in
|
|
ipv4) OPENBAO_BIND="$OPENBAO_ADDR"; log "Binding the API to ${OPENBAO_BIND} only (set OPENBAO_BIND to override)." ;;
|
|
ipv6) OPENBAO_BIND="[${OPENBAO_ADDR}]"; log "Binding the API to ${OPENBAO_BIND} only (set OPENBAO_BIND to override)." ;;
|
|
*) 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." ;;
|
|
esac
|
|
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"
|
|
|
|
# Record whether this is the first deploy (raft volume not yet created) BEFORE any
|
|
# `docker compose run` below instantiates the volume -- the first-run data chown
|
|
# gates on this.
|
|
FIRST_RUN=0
|
|
docker volume inspect openbao_openbao-data >/dev/null 2>&1 || FIRST_RUN=1
|
|
|
|
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 (-T: no TTY on the piped stdout) and
|
|
# align ownership; a no-op when the image runs as root.
|
|
BAO_UID=$(docker compose run --rm --no-deps -T --entrypoint id openbao -u 2>/dev/null | tr -dc '0-9')
|
|
if [[ -z "$BAO_UID" ]]; then
|
|
# `id -u` always prints a number if it ran, so empty means detection failed --
|
|
# do NOT silently assume root (that would re-create the unreadable-config
|
|
# crash-loop on a non-root image). Fall back to root but say so loudly.
|
|
warn "Could not detect the OpenBao container UID; assuming root. If OpenBao crash-loops on start, chown $STACK_DIR/config.hcl + $STACK_DIR/tls (and the openbao-data volume) to the container's UID (docker compose exec openbao id -u)."
|
|
BAO_UID=0
|
|
fi
|
|
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: created root-owned on first mount. Only the FIRST run needs
|
|
# the chown (idempotent re-runs would recursively re-chown a live raft dir).
|
|
if [[ "$FIRST_RUN" == "1" ]]; then
|
|
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}."
|
|
fi
|
|
|
|
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). Snapshot
|
|
save is token-gated -- pass a token with sys/storage/raft/snapshot (the root
|
|
token works):
|
|
docker compose exec -T -e BAO_TOKEN=<token> 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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
|
|
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=
|