feat(dlp): add compliance reporting scheduler and admin CLI
This commit is contained in:
@@ -308,6 +308,9 @@
|
||||
AW_DLP_CASE_BIND_HOST={{ aw_dlp_case_bind_host }}
|
||||
AW_DLP_CASE_PORT={{ aw_dlp_case_port }}
|
||||
AW_DLP_CASE_DB_PATH={{ aw_dlp_case_db_path }}
|
||||
AW_DLP_COMPLIANCE_REPORT_DIR={{ aw_dlp_compliance_report_dir }}
|
||||
AW_DLP_COMPLIANCE_TEMPLATE={{ aw_dlp_compliance_template_path }}
|
||||
AW_SERVER_URL=http://127.0.0.1:5600/api/0
|
||||
XDG_DATA_HOME={{ aw_server_data_dir }}/.local/share
|
||||
XDG_CONFIG_HOME={{ aw_server_data_dir }}/.config
|
||||
|
||||
@@ -534,6 +537,55 @@
|
||||
mode: "0644"
|
||||
when: aw_dlp_case_management_enabled | default(true) | bool
|
||||
|
||||
- name: Создать каталоги DLP compliance
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "0755"
|
||||
loop:
|
||||
- /opt/activitywatch/dlp-compliance
|
||||
- /opt/activitywatch/dlp-compliance/templates
|
||||
- "{{ aw_dlp_compliance_report_dir }}"
|
||||
when: aw_dlp_compliance_enabled | default(true) | bool
|
||||
|
||||
- name: Скопировать файлы DLP compliance
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/dlp-compliance/{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
owner: "{{ aw_server_user }}"
|
||||
group: "{{ aw_server_group }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- { src: "report_generator.py", dest: "/opt/activitywatch/dlp-compliance/report_generator.py", mode: "0755" }
|
||||
- { src: "requirements.txt", dest: "/opt/activitywatch/dlp-compliance/requirements.txt", mode: "0644" }
|
||||
- { src: "templates/152-fz-report.html", dest: "/opt/activitywatch/dlp-compliance/templates/152-fz-report.html", mode: "0644" }
|
||||
- { src: "report-scheduler.service", dest: "/etc/systemd/system/aw-dlp-report-scheduler.service", mode: "0644" }
|
||||
- { src: "report-scheduler.timer", dest: "/etc/systemd/system/aw-dlp-report-scheduler.timer", mode: "0644" }
|
||||
when: aw_dlp_compliance_enabled | default(true) | bool
|
||||
|
||||
- name: Создать virtualenv DLP compliance
|
||||
ansible.builtin.command:
|
||||
cmd: python3 -m venv /opt/activitywatch/dlp-compliance/.venv
|
||||
args:
|
||||
creates: /opt/activitywatch/dlp-compliance/.venv/bin/python
|
||||
when: aw_dlp_compliance_enabled | default(true) | bool
|
||||
|
||||
- name: Установить зависимости DLP compliance
|
||||
ansible.builtin.pip:
|
||||
requirements: /opt/activitywatch/dlp-compliance/requirements.txt
|
||||
virtualenv: /opt/activitywatch/dlp-compliance/.venv
|
||||
when: aw_dlp_compliance_enabled | default(true) | bool
|
||||
|
||||
- name: Установить dlp-admin-cli
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/scripts/dlp-admin-cli.py"
|
||||
dest: /usr/local/bin/dlp-admin-cli.py
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Установить скрипт AW worktime API
|
||||
ansible.builtin.copy:
|
||||
src: "{{ aw_repo_root }}/aw-server/aw-worktime-api.py"
|
||||
@@ -638,6 +690,20 @@
|
||||
state: restarted
|
||||
when: aw_dlp_case_management_enabled | default(true) | bool
|
||||
|
||||
- name: Включить и перезапустить timer DLP compliance report
|
||||
ansible.builtin.systemd:
|
||||
name: aw-dlp-report-scheduler.timer
|
||||
enabled: true
|
||||
state: restarted
|
||||
when: aw_dlp_compliance_enabled | default(true) | bool
|
||||
|
||||
- name: Выполнить разовый прогон DLP compliance report
|
||||
ansible.builtin.systemd:
|
||||
name: aw-dlp-report-scheduler.service
|
||||
state: started
|
||||
failed_when: false
|
||||
when: aw_dlp_compliance_enabled | default(true) | bool
|
||||
|
||||
- name: Включить и перезапустить AW worktime API
|
||||
ansible.builtin.systemd:
|
||||
name: aw-worktime-api.service
|
||||
|
||||
@@ -11,3 +11,4 @@
|
||||
- role: dlp-content-analysis
|
||||
- role: dlp-integrations
|
||||
- role: dlp-case-management
|
||||
- role: dlp-compliance
|
||||
|
||||
@@ -46,4 +46,7 @@ aw_dlp_case_management_enabled: true
|
||||
aw_dlp_case_bind_host: "0.0.0.0"
|
||||
aw_dlp_case_port: 5602
|
||||
aw_dlp_case_db_path: "/opt/activitywatch/dlp-case-management/cases.db"
|
||||
aw_dlp_compliance_enabled: true
|
||||
aw_dlp_compliance_report_dir: "/opt/activitywatch/dlp-compliance/reports"
|
||||
aw_dlp_compliance_template_path: "/opt/activitywatch/dlp-compliance/templates/152-fz-report.html"
|
||||
aw_server_post_deploy_health_check_enabled: true
|
||||
|
||||
@@ -37,6 +37,9 @@ aw_dlp_case_management_enabled: true
|
||||
aw_dlp_case_bind_host: "0.0.0.0"
|
||||
aw_dlp_case_port: 5602
|
||||
aw_dlp_case_db_path: "/opt/activitywatch/dlp-case-management/cases.db"
|
||||
aw_dlp_compliance_enabled: true
|
||||
aw_dlp_compliance_report_dir: "/opt/activitywatch/dlp-compliance/reports"
|
||||
aw_dlp_compliance_template_path: "/opt/activitywatch/dlp-compliance/templates/152-fz-report.html"
|
||||
aw_server_post_deploy_health_check_enabled: true
|
||||
|
||||
aw_worktime_from: "08:00"
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
- name: Ensure DLP compliance directory tree
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ aw_server_user | default('activitywatch') }}"
|
||||
group: "{{ aw_server_group | default('activitywatch') }}"
|
||||
mode: "0755"
|
||||
loop:
|
||||
- /opt/activitywatch/dlp-compliance
|
||||
- /opt/activitywatch/dlp-compliance/templates
|
||||
- /opt/activitywatch/dlp-compliance/reports
|
||||
|
||||
- name: Copy DLP compliance files
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../aw-server/dlp-compliance/{{ item.src }}"
|
||||
dest: "{{ item.dest }}"
|
||||
owner: "{{ aw_server_user | default('activitywatch') }}"
|
||||
group: "{{ aw_server_group | default('activitywatch') }}"
|
||||
mode: "{{ item.mode }}"
|
||||
loop:
|
||||
- { src: "report_generator.py", dest: "/opt/activitywatch/dlp-compliance/report_generator.py", mode: "0755" }
|
||||
- { src: "requirements.txt", dest: "/opt/activitywatch/dlp-compliance/requirements.txt", mode: "0644" }
|
||||
- { src: "templates/152-fz-report.html", dest: "/opt/activitywatch/dlp-compliance/templates/152-fz-report.html", mode: "0644" }
|
||||
- { src: "report-scheduler.service", dest: "/etc/systemd/system/aw-dlp-report-scheduler.service", mode: "0644" }
|
||||
- { src: "report-scheduler.timer", dest: "/etc/systemd/system/aw-dlp-report-scheduler.timer", mode: "0644" }
|
||||
|
||||
- name: Create venv for DLP compliance
|
||||
ansible.builtin.command:
|
||||
cmd: python3 -m venv /opt/activitywatch/dlp-compliance/.venv
|
||||
args:
|
||||
creates: /opt/activitywatch/dlp-compliance/.venv/bin/python
|
||||
|
||||
- name: Install Python dependencies for DLP compliance
|
||||
ansible.builtin.pip:
|
||||
requirements: /opt/activitywatch/dlp-compliance/requirements.txt
|
||||
virtualenv: /opt/activitywatch/dlp-compliance/.venv
|
||||
|
||||
- name: Reload systemd
|
||||
ansible.builtin.systemd:
|
||||
daemon_reload: true
|
||||
|
||||
- name: Enable and start DLP compliance timer
|
||||
ansible.builtin.systemd:
|
||||
name: aw-dlp-report-scheduler.timer
|
||||
enabled: true
|
||||
state: restarted
|
||||
|
||||
- name: Run DLP compliance report once
|
||||
ansible.builtin.systemd:
|
||||
name: aw-dlp-report-scheduler.service
|
||||
state: started
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=AWatch DLP 152-FZ compliance report generator
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
EnvironmentFile=-/etc/activitywatch/aw-server.env
|
||||
WorkingDirectory=/opt/activitywatch/dlp-compliance
|
||||
ExecStart=/opt/activitywatch/dlp-compliance/.venv/bin/python /opt/activitywatch/dlp-compliance/report_generator.py
|
||||
User=activitywatch
|
||||
Group=activitywatch
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Monthly AWatch DLP 152-FZ compliance report schedule
|
||||
|
||||
[Timer]
|
||||
OnCalendar=monthly
|
||||
Persistent=true
|
||||
RandomizedDelaySec=5m
|
||||
Unit=aw-dlp-report-scheduler.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
@@ -1,20 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from urllib.parse import quote
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
|
||||
def render_html(period: str) -> str:
|
||||
return f"""<html><body><h1>Отчет 152-ФЗ</h1><p>Период: {period}</p><p>Сгенерирован: {datetime.now().isoformat()}</p></body></html>"""
|
||||
def _env(name: str, default: str) -> str:
|
||||
value = os.environ.get(name)
|
||||
return value if value not in (None, "") else default
|
||||
|
||||
|
||||
AW_API_BASE = _env("AW_SERVER_URL", "http://127.0.0.1:5600/api/0").rstrip("/")
|
||||
OUTPUT_DIR = Path(_env("AW_DLP_COMPLIANCE_REPORT_DIR", "/opt/activitywatch/dlp-compliance/reports"))
|
||||
TEMPLATE_PATH = Path(_env("AW_DLP_COMPLIANCE_TEMPLATE", "/opt/activitywatch/dlp-compliance/templates/152-fz-report.html"))
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReportStats:
|
||||
total_incidents: int
|
||||
high: int
|
||||
medium: int
|
||||
low: int
|
||||
by_host: dict[str, int]
|
||||
channels: dict[str, int]
|
||||
|
||||
|
||||
def _http_json(url: str) -> object:
|
||||
req = Request(url, headers={"Accept": "application/json"})
|
||||
with urlopen(req, timeout=30) as response:
|
||||
return json.loads(response.read().decode("utf-8"))
|
||||
|
||||
|
||||
def _parse_ts(value: str | None) -> datetime | None:
|
||||
if not value:
|
||||
return None
|
||||
text = value.replace("Z", "+00:00")
|
||||
try:
|
||||
return datetime.fromisoformat(text).astimezone(UTC)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _load_incidents(start: datetime, end: datetime) -> list[dict]:
|
||||
buckets = _http_json(f"{AW_API_BASE}/buckets")
|
||||
if not isinstance(buckets, dict):
|
||||
return []
|
||||
bucket_ids = sorted([bid for bid in buckets.keys() if str(bid).startswith("aw-dlp-incidents_")])
|
||||
|
||||
incidents: list[dict] = []
|
||||
for bucket_id in bucket_ids:
|
||||
encoded = quote(str(bucket_id), safe="")
|
||||
events = _http_json(f"{AW_API_BASE}/buckets/{encoded}/events?limit=2000")
|
||||
if not isinstance(events, list):
|
||||
continue
|
||||
for event in events:
|
||||
if not isinstance(event, dict):
|
||||
continue
|
||||
ts = _parse_ts(event.get("timestamp"))
|
||||
if ts is None or ts < start or ts > end:
|
||||
continue
|
||||
incidents.append(event)
|
||||
return incidents
|
||||
|
||||
|
||||
def _build_stats(incidents: list[dict]) -> ReportStats:
|
||||
by_host: dict[str, int] = {}
|
||||
channels: dict[str, int] = {}
|
||||
high = medium = low = 0
|
||||
for event in incidents:
|
||||
data = event.get("data") or {}
|
||||
if not isinstance(data, dict):
|
||||
data = {}
|
||||
host = str(data.get("hostname") or "unknown")
|
||||
by_host[host] = by_host.get(host, 0) + 1
|
||||
|
||||
severity = str(data.get("severity") or "low").lower()
|
||||
if severity == "high":
|
||||
high += 1
|
||||
elif severity == "medium":
|
||||
medium += 1
|
||||
else:
|
||||
low += 1
|
||||
|
||||
channel = str(data.get("signalType") or data.get("source") or "unknown")
|
||||
channels[channel] = channels.get(channel, 0) + 1
|
||||
|
||||
return ReportStats(
|
||||
total_incidents=len(incidents),
|
||||
high=high,
|
||||
medium=medium,
|
||||
low=low,
|
||||
by_host=dict(sorted(by_host.items(), key=lambda item: item[1], reverse=True)),
|
||||
channels=dict(sorted(channels.items(), key=lambda item: item[1], reverse=True)),
|
||||
)
|
||||
|
||||
|
||||
def _render_table(title: str, rows: list[tuple[str, int]]) -> str:
|
||||
if not rows:
|
||||
return f"<h3>{title}</h3><p>Нет данных</p>"
|
||||
body = "".join([f"<tr><td>{name}</td><td>{count}</td></tr>" for name, count in rows])
|
||||
return f"<h3>{title}</h3><table><thead><tr><th>Параметр</th><th>Значение</th></tr></thead><tbody>{body}</tbody></table>"
|
||||
|
||||
|
||||
def _render_html(period_label: str, stats: ReportStats, generated_at: str) -> str:
|
||||
template = TEMPLATE_PATH.read_text(encoding="utf-8")
|
||||
return (
|
||||
template.replace("{{PERIOD}}", period_label)
|
||||
.replace("{{GENERATED_AT}}", generated_at)
|
||||
.replace("{{TOTAL}}", str(stats.total_incidents))
|
||||
.replace("{{HIGH}}", str(stats.high))
|
||||
.replace("{{MEDIUM}}", str(stats.medium))
|
||||
.replace("{{LOW}}", str(stats.low))
|
||||
.replace("{{HOST_TABLE}}", _render_table("Инциденты по хостам", list(stats.by_host.items())))
|
||||
.replace("{{CHANNEL_TABLE}}", _render_table("Инциденты по каналам", list(stats.channels.items())))
|
||||
)
|
||||
|
||||
|
||||
def _period_bounds(month: str | None) -> tuple[datetime, datetime, str]:
|
||||
if month:
|
||||
start = datetime.fromisoformat(f"{month}-01T00:00:00+00:00").astimezone(UTC)
|
||||
else:
|
||||
now = datetime.now(UTC)
|
||||
start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
if start.month == 12:
|
||||
end = start.replace(year=start.year + 1, month=1)
|
||||
else:
|
||||
end = start.replace(month=start.month + 1)
|
||||
end = end.replace(second=0, microsecond=0)
|
||||
return start, end, start.strftime("%Y-%m")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
period = datetime.now().strftime("%Y-%m")
|
||||
out = Path("/opt/activitywatch/dlp-compliance/reports")
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
html = out / f"152-fz-{period}.html"
|
||||
html.write_text(render_html(period), encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Generate 152-FZ compliance report from AW DLP incidents")
|
||||
parser.add_argument("--month", help="Month in YYYY-MM format (default: current month)")
|
||||
parser.add_argument("--stdout-json", action="store_true", help="Print report metadata as JSON")
|
||||
args = parser.parse_args()
|
||||
|
||||
start, end, period_label = _period_bounds(args.month)
|
||||
incidents = _load_incidents(start, end)
|
||||
stats = _build_stats(incidents)
|
||||
|
||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
||||
generated_at = datetime.now(UTC).isoformat().replace("+00:00", "Z")
|
||||
html_out = OUTPUT_DIR / f"152-fz-{period_label}.html"
|
||||
html_out.write_text(_render_html(period_label, stats, generated_at), encoding="utf-8")
|
||||
|
||||
metadata = {
|
||||
"period": period_label,
|
||||
"generated_at": generated_at,
|
||||
"aw_api_base": AW_API_BASE,
|
||||
"report_path": str(html_out),
|
||||
"stats": {
|
||||
"total_incidents": stats.total_incidents,
|
||||
"high": stats.high,
|
||||
"medium": stats.medium,
|
||||
"low": stats.low,
|
||||
},
|
||||
}
|
||||
(OUTPUT_DIR / f"152-fz-{period_label}.json").write_text(
|
||||
json.dumps(metadata, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
if args.stdout_json:
|
||||
print(json.dumps(metadata, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
requests==2.32.3
|
||||
@@ -0,0 +1,33 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Отчёт 152-ФЗ {{PERIOD}}</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 24px; color: #1f2937; }
|
||||
h1, h2, h3 { margin: 0 0 12px 0; }
|
||||
.meta { margin-bottom: 18px; color: #4b5563; }
|
||||
.cards { display: flex; gap: 12px; margin: 18px 0; }
|
||||
.card { border: 1px solid #d1d5db; border-radius: 8px; padding: 10px 14px; min-width: 120px; }
|
||||
.label { color: #6b7280; font-size: 12px; }
|
||||
.value { font-size: 22px; font-weight: 700; }
|
||||
table { border-collapse: collapse; width: 100%; margin-bottom: 18px; }
|
||||
th, td { border: 1px solid #d1d5db; padding: 8px; text-align: left; }
|
||||
th { background: #f3f4f6; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Compliance отчёт 152-ФЗ</h1>
|
||||
<div class="meta">Период: {{PERIOD}} | Сформирован: {{GENERATED_AT}}</div>
|
||||
|
||||
<div class="cards">
|
||||
<div class="card"><div class="label">Всего инцидентов</div><div class="value">{{TOTAL}}</div></div>
|
||||
<div class="card"><div class="label">High</div><div class="value">{{HIGH}}</div></div>
|
||||
<div class="card"><div class="label">Medium</div><div class="value">{{MEDIUM}}</div></div>
|
||||
<div class="card"><div class="label">Low</div><div class="value">{{LOW}}</div></div>
|
||||
</div>
|
||||
|
||||
{{HOST_TABLE}}
|
||||
{{CHANNEL_TABLE}}
|
||||
</body>
|
||||
</html>
|
||||
+134
-8
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user