From 18562af07a9c6323ed390ff2ac6f3215ea3764c9 Mon Sep 17 00:00:00 2001 From: PTah Date: Wed, 27 May 2026 11:24:19 +1000 Subject: [PATCH] feat: map security events to SAC typed payloads --- sac-client.sh | 40 +++++++++++++++++++++++++++++++++++----- ssh-monitor | 36 ++++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/sac-client.sh b/sac-client.sh index fb94333..86a9d61 100644 --- a/sac-client.sh +++ b/sac-client.sh @@ -99,6 +99,19 @@ def details(): return json.loads(raw) host = socket.gethostname() +etype = os.environ["SAC_EVENT_TYPE"] +if etype.startswith(("ssh.", "auth.", "rdp.")): + category = "auth" +elif etype.startswith("privilege."): + category = "privilege" +elif etype.startswith("session."): + category = "session" +elif etype.startswith("report."): + category = "report" +elif etype.startswith("rdg."): + category = "network" +else: + category = "agent" payload = { "schema_version": "1.0", "event_id": str(uuid.uuid4()), @@ -112,7 +125,7 @@ payload = { "hostname": host, "os_family": "linux", }, - "category": "agent", + "category": category, "type": os.environ["SAC_EVENT_TYPE"], "severity": os.environ["SAC_SEVERITY"], "title": os.environ["SAC_TITLE"], @@ -152,13 +165,30 @@ PY return 1 } -# notify_or_sac type severity title summary telegram_message +# JSON details из переменных SAC_D_* (опционально) +sac_build_details_json() { + python3 <<'PY' +import json, os +out = {} +for k, v in os.environ.items(): + if k.startswith("SAC_D_") and v: + out[k[6:].lower()] = v +print(json.dumps(out) if out else "") +PY +} + +# notify_or_sac type severity title summary telegram_message [details_json] notify_or_sac() { local event_type="$1" local severity="$2" local title="$3" local summary="$4" local telegram_message="${5:-$summary}" + local details_json="${6:-}" + if [ -z "$details_json" ] && [ -n "${SAC_D_USER:-}${SAC_D_IP:-}${SAC_D_COMMAND:-}" ]; then + details_json="$(sac_build_details_json)" + fi + unset SAC_D_USER SAC_D_IP SAC_D_COMMAND SAC_D_ATTEMPT SAC_D_MAX SAC_D_RUN_AS SAC_D_PWD 2>/dev/null || true local mode mode="$(sac_normalize_mode "${UseSAC:-off}")" @@ -167,14 +197,14 @@ notify_or_sac() { notify_send "$telegram_message" ;; exclusive) - sac_send_event "$event_type" "$severity" "$title" "$summary" + sac_send_event "$event_type" "$severity" "$title" "$summary" "$details_json" ;; dual) - sac_send_event "$event_type" "$severity" "$title" "$summary" || true + sac_send_event "$event_type" "$severity" "$title" "$summary" "$details_json" || true NOTIFY_SKIP_SAC_MIRROR=1 notify_send "$telegram_message" ;; fallback) - if sac_send_event "$event_type" "$severity" "$title" "$summary"; then + if sac_send_event "$event_type" "$severity" "$title" "$summary" "$details_json"; then return 0 fi NOTIFY_SKIP_SAC_MIRROR=1 notify_send "$telegram_message" diff --git a/ssh-monitor b/ssh-monitor index 50cdd45..ac22c48 100644 --- a/ssh-monitor +++ b/ssh-monitor @@ -991,7 +991,8 @@ block_ip() { message="${message}👤 Попыток: ${attempts} (порог ${MAX_ATTEMPTS}; ENABLE_IP_BAN=0)"$'\n' message="${message}ℹ️ ipset/firewall не меняются. При ENABLE_IP_BAN=1 бан был бы ~${ban_time_hours} ч."$'\n' message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" - notify_send "$message" + SAC_D_IP="$ip" SAC_D_ATTEMPT="$attempts" SAC_D_MAX="$MAX_ATTEMPTS" \ + notify_or_sac "ssh.ip.bruteforce.threshold" "warning" "Лимит SSH без бана" "${ip}: ${attempts} попыток" "$message" write_log "Порог неудачных SSH для $ip ($attempts попыток), автобан отключён (ENABLE_IP_BAN=0)" return 0 fi @@ -1015,7 +1016,8 @@ block_ip() { message="${message}📊 Неудачных попыток: ${attempts}"$'\n' message="${message}⏱️ Блокировка на: ${ban_time_hours} час(ов) (ipset timeout)"$'\n' message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" - notify_send "$message" + SAC_D_IP="$ip" SAC_D_ATTEMPT="$attempts" \ + notify_or_sac "ssh.ip.banned" "high" "IP заблокирован" "${ip} (${attempts} попыток)" "$message" write_log "AUTO-BAN: IP $ip в ipset на $ban_time_hours ч. ($attempts попыток)" # Один IP — одна строка: после истечения ipset-таймаута is_ip_blocked=0, а в файле @@ -1281,13 +1283,15 @@ monitor_ssh() { message="${message}👤 Пользователь: ${user}"$'\n' message="${message}🌐 IP адрес: ${ip:-local}"$'\n' message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" - notify_send "$message" + SAC_D_USER="$user" SAC_D_IP="${ip:-}" \ + notify_or_sac "ssh.login.success" "info" "Успешное SSH" "${user} с ${ip:-local}" "$message" write_log "SSH успешный вход: $user с IP ${ip:-local}" if [ "$user" = "root" ]; then local rmsg=" 🔑 SSH ВХОД ПОД ROOT "$'\n' rmsg="${rmsg}🌐 IP: ${ip:-local}"$'\n' rmsg="${rmsg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" - notify_send "$rmsg" + SAC_D_USER="root" SAC_D_IP="${ip:-}" \ + notify_or_sac "ssh.login.success" "warning" "SSH вход под root" "root с ${ip:-local}" "$rmsg" fi fi elif echo "$line" | grep -q "Failed password"; then @@ -1316,7 +1320,8 @@ monitor_ssh() { message="${message}🌐 IP адрес: ${ip}"$'\n' message="${message}📊 Попытка ${current_attempts} из ${MAX_ATTEMPTS}"$'\n' message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" - notify_send "$message" + SAC_D_USER="$user" SAC_D_IP="$ip" SAC_D_ATTEMPT="$current_attempts" SAC_D_MAX="$MAX_ATTEMPTS" \ + notify_or_sac "ssh.login.failed" "warning" "Неудачная SSH" "${user} с ${ip} (${current_attempts}/${MAX_ATTEMPTS})" "$message" fi write_log "SSH неудачная попытка: $user с IP $ip (попытка $current_attempts из $MAX_ATTEMPTS)" fi @@ -1432,6 +1437,7 @@ monitor_sudo() { message="${message}📁 Директория: ${pwd:-unknown}"$'\n' message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" local sac_summary="${user}: ${sudo_cmd}" + SAC_D_USER="$user" SAC_D_COMMAND="$sudo_cmd" SAC_D_RUN_AS="${run_as:-}" SAC_D_PWD="${pwd:-}" \ notify_or_sac "privilege.sudo.command" "warning" "Использование sudo" "$sac_summary" "$message" write_log "SUDO: $user выполнил $sudo_cmd${run_as:+ (USER=$run_as)}" fi @@ -1473,7 +1479,7 @@ monitor_security_events() { local m=" 🔐 ВНИМАНИЕ: изменение SSH host key / MITM? "$'\n' m="${m}$(printf '%s\n' "$hk")" m="${m}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" - notify_send "$m" + notify_or_sac "agent.notification" "high" "SSH host key changed" "MITM?" "$m" write_log "SECURITY: обнаружено сообщение об изменении host key" fi fi @@ -1494,7 +1500,8 @@ monitor_security_events() { bm="${bm}🌐 IP: ${ip}"$'\n' bm="${bm}📊 Неудачных попыток за ~${BRUTE_WINDOW_SEC}s: ${count}"$'\n' bm="${bm}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" - notify_send "$bm" + SAC_D_IP="$ip" SAC_D_ATTEMPT="$count" \ + notify_or_sac "ssh.bruteforce.mass" "high" "Массовый брутфорс" "${ip}: ${count} за ${BRUTE_WINDOW_SEC}s" "$bm" write_log "SECURITY: массовый брутфорс? IP=$ip fails=${count} за окно ${BRUTE_WINDOW_SEC}s" fi fi @@ -1605,7 +1612,8 @@ monitor_logind() { msg="${msg}👤 Пользователь: ${user:-неизвестно}"$'\n' msg="${msg}🆔 Сессия: ${sid:-?}"$'\n' msg="${msg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" - notify_send "$msg" + SAC_D_USER="${user:-}" \ + notify_or_sac "session.logind.new" "info" "Новая сессия logind" "${user:-?} sid=${sid:-?}" "$msg" write_log "LOGIND: новая сессия: ${line}" else write_log "LOGIND: новая сессия (разбор user/sid не удался): ${line}" @@ -1622,7 +1630,7 @@ monitor_logind() { local msg_r=" 🚪 СЕССИЯ ЗАКРЫТА (systemd-logind) "$'\n' msg_r="${msg_r}🆔 Сессия: ${sess_r:-?}"$'\n' msg_r="${msg_r}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" - notify_send "$msg_r" + notify_or_sac "session.logind.removed" "info" "Сессия закрыта" "sid=${sess_r:-?}" "$msg_r" fi elif [[ "$low" == *"failed"* ]]; then @@ -1630,7 +1638,7 @@ monitor_logind() { local msg_f=" ❌ СБОЙ (systemd-logind) "$'\n' msg_f="${msg_f}📝 ${line}"$'\n' msg_f="${msg_f}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" - notify_send "$msg_f" + notify_or_sac "session.logind.failed" "warning" "Сбой logind" "logind failed" "$msg_f" write_log "LOGIND: сбой: $line" fi fi @@ -1746,7 +1754,7 @@ send_daily_report() { message="${message} Нет активных пользователей"$'\n' fi - notify_send "$message" + notify_or_sac "report.daily.ssh" "info" "Ежедневный отчёт SSH" "Отчёт за сутки" "$message" write_log "Ежедневный отчет отправлен" echo "$(report_date '+%F')" >"$LAST_REPORT_FILE" @@ -1804,7 +1812,7 @@ send_heartbeat() { local message=" ❤️ Heartbeat - скрипт мониторинга работает "$'\n' message="${message}🕐 Время: ${timestamp_human}" - notify_send "$message" + notify_or_sac "agent.heartbeat" "info" "Heartbeat" "ssh-monitor alive" "$message" echo "$timestamp" >"$LAST_HEARTBEAT_FILE" write_log "Heartbeat отправлен" } @@ -1871,7 +1879,7 @@ send_startup_notification() { message="${message}❤️ Heartbeat будет отправляться каждые 12 часов"$'\n' message="${message}📢 Каналы уведомлений (все по списку): $(notify_chain_human)" - notify_send "$message" + notify_or_sac "agent.lifecycle" "info" "Мониторинг запущен" "ssh-monitor started" "$message" write_log "Скрипт мониторинга запущен (белый список: $whitelist_count записей)" } @@ -1884,7 +1892,7 @@ send_shutdown_notification() { local message=" ⚠️ СКРИПТ МОНИТОРИНГА ОСТАНОВЛЕН "$'\n' message="${message}🕐 Время остановки: ${timestamp}" - notify_send "$message" + notify_or_sac "agent.lifecycle" "warning" "Мониторинг остановлен" "ssh-monitor stopped" "$message" write_log "Скрипт мониторинга остановлен" }