ops-mason/scripts/bao-session.sh
tegwick 680fe471c5 fix(bao-session): drop the TTY guard on login
The OIDC flow reads nothing from stdin — it starts a local callback
listener, prints a URL, and waits for the browser. The guard was copied
from scripts that prompt for secrets, where it belongs, and here it only
blocked the non-interactive invocation the flow supports.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3377672@bnt-lap001
Assistant-Session: 15463ccf-238f-4e13-b163-93aa25c6d166
2026-08-28 11:06:53 +02:00

121 lines
4.6 KiB
Bash
Executable file

#!/usr/bin/env bash
# bao-session.sh — check, or grant, an OpenBao session an agent session can use.
#
# ./scripts/bao-session.sh status what session exists, if any
# ./scripts/bao-session.sh login refresh YOUR operator session (OIDC)
# ./scripts/bao-session.sh grant <task> mint a scoped, time-boxed token for Claude
# ./scripts/bao-session.sh revoke end the granted token immediately
#
# Why `grant` exists
# ------------------
# Handing an agent your own platform-admin session gives it everything you have,
# for as long as you have it, indistinguishable from you in the audit log. The
# grant path instead mints a separate token that is:
#
# * scoped — carries the ops-mason-build policy, which can create policies,
# auth roles and KV *structure* but cannot read secret values.
# SCOPE.md's "never touch secret values" stops being a promise in
# a document and becomes something OpenBao enforces.
# * bounded — 45 minutes, no renewal past one hour.
# * named — display_name claude-<task>, so the audit log says which task an
# action belonged to, not just that platform-root did it.
#
# It is written to ~/.claude-bao-token, NOT ~/.vault-token, so your own operator
# session is untouched and the two can be revoked independently.
#
# Under WSL2 the OIDC browser launch fails (gio cannot open a browser). This
# script prints the URL plainly and waits, rather than appearing to hang.
set -euo pipefail
export BAO_ADDR="${BAO_ADDR:-https://bao.coulomb.social}"
GRANT_FILE="${GRANT_FILE:-$HOME/.claude-bao-token}"
GRANT_POLICY="${GRANT_POLICY:-ops-mason-build}"
GRANT_TTL="${GRANT_TTL:-45m}"
GRANT_MAX_TTL="${GRANT_MAX_TTL:-1h}"
OIDC_PATH="${OIDC_PATH:-netkingdom}"
green() { printf '\033[32m%s\033[0m\n' "$1"; }
red() { printf '\033[31m%s\033[0m\n' "$1"; }
describe() {
# Prints session metadata only. Never the token.
local token="${1:-}" label="$2"
local out
if ! out="$(BAO_TOKEN="$token" bao token lookup -format=json 2>/dev/null)"; then
red " $label: none or expired"
return 1
fi
printf '%s' "$out" | python3 -c "
import sys, json
d = json.load(sys.stdin)['data']
print(' $label: ttl=%s policies=%s name=%s' % (
d.get('ttl'), ','.join(d.get('policies', [])), d.get('display_name') or '-'))
"
}
cmd_status() {
echo "BAO_ADDR=$BAO_ADDR"
describe "$(cat "$HOME/.vault-token" 2>/dev/null || true)" "your operator session" || true
if [[ -f "$GRANT_FILE" ]]; then
describe "$(cat "$GRANT_FILE")" "granted to Claude " || true
else
echo " granted to Claude : none ($GRANT_FILE absent)"
fi
}
cmd_login() {
# No TTY guard: the OIDC flow reads nothing from stdin. It starts a local
# callback listener, prints a URL, and waits for the browser to complete it,
# so this works fine from a non-interactive invocation.
echo "Opening OIDC login. Under WSL2 no browser will launch —"
echo "copy the URL below into your Windows browser, then come back here."
echo
bao login -method=oidc -path="$OIDC_PATH"
echo
cmd_status
}
cmd_grant() {
local task="${1:-}"
if [[ -z "$task" ]]; then
red "usage: $0 grant <task-name> e.g. 'operators-mount'"; exit 2
fi
if ! bao policy read "$GRANT_POLICY" >/dev/null 2>&1; then
red "policy $GRANT_POLICY does not exist yet."
echo "Create it first — see docs, or ask Claude to draft it. Refusing to"
echo "fall back to a broader policy: an unscoped grant is the thing this"
echo "script exists to avoid."
exit 1
fi
umask 077
bao token create \
-policy="$GRANT_POLICY" \
-ttl="$GRANT_TTL" \
-explicit-max-ttl="$GRANT_MAX_TTL" \
-display-name="claude-${task}" \
-field=token > "$GRANT_FILE"
green "granted: $GRANT_POLICY for '$task', ttl $GRANT_TTL (max $GRANT_MAX_TTL)"
echo " file: $GRANT_FILE"
describe "$(cat "$GRANT_FILE")" "granted to Claude " || true
echo
echo "Claude uses it as: BAO_TOKEN=\$(cat $GRANT_FILE) bao <cmd>"
echo "Revoke any time: $0 revoke"
}
cmd_revoke() {
if [[ ! -f "$GRANT_FILE" ]]; then
echo "nothing to revoke ($GRANT_FILE absent)"; return 0
fi
bao token revoke "$(cat "$GRANT_FILE")" >/dev/null 2>&1 || true
shred -u "$GRANT_FILE" 2>/dev/null || rm -f "$GRANT_FILE"
green "granted token revoked and removed"
}
case "${1:-status}" in
status) cmd_status ;;
login) cmd_login ;;
grant) shift; cmd_grant "${1:-}" ;;
revoke) cmd_revoke ;;
*) echo "usage: $0 status|login|grant <task>|revoke" >&2; exit 2 ;;
esac