Compare commits
2 Commits
9f4b8c9149
...
ab00fc6296
| Author | SHA1 | Date | |
|---|---|---|---|
| ab00fc6296 | |||
|
|
7e8568777e |
96
scripts/provision-openbao.sh
Executable file
96
scripts/provision-openbao.sh
Executable file
@@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Idempotently (re)provision the OpenBao objects mcpd's secret backend needs.
|
||||||
|
#
|
||||||
|
# WHY THIS EXISTS: the KV mount + `app-mcpd` ACL policy + `app-mcpd-role` token
|
||||||
|
# role were hand-created and NOT captured anywhere, so an OpenBao rebuild/re-init
|
||||||
|
# silently dropped the policy — leaving mcpd with valid-looking tokens that
|
||||||
|
# granted nothing (403 on every secret write). This script makes that
|
||||||
|
# provisioning reproducible: run it after any OpenBao (re)init to restore mcpd's
|
||||||
|
# access. It is safe to run repeatedly.
|
||||||
|
#
|
||||||
|
# It mirrors mcpd's own wizard (src/shared/src/vault/policy.ts) — keep them in sync.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/provision-openbao.sh [--seed] [--dry-run]
|
||||||
|
# --seed also mint a fresh role token and write it into the `bao-creds`
|
||||||
|
# secret (mcpctl --direct ... --force), then restart mcpd.
|
||||||
|
# --dry-run print what would change; make no writes.
|
||||||
|
#
|
||||||
|
# Env (with sensible homelab defaults):
|
||||||
|
# BAO_ADDR=https://bao.ad.itaz.eu MOUNT=secret PREFIX=mcpctl ROLE=app-mcpd-role
|
||||||
|
# POLICY=app-mcpd PERIOD=2592000 KUBE_CONTEXT=worker0-k8s0
|
||||||
|
# BAO_TOKEN (admin/root) — else read from the openbao-init-credentials secret.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BAO_ADDR="${BAO_ADDR:-https://bao.ad.itaz.eu}"
|
||||||
|
MOUNT="${MOUNT:-secret}"
|
||||||
|
PREFIX="${PREFIX:-mcpctl}"
|
||||||
|
ROLE="${ROLE:-app-mcpd-role}"
|
||||||
|
POLICY="${POLICY:-app-mcpd}"
|
||||||
|
PERIOD="${PERIOD:-2592000}" # 30d periodic token → renews forever
|
||||||
|
KUBE_CONTEXT="${KUBE_CONTEXT:-worker0-k8s0}"
|
||||||
|
MCPCTL_NS="${MCPCTL_NS:-mcpctl}"
|
||||||
|
|
||||||
|
SEED=false; DRY=false
|
||||||
|
for a in "$@"; do case "$a" in --seed) SEED=true;; --dry-run) DRY=true;; esac; done
|
||||||
|
|
||||||
|
say() { printf '\n\033[1;36m>>> %s\033[0m\n' "$*"; }
|
||||||
|
ok() { printf '\033[1;32m ✓ %s\033[0m\n' "$*"; }
|
||||||
|
die() { printf '\033[1;31mERROR: %s\033[0m\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ── Admin token ──
|
||||||
|
if [ -z "${BAO_TOKEN:-}" ]; then
|
||||||
|
BAO_TOKEN="$(kubectl --context "$KUBE_CONTEXT" -n openbao get secret openbao-init-credentials -o jsonpath='{.data.root-token}' 2>/dev/null | base64 -d || true)"
|
||||||
|
fi
|
||||||
|
[ -n "${BAO_TOKEN:-}" ] || die "no BAO_TOKEN and could not read openbao-init-credentials root-token"
|
||||||
|
bao() { curl -fsS -m 15 -H "X-Vault-Token: $BAO_TOKEN" "$@"; }
|
||||||
|
bao -o /dev/null "$BAO_ADDR/v1/auth/token/lookup-self" || die "admin token rejected by $BAO_ADDR (OpenBao may have been re-initialized — update the init-credentials secret)"
|
||||||
|
ok "admin token valid"
|
||||||
|
|
||||||
|
# Canonical policy HCL — must match src/shared/src/vault/policy.ts
|
||||||
|
read -r -d '' POLICY_HCL <<EOF || true
|
||||||
|
path "$MOUNT/data/$PREFIX/*" { capabilities = ["create", "read", "update"] }
|
||||||
|
path "$MOUNT/metadata/$PREFIX/*" { capabilities = ["list", "delete"] }
|
||||||
|
path "$MOUNT/metadata/$PREFIX/" { capabilities = ["list"] }
|
||||||
|
path "auth/token/create/$ROLE" { capabilities = ["create", "update"] }
|
||||||
|
path "auth/token/revoke-accessor" { capabilities = ["update"] }
|
||||||
|
path "auth/token/lookup-self" { capabilities = ["read"] }
|
||||||
|
EOF
|
||||||
|
|
||||||
|
say "Plan: mount=$MOUNT (kv2) prefix=$PREFIX policy=$POLICY role=$ROLE (periodic ${PERIOD}s) seed=$SEED dry-run=$DRY"
|
||||||
|
if [ "$DRY" = true ]; then printf '%s\n' "$POLICY_HCL"; say "dry-run: no writes."; exit 0; fi
|
||||||
|
|
||||||
|
# ── 1. KV v2 mount ──
|
||||||
|
say "1. Ensure KV v2 mount '$MOUNT/'"
|
||||||
|
if bao -o /dev/null "$BAO_ADDR/v1/sys/mounts/$MOUNT" 2>/dev/null; then
|
||||||
|
ok "mount '$MOUNT/' already exists"
|
||||||
|
else
|
||||||
|
bao -X POST "$BAO_ADDR/v1/sys/mounts/$MOUNT" -d '{"type":"kv","options":{"version":"2"}}' >/dev/null
|
||||||
|
ok "created KV v2 mount '$MOUNT/'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 2. Policy ──
|
||||||
|
say "2. Write ACL policy '$POLICY'"
|
||||||
|
bao -X PUT "$BAO_ADDR/v1/sys/policies/acl/$POLICY" \
|
||||||
|
-d "$(python3 -c 'import json,sys; print(json.dumps({"policy": sys.stdin.read()}))' <<<"$POLICY_HCL")" >/dev/null
|
||||||
|
ok "policy '$POLICY' written ($(printf '%s' "$POLICY_HCL" | wc -l) rules)"
|
||||||
|
|
||||||
|
# ── 3. Periodic token role ──
|
||||||
|
say "3. Write token role '$ROLE' (periodic, renewable)"
|
||||||
|
bao -X POST "$BAO_ADDR/v1/auth/token/roles/$ROLE" \
|
||||||
|
-d "$(python3 -c 'import json,sys; print(json.dumps({"allowed_policies":[sys.argv[1]],"period":int(sys.argv[2]),"renewable":True,"token_type":"service","orphan":False}))' "$POLICY" "$PERIOD")" >/dev/null
|
||||||
|
ok "role '$ROLE' written (period=${PERIOD}s, allowed_policies=[$POLICY])"
|
||||||
|
|
||||||
|
# ── 4. Optional: seed bao-creds + restart mcpd ──
|
||||||
|
if [ "$SEED" = true ]; then
|
||||||
|
say "4. Mint token + seed bao-creds + restart mcpd"
|
||||||
|
NEW_TOK="$(bao -X POST "$BAO_ADDR/v1/auth/token/create/$ROLE" | python3 -c 'import sys,json; print(json.load(sys.stdin)["auth"]["client_token"])')"
|
||||||
|
[ -n "$NEW_TOK" ] || die "failed to mint role token"
|
||||||
|
mcpctl --direct create secret bao-creds --data "token=$NEW_TOK" --force >/dev/null
|
||||||
|
ok "bao-creds updated"
|
||||||
|
kubectl --context "$KUBE_CONTEXT" -n "$MCPCTL_NS" rollout restart deployment/mcpd >/dev/null
|
||||||
|
kubectl --context "$KUBE_CONTEXT" -n "$MCPCTL_NS" rollout status deployment/mcpd --timeout=3m
|
||||||
|
ok "mcpd restarted — verify with: mcpctl status (Secrets line should be ✓)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
say "OpenBao provisioning complete."
|
||||||
Reference in New Issue
Block a user