175 lines
6 KiB
Bash
Executable file
175 lines
6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -Eeuo pipefail
|
|
# vault-audit-mini-scan-v2.sh
|
|
# Ziel:
|
|
# - Nutzung aus Vault-Audit-Log (sudo-read) zeigen
|
|
# - CERT-Mappings erkennen wie in deinen mTLS-Skripten (Priorität):
|
|
# 1) request.data.name == mapping
|
|
# 2) Marker-Policy (z.B. marker-cert-auth) im Event
|
|
# 3) Superset aller Mapping-Policies im Event
|
|
# 4) CN-Match gegen allowed_common_names
|
|
# 5) Display-Name-Match
|
|
# - PKI-Rollen: issue/sign-Events zählen
|
|
# - Policies: bei Logins vergebene Policies zählen
|
|
#
|
|
# Aufruf:
|
|
# ./vault-audit-mini-scan.sh \
|
|
# --audit /home/vault/file/audit.log --mount pki-test \
|
|
# [--since 2025-09-30T00:00:00Z] [--strict]
|
|
#./vault-audit-mini-scan.sh --since "$(date -u -d '7 days ago' +%Y-%m-%dT00:00:00Z)"
|
|
# --strict: Nur Marker oder Policy-Superset werten (ohne Name/CN/Display)
|
|
#
|
|
# Exit: 0 OK, 2 fehlende Tools/Args
|
|
|
|
# ---------- Defaults ----------
|
|
AUDIT="/home/vault/file/audit.log"
|
|
MOUNT="pki-test"
|
|
SINCE=""
|
|
STRICT=false
|
|
|
|
# ---------- Args ----------
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--audit) AUDIT="$2"; shift 2;;
|
|
--mount) MOUNT="$2"; shift 2;;
|
|
--since) SINCE="$2"; shift 2;;
|
|
--strict) STRICT=true; shift 1;;
|
|
-h|--help) sed -n '1,120p' "$0"; exit 0;;
|
|
*) echo "unknown arg: $1" >&2; exit 2;;
|
|
esac
|
|
done
|
|
|
|
# ---------- Deps ----------
|
|
need(){ command -v "$1" >/dev/null || { echo "missing: $1" >&2; exit 2; }; }
|
|
need jq; need awk; need sort; need wc; need sed
|
|
VAULT_OK=true; command -v vault >/dev/null || VAULT_OK=false
|
|
[[ -r "$AUDIT" ]] || sudo test -r "$AUDIT" || { echo "audit not readable: $AUDIT" >&2; exit 2; }
|
|
|
|
# ---------- Helpers ----------
|
|
SUDO_JQ(){ sudo jq -r "$@" "$AUDIT"; }
|
|
|
|
# usage: JQ_TIMES '<jq-filter>' [jq-extra-args...]
|
|
# gibt NUR .time zurück; respektiert --since
|
|
JQ_TIMES(){
|
|
local filter="$1"; shift || true
|
|
if [[ -n "$SINCE" ]]; then
|
|
SUDO_JQ "$@" --arg since "$SINCE" "$filter | .time | select(. >= \$since)"
|
|
else
|
|
SUDO_JQ "$@" "$filter | .time"
|
|
fi
|
|
}
|
|
|
|
summarize_times(){
|
|
local lines cnt last
|
|
lines="$(cat)"
|
|
cnt=$(printf "%s\n" "$lines" | sed '/^$/d' | wc -l | awk '{print $1}')
|
|
last=$(printf "%s\n" "$lines" | sed '/^$/d' | tail -n1)
|
|
printf "use=%-6s last=%s\n" "${cnt:-0}" "${last:--}"
|
|
}
|
|
|
|
# ---------- Inventar ----------
|
|
readarray -t CERT_MAPS < <(
|
|
if $VAULT_OK && vault list -format=json auth/cert/certs >/dev/null 2>&1; then
|
|
vault list -format=json auth/cert/certs | jq -r '.[]'
|
|
else
|
|
SUDO_JQ 'select(.request.path=="auth/cert/login") | .request.data.name' 2>/dev/null | sort -u
|
|
fi
|
|
)
|
|
|
|
readarray -t PKI_ROLES < <(
|
|
if $VAULT_OK && vault list -format=json "$MOUNT/roles" >/dev/null 2>&1; then
|
|
vault list -format=json "$MOUNT/roles" | jq -r '.[]'
|
|
else
|
|
SUDO_JQ --arg m "$MOUNT" '
|
|
select((.request.path|startswith($m+"/issue/")) or (.request.path|startswith($m+"/sign/")))
|
|
| .request.path | capture("(?<op>issue|sign)/(?<role>[^/]+)$").role' | sort -u
|
|
fi
|
|
)
|
|
|
|
readarray -t POLICIES < <(
|
|
if $VAULT_OK && vault policy list -format=json >/dev/null 2>&1; then
|
|
vault policy list -format=json | jq -r '.[]'
|
|
else
|
|
SUDO_JQ 'select(.response.auth.policies!=null) | .response.auth.policies[]' | sort -u
|
|
fi
|
|
)
|
|
|
|
printf "=== AUDIT === %s\n\n" "$AUDIT"
|
|
|
|
# ---------- CERT MAPPINGS ----------
|
|
echo "=== CERT MAPPINGS (auth/cert/login) ==="
|
|
for m in "${CERT_MAPS[@]}"; do
|
|
[[ -n "$m" ]] || continue
|
|
printf "%-32s " "$m"
|
|
|
|
PM="[]"; ACN="[]"; DISP=""; MARKER=""
|
|
if $VAULT_OK; then
|
|
MAPJSON="$(vault read -format=json "auth/cert/certs/$m" 2>/dev/null || true)"
|
|
if [[ -n "$MAPJSON" ]]; then
|
|
PM="$(jq -c '(.data.policies // [])' <<<"$MAPJSON")"
|
|
ACN="$(jq -c '(.data.allowed_common_names // [])' <<<"$MAPJSON")"
|
|
DISP="$(jq -r '(.data.display_name // empty)' <<<"$MAPJSON")"
|
|
MARKER="$(jq -r '(.data.policies // [])[]? | select(test("^marker-"))' <<<"$MAPJSON" | head -n1 || true)"
|
|
fi
|
|
fi
|
|
|
|
if $STRICT; then
|
|
if [[ -n "$MARKER" ]]; then
|
|
# Marker-Policy hart werten -> Event-Objekt zurückgeben
|
|
JQ_TIMES '
|
|
select(.request.path=="auth/cert/login") as $e
|
|
| ($e.response.auth.policies // []) as $pol
|
|
| if (($marker != "") and (($pol | index($marker)) != null)) then $e else empty end
|
|
' --arg marker "$MARKER" | summarize_times
|
|
else
|
|
# Policy-Superset
|
|
JQ_TIMES '
|
|
select(.request.path=="auth/cert/login") as $e
|
|
| ($e.response.auth.policies // []) as $pol
|
|
| (reduce $pm[] as $x (true; . and (($pol | index($x)) != null))) as $ok
|
|
| if $ok then $e else empty end
|
|
' --argjson pm "$PM" | summarize_times
|
|
fi
|
|
else
|
|
# Breite Heuristik: Name → Marker → Policies → CN → Display
|
|
JQ_TIMES '
|
|
select(.request.path=="auth/cert/login") as $e
|
|
| (
|
|
((($e.request.data.name // "") == $m))
|
|
or
|
|
((($marker != "")) and ((($e.response.auth.policies // []) | index($marker)) != null))
|
|
or
|
|
((reduce $pm[] as $x (true; . and (((($e.response.auth.policies // [])) | index($x)) != null))))
|
|
or
|
|
((($e.response.auth.metadata.common_name // "") as $cn
|
|
| ( ($acn | length) > 0 ) and ( [ $acn[] | select(. == $cn) ] | length > 0 )))
|
|
or
|
|
((($disp != "")) and (($e.response.auth.display_name // "") == $disp))
|
|
) as $ok
|
|
| if $ok then $e else empty end
|
|
' \
|
|
--arg m "$m" \
|
|
--arg marker "$MARKER" \
|
|
--arg disp "$DISP" \
|
|
--argjson pm "$PM" \
|
|
--argjson acn "$ACN" | summarize_times
|
|
fi
|
|
done
|
|
|
|
# ---------- PKI ROLES ----------
|
|
echo; echo "=== PKI ROLES ($MOUNT/issue|sign) ==="
|
|
for r in "${PKI_ROLES[@]}"; do
|
|
[[ -n "$r" ]] || continue
|
|
printf "%-32s " "$r"
|
|
JQ_TIMES 'select((.request.path==($m+"/issue/"+$r)) or (.request.path==($m+"/sign/"+$r)))' \
|
|
--arg r "$r" --arg m "$MOUNT" | summarize_times
|
|
done
|
|
|
|
# ---------- POLICIES ----------
|
|
echo; echo "=== POLICIES (minted on login) ==="
|
|
for p in "${POLICIES[@]}"; do
|
|
[[ -n "$p" ]] || continue
|
|
printf "%-32s " "$p"
|
|
JQ_TIMES 'select(.response.auth.policies? and (.response.auth.policies | index($p)))' \
|
|
--arg p "$p" | summarize_times
|
|
done
|