vault-ops/infra/versions/cleanup-free-pki-roles-v2.sh
2026-04-14 11:45:15 +07:00

184 lines
5.7 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
set -Eeuo pipefail
# cleanup-free-pki-roles.sh (v2)
# Löscht NUR PKI-Rollen, die "chain-free" sind:
# - Standard (konservativ): Rolle wird von KEINER Policy referenziert (policy_free)
# UND es gibt damit auch keine Policy-Kette via Cert/AppRole (chain_free).
# - --aggressive: löscht zusätzlich Rollen, die zwar von Policies referenziert werden,
# deren Policies aber von KEINEM Cert/AppRole genutzt werden (also "policy_used=true, chain_used=false").
#
# Optionen:
# --apply wirklich löschen (ohne: Dry-Run)
# --report FILE Report-Datei (aus vault-inventory-report.sh); sonst: neueste /tmp/vault-inventory-*.json
# --aggressive s. oben
# --debug ausführliche Fortschrittslogs
#
# Exit-Codes: 0=ok, 1=nichts zu tun, 2=Fehler
if [[ -t 1 ]]; then B=$'\e[1m'; R=$'\e[0m'; else B= R=; fi
ts(){ date +"%Y-%m-%d %H:%M:%S"; }
info(){ echo -e "🟦 ${B}[$(ts)]${R} $*"; }
ok(){ echo -e "🟩 ${B}[$(ts)]${R} $*"; }
warn(){ echo -e "🟨 ${B}[$(ts)]${R} $*"; }
err(){ echo -e "🟥 ${B}[$(ts)]${R} $*" >&2; }
need(){ command -v "$1" >/dev/null 2>&1 || { err "missing tool: $1"; exit 2; }; }
need vault
need jq
APPLY=0
REPORT=""
AGGR=0
DEBUG=0
while (( $# )); do
case "$1" in
--apply) APPLY=1 ;;
--report) REPORT="${2:-}"; shift ;;
--aggressive) AGGR=1 ;;
--debug) DEBUG=1 ;;
-h|--help)
cat <<EOF
Usage: $0 [--report FILE] [--apply] [--aggressive] [--debug]
Deletes PKI roles that are safe to remove:
default: only roles not referenced by any policy (policy_free) -> implies chain_free
--aggressive: also delete roles referenced only by policies not used by any cert/approle (chain_free but not policy_free)
Options:
--report FILE Use specific vault-inventory-*.json
--apply Perform deletions (default: dry-run)
--aggressive Include roles referenced only by unused policies
--debug Verbose progress
EOF
exit 0 ;;
*) err "unknown arg: $1"; exit 2 ;;
esac
shift
done
dbg(){ [[ $DEBUG -eq 1 ]] && echo -e " · $*"; }
# Report wählen
if [[ -z "$REPORT" ]]; then
REPORT="$(ls -1t /tmp/vault-inventory-*.json 2>/dev/null | head -n1 || true)"
fi
[[ -f "$REPORT" ]] || { err "Report nicht gefunden. Nutze --report FILE"; exit 2; }
# Reachability
if ! S="$(vault status -format=json 2>/dev/null)"; then
err "Vault nicht erreichbar"; exit 2
fi
if [[ "$(jq -r '.sealed' <<<"$S")" != "false" ]]; then
err "Vault ist sealed"; exit 2
fi
info "Report: $REPORT"
info "Modus: $([[ $APPLY -eq 1 ]] && echo APPLY || echo DRY-RUN) | aggressive=$AGGR | debug=$DEBUG"
# Schritt 1: Grundmengen aus Report
dbg "lade pki_roles/all"
PKI_ALL="$(jq -r '[.pki_roles[]?.name] | unique' "$REPORT")"
dbg "lade policies/all"
POL_ALL="$(jq -r '.policies // []' "$REPORT")"
dbg "lade cert+approle referenzierte policies"
USED_POL_NAMES="$(
jq -r '
[ (.policies // [])
| .[]
| select((.referenced_by.cert_mappings|length>0) or (.referenced_by.approle_roles|length>0))
| .name
] | unique
' "$REPORT"
)"
# Schritt 2: Rollen, die von IRGEND einer Policy referenziert werden (policy_used)
dbg "berechne roles_referenced_by_any_policy"
ROLES_BY_ANY_POLICY="$(
jq -r '
[ (.policies // [])
| .[]
| (.pki_issue_roles // [])[]
] | unique
' "$REPORT"
)"
# Schritt 3: Rollen, die von "USED" Policies (via cert/approle) referenziert werden (chain_used)
dbg "berechne roles_referenced_by_used_policies"
ROLES_BY_USED_POLICY="$(
jq -r --argjson used "$USED_POL_NAMES" '
def as_set: reduce .[] as $x ({}; .[$x]=true);
($used // []) as $U
| ($U | as_set) as $USET
| [ (.policies // [])
| .[]
| select(($USET[.name] // false))
| (.pki_issue_roles // [])[]
] | unique
' "$REPORT"
)"
# Schritt 4: Kandidaten bilden
dbg "berechne non_aggressive deletions (policy_free ∧ chain_free)"
TO_DELETE_NON_AGGR="$(
jq -r --argjson all "$PKI_ALL" --argjson byAny "$ROLES_BY_ANY_POLICY" '
def as_set: reduce .[] as $x ({}; .[$x]=true);
($byAny | as_set) as $ANY
| [ $all[] | select(($ANY[.] // false) | not) ]
' <<< '{}'
)"
dbg "berechne aggressive-only deletions (chain_free ∧ ¬policy_free)"
TO_DELETE_ONLY_AGGR="$(
jq -r --argjson all "$PKI_ALL" --argjson byAny "$ROLES_BY_ANY_POLICY" --argjson byUsed "$ROLES_BY_USED_POLICY" '
def as_set: reduce .[] as $x ({}; .[$x]=true);
($byAny | as_set) as $ANY
| ($byUsed | as_set) as $USED
| [ $all[]
| select( ($USED[.] // false) | not ) # chain_free
| select( ($ANY[.] // false) ) # aber policy_used
] | unique
' <<< '{}'
)"
if [[ "$AGGR" == "1" ]]; then
dbg "vereinige non_aggr only_aggr"
TO_DELETE_NAMES="$(
jq -r --argjson a "$TO_DELETE_NON_AGGR" --argjson b "$TO_DELETE_ONLY_AGGR" '
[ ($a + $b)[] ] | unique
' <<< '{}'
)"
else
TO_DELETE_NAMES="$TO_DELETE_NON_AGGR"
fi
COUNT="$(jq -r 'length' <<<"$TO_DELETE_NAMES")"
dbg "PKI_ALL=$(jq -r 'length' <<<"$PKI_ALL"), byAny=$(jq -r 'length' <<<"$ROLES_BY_ANY_POLICY"), byUsed=$(jq -r 'length' <<<"$ROLES_BY_USED_POLICY"), del=$COUNT"
if (( COUNT == 0 )); then
ok "Keine passenden PKI-Rollen zu löschen."
exit 1
fi
info "Zu löschende PKI-Rollen ($COUNT):"
jq -r '.[] | " - " + .' <<<"$TO_DELETE_NAMES"
# Schritt 5: Live-Doppelcheck & Aktion
while read -r role; do
[[ -n "$role" ]] || continue
if ! vault read -format=json "pki-test/roles/$role" >/dev/null 2>&1; then
warn "skip (nicht gefunden): pki-test/roles/$role"
continue
fi
if (( APPLY == 1 )); then
vault delete "pki-test/roles/$role" \
&& ok "deleted: pki-test/roles/$role" \
|| err "FAIL delete: $role"
else
echo "DRY-RUN: would delete pki-test/roles/$role"
fi
done < <(jq -r '.[]' <<<"$TO_DELETE_NAMES")
ok "fertig."