#!/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 <> 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 # 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= 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= 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=