diff --git a/docs/services/lxc208-manage/alert/README.md b/docs/services/lxc208-manage/alert/README.md
index c8e5392..70cc3aa 100644
--- a/docs/services/lxc208-manage/alert/README.md
+++ b/docs/services/lxc208-manage/alert/README.md
@@ -10,7 +10,8 @@
flowchart LR
subgraph VPS["VPS (Charon-vpn)"]
CS["CrowdSec
━━━━━━━━━━━━
• ssh-bf
• ssh-slow-bf
• ssh-bf_user-enum
• 3xui-tls-errors
• http-bad-user-agent
• http-cve"]
- SM["security-monitor.sh
━━━━━━━━━━━━
• ssh-successful-login
• service-disabled
• sudo-escalation"]
+ SM["security-monitor.sh
━━━━━━━━━━━━
• ssh-successful-login
• service-disabled
• sudo-escalation
• critical-file-change
• package-installed
• new-systemd-service
• suspicious-outbound"]
+ MAM["mass-attack-monitor.sh
━━━━━━━━━━━━
• crowdsec/mass-attack
(>50 банов за 5 мин)"]
end
subgraph PVE["Proxmox VE / PBS"]
@@ -39,6 +40,7 @@ flowchart LR
CS -->|POST JSON
crowdsec_alerts| NPM
SM -->|POST JSON
crowdsec_alerts| NPM
+ MAM -->|POST JSON
crowdsec_alerts| NPM
PBS -->|POST JSON
backup format| MR
GRAF -->|POST JSON
alerts| MR
NPM -->|HTTP forward| MR
@@ -57,7 +59,7 @@ flowchart LR
- `alerts.py` — словарь `ALERTS_CONFIG` с описанием всех известных типов алертов.
- `grafana.py` — обработчик стандартных вебхуков от Grafana/Prometheus.
- `pbs.py` — обработчик специфичных вебхуков от Proxmox VE / Proxmox Backup Server.
-- `crowdsec.py` — обработчик вебхуков от CrowdSec.
+- `crowdsec.py` — обработчик вебхуков от CrowdSec и security-monitor.
- `formatters.py` — функции красивого форматирования текста для специфичных алертов.
- `matrix.py` — низкоуровневая отправка HTML-сообщений в Matrix API.
- `utils.py` — вспомогательные утилиты (экранирование HTML, форматирование байтов и т.д.).
@@ -192,14 +194,15 @@ class WebhookHandler(BaseHTTPRequestHandler):
## 4. Сценарий В: Интеграция CrowdSec (VPS мониторинг безопасности)
-CrowdSec — система обнаружения и блокировки атак. На VPS установлен CrowdSec, который автоматически банит IP за brute-force, CVE-эксплойты и другие атаки. Мы настроили отправку уведомлений в Matrix при каждом бане.
+CrowdSec — система обнаружения и блокировки атак. На VPS установлен CrowdSec, который автоматически банит IP за brute-force, CVE-эксплойты и другие атаки. Дополнительно работает bash-скрипт `security-monitor.sh`, отслеживающий системные события (SSH логины, sudo, остановка сервисов, изменение критических файлов, установка пакетов, новые systemd-сервисы, подозрительные исходящие подключения). Отдельный скрипт `mass-attack-monitor.sh` отслеживает массовые атаки (>50 банов за 5 минут).
### 4.1. Архитектура интеграции
1. **CrowdSec** на VPS детектирует атаку и выносит решение (ban/alert)
2. **Notification plugin** отправляет POST-запрос на `https://security.zailon.ru/`
-3. **NPM** на Gateway форвардит запрос на `192.168.1.208:8086` (manage-сервер)
-4. **matrix-relay** принимает запрос, парсит и отправляет в комнату `security` в Matrix
+3. **security-monitor.sh** и **mass-attack-monitor.sh** отправляют вебхуки напрямую через curl
+4. **NPM** на Gateway форвардит запрос на `192.168.1.208:8086` (manage-сервер)
+5. **matrix-relay** принимает запрос, парсит и отправляет в комнату `security` в Matrix
### 4.2. Настройка CrowdSec на VPS
@@ -291,7 +294,7 @@ on_success: break
### 4.3. Настройка security-monitor.sh на VPS
-CrowdSec отлично справляется с паттернами в логах (брутфорс, веб-атаки), но для системных событий (остановка сервисов, sudo) используем bash-скрипт, который мониторит логи и отправляет вебхуки напрямую в relay.
+CrowdSec отлично справляется с паттернами в логах (брутфорс, веб-атаки), но для системных событий (остановка сервисов, sudo, изменение критических файлов, установка пакетов, новые systemd-сервисы, подозрительные исходящие) используем bash-скрипт, который мониторит логи и отправляет вебхуки напрямую в relay.
#### Шаг 4.3.1. Создание скрипта
@@ -305,6 +308,7 @@ HOST_NAME="Charon-vpn"
FAILED_IPS_FILE="/tmp/failed_ssh_ips.txt"
WHITELIST_FILE="/etc/crowdsec/whitelist.txt"
STATE_FILE="/tmp/ssh-monitor-state.txt"
+PKG_STATE_FILE="/tmp/pkg-monitor-state.txt"
send_alert() {
local scenario="$1"
@@ -350,6 +354,21 @@ check_recent_login() {
return 1
}
+check_recent_package() {
+ local current_time=$(date +%s)
+
+ if [ -f "$PKG_STATE_FILE" ]; then
+ local last_time=$(cat "$PKG_STATE_FILE")
+ local diff=$((current_time - last_time))
+ if [ $diff -lt 10 ]; then
+ return 0
+ fi
+ fi
+
+ echo "$current_time" > "$PKG_STATE_FILE"
+ return 1
+}
+
journalctl -u ssh.service -f --since now --no-pager | while read line; do
if echo "$line" | grep -q "Accepted"; then
IP=$(echo "$line" | grep -oP 'from \K[\d.]+')
@@ -392,18 +411,51 @@ tail -F /var/log/auth.log 2>/dev/null | grep --line-buffered "sudo:" | grep --li
send_alert "sudo-escalation" "$USER" "User" "$CMD"
done &
+inotifywait -m -e modify -e create -e delete -e attrib /etc/passwd /etc/shadow /root/.ssh/authorized_keys /etc/ssh/sshd_config /etc/crontab /var/spool/cron/crontabs/ 2>/dev/null | while read path event file; do
+ send_alert "critical-file-change" "${path}${file}" "File" "Critical file modified: $event"
+done &
+
+tail -F /var/log/apt/history.log 2>/dev/null | grep --line-buffered "^Commandline:" | while read line; do
+ if check_recent_package; then
+ continue
+ fi
+ CMD=$(echo "$line" | sed 's/^Commandline: //')
+ send_alert "package-installed" "$CMD" "Package" "APT command executed"
+done &
+
+inotifywait -m -e create /etc/systemd/system/ 2>/dev/null | while read path event file; do
+ if [[ "$file" == *.service ]]; then
+ send_alert "new-systemd-service" "$file" "Service" "New systemd service created"
+ fi
+done &
+
+while true; do
+ SUSPICIOUS=$(ss -tnp state established 2>/dev/null | awk 'NR>1 {print $5}' | grep -vE ':(80|443|53|22|8080|8086|3000|9090)$' | grep -vE '^(127\.|192\.168\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.)' | head -1)
+ if [ -n "$SUSPICIOUS" ]; then
+ send_alert "suspicious-outbound" "$SUSPICIOUS" "Connection" "Suspicious outbound connection detected"
+ sleep 300
+ fi
+ sleep 60
+done &
+
wait
```
**Теория:**
- `send_alert()` — функция отправки вебхука в relay с форматом `crowdsec_alerts`
- `is_whitelisted()` — проверяет, есть ли IP в whitelist
-- `check_recent_login()` — дедупликация: если с того же IP было подключение в последние 30 секунд, игнорируем
+- `check_recent_login()` — дедупликация SSH: если с того же IP было подключение в последние 30 секунд, игнорируем
+- `check_recent_package()` — дедупликация пакетов: если команда apt была в последние 10 секунд, игнорируем
- Первый `journalctl` блок — мониторит SSH логины (успешные и неудачные)
- Второй `journalctl` блок — мониторит остановку защитных сервисов (crowdsec, ufw, auditd, docker-logs-converter)
- Третий `tail -F` блок — мониторит sudo команды от не-root пользователей
+- Четвёртый `inotifywait` блок — мониторит изменения критических файлов (`/etc/passwd`, `/etc/shadow`, `/root/.ssh/authorized_keys`, `/etc/ssh/sshd_config`, `/etc/crontab`, `/var/spool/cron/crontabs/`)
+- Пятый `tail -F` блок — мониторит команды apt в `/var/log/apt/history.log`
+- Шестой `inotifywait` блок — мониторит создание новых `.service` файлов в `/etc/systemd/system/`
+- Седьмой `while true` блок — мониторит подозрительные исходящие подключения на нестандартные порты (исключены 80/443/53/22/8080/8086/3000/9090 и приватные сети)
- `--since now` — читаем только новые события, не историю
- `--line-buffered` — для grep, чтобы обрабатывать строки сразу, не буферизируя
+- Для работы `inotifywait` нужен пакет `inotify-tools`: `apt install inotify-tools -y`
#### Шаг 4.3.2. Создание systemd сервиса
@@ -424,10 +476,6 @@ RestartSec=10
WantedBy=multi-user.target
```
-**Теория:**
-- `Restart=always` — автоматический перезапуск при падении
-- `RestartSec=10` — пауза 10 секунд перед перезапуском
-
#### Шаг 4.3.3. Запуск сервиса
```bash
@@ -437,16 +485,102 @@ systemctl enable --now security-monitor
systemctl status security-monitor --no-pager
```
-### 4.4. Настройка relay на manage-сервере
+### 4.4. Настройка mass-attack-monitor.sh на VPS
-#### Шаг 4.4.1. Добавление сценариев в alerts.py
+Отдельный скрипт для детекции массовых атак — когда за 5 минут создаётся более 50 банов. Это может быть признаком координированной атаки с разных IP.
+
+#### Шаг 4.4.1. Создание скрипта
+
+Создай файл `/usr/local/bin/mass-attack-monitor.sh`:
+
+```bash
+#!/bin/bash
+
+RELAY_URL="https://security.zailon.ru/"
+HOST_NAME="Charon-vpn"
+STATE_FILE="/tmp/mass-attack-state.txt"
+THRESHOLD=50
+INTERVAL=300
+
+send_alert() {
+ local count="$1"
+ curl -s -X POST "$RELAY_URL" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"crowdsec_alerts\": [{
+ \"scenario\": \"crowdsec/mass-attack\",
+ \"value\": \"$count\",
+ \"scope\": \"System\",
+ \"action\": \"alert\",
+ \"reason\": \"Mass attack detected: $count bans in 5 minutes\",
+ \"host\": \"$HOST_NAME\"
+ }]
+ }" > /dev/null 2>&1
+}
+
+while true; do
+ CURRENT_TIME=$(date +%s)
+ COUNT=$(cscli decisions list --since "${INTERVAL}s" -o json 2>/dev/null | jq 'length')
+
+ if [ "$COUNT" -gt "$THRESHOLD" ]; then
+ LAST_ALERT_TIME=$(cat "$STATE_FILE" 2>/dev/null || echo "0")
+ TIME_DIFF=$((CURRENT_TIME - LAST_ALERT_TIME))
+
+ if [ "$TIME_DIFF" -gt "$INTERVAL" ]; then
+ send_alert "$COUNT"
+ echo "$CURRENT_TIME" > "$STATE_FILE"
+ fi
+ fi
+
+ sleep 60
+done
+```
+
+**Теория:**
+- `THRESHOLD=50` — порог срабатывания (50 банов за 5 минут)
+- `INTERVAL=300` — окно наблюдения в секундах (5 минут)
+- `cscli decisions list --since 300s` — получает список банов за последние 5 минут
+- `STATE_FILE` — хранит время последней отправки, чтобы не спамить алертами
+- Цикл `sleep 60` — проверка раз в минуту
+- `jq` — утилита для работы с JSON, нужна установка: `apt install jq -y`
+
+#### Шаг 4.4.2. Создание systemd сервиса
+
+Создай файл `/etc/systemd/system/mass-attack-monitor.service`:
+
+```ini
+[Unit]
+Description=Mass Attack Monitor
+After=network.target crowdsec.service
+
+[Service]
+Type=simple
+ExecStart=/usr/local/bin/mass-attack-monitor.sh
+Restart=always
+RestartSec=10
+
+[Install]
+WantedBy=multi-user.target
+```
+
+#### Шаг 4.4.3. Запуск сервиса
+
+```bash
+chmod +x /usr/local/bin/mass-attack-monitor.sh
+apt install jq -y
+systemctl daemon-reload
+systemctl enable --now mass-attack-monitor
+systemctl status mass-attack-monitor --no-pager
+```
+
+### 4.5. Настройка relay на manage-сервере
+
+#### Шаг 4.5.1. Добавление сценариев в alerts.py
Открой `/usr/local/bin/alerts/alerts.py` и добавь конфигурацию CrowdSec алертов:
```python
ALERTS_CONFIG = {
- # ... существующие алерты ...
-
"3xui-tls-errors": {
"room": "security",
"title": "\U0001F525 CrowdSec: TLS Errors",
@@ -494,6 +628,14 @@ ALERTS_CONFIG = {
"formatter": "crowdsec_ban",
"skip_zero_firing": False,
},
+ "crowdsec/mass-attack": {
+ "room": "security",
+ "title": "\U0001F6A8 CrowdSec: \u041c\u0430\u0441\u0441\u043e\u0432\u0430\u044f \u0430\u0442\u0430\u043a\u0430",
+ "summary": "\U0001F525 \u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e \u0431\u043e\u043b\u0435\u0435 50 \u0431\u0430\u043d\u043e\u0432 \u0437\u0430 5 \u043c\u0438\u043d\u0443\u0442",
+ "info": "\U0001F6E0 \u041f\u0440\u043e\u0432\u0435\u0440\u044c \u043b\u043e\u0433\u0438 \u043d\u0430 \u043f\u0440\u0435\u0434\u043c\u0435\u0442 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0430\u0442\u0430\u043a\u0438",
+ "formatter": "crowdsec_mass_attack",
+ "skip_zero_firing": False,
+ },
"ssh-successful-login": {
"room": "security",
"title": "\u2705 CrowdSec: SSH \u0423\u0441\u043f\u0435\u0448\u043d\u044b\u0439 \u0432\u0445\u043e\u0434",
@@ -515,6 +657,34 @@ ALERTS_CONFIG = {
"formatter": "crowdsec_sudo",
"skip_zero_firing": False,
},
+ "critical-file-change": {
+ "room": "security",
+ "title": "\U0001F5C2\uFE0F CrowdSec: \u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0444\u0430\u0439\u043b\u0430",
+ "summary": "\U0001F4CB \u041a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0431\u044b\u043b \u0438\u0437\u043c\u0435\u043d\u0451\u043d",
+ "formatter": "crowdsec_file_change",
+ "skip_zero_firing": False,
+ },
+ "package-installed": {
+ "room": "security",
+ "title": "\U0001F4E5 CrowdSec: \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043f\u0430\u043a\u0435\u0442\u0430",
+ "summary": "\U0001F4E6 \u041d\u043e\u0432\u044b\u0439 \u043f\u0430\u043a\u0435\u0442 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u0447\u0435\u0440\u0435\u0437 apt/dpkg",
+ "formatter": "crowdsec_package",
+ "skip_zero_firing": False,
+ },
+ "new-systemd-service": {
+ "room": "security",
+ "title": "\U0001F6E0 CrowdSec: \u041d\u043e\u0432\u044b\u0439 systemd \u0441\u0435\u0440\u0432\u0438\u0441",
+ "summary": "\U0001F6E0 \u0421\u043e\u0437\u0434\u0430\u043d \u043d\u043e\u0432\u044b\u0439 systemd \u0441\u0435\u0440\u0432\u0438\u0441",
+ "formatter": "crowdsec_service",
+ "skip_zero_firing": False,
+ },
+ "suspicious-outbound": {
+ "room": "security",
+ "title": "\U0001F310 CrowdSec: \u041f\u043e\u0434\u043e\u0437\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435",
+ "summary": "\U0001F310 \u041e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e \u043f\u043e\u0434\u043e\u0437\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0435\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435",
+ "formatter": "crowdsec_connection",
+ "skip_zero_firing": False,
+ },
}
```
@@ -524,7 +694,7 @@ ALERTS_CONFIG = {
- `title` — заголовок алерта (используй Unicode escape для эмодзи)
- `formatter` — имя функции из `formatters.py` для форматирования тела сообщения
-#### Шаг 4.4.2. Добавление форматтеров в formatters.py
+#### Шаг 4.5.2. Добавление форматтеров в formatters.py
Открой `/usr/local/bin/alerts/formatters.py` и добавь функции форматирования:
@@ -539,6 +709,7 @@ def crowdsec_ban(alert_info):
lines.append(f"\u26A1 \u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435: \u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d")
return "\n".join(lines)
+
def crowdsec_alert(alert_info):
lines = []
host = alert_info.get("host", "unknown")
@@ -549,6 +720,7 @@ def crowdsec_alert(alert_info):
lines.append(f"\U0001F4CD \u0426\u0435\u043b\u044c: {scope}:{value}")
return "\n".join(lines)
+
def crowdsec_service_disabled(alert_info):
lines = []
host = alert_info.get("host", "unknown")
@@ -558,6 +730,7 @@ def crowdsec_service_disabled(alert_info):
lines.append(f"\u26A1 \u0421\u0442\u0430\u0442\u0443\u0441: \u041e\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0430")
return "\n".join(lines)
+
def crowdsec_sudo(alert_info):
lines = []
host = alert_info.get("host", "unknown")
@@ -568,6 +741,53 @@ def crowdsec_sudo(alert_info):
lines.append(f"\U0001F4DD \u041a\u043e\u043c\u0430\u043d\u0434\u0430: {reason}")
return "\n".join(lines)
+
+def crowdsec_mass_attack(alert_info):
+ lines = []
+ host = alert_info.get("host", "unknown")
+ value = alert_info.get("value", "")
+ lines.append(f"\U0001F5A5 Host: {host}")
+ lines.append(f"\U0001F4CA \u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0431\u0430\u043d\u043e\u0432: {value}")
+ lines.append(f"\u26A1 \u0421\u0442\u0430\u0442\u0443\u0441: \u041a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044f")
+ return "\n".join(lines)
+
+
+def crowdsec_file_change(alert_info):
+ lines = []
+ host = alert_info.get("host", "unknown")
+ value = alert_info.get("value", "")
+ lines.append(f"\U0001F5A5 Host: {host}")
+ lines.append(f"\U0001F5C2\uFE0F \u0424\u0430\u0439\u043b: {value}")
+ return "\n".join(lines)
+
+
+def crowdsec_package(alert_info):
+ lines = []
+ host = alert_info.get("host", "unknown")
+ value = alert_info.get("value", "")
+ lines.append(f"\U0001F5A5 Host: {host}")
+ lines.append(f"\U0001F9F0 \u041f\u0430\u043a\u0435\u0442: {value}")
+ return "\n".join(lines)
+
+
+def crowdsec_service(alert_info):
+ lines = []
+ host = alert_info.get("host", "unknown")
+ value = alert_info.get("value", "")
+ lines.append(f"\U0001F5A5 Host: {host}")
+ lines.append(f"\U0001F6E0 \u0421\u0435\u0440\u0432\u0438\u0441: {value}")
+ return "\n".join(lines)
+
+
+def crowdsec_connection(alert_info):
+ lines = []
+ host = alert_info.get("host", "unknown")
+ value = alert_info.get("value", "")
+ lines.append(f"\U0001F5A5 Host: {host}")
+ lines.append(f"\U0001F310 \u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435: {value}")
+ return "\n".join(lines)
+
+
FORMATTERS = {
"temperature": temperature,
"bad_blocks": bad_blocks,
@@ -581,15 +801,25 @@ FORMATTERS = {
"crowdsec_alert": crowdsec_alert,
"crowdsec_service_disabled": crowdsec_service_disabled,
"crowdsec_sudo": crowdsec_sudo,
+ "crowdsec_mass_attack": crowdsec_mass_attack,
+ "crowdsec_file_change": crowdsec_file_change,
+ "crowdsec_package": crowdsec_package,
+ "crowdsec_service": crowdsec_service,
+ "crowdsec_connection": crowdsec_connection,
}
+
+
+def get_formatter(name):
+ return FORMATTERS.get(name)
```
**Теория:**
- Каждая функция принимает `alert_info` — словарь с данными из JSON
- Возвращает строку с HTML-форматированием для Matrix
- Используй Unicode escape (`\U0001F5A5`) для эмодзи, чтобы избежать проблем с кодировкой
+- Словарь `FORMATTERS` должен быть объявлен **после** всех функций, иначе Python выбросит `NameError`
-#### Шаг 4.4.3. Создание обработчика crowdsec.py
+#### Шаг 4.5.3. Создание обработчика crowdsec.py
Создай файл `/usr/local/bin/alerts/crowdsec.py`:
@@ -650,7 +880,7 @@ def process_crowdsec_alert(obj: Dict) -> None:
- Применяет форматтер для тела сообщения
- Отправляет в комнату Matrix
-#### Шаг 4.4.4. Подключение обработчика в matrix-relay.py
+#### Шаг 4.5.4. Подключение обработчика в matrix-relay.py
Открой `/usr/local/bin/alerts/matrix-relay.py` и добавь импорт и обработку:
@@ -688,9 +918,9 @@ class WebhookHandler(BaseHTTPRequestHandler):
- Проверка `"crowdsec_alerts" in obj` должна быть **перед** проверкой `"alerts" in obj`, иначе relay попытается обработать CrowdSec как Grafana
- Порядок проверок важен: сначала специфичные форматы, потом общие
-### 4.5. Настройка NPM Reverse Proxy на Gateway
+### 4.6. Настройка NPM Reverse Proxy на Gateway
-#### Шаг 4.5.1. Создание Proxy Host
+#### Шаг 4.6.1. Создание Proxy Host
В NPM создай новый Proxy Host:
- **Domain Names:** `security.zailon.ru`
@@ -706,9 +936,9 @@ class WebhookHandler(BaseHTTPRequestHandler):
- SSL terminates на NPM, дальше идёт HTTP
- Это позволяет VPS отправлять вебхуки через публичный домен без открытия портов на роутере
-### 4.6. Тестирование интеграции
+### 4.7. Тестирование интеграции
-#### Шаг 4.6.1. Тест с VPS
+#### Шаг 4.7.1. Тест с VPS
```bash
curl -X POST https://security.zailon.ru/ \
@@ -727,7 +957,7 @@ curl -X POST https://security.zailon.ru/ \
Должно прийти сообщение в комнату `security` в Matrix.
-#### Шаг 4.6.2. Генерация тестового алерта CrowdSec
+#### Шаг 4.7.2. Генерация тестового алерта CrowdSec
```bash
> /var/log/3xui-access.log
@@ -738,7 +968,90 @@ done
Через 5-10 секунд должен прийти алерт о бане.
-#### Шаг 4.6.3. Проверка логов
+#### Шаг 4.7.3. Финальный тест всех сценариев
+
+Создай скрипт `/tmp/final-test.sh` на VPS:
+
+```bash
+#!/bin/bash
+
+echo "=== Запуск финального теста алертов ==="
+
+echo "[1/7] Тест: Остановка сервиса (ufw)..."
+systemctl stop ufw
+sleep 3
+systemctl start ufw
+sleep 3
+
+echo "[2/7] Тест: Изменение критического файла (/etc/crontab)..."
+echo "# final test $(date)" >> /etc/crontab
+sleep 3
+
+echo "[3/7] Тест: Установка пакета (htop)..."
+apt install -y --reinstall htop > /dev/null 2>&1
+sleep 3
+
+echo "[4/7] Тест: Новый systemd сервис..."
+cat > /etc/systemd/system/test-final.service << 'EOF'
+[Unit]
+Description=Test Final
+
+[Service]
+ExecStart=/bin/true
+
+[Install]
+WantedBy=multi-user.target
+EOF
+systemctl daemon-reload
+sleep 3
+rm /etc/systemd/system/test-final.service
+systemctl daemon-reload
+sleep 3
+
+echo "[5/7] Тест: Sudo эскалация..."
+useradd -m -s /bin/bash testuser 2>/dev/null
+echo "testuser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
+su - testuser -c "sudo ls /root" > /dev/null 2>&1
+sleep 3
+userdel -r testuser 2>/dev/null
+sed -i '/testuser/d' /etc/sudoers
+sleep 3
+
+echo "[6/7] Тест: CrowdSec стандартный бан..."
+> /var/log/3xui-access.log
+for i in {1..10}; do
+ echo "$(date +'%Y/%m/%d %H:%M:%S') http: TLS handshake error from 198.51.100.55:12345: tls: client offered only unsupported versions" >> /var/log/3xui-access.log
+done
+sleep 5
+
+echo "[7/7] Тест: CrowdSec массовая атака..."
+> /var/log/3xui-access.log
+for ip in {200..214}; do
+ for i in {1..5}; do
+ echo "$(date +'%Y/%m/%d %H:%M:%S') http: TLS handshake error from 198.51.100.$ip:12345: tls: client offered only unsupported versions" >> /var/log/3xui-access.log
+ done
+done
+sleep 5
+
+echo "=== Тест завершён! Проверь Matrix ==="
+```
+
+```bash
+chmod +x /tmp/final-test.sh
+/tmp/final-test.sh
+```
+
+В Matrix должны прийти:
+1. **⛔ Отключение защиты** — ufw
+2. **🗂️ Изменение критического файла** — `/etc/crontab`
+3. **📥 Установка пакета** — `apt install --reinstall htop`
+4. **🛠 Новый systemd сервис** — `test-final.service`
+5. **🗂️ Изменение критического файла** — `/etc/passwd` и `/etc/shadow` (от useradd/userdel)
+6. **⚠️ Повышение привилегий** — `testuser` выполнил `sudo ls /root`
+7. **🔥 TLS Errors** — бан IP `198.51.100.55`
+8. **🚨 Массовая атака** — придёт через ~1 минуту (цикл mass-attack-monitor)
+
+#### Шаг 4.7.4. Проверка логов
На VPS:
```bash
@@ -806,6 +1119,15 @@ DEBUG_LOG="/var/log/security-monitor-debug.log"
echo "$(date): ALERT $scenario $value" >> "$DEBUG_LOG"
```
+### Отладка mass-attack-monitor.sh:
+
+```bash
+systemctl status mass-attack-monitor
+journalctl -u mass-attack-monitor -f
+cscli decisions list --since 5m -o json | jq 'length'
+cat /tmp/mass-attack-state.txt
+```
+
---
## 7. Добавление новых сценариев CrowdSec
@@ -867,7 +1189,9 @@ cat /etc/crowdsec/whitelist.txt
- `/etc/crowdsec/profiles.yaml` — профили с notification
- `/etc/crowdsec/whitelist.txt` — whitelist IP для security-monitor
- `/usr/local/bin/security-monitor.sh` — скрипт мониторинга системных событий
+- `/usr/local/bin/mass-attack-monitor.sh` — скрипт мониторинга массовых атак
- `/etc/systemd/system/security-monitor.service` — systemd сервис
+- `/etc/systemd/system/mass-attack-monitor.service` — systemd сервис
### Manage (Home Server):
- `/usr/local/bin/alerts/matrix-relay.py` — HTTP сервер relay
@@ -885,9 +1209,13 @@ cat /etc/crowdsec/whitelist.txt
1. **`$.Env.HOSTNAME` в шаблоне CrowdSec** → жёстко заданное имя хоста `"host": "Charon-vpn"`
2. **GET вместо POST в notification** → добавлен `method: POST`
-3. **Дублирование алертов** → дедупликация по времени (30 сек) в `security-monitor.sh`
-4. **CPU 100% от journalctl** → переключение на `tail -F` + правильный `journalctl --since now`
-5. **Старые события из журнала** → `--since now` + state file
-6. **Спам от своего IP** → whitelist `/etc/crowdsec/whitelist.txt`
-7. **Кириллица в Python файлах** → Unicode escape последовательности (`\U0001F5A5`)
-8. **Порядок проверок в relay** → `"crowdsec_alerts"` должен проверяться **перед** `"alerts"`
\ No newline at end of file
+3. **Дублирование алертов SSH** → дедупликация по времени (30 сек) в `security-monitor.sh`
+4. **Дублирование алертов пакетов** → дедупликация по времени (10 сек) через `PKG_STATE_FILE`
+5. **CPU 100% от journalctl** → переключение на `tail -F` + правильный `journalctl --since now`
+6. **Старые события из журнала** → `--since now` + state file
+7. **Спам от своего IP** → whitelist `/etc/crowdsec/whitelist.txt`
+8. **Кириллица в Python файлах** → Unicode escape последовательности (`\U0001F5A5`)
+9. **Порядок проверок в relay** → `"crowdsec_alerts"` должен проверяться **перед** `"alerts"`
+10. **Дублирование systemd-сервисов** → `inotifywait -e create` без `-e modify`
+11. **`NameError` в formatters.py** → словарь `FORMATTERS` объявляется **после** всех функций
+12. **Битые unicode escape** → пересоздание `alerts.py` целиком при `cat >>`
\ No newline at end of file