68 lines
2.7 KiB
Bash
68 lines
2.7 KiB
Bash
#!/usr/bin/env bash
|
|
# edr-isolation-watch.sh — interim EDR auto-isolation alerter.
|
|
#
|
|
# Polls Datto EDR for detections whose automated response included `isolate-host`
|
|
# (i.e. a machine was auto-isolated / "quarantined") and posts each NEW one to the
|
|
# private #dev-alerts channel (Mike + Howard). Runs as a plain scheduled job — no
|
|
# LLM, no tokens. Retire once the GuruRMM EDR webhook integration (Feature 6) lands.
|
|
#
|
|
# Schedule (every 10 min), e.g. cron:
|
|
# */10 * * * * bash /path/to/claudetools/.claude/scripts/edr-isolation-watch.sh >/dev/null 2>&1
|
|
#
|
|
# State: .edr-watch-state.json (gitignored) holds already-alerted alert IDs so each
|
|
# isolation is announced exactly once, even though Mike may un-isolate before the poll.
|
|
|
|
set -uo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
EDR="bash $ROOT/.claude/scripts/py.sh $ROOT/.claude/skills/datto-edr/scripts/edr.py"
|
|
DM="bash $ROOT/.claude/scripts/discord-dm.sh"
|
|
export STATE="$ROOT/.claude/scripts/.edr-watch-state.json"
|
|
TARGET="dev" # #dev-alerts = Mike + Howard, private
|
|
|
|
# Pull last 24h of detections (list includes responseData) and emit any NEW
|
|
# isolate-host events as one pipe-delimited line each; update state in-place.
|
|
new_events="$($EDR --json detections --days 1 --limit 500 2>/dev/null | python -c '
|
|
import sys, json, os
|
|
state_path = os.environ["STATE"]
|
|
try:
|
|
rows = json.load(sys.stdin)
|
|
rows = rows if isinstance(rows, list) else rows.get("data", rows.get("alerts", []))
|
|
except Exception:
|
|
sys.exit(0) # API hiccup: emit nothing, do not churn state
|
|
try:
|
|
seen = set(json.load(open(state_path)))
|
|
except Exception:
|
|
seen = set()
|
|
fresh = []
|
|
for a in rows:
|
|
rd = a.get("responseData") or []
|
|
names = [r.get("name") for r in rd] if isinstance(rd, list) else []
|
|
if "isolate-host" not in names:
|
|
continue
|
|
aid = a.get("id")
|
|
if not aid or aid in seen:
|
|
continue
|
|
seen.add(aid)
|
|
fresh.append("|".join(str(a.get(k, "")) for k in
|
|
("id", "hostname", "organizationName", "severity", "sourceName", "eventTime")))
|
|
# keep state bounded
|
|
json.dump(sorted(seen)[-500:], open(state_path, "w"))
|
|
print("\n".join(fresh))
|
|
' 2>/dev/null)"
|
|
|
|
[ -z "$new_events" ] && exit 0
|
|
|
|
while IFS='|' read -r aid host org sev rule ts; do
|
|
[ -z "$aid" ] && continue
|
|
msg="[EDR AUTO-ISOLATION] **$host** was auto-isolated by Datto EDR
|
|
- Client: $org
|
|
- Rule: $rule (severity: $sev)
|
|
- Time: $ts
|
|
- This machine was cut off the network by an EDR automated response. Verify it is intended before restoring.
|
|
- Console: https://azcomp4587.infocyte.com (alert id: $aid)"
|
|
if ! printf '%s' "$msg" | $DM "$TARGET" 2>/dev/null; then
|
|
bash "$ROOT/.claude/scripts/log-skill-error.sh" "edr-isolation-watch" "discord post failed for $host ($aid)" 2>/dev/null || true
|
|
fi
|
|
done <<< "$new_events"
|