#!/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 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-, 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 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 " 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 |revoke" >&2; exit 2 ;; esac