Reviewable governance scaffolding — NOTHING applied to live OpenBAO yet: - policies/: materialize all existing policies as code (faithfully fetched from live) + new `operator` (use engines, no admin) and `auditor` (read-only governance visibility, no secret material) - scripts/apply-policies.sh: idempotent policy-as-code apply, with a read-only --dry-run that diffs files vs live (ignores comments) - scripts/setup-identity.sh: identity-as-code — policy-bound groups (g-admins/operators/auditors/personal) + a human entity/alias; DEFAULT DRY-RUN, --apply to execute - GOVERNANCE.md: the layered model, policy catalog, naming, apply order, and cross-cutting controls (audit device, root offline, AppRole migration) Dry-runs verified read-only: apply-policies shows operator/auditor as NEW, all others unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
40 lines
2.1 KiB
Bash
Executable File
40 lines
2.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Policy-as-code: apply every policies/*.hcl to OpenBAO. Idempotent.
|
|
# apply-policies.sh --dry-run # read-only: show NEW/CHANGED/unchanged, change nothing
|
|
# apply-policies.sh # apply (writes policies that are NEW or CHANGED)
|
|
#
|
|
# Auth: needs a token allowed to write sys/policies/acl/* (root or admin).
|
|
# Uses $BAO_TOKEN if set, else falls back to the root token in init-output.json.
|
|
# Built-in `default`/`root` policies have no file here and are never touched.
|
|
set -euo pipefail
|
|
|
|
SELF="$(cd "$(dirname "$0")" && pwd)"; ROOT_DIR="$(cd "$SELF/.." && pwd)"
|
|
DIR="$ROOT_DIR/policies"; INIT="$ROOT_DIR/init-output.json"
|
|
ADDR="${BAO_ADDR:-http://127.0.0.1:8200}"
|
|
DRY=0; [ "${1:-}" = "--dry-run" ] && DRY=1
|
|
|
|
TOKEN="${BAO_TOKEN:-}"
|
|
[ -z "$TOKEN" ] && [ -r "$INIT" ] && TOKEN="$(python3 -c "import json;print(json.load(open('$INIT'))['root_token'])")"
|
|
[ -n "$TOKEN" ] || { echo "no token (set BAO_TOKEN or provide init-output.json)" >&2; exit 1; }
|
|
|
|
# functional comparison: ignore comments and blank lines
|
|
norm(){ grep -vE '^[[:space:]]*#' | grep -vE '^[[:space:]]*$' | sed 's/[[:space:]]*$//'; }
|
|
|
|
echo "${DRY:+[dry-run] }policies under $DIR -> $ADDR"
|
|
for f in "$DIR"/*.hcl; do
|
|
name="$(basename "$f" .hcl)"; filetxt="$(cat "$f")"
|
|
live="$(curl -sS -H "X-Vault-Token: $TOKEN" "$ADDR/v1/sys/policies/acl/$name" \
|
|
| python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('data',{}).get('policy','') if 'data' in d else '')" 2>/dev/null || true)"
|
|
if [ -z "$live" ]; then status="NEW"
|
|
elif [ "$(printf '%s' "$filetxt" | norm)" = "$(printf '%s' "$live" | norm)" ]; then status="unchanged"
|
|
else status="CHANGED"; fi
|
|
|
|
if [ "$DRY" = 1 ] || [ "$status" = unchanged ]; then
|
|
printf ' %-10s %s\n' "$status" "$name"; continue
|
|
fi
|
|
code="$(printf '%s' "$filetxt" | python3 -c 'import json,sys;print(json.dumps({"policy":sys.stdin.read()}))' \
|
|
| curl -sS -o /dev/null -w '%{http_code}' -H "X-Vault-Token: $TOKEN" --data @- "$ADDR/v1/sys/policies/acl/$name")"
|
|
printf ' %-10s %s (HTTP %s)\n' "applied[$status]" "$name" "$code"
|
|
done
|
|
[ "$DRY" = 1 ] && echo "(dry-run — nothing changed)" || true
|