diff --git a/README.md b/README.md index a62803f..7a50149 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,7 @@ Bash-скрипт мониторинга **SSH**, **SUDO** и событий **` - `DAILY_REPORT_TZ` — необязательная **IANA**-зона (`Europe/Moscow`, `Asia/Yekaterinburg`, …). Если **пусто**, для отчёта используется та же зона, что и у команды `date` у процесса монитора (как правило, совпадает с `timedatectl` / `/etc/localtime` на сервере). Если сервис запускается с `TZ=UTC` в unit-файле, без `DAILY_REPORT_TZ` отчёт ориентируется на **UTC** — тогда задайте явную зону в конфиге. - `NOTIFY_TZ` — **IANA**-зона для строк **«🕐 Время»** в Telegram/email (и аналогичных текстах). **Пусто** — используется **`DAILY_REPORT_TZ`**, если она задана, иначе зона процесса. Частая причина «время в теле сообщения UTC, а в заголовке чата локальное»: в **`ssh-monitor.service`** задано **`Environment=TZ=UTC`** — тогда задайте **`NOTIFY_TZ`** (или **`DAILY_REPORT_TZ`**) на вашу локаль, например `Asia/Vladivostok`. - `SERVER_DISPLAY_NAME` — подпись в строке **«🖥️ Сервер»** во всех уведомлениях. **Пусто** — `hostname` и при возможности основной IPv4 (`hostname (10.x.x.x)`). Задайте явное имя, если несколько хостов с одинаковым hostname шлют в один чат. Подробнее: [docs/notifications.ru.md](docs/notifications.ru.md). +- `SERVER_IPV4` — опциональный override для SAC `host.ipv4`. Если пусто, IPv4 определяется автоматически. - `SSH_ACCEPT_NOTIFY_DEDUP_SEC` — не чаще одного Telegram по **успешному SSH (`Accepted`)** на одну пару **пользователь + IP** за указанное число **секунд** (по умолчанию **5**). Снимает дубли, когда в journal две строки с **разными портами клиента** за один вход. **`0`** — отключить этот антидубль (останется только дедупликация внутри одного прохода `monitor_ssh` по `user|ip|port`). - `DAILY_REPORT_TOP_IPS` — сколько IP показывать в топе неудачных попыток за 24 часа. - `BRUTE_WINDOW_SEC` — окно (секунды) для оценки «массового» брутфорса по `journalctl`. @@ -155,7 +156,7 @@ bash -n ./ssh-monitor ## Релизный архив -Версия задаётся в **`SSH_MONITOR_VERSION`** в файле **`ssh-monitor`** и дублируется в **`version.txt`** (сейчас **1.2.6-SAC**). Модуль **`sac-client.sh`** использует ту же версию для SAC `product_version`. Сборка tarball из текущего git-дерева: +Версия задаётся в **`SSH_MONITOR_VERSION`** в файле **`ssh-monitor`** и дублируется в **`version.txt`** (сейчас **1.2.7-SAC**). Модуль **`sac-client.sh`** использует ту же версию для SAC `product_version`. Сборка tarball из текущего git-дерева: ```bash make dist diff --git a/readme_eng.md b/readme_eng.md index aab5e82..b4cc588 100644 --- a/readme_eng.md +++ b/readme_eng.md @@ -69,6 +69,7 @@ If `/etc/ssh-monitor.conf` is missing or some values are empty, script defaults - `DAILY_REPORT_TZ` — optional **IANA** zone (`Europe/Moscow`, …). If **empty**, report uses the monitor process zone (usually matches `timedatectl` / `/etc/localtime`). If the unit sets `TZ=UTC`, without `DAILY_REPORT_TZ` the report uses **UTC** — set an explicit zone in config. - `NOTIFY_TZ` — **IANA** zone for **«🕐 Время»** lines in Telegram/email. **Empty** — use **`DAILY_REPORT_TZ`** if set, else process zone. Common mismatch: **`ssh-monitor.service`** has **`Environment=TZ=UTC`** — set **`NOTIFY_TZ`** or **`DAILY_REPORT_TZ`** to your locale, e.g. `Asia/Vladivostok`. - `SERVER_DISPLAY_NAME` — label in **«🖥️ Server»** line on every alert. **Empty** — `hostname` plus primary IPv4 when available. Set a fixed name when many hosts share one Telegram chat. Details: [docs/notifications.ru.md](docs/notifications.ru.md) (Russian). +- `SERVER_IPV4` — optional override for SAC `host.ipv4`. If empty, the script auto-detects host IPv4. - `SSH_ACCEPT_NOTIFY_DEDUP_SEC` — at most one Telegram per successful SSH (**Accepted**) per **user + IP** pair within **N** seconds (default **5**). Removes duplicates when journal has two lines with **different client ports** for one login. **`0`** disables this cross-iteration dedup (in-pass dedup by `user|ip|port` remains). - `DAILY_REPORT_TOP_IPS` — how many IPs in the failed-attempts top for 24 hours. - `BRUTE_WINDOW_SEC` — window (seconds) for “mass” brute-force evaluation via `journalctl`. @@ -153,7 +154,7 @@ bash -n ./ssh-monitor ## Release tarball -Version is set as **`SSH_MONITOR_VERSION`** in **`ssh-monitor`** and **`version.txt`** (currently **1.2.6-SAC**). **`sac-client.sh`** reports the same version to SAC. Build from the git tree: +Version is set as **`SSH_MONITOR_VERSION`** in **`ssh-monitor`** and **`version.txt`** (currently **1.2.7-SAC**). **`sac-client.sh`** reports the same version to SAC. Build from the git tree: ```bash make dist diff --git a/sac-client.sh b/sac-client.sh index 37c7da9..3da83e3 100644 --- a/sac-client.sh +++ b/sac-client.sh @@ -229,6 +229,7 @@ sac_send_event() { SAC_AGENT_ID="$(sac_agent_instance_id)" \ SAC_PRODUCT_VERSION="${SSH_MONITOR_VERSION:-unknown}" \ SAC_SERVER_DISPLAY_NAME="${SERVER_DISPLAY_NAME:-}" \ + SAC_HOST_IPV4="${SERVER_IPV4:-}" \ python3 <<'PY' import json, os, socket, uuid from datetime import datetime, timezone @@ -239,8 +240,39 @@ def details(): return None return json.loads(raw) +def valid_ipv4(value: str) -> bool: + try: + parts = value.split(".") + return len(parts) == 4 and all(0 <= int(p) <= 255 for p in parts) + except Exception: + return False + +def detect_host_ipv4() -> str: + manual = os.environ.get("SAC_HOST_IPV4", "").strip() + if manual and valid_ipv4(manual): + return manual + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("1.1.1.1", 53)) + ip = s.getsockname()[0] + s.close() + if valid_ipv4(ip) and not ip.startswith(("127.", "169.254.")): + return ip + except Exception: + pass + try: + infos = socket.getaddrinfo(socket.gethostname(), None, family=socket.AF_INET) + for info in infos: + ip = info[4][0] + if valid_ipv4(ip) and not ip.startswith(("127.", "169.254.")): + return ip + except Exception: + pass + return "" + host = socket.gethostname() display = os.environ.get("SAC_SERVER_DISPLAY_NAME", "").strip() +host_ipv4 = detect_host_ipv4() etype = os.environ["SAC_EVENT_TYPE"] if etype.startswith(("ssh.", "auth.", "rdp.")): category = "auth" @@ -267,6 +299,7 @@ payload = { "hostname": host, "os_family": "linux", **({"display_name": display} if display else {}), + **({"ipv4": host_ipv4} if host_ipv4 else {}), }, "category": category, "type": os.environ["SAC_EVENT_TYPE"], diff --git a/ssh-monitor b/ssh-monitor index a92fdeb..5a9d6c2 100644 --- a/ssh-monitor +++ b/ssh-monitor @@ -7,7 +7,7 @@ IFS=$'\n\t' # ============================================ CONFIG_FILE="/etc/ssh-monitor.conf" -SSH_MONITOR_VERSION="1.2.6-SAC" +SSH_MONITOR_VERSION="1.2.7-SAC" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ -f "$SCRIPT_DIR/sac-client.sh" ]; then @@ -61,6 +61,8 @@ DAILY_REPORT_TZ="" NOTIFY_TZ="" # Подпись сервера в уведомлениях: пусто = hostname (+ IPv4); иначе фиксированное имя (удобно при одинаковых hostname). SERVER_DISPLAY_NAME="" +# Явный IPv4 хоста для SAC payload (host.ipv4). Пусто = автоопределение. +SERVER_IPV4="" # Не слать повтор «Accepted» для той же пары user+IP чаще, чем раз в N секунд (разные port в журнале — один вход). SSH_ACCEPT_NOTIFY_DEDUP_SEC=5 diff --git a/ssh-monitor.conf.example b/ssh-monitor.conf.example index 9eb745d..3fe10ef 100644 --- a/ssh-monitor.conf.example +++ b/ssh-monitor.conf.example @@ -75,6 +75,8 @@ DAILY_REPORT_TOP_IPS="5" NOTIFY_TZ="" # Подпись «🖥️ Сервер» в уведомлениях: пусто = hostname (+ IPv4); иначе фиксированное имя (например prod-web-01). # SERVER_DISPLAY_NAME="prod-web-01" +# Явный IPv4 для SAC payload host.ipv4. Пусто = автоопределение. +# SERVER_IPV4="10.10.20.31" # Не чаще 1 успешного SSH (Accepted) в Telegram на пару user+IP за N секунд (разные port в журнале). 0 = только дедуп внутри одного прохода. SSH_ACCEPT_NOTIFY_DEDUP_SEC="5" diff --git a/version.txt b/version.txt index 6d70a47..5d52c6a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.2.6-SAC +1.2.7-SAC