184 lines
5.7 KiB
Bash
Executable file
184 lines
5.7 KiB
Bash
Executable file
#!/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."
|
||
|