feat(dlp): add compliance reporting scheduler and admin CLI

This commit is contained in:
igor04091968
2026-05-13 03:31:40 +03:00
parent 28a4dae67f
commit 070a8f8f7e
11 changed files with 481 additions and 16 deletions
+134 -8
View File
@@ -3,29 +3,155 @@ from __future__ import annotations
import argparse
import json
from datetime import UTC, datetime, timedelta
from urllib import request
from urllib.parse import quote
def get_json(url: str):
with request.urlopen(url, timeout=10) as r:
with request.urlopen(url, timeout=30) as r:
return json.loads(r.read().decode("utf-8"))
def send_json(url: str, method: str, payload: dict | None = None):
body = None if payload is None else json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = request.Request(url, data=body, method=method, headers={"Content-Type": "application/json"})
with request.urlopen(req, timeout=30) as r:
raw = r.read().decode("utf-8")
return json.loads(raw) if raw else {}
def parse_iso(ts: str | None) -> datetime | None:
if not ts:
return None
try:
return datetime.fromisoformat(ts.replace("Z", "+00:00")).astimezone(UTC)
except ValueError:
return None
def main() -> None:
p = argparse.ArgumentParser(description="AWatch DLP admin CLI")
p.add_argument("--server", default="http://127.0.0.1:5601")
p.add_argument("--policy-server", default="http://127.0.0.1:5601")
p.add_argument("--case-server", default="http://127.0.0.1:5602")
p.add_argument("--aw-server", default="http://127.0.0.1:5600")
sub = p.add_subparsers(dest="cmd", required=True)
sub.add_parser("policies-list")
sub.add_parser("health-check")
policies = sub.add_parser("policies")
policies_sub = policies.add_subparsers(dest="policies_cmd", required=True)
policies_sub.add_parser("list")
policies_sub.add_parser("active")
incidents = sub.add_parser("incidents")
incidents_sub = incidents.add_subparsers(dest="incidents_cmd", required=True)
incidents_list = incidents_sub.add_parser("list")
incidents_list.add_argument("--host")
incidents_list.add_argument("--severity")
incidents_list.add_argument("--limit", type=int, default=100)
incidents_list.add_argument("--since-hours", type=int, default=24)
cases = sub.add_parser("cases")
cases_sub = cases.add_subparsers(dest="cases_cmd", required=True)
cases_list = cases_sub.add_parser("list")
cases_list.add_argument("--host")
cases_list.add_argument("--status")
cases_list.add_argument("--limit", type=int, default=100)
cases_create = cases_sub.add_parser("create")
cases_create.add_argument("--incident-id", required=True)
cases_create.add_argument("--title", required=True)
cases_create.add_argument("--host")
cases_create.add_argument("--severity", default="medium")
health = sub.add_parser("health")
health_sub = health.add_subparsers(dest="health_cmd", required=True)
health_sub.add_parser("check")
args = p.parse_args()
if args.cmd == "policies-list":
data = get_json(f"{args.server}/api/0/dlp/policies")
if args.cmd == "policies" and args.policies_cmd == "list":
data = get_json(f"{args.policy_server}/api/0/dlp/policies")
print(json.dumps(data, ensure_ascii=False, indent=2))
elif args.cmd == "health-check":
data = get_json(f"{args.server}/health")
return
if args.cmd == "policies" and args.policies_cmd == "active":
data = get_json(f"{args.policy_server}/api/0/dlp/policies/active")
print(json.dumps(data, ensure_ascii=False, indent=2))
return
if args.cmd == "incidents" and args.incidents_cmd == "list":
bucket_map = get_json(f"{args.aw_server}/api/0/buckets")
if not isinstance(bucket_map, dict):
print("[]")
return
bucket_ids = [x for x in bucket_map.keys() if str(x).startswith("aw-dlp-incidents_")]
if args.host:
bucket_ids = [x for x in bucket_ids if str(x).endswith("_" + args.host)]
after = datetime.now(UTC) - timedelta(hours=max(1, args.since_hours))
rows = []
for bucket_id in sorted(bucket_ids):
encoded = quote(str(bucket_id), safe="")
events = get_json(f"{args.aw_server}/api/0/buckets/{encoded}/events?limit={max(1, args.limit)}")
if not isinstance(events, list):
continue
for ev in events:
if not isinstance(ev, dict):
continue
ts = parse_iso(ev.get("timestamp"))
if ts is None or ts < after:
continue
data = ev.get("data") or {}
if args.severity and str((data or {}).get("severity", "")).lower() != args.severity.lower():
continue
rows.append(ev)
print(json.dumps(rows, ensure_ascii=False, indent=2))
return
if args.cmd == "cases" and args.cases_cmd == "list":
query = []
if args.host:
query.append(f"host={quote(args.host, safe='')}")
if args.status:
query.append(f"status={quote(args.status, safe='')}")
query.append(f"limit={max(1, args.limit)}")
data = get_json(f"{args.case_server}/api/0/dlp/cases?{'&'.join(query)}")
print(json.dumps(data, ensure_ascii=False, indent=2))
return
if args.cmd == "cases" and args.cases_cmd == "create":
payload = {
"incident_id": args.incident_id,
"title": args.title,
"host": args.host,
"severity": args.severity,
"evidence": {"source": "dlp-admin-cli"},
}
data = send_json(f"{args.case_server}/api/0/dlp/cases", "POST", payload)
print(json.dumps(data, ensure_ascii=False, indent=2))
return
if args.cmd == "health" and args.health_cmd == "check":
out = {}
try:
out["policy"] = get_json(f"{args.policy_server}/healthz")
except Exception as exc:
out["policy"] = {"status": "error", "error": str(exc)}
try:
out["cases"] = get_json(f"{args.case_server}/health")
except Exception as exc:
out["cases"] = {"status": "error", "error": str(exc)}
try:
out["aw"] = get_json(f"{args.aw_server}/api/0/info")
except Exception as exc:
out["aw"] = {"status": "error", "error": str(exc)}
print(json.dumps(out, ensure_ascii=False, indent=2))
return
raise SystemExit("unsupported command")
if __name__ == "__main__":