L

logowl

logowl

 ,___,
 (O,O)
 (   )
 --"-"--

LogOwl — страж nginx-логов: детект DDoS/флуда/сканеров в реальном времени, псевдографический TUI (curses) с совой, гибкая конфигурация, белый список, настраиваемые пороги и действия (бан / rate-limit / 444), уведомления (webhook, email), сохранение отчётов.

Только стандартная библиотека Python 3.8+. На целевую машину не нужно ставить ничего: логи читаются из файлов, journalctl или по обычному SSH (tail -F).

Возможности

  • Источники: локальные файлы (с обработкой ротации и truncate), systemd journal (journalctl -f), удалённая машина по SSH (файлы и journal) — без агентов на цели.
  • Детекция: скользящие окна на IP, пороги запросов, доля 4xx/5xx, уникальные URL (сканеры), regex по URL, брутфорс конкретных путей, глобальный объёмный порог.
  • Действия: ban (iptables/nftables — шаблоны команд), limit (динамическая nginx-map для limit_req), 444 (динамическая nginx-map с return 444), TTL с автобаном-разбаном, анти-дребезг, жёсткий лимит команд в минуту.
  • Безопасность: dry_run по умолчанию (ничего не исполняется), whitelist никогда не трогается, IP строго валидируются перед подстановкой в команды, отчёты не содержат секретов.
  • Уведомления: webhook (JSON POST) и email (SMTP/STARTTLS/SSL), троттлинг и капы против спама.
  • Отчёты: txt / json / html — вручную (клавиша S в TUI) и по расписанию.
  • TUI: сова (моргает), спарклайн RPS, таблица топ-обидчиков, события, ручные баны/лимиты прямо из интерфейса.

Быстрый старт

# сгенерировать конфиг с комментариями и отредактировать
python3 -m logowl --gen-config my.json

# live-режим с TUI
python3 -m logowl -c my.json

# без TUI (например, на сервере в tmux или для отладки)
python3 -m logowl -c my.json --no-tui

# офлайн-анализ файла с сохранением отчёта (без действий и уведомлений)
python3 -m logowl --once /var/log/nginx/access.log --report-dir reports

# демо на синтетической атаке
python3 examples/gen_attack_log.py attack.log 20000
python3 -m logowl --once attack.log

# наглядный стенд с живой эмуляцией атаки (см. demo/README.md)
python3 demo/attack_sim.py --log demo/access.log --loop
python3 -m logowl -c demo/demo_config.jsonc

TUI требует Unix-терминал (curses). На Windows доступен --no-tui и --once; для TUI на Windows поставьте pip install windows-curses.

Ключи командной строки

Ключ Значение
-c, --config PATH путь к конфигу (JSON с //-комментариями и хвостовыми запятыми)
--no-tui без curses, статусная строка раз в 10 с
--once FILE офлайн-анализ файла(ов), отчёт, выход. Действия и уведомления отключены
--report-dir DIR переопределить reports.dir
--dry-run / --live принудительно включить/выключить dry-run (перекрывает конфиг)
--gen-config PATH записать пример конфига
--version версия

Конфигурация

Полный пример с комментариями: config.example.jsonc (или --gen-config). Неизвестные поля и отсутствующие секции берутся из безопасных дефолтов. Ключевые поля:

general.log_files      локальные файлы логов
general.journal        journalctl-источники: "nginx.service" или {"unit": "...", "identifier": "...", "sudo":..., "stdbuf":..., "extra_args":[...]}
general.remote         ssh-источник: {host, port, user, key, sudo, log_files[], journal[]}
general.from_end       false = читать с начала (иначе tail с конца)
general.dry_run        true (по умолчанию!) = только логировать действия
general.poll_interval / update_interval / max_lines_per_tick / queue_size / tracked_ip_cap
general.trust_xff      брать IP из X-Forwarded-For (только за своим доверенным прокси)
log_format             "combined" или строка формата nginx с $переменными
whitelist              IP и CIDR — никогда не блокируются
rules[]                правила детекции (см. ниже)
actions                команды бана, map-файлы, reload, лимиты
notifications          webhook / email
reports                каталог, интервал, форматы
tui                    owl, blink, top_n, events_n

Правила детекции

{
  "name": "http-flood",          // имя (уникальное)
  "window": 10,                  // окно, секунд
  "requests_per_ip": 100,        // порог запросов с одного IP за окно
  "status": ["4xx"],             // фильтр: "4xx","5xx",404,"any"; + "min_ratio": 0.7 — доля таких строк среди всех запросов IP
  "methods": ["GET","POST"],     // фильтр по методу
  "url_regex": "wp-login\\.php", // фильтр по URL (брутфорс конкретного пути)
  "unique_urls": 30,             // IP тронул N разных URL — сканер
  "min_total": 20,               // минимум запросов за окно, прежде чем ratio-правило сработает
  "requests_total": 2000,        // глобальный порог (scope: "global") — объёмная атака с многих IP
  "actions": ["ban"],            // "ban" | "limit" | "444"
  "ban_ttl": 3600,               // срок действия бана/лимита, сек
  "cooldown": 300                // анти-дребезг повторных срабатываний на тот же IP
}

Условия внутри одного правила объединяются по И. Правил может быть сколько угодно — каждое со своим окном, фильтрами и действием.

Действия

  • ban — шаблоны команд, {ip} подставляется после строгой валидации:

    "ban": {
      "commands": ["iptables -I INPUT -s {ip} -j DROP"],
      "unban_commands": ["iptables -D INPUT -s {ip} -j DROP"],
      "ttl": 3600
    }

    Вариант nftables: nft add element inet logowl blacklist { {ip} }.

  • limit / 444 — logowl ведёт динамические map-файлы и перезагружает nginx. Добавьте в конфиг nginx один раз:

    # /etc/nginx/conf.d/logowl.conf
    geo $logowl_limit     { default 0; include /etc/nginx/logowl/limit.map; }
    geo $logowl_block_444 { default 0; include /etc/nginx/logowl/444.map; }
    
    # ограничение скорости: обычные клиенты не считаются (пустой ключ), лимитируемые — считаются
    map $logowl_limit $logowl_limit_key {
        0 "";
        1 $binary_remote_addr;
    }
    limit_req_zone $logowl_limit_key zone=logowl_limit:10m rate=10r/s;
    
    server {
        ...
        limit_req zone=logowl_limit burst=20 nodelay;
        if ($logowl_block_444) { return 444; }
    }

    В конфиге logowl:

    "limit": { "map_path": "/etc/nginx/logowl/limit.map", "reload": ["nginx -t && nginx -s reload"], "min_reload_interval": 10, "ttl": 3600 },
    "444":   { "map_path": "/etc/nginx/logowl/444.map",   "reload": ["nginx -t && nginx -s reload"], "min_reload_interval": 10, "ttl": 1800 }

Перезапись map-файла и reload выполняются только при реальных изменениях и не чаще min_reload_interval. При remote файл заливается через ssh "cat > ... && mv ...".

Все реальные команды ограничены actions.max_per_minute (анти-runaway), whitelist-IP отказываются всегда, дубликаты (уже забанен/уже в map) пропускаются.

Источники без установки ПО на цель

  • файлы: tail-подобное чтение с обработкой ротации;
  • journal: nginx должен писать в journald, например access_log syslog:server=unix:/dev/log,facility=local1,tag=access,severity=info; затем "journal": ["nginx.service"] (или "journal": [{"identifier": "access"}]);
  • ssh: пользователю на цели достаточно прав на чтение лога (группа adm/systemd-journal) и, при sudo: true, passwordless sudo. Используются только ssh, tail, journalctl.

TUI

 ,___,   LOGOWL v1.0.0  [LIVE]  host  2026-10-06 16:30:00
 (O,O)   RPS  57 ▂▃▅▇█▅▃▂▁...
 (   )   total 50 005  IPs 12  bans 13 lim 2 blk 7 ...
 --"-"--
──────────────────────────────────────────────────────
 TOP OFFENDERS ... ip, r10, r60, total, 4xx, 5xx, flags(B/L/4), UA
 EVENTS          ... детекты, действия, ошибки источников
──────────────────────────────────────────────────────
 q:quit  p:pause  s:report  v:bans  b:ban  l:limit  4:block444  u:unban  x:un444  h:help

Клавиши: q выход, p пауза, s сохранить отчёт, v баны/map'ы, b/l/4 действие по выбранному IP (↑/↓ — выбор), u unban, x снять 444, h помощь. Бейдж DRY-RUN означает, что команды не исполняются.

Уведомления

  • webhook: POST JSON {type, ts, ip, rule, ttl, dry_run, host, ...} на каждый детект/бан/лимит; заголовок X-LogOwl-Secret при "secret".
  • email: SMTP с STARTTLS или SSL.
  • Антиспам: min_interval на пару (тип события, IP) и max_per_minute глобально.

Отчёты

snapshots в reports.dir (report-YYYYmmdd-HHMMSS.txt|json|html): суммарная статистика, статусы, топ IP, все детекции, активные баны, обезличенный конфиг (пароли/секреты маскируются). Автосохранение по reports.interval (0 = только вручную).

Производительность и безопасность

  • один процесс, thread на источник + главный цикл; ограничение батча max_lines_per_tick, ограниченная очередь (queue_size, переполнение считается, а не ломает систему);
  • память ограничена: tracked_ip_cap с вытеснением старых IP, оконные деки с жёсткими капами, периодическая чистка prune_interval;
  • CPU: слэбы по update_interval; TUI перерисовывается 2–5 раз в секунду;
  • действия: dry-run по умолчанию, валидация IP (ipaddress), капы, дедупликация, таймауты на subprocess, никакого shell-инжекта из данных лога.

systemd-юнит для самого logowl

[Unit]
Description=LogOwl nginx DDoS sentinel
After=network-online.target

[Service]
ExecStart=/usr/bin/python3 -m logowl -c /etc/logowl/logowl.json --no-tui
Restart=always
RestartSec=5
# для исполнения банов
AmbientCapabilities=CAP_NET_ADMIN
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target

Разработка

python3 -m unittest discover -s tests

Структура: logowl/parser.py (разбор формата nginx), tailer.py (источники), analyzer.py (окна и правила), actions.py (ban/limit/444), notify.py, reports.py, engine.py (связка), tui.py (интерфейс с совой).