feat(dlp): add server-side analyzer, evidence chain, compliance scheduler, and syslog forwarder

This commit is contained in:
igor04091968
2026-05-13 04:54:08 +03:00
parent 0f1b82233a
commit 6ebf0ac67e
17 changed files with 505 additions and 19 deletions
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
from report_generator import generate_report
def main() -> None:
parser = argparse.ArgumentParser(description="Run one or more DLP compliance report profiles")
parser.add_argument("--month", help="Month in YYYY-MM format")
parser.add_argument("--profiles", default="152-fz,pci-dss", help="Comma-separated profiles to generate")
parser.add_argument("--stdout-json", action="store_true")
args = parser.parse_args()
profiles = [item.strip() for item in str(args.profiles).split(",") if item.strip()]
results = [generate_report(month=args.month, profile=profile) for profile in profiles]
if args.stdout_json:
print(json.dumps({"items": results}, ensure_ascii=False))
if __name__ == "__main__":
main()
@@ -6,7 +6,6 @@ After=network-online.target
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
ExecStart=/opt/activitywatch/dlp-compliance/.venv/bin/python /opt/activitywatch/dlp-compliance/compliance_scheduler.py
User=activitywatch
Group=activitywatch
+31 -13
View File
@@ -18,7 +18,11 @@ def _env(name: str, default: str) -> str:
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"))
BASE_DIR = Path(__file__).resolve().parent
PROFILE_TEMPLATE_MAP = {
"152-fz": BASE_DIR / "templates" / "152-fz-report.html",
"pci-dss": BASE_DIR / "templates" / "pci-dss-report.html",
}
@dataclass
@@ -108,10 +112,18 @@ def _render_table(title: str, rows: list[tuple[str, int]]) -> str:
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")
def _resolve_template_path(profile: str) -> Path:
if profile == "152-fz":
explicit = _env("AW_DLP_COMPLIANCE_TEMPLATE", str(PROFILE_TEMPLATE_MAP["152-fz"]))
return Path(explicit)
return PROFILE_TEMPLATE_MAP.get(profile, PROFILE_TEMPLATE_MAP["152-fz"])
def _render_html(profile: str, period_label: str, stats: ReportStats, generated_at: str) -> str:
template = _resolve_template_path(profile).read_text(encoding="utf-8")
return (
template.replace("{{PERIOD}}", period_label)
.replace("{{PROFILE}}", profile)
.replace("{{GENERATED_AT}}", generated_at)
.replace("{{TOTAL}}", str(stats.total_incidents))
.replace("{{HIGH}}", str(stats.high))
@@ -136,22 +148,18 @@ def _period_bounds(month: str | None) -> tuple[datetime, datetime, str]:
return start, end, start.strftime("%Y-%m")
def main() -> None:
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)
def generate_report(month: str | None = None, profile: str = "152-fz") -> dict[str, object]:
start, end, period_label = _period_bounds(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")
html_out = OUTPUT_DIR / f"{profile}-{period_label}.html"
html_out.write_text(_render_html(profile, period_label, stats, generated_at), encoding="utf-8")
metadata = {
"profile": profile,
"period": period_label,
"generated_at": generated_at,
"aw_api_base": AW_API_BASE,
@@ -163,10 +171,20 @@ def main() -> None:
"low": stats.low,
},
}
(OUTPUT_DIR / f"152-fz-{period_label}.json").write_text(
(OUTPUT_DIR / f"{profile}-{period_label}.json").write_text(
json.dumps(metadata, ensure_ascii=False, indent=2),
encoding="utf-8",
)
return metadata
def main() -> None:
parser = argparse.ArgumentParser(description="Generate DLP compliance report from AW DLP incidents")
parser.add_argument("--month", help="Month in YYYY-MM format (default: current month)")
parser.add_argument("--profile", default="152-fz", help="Profile name: 152-fz or pci-dss")
parser.add_argument("--stdout-json", action="store_true", help="Print report metadata as JSON")
args = parser.parse_args()
metadata = generate_report(month=args.month, profile=args.profile)
if args.stdout_json:
print(json.dumps(metadata, ensure_ascii=False))
@@ -0,0 +1,33 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>PCI DSS Report {{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 отчёт PCI DSS</h1>
<div class="meta">Профиль: {{PROFILE}} | Период: {{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>