fix(openbao): never put the vault token on a command line

`BAO_TOKEN=<root> bash update.sh update` -- which the previous commit and the
pre-existing DR runbook both recommended -- leaks the token twice over. It goes
into the shell history, and `docker compose exec -e BAO_TOKEN=<value>` puts it
in the docker process's argv, where /proc/<pid>/cmdline makes it readable by
every user on the host. The second is the worse one and is not fixed by
anything the operator does at their prompt.

The token now reaches the container over STDIN and is never an argument to
anything: the container's own shell reads one line, exports it, and execs bao.
Verified locally that the child process sees the exact value -- including &, |
and a backslash -- while its argv contains zero occurrences of it.

How a token is supplied, in order: an already-exported BAO_TOKEN; a 0600 file
named by the new BAO_TOKEN_FILE; otherwise an echo-off prompt. With no token
and no terminal it dies telling the operator to use BAO_TOKEN_FILE rather than
an inline assignment, and says why.

Added a `snapshot` subcommand so the DR runbook no longer needs an inline
pipeline at all. That runbook and the deploy banner both carried the -e form
before this deployment had an updater, so both are corrected: taking a backup
is now `bash update.sh snapshot`, and restore keeps the stdin shape rather than
-e. The Kanrisha bootstrap lines say to export the token first instead of
passing it inline.

The conf file still refuses to hold a token, but now points at BAO_TOKEN_FILE
as the unattended answer -- and suggests a snapshot-policy token rather than
the root token for it.

Not verified without a live host: that `docker compose exec -T` forwards stdin
as expected. If it does not, the snapshot fails loudly with a permission error
and the upgrade aborts before touching anything, which is the safe direction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-22 09:31:47 -05:00
co-authored by Claude Opus 5
parent 2d3d322b0e
commit 0eb7f26663
3 changed files with 311 additions and 217 deletions
+31 -17
View File
@@ -120,7 +120,7 @@ Docker. Copy `tls.crt` to that host first and pass it as `BAO_CACERT`:
```bash ```bash
BAO_ADDR=https://$OPENBAO_ADDR:8200 BAO_CACERT=/path/to/openbao-ca.crt \ BAO_ADDR=https://$OPENBAO_ADDR:8200 BAO_CACERT=/path/to/openbao-ca.crt \
BAO_TOKEN=<root> bash bootstrap.sh bash bootstrap.sh # export BAO_TOKEN beforehand rather than passing it inline
``` ```
It enables the `kanrisha-tape` KV-v2 mount (with **effectively-unlimited** It enables the `kanrisha-tape` KV-v2 mount (with **effectively-unlimited**
@@ -178,7 +178,7 @@ deploy step.
```bash ```bash
cd /srv/openbao cd /srv/openbao
bash update.sh check # declared / running / latest + seal state bash update.sh check # declared / running / latest + seal state
BAO_TOKEN=<token> bash update.sh update # snapshot, then upgrade bash update.sh update # snapshot, then upgrade (prompts)
``` ```
Unlike the copyparty and ergo updaters in this repo, this one **will not update Unlike the copyparty and ergo updaters in this repo, this one **will not update
@@ -191,8 +191,11 @@ configured — only auto-unseal makes the vault come back on its own.
Before it changes anything, `update` requires that the container is running, the Before it changes anything, `update` requires that the container is running, the
vault is **unsealed** (a sealed vault cannot produce a snapshot, so there would vault is **unsealed** (a sealed vault cannot produce a snapshot, so there would
be no rollback plan), and a `BAO_TOKEN` with `sys/storage/raft/snapshot` — the be no rollback plan), and a token with `sys/storage/raft/snapshot` — the root
root token works. It writes the snapshot to `/var/backups/openbao` at `0600`, token works. It **prompts** for that token with echo off; never pass it as
`BAO_TOKEN=... bash update.sh`, which puts it in your shell history and in
`/proc/<pid>/cmdline`, where every user on the box can read it. For unattended
use set `BAO_TOKEN_FILE` to a `0600` file. It writes the snapshot to `/var/backups/openbao` at `0600`,
verifies it is a valid non-empty gzip archive, and refuses to continue if it is verifies it is a valid non-empty gzip archive, and refuses to continue if it is
not. **Copy it off the host**: it is the rollback plan, and OpenBao's upgrade not. **Copy it off the host**: it is the rollback plan, and OpenBao's upgrade
guide is explicit that reverting the image alone does not roll back the data guide is explicit that reverting the image alone does not roll back the data
@@ -211,19 +214,26 @@ the external `plugin "kms" "pkcs11"` first.
The vault is the sole recovery path for encrypted tapes — back it up: The vault is the sole recovery path for encrypted tapes — back it up:
Snapshot save/restore are **token-gated** (`sys/storage/raft/snapshot` is Snapshot save/restore are **token-gated** (`sys/storage/raft/snapshot` is
sudo-capable) — pass a token that has that path (the root token works, or mint a sudo-capable) — they need a token that has that path (the root token works, or
dedicated snapshot-policy token). The container has no ambient token, so supply mint a dedicated snapshot-policy token). The container has no ambient token.
it via `-e BAO_TOKEN`.
**Do not put the token on a command line.** `docker ... -e BAO_TOKEN=<value>`
places it in the docker process's argv, and `/proc/<pid>/cmdline` is readable by
every user on the host — so it leaks further than your shell history does. Use
`update.sh snapshot`, which prompts with echo off and passes the token to the
container over stdin:
```bash ```bash
# Consistent raft snapshot (safe while running); write it to a scratch path, NOT cd /srv/openbao
# into the live raft dir: bash update.sh snapshot # prompts; writes to /var/backups/openbao, 0600
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
# then stream the RAW file off-box, age-encrypted. Use `exec -T ... cat`, not Then age-encrypt it and move it off-box:
# `compose cp openbao:… -` (which emits a TAR wrapper that won't restore):
docker compose exec -T openbao cat /tmp/openbao.snap | \ ```bash
age -r "$(cat /path/to/globals/age-pubkey.txt)" > "openbao-$(date +%F).snap.age" age -r "$(cat /path/to/globals/age-pubkey.txt)" \
< /var/backups/openbao/openbao-pre-manual-*.snap \
> "openbao-$(date +%F).snap.age"
``` ```
Restore (into a fresh, unsealed vault): decrypt, copy the raw `.snap` in, and Restore (into a fresh, unsealed vault): decrypt, copy the raw `.snap` in, and
@@ -232,8 +242,12 @@ apply it (also token-gated):
```bash ```bash
age -d -i ~/.age/key.txt openbao-DATE.snap.age > openbao.snap age -d -i ~/.age/key.txt openbao-DATE.snap.age > openbao.snap
docker compose cp openbao.snap openbao:/tmp/openbao.snap docker compose cp openbao.snap openbao:/tmp/openbao.snap
docker compose exec -T -e BAO_TOKEN=<token> openbao \ # Same argv problem as above, so hand the token over stdin rather than -e:
bao operator raft snapshot restore -address=https://127.0.0.1:8200 -tls-skip-verify /tmp/openbao.snap read -rs -p "token: " T; echo
printf '%s\n' "$T" | docker compose exec -T openbao sh -c \
'read -r t; BAO_TOKEN="$t" exec bao operator raft snapshot restore \
-address=https://127.0.0.1:8200 -tls-skip-verify /tmp/openbao.snap'
unset T
``` ```
Snapshots do **not** contain the unseal keys or root token — you still need Snapshots do **not** contain the unseal keys or root token — you still need
+203 -188
View File
@@ -496,23 +496,23 @@ TLS: ${STACK_DIR}/tls/tls.crt (give this to the Kanrisha daemon as
this vault host only ships Docker), pointed at this vault. Copy tls.crt there this vault host only ships Docker), pointed at this vault. Copy tls.crt there
first as the BAO_CACERT: first as the BAO_CACERT:
BAO_ADDR=https://${OPENBAO_ADDR}:8200 BAO_CACERT=/path/to/openbao-ca.crt \\ BAO_ADDR=https://${OPENBAO_ADDR}:8200 BAO_CACERT=/path/to/openbao-ca.crt \\
BAO_TOKEN=<root> bash bootstrap.sh bash bootstrap.sh # export BAO_TOKEN first; do not pass it inline
Then point the Kanrisha daemon at: Then point the Kanrisha daemon at:
[encryption.openbao] [encryption.openbao]
address = "https://${OPENBAO_ADDR}:8200" address = "https://${OPENBAO_ADDR}:8200"
ca_cert = "/etc/kanrisha/openbao-ca.crt" # = tls.crt above 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 3. Back it up (this vault is the sole recovery path for its secrets). Snapshot
save is token-gated -- pass a token with sys/storage/raft/snapshot (the root save is token-gated; update.sh prompts for the token with echo off and hands
token works): it to the container over stdin, so it never reaches a command line:
docker compose exec -T -e BAO_TOKEN=<token> openbao \\ cd ${STACK_DIR} && bash update.sh snapshot
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. ...then age-encrypt + copy it off-box. See the README for the full DR flow.
Do NOT use `-e BAO_TOKEN=<value>`: that puts the token in the docker
process's argv, and /proc/<pid>/cmdline is world-readable.
Manage (run these from ${STACK_DIR}): Manage (run these from ${STACK_DIR}):
bash update.sh check # declared/running/latest + seal state bash update.sh check # declared/running/latest + seal state
BAO_TOKEN=<token> bash update.sh update # snapshot, then upgrade (comes back SEALED) bash update.sh update # snapshot, then upgrade (comes back SEALED)
docker compose logs -f docker compose logs -f
docker compose pull && docker compose up -d # update docker compose pull && docker compose up -d # update
docker compose down # stop, keep the vault data docker compose down # stop, keep the vault data
@@ -530,183 +530,198 @@ fi
# __ARCHIVE_BELOW__ is the embedded tar.gz payload (base64), added by build.sh. # __ARCHIVE_BELOW__ is the embedded tar.gz payload (base64), added by build.sh.
exit 0 exit 0
__ARCHIVE_BELOW__ __ARCHIVE_BELOW__
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
dlkVEkD/zejt8dnGy/WlRwz3o1m1JsTHXMi7PzUPDm5aLw72g5uv3vOXZiA+Vz+FJPIvLarBLZ0k Cw98XaptU9Dki1OPLvKYn/Pfknxi+GOdzPHg+M1+8+mm6wn86/kp/d0JXVl+3R+/m4xPLs4OBped
xKCHYw6Cm88eGaNrePcV15OPz9UKbu4+OW6j9qevYxTXCxQwEN1Ci9bAikrWnBeWb5/vmStXtOmR D/fNFrcYk1uv1X66SZfTPj2w0Vi59Gyi9sHJ6E1v/1P54L5ZdDZTzH1k6kozk2Q5WMsf1d8mEb2e
bAi/ENytV9wJr6QqONQUQuKwsjWZ9fQ7S9SYETjqCGR4KJz+29Pejy/Ft3wp5WU7387RmJ886ith Lpppxxye1rrxeK6vCKOWg9MN34z3dRTG8hN2jp9/Zm4icMMP9PpecUFd0Nvy1f5/i/+6vOypxHFl
FPJd3r3HVFE1EeN7vsoy00n8+lIA9Nq/PB+86w2u9kxuwXeJ46j4ows+EaZym044bdtQpGti3zw7 78OHLzdouvU3kgKPaLdrb9v3RIukWZwWlzQn1/vFlM8/3//yvvlSTG7KRxtf7vMTPeh6nWidM833
mnz9zTeiIVpfH06fNZ/x8cRyvtIBhovPBIFgGS2Lim34sG+JNIj1Hylx1PtMl49zE9sKdsXGNiWP NS8EVh4sVAz5+Nnk+qu7+2Icx/MZtXL17Gefq2f0640mzneavv2EfrgZzIovU3hvJYeDQ369ksV7
7OR641Kl9RQkocCX5eHFqEQSHBNNboCD0i/MTBE/uE1lENd039GwN4IUO4Yoff2+S8bMxJ3lrdgt Jo0XL0s/6O0/eMSuUT2v58Heo4mwGLnqIxUFk9p6+52SZj1J1Z6bnNNbTzrUTh2MvtdO23xa7rxN
aL7qvQE8/ijoDpCR3Y1lCFhge7HdDkrUDmGFeNUU9wWpz7gNBuAhI76rPm3ugarVsWqi0xGt3H51 Z9DNBglm+Hby7uB45WV1FRnTg3i+2RLiYyH8Zz91dnYuuy93tsPLrz7wl04oPlc/RaSKL5vUGb91
m1gUJGCjIRVZ14TynuuW3uAErZBulSepbZIyHXc3ZE4ihHAs86dBqfkqHoo2CbS+uDesFIY3msl0 0giTHs7ZCS8/e2SO7qzfNzxfPr5WN7y8++S8lY68viRVXvpRbkzo3+QOfWcBUYTRWv1vQrbMRUjK
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
AA== gste4Y31BuAoLf5a13nNS7qGp1/LTNdmzimJ7SSy/96lGH+XCyNi0vxDNhif/2alLxf1//7Dlw/D
/m+P/vhoWf/zP3Hd4xA9UAooNgoJL3WiwCKUoYetwGp+NOxiTEr7AHRzzIG7Qh3EdpkpKQyIBkqH
qqI0HcKQJGyh9csUeKeBaqaSXE/RZrUuPPIETuUG7XN8wZUmCHVt1xVsvLLPLWgKLHB4Zx1V/mP/
YaaXFOhuYUviSbzz/CBGwpbQq3+3/Xx/7+DbbadvOpXNdOp1m5Z4o6Gomc+2n2PkAxcp9Tu4UZVn
rBTOiqE0NafCQOhMIT0/mtM8TSuJw9jWTR1VkzRyv+yaOvW4cXvaNpl31nRK3v5hj7slg3jIFTXd
fN74GcXsnJxnw3cdZAtc/NXiHzc81upds81+1S5KbX7Zv4wR5lKVk3KnYb6gHBz3iU7j1nYctHRe
1LxZG9fJhkhLP/pyJEbjgkFfUbX9Ymj7Sbu9ntMkctsQd6UlnX8CqHPc743Id3smdv/2cn+bqqOb
CmEhFq8BCl+hluVt9gOMABqT+6lFmY0elnCXMS2QX2pHSeqIpHUag4I3iO08AQnK2NaC21Kgjojn
tNRM78JUk1XN3ymMjC0cqEpZ2BahO7etwwZrSGYSrNlvXwU7Xk7kgJ9i8QDTL4Kdb1Iao1CijIkQ
ObkiQSxM4peI7tyHLeirobUPub+Gbahhu2msBZ00tI8GdvzAp9r6RpJVHcP0IwzGnW3naEmFAUgU
toDo/vnhIx/jX1HTs/8FpIfX2qDk7ImEJAaN3ZQKek3YbLnUNUr0RtqJbjnEsOcU4T/ML522bibl
WzMWhXewM3luuzancxc1a3ulaZG8LA12tK3ZuKGghDr5PdnQxkiJxu/yfCwRNU7lbGmEOtN/TQvJ
RLaBXXfDx4U9ckJjZu/vvpmfgwU/YB87OqQca4cp13L2S+eQbH/DhQ19GNQ2n9syxis4gdJ4jsck
dwMq7DmGzmBs/b6wl3x9D3nuQlJxtzmYjzuedbTfWclN0bTEmBcnCMT7RSXd5iWkl8QDBwlxQnGs
avM2RHvt3RYFHdO6/5/q4PJaXstreS2v5bW8ltfyWl7La3ktr+W1vJbX8lpey2t5La//ketfxBC2
bgCgAAA=
+77 -12
View File
@@ -23,6 +23,7 @@
# Subcommands: # Subcommands:
# check (default) declared vs running vs latest, seal state, and whether # check (default) declared vs running vs latest, seal state, and whether
# an unattended update would be safe here. Changes nothing. # an unattended update would be safe here. Changes nothing.
# snapshot take a raft snapshot and leave it in SNAPSHOT_DIR. Nothing else.
# update do it now: preflight -> snapshot -> down -> pin -> pull -> up # update do it now: preflight -> snapshot -> down -> pin -> pull -> up
# run what the schedule invokes; obeys UPDATE_POLICY # run what the schedule invokes; obeys UPDATE_POLICY
# install schedule the daily `run` # install schedule the daily `run`
@@ -35,17 +36,29 @@
# #
# Env (also read from /etc/openbao-update.conf; environment wins): # Env (also read from /etc/openbao-update.conf; environment wins):
# STACK_DIR=/srv/openbao UPDATE_POLICY=notify # STACK_DIR=/srv/openbao UPDATE_POLICY=notify
# BAO_TOKEN= required by `update`: the snapshot is token-gated # BAO_TOKEN= required by `update`/`snapshot`. Prefer NOT to set
# this inline -- see "Supplying the token" below.
# BAO_TOKEN_FILE= read the token from a file (0600) instead
# SNAPSHOT_DIR=/var/backups/openbao # SNAPSHOT_DIR=/var/backups/openbao
# TARGET_VERSION= pin a specific version instead of latest # TARGET_VERSION= pin a specific version instead of latest
# DRY_RUN=0 print what would happen, change nothing # DRY_RUN=0 print what would happen, change nothing
# SKIP_SNAPSHOT=0 DANGEROUS: upgrade with no rollback plan # SKIP_SNAPSHOT=0 DANGEROUS: upgrade with no rollback plan
# GH_REPO=openbao/openbao # GH_REPO=openbao/openbao
# #
# Supplying the token:
# `BAO_TOKEN=<root> bash update.sh update` works, but DO NOT do it: the value
# lands in your shell history, and anything on the argv of a child process is
# readable by every user on the box via /proc/<pid>/cmdline. This script never
# puts the token on any command line -- it hands it to the container over
# stdin -- and it will prompt for it with echo off if you do not supply one.
# For unattended use, point BAO_TOKEN_FILE at a 0600 file.
#
# Usage: # Usage:
# bash update.sh check # bash update.sh check # no token needed
# BAO_TOKEN=<root> bash update.sh update # bash update.sh snapshot # prompts for the token
# BAO_TOKEN=<root> TARGET_VERSION=2.6.2 bash update.sh update # bash update.sh update # prompts for the token
# BAO_TOKEN_FILE=/root/.bao-root bash update.sh update
# TARGET_VERSION=2.6.2 bash update.sh update
set -euo pipefail set -euo pipefail
@@ -65,6 +78,7 @@ fi
: "${STACK_DIR:=/srv/openbao}" : "${STACK_DIR:=/srv/openbao}"
: "${UPDATE_POLICY:=notify}" : "${UPDATE_POLICY:=notify}"
: "${BAO_TOKEN:=}" : "${BAO_TOKEN:=}"
: "${BAO_TOKEN_FILE:=}"
: "${SNAPSHOT_DIR:=/var/backups/openbao}" : "${SNAPSHOT_DIR:=/var/backups/openbao}"
: "${TARGET_VERSION:=}" : "${TARGET_VERSION:=}"
: "${DRY_RUN:=0}" : "${DRY_RUN:=0}"
@@ -105,6 +119,44 @@ env_get() { # <KEY> -> value from .env ('' if absent)
sed -n "s/^$1=//p" "$ENV_FILE" | tail -n1 sed -n "s/^$1=//p" "$ENV_FILE" | tail -n1
} }
# ---------------------------------------------------------------------------
# Token handling
#
# The token never appears on a command line. `docker exec -e BAO_TOKEN=<value>`
# would put it in the docker process's argv, and /proc/<pid>/cmdline is
# world-readable -- so it would leak to every user on the host, not just to the
# shell history. Instead the value is written to the container's stdin and the
# container's own shell reads it into the environment it then execs `bao` with.
# ---------------------------------------------------------------------------
acquire_token() {
[[ -n "$BAO_TOKEN" ]] && return 0
if [[ -n "$BAO_TOKEN_FILE" ]]; then
[[ -r "$BAO_TOKEN_FILE" ]] || die "BAO_TOKEN_FILE=${BAO_TOKEN_FILE} is not readable."
BAO_TOKEN="$(head -n1 "$BAO_TOKEN_FILE")"
[[ -n "$BAO_TOKEN" ]] || die "BAO_TOKEN_FILE=${BAO_TOKEN_FILE} is empty."
return 0
fi
if [[ -t 0 ]]; then
read -rs -p "OpenBao token (input hidden): " BAO_TOKEN
printf '\n' >&2
[[ -n "$BAO_TOKEN" ]] || die "No token entered."
return 0
fi
die "No token available and stdin is not a terminal. Set BAO_TOKEN_FILE=/path/to/a/0600/file, or run interactively to be prompted. Avoid BAO_TOKEN=... on the command line: it goes into your shell history and into /proc/<pid>/cmdline."
}
# Run `bao <args...>` inside the container with BAO_TOKEN set, passing the token
# over stdin so it never reaches an argv anywhere on the host.
bao_with_token() {
printf '%s\n' "$BAO_TOKEN" | dc exec -T openbao sh -c '
read -r _t
BAO_TOKEN="$_t"
export BAO_TOKEN
exec "$@"
' sh bao "$@"
}
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# State # State
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -178,7 +230,7 @@ take_snapshot() { # <target-version> -> echoes the snapshot path
return 0 return 0
fi fi
[[ -n "$BAO_TOKEN" ]] || die "BAO_TOKEN is required: the raft snapshot is token-gated (sys/storage/raft/snapshot). The root token works." acquire_token
if [[ "$DRY_RUN" == "1" ]]; then if [[ "$DRY_RUN" == "1" ]]; then
echo "DRY: snapshot -> ${dest}" >&2 echo "DRY: snapshot -> ${dest}" >&2
@@ -190,8 +242,8 @@ take_snapshot() { # <target-version> -> echoes the snapshot path
log "Taking a raft snapshot to ${dest}..." >&2 log "Taking a raft snapshot to ${dest}..." >&2
# Write inside the container, then stream the RAW file out. `compose cp` is # Write inside the container, then stream the RAW file out. `compose cp` is
# NOT usable here: it emits a TAR wrapper that will not restore. # NOT usable here: it emits a TAR wrapper that will not restore.
dc exec -T -e BAO_TOKEN="$BAO_TOKEN" openbao \ bao_with_token operator raft snapshot save \
bao operator raft snapshot save -address=https://127.0.0.1:8200 -tls-skip-verify /tmp/pre-upgrade.snap \ -address=https://127.0.0.1:8200 -tls-skip-verify /tmp/pre-upgrade.snap \
>/dev/null || die "Snapshot failed -- not upgrading. Check the token has sys/storage/raft/snapshot." >/dev/null || die "Snapshot failed -- not upgrading. Check the token has sys/storage/raft/snapshot."
dc exec -T openbao cat /tmp/pre-upgrade.snap > "$dest" || die "Could not stream the snapshot out -- not upgrading." dc exec -T openbao cat /tmp/pre-upgrade.snap > "$dest" || die "Could not stream the snapshot out -- not upgrading."
dc exec -T openbao rm -f /tmp/pre-upgrade.snap >/dev/null 2>&1 || true dc exec -T openbao rm -f /tmp/pre-upgrade.snap >/dev/null 2>&1 || true
@@ -250,7 +302,7 @@ do_check() {
log "Update available: ${declared:-?} -> ${latest}" log "Update available: ${declared:-?} -> ${latest}"
if [[ "$auto" == no ]]; then if [[ "$auto" == no ]]; then
warn "A restart will leave the vault SEALED until someone enters three unseal keys." warn "A restart will leave the vault SEALED until someone enters three unseal keys."
warn "Run it when you can do that: BAO_TOKEN=<token> bash ${SELF} update" warn "Run it when you can do that: bash ${SELF} update (it will prompt for a token)"
fi fi
} }
@@ -332,6 +384,16 @@ data store. Restore the pre-upgrade snapshot -- see the README's DR runbook.
EOF EOF
} }
do_snapshot() {
[[ -d "$STACK_DIR" ]] || die "No stack at $STACK_DIR (set STACK_DIR)."
container_running || die "The openbao container is not running."
local state; state="$(seal_state)"
[[ "$state" == 0 ]] || die "The vault is SEALED (or unreadable): a snapshot can only be taken from an unsealed vault."
local dest; dest="$(take_snapshot manual)"
[[ -n "$dest" ]] || die "No snapshot was taken."
log "Snapshot written to ${dest} -- copy it OFF this host."
}
do_run() { do_run() {
# What the schedule invokes. The whole point of this branch is that it is # What the schedule invokes. The whole point of this branch is that it is
# conservative: an unattended update of a manually-unsealed vault takes it # conservative: an unattended update of a manually-unsealed vault takes it
@@ -371,9 +433,11 @@ STACK_DIR=${STACK_DIR}
UPDATE_POLICY=${UPDATE_POLICY} UPDATE_POLICY=${UPDATE_POLICY}
SNAPSHOT_DIR=${SNAPSHOT_DIR} SNAPSHOT_DIR=${SNAPSHOT_DIR}
GH_REPO=${GH_REPO} GH_REPO=${GH_REPO}
# BAO_TOKEN is intentionally absent. \`update\` needs one for the snapshot; pass # BAO_TOKEN is intentionally absent: a long-lived root token in a file next to
# it in the environment for that run. UPDATE_POLICY=auto therefore only works # the vault it opens defeats the vault. \`update\`/\`snapshot\` prompt for one.
# with auto-unseal AND a token supplied some other way. # For unattended use set BAO_TOKEN_FILE below to a 0600 file holding a token
# with sys/storage/raft/snapshot -- not the root token.
#BAO_TOKEN_FILE=/root/.bao-snapshot-token
EOF EOF
chmod 0600 "$OPENBAO_UPDATE_CONF" chmod 0600 "$OPENBAO_UPDATE_CONF"
} }
@@ -439,9 +503,10 @@ do_uninstall() {
case "${1:-check}" in case "${1:-check}" in
check) do_check ;; check) do_check ;;
snapshot) do_snapshot ;;
update) do_update ;; update) do_update ;;
run) do_run ;; run) do_run ;;
install) do_install ;; install) do_install ;;
uninstall) do_uninstall ;; uninstall) do_uninstall ;;
*) die "Usage: $(basename "$0") {check|update|run|install|uninstall}" ;; *) die "Usage: $(basename "$0") {check|snapshot|update|run|install|uninstall}" ;;
esac esac