feat: map security events to SAC typed payloads

This commit is contained in:
PTah
2026-05-27 11:24:19 +10:00
parent 52654e2735
commit 18562af07a
2 changed files with 57 additions and 19 deletions
+35 -5
View File
@@ -99,6 +99,19 @@ def details():
return json.loads(raw) return json.loads(raw)
host = socket.gethostname() 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 = { payload = {
"schema_version": "1.0", "schema_version": "1.0",
"event_id": str(uuid.uuid4()), "event_id": str(uuid.uuid4()),
@@ -112,7 +125,7 @@ payload = {
"hostname": host, "hostname": host,
"os_family": "linux", "os_family": "linux",
}, },
"category": "agent", "category": category,
"type": os.environ["SAC_EVENT_TYPE"], "type": os.environ["SAC_EVENT_TYPE"],
"severity": os.environ["SAC_SEVERITY"], "severity": os.environ["SAC_SEVERITY"],
"title": os.environ["SAC_TITLE"], "title": os.environ["SAC_TITLE"],
@@ -152,13 +165,30 @@ PY
return 1 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() { notify_or_sac() {
local event_type="$1" local event_type="$1"
local severity="$2" local severity="$2"
local title="$3" local title="$3"
local summary="$4" local summary="$4"
local telegram_message="${5:-$summary}" 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 local mode
mode="$(sac_normalize_mode "${UseSAC:-off}")" mode="$(sac_normalize_mode "${UseSAC:-off}")"
@@ -167,14 +197,14 @@ notify_or_sac() {
notify_send "$telegram_message" notify_send "$telegram_message"
;; ;;
exclusive) exclusive)
sac_send_event "$event_type" "$severity" "$title" "$summary" sac_send_event "$event_type" "$severity" "$title" "$summary" "$details_json"
;; ;;
dual) 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" NOTIFY_SKIP_SAC_MIRROR=1 notify_send "$telegram_message"
;; ;;
fallback) 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 return 0
fi fi
NOTIFY_SKIP_SAC_MIRROR=1 notify_send "$telegram_message" NOTIFY_SKIP_SAC_MIRROR=1 notify_send "$telegram_message"
+22 -14
View File
@@ -991,7 +991,8 @@ block_ip() {
message="${message}👤 Попыток: ${attempts} (порог ${MAX_ATTEMPTS}; ENABLE_IP_BAN=0)"$'\n' message="${message}👤 Попыток: ${attempts} (порог ${MAX_ATTEMPTS}; ENABLE_IP_BAN=0)"$'\n'
message="${message}️ ipset/firewall не меняются. При ENABLE_IP_BAN=1 бан был бы ~${ban_time_hours} ч."$'\n' message="${message}️ ipset/firewall не меняются. При ENABLE_IP_BAN=1 бан был бы ~${ban_time_hours} ч."$'\n'
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" 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)" write_log "Порог неудачных SSH для $ip ($attempts попыток), автобан отключён (ENABLE_IP_BAN=0)"
return 0 return 0
fi fi
@@ -1015,7 +1016,8 @@ block_ip() {
message="${message}📊 Неудачных попыток: ${attempts}"$'\n' message="${message}📊 Неудачных попыток: ${attempts}"$'\n'
message="${message}⏱️ Блокировка на: ${ban_time_hours} час(ов) (ipset timeout)"$'\n' message="${message}⏱️ Блокировка на: ${ban_time_hours} час(ов) (ipset timeout)"$'\n'
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" 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 попыток)" write_log "AUTO-BAN: IP $ip в ipset на $ban_time_hours ч. ($attempts попыток)"
# Один IP — одна строка: после истечения ipset-таймаута is_ip_blocked=0, а в файле # Один IP — одна строка: после истечения ipset-таймаута is_ip_blocked=0, а в файле
@@ -1281,13 +1283,15 @@ monitor_ssh() {
message="${message}👤 Пользователь: ${user}"$'\n' message="${message}👤 Пользователь: ${user}"$'\n'
message="${message}🌐 IP адрес: ${ip:-local}"$'\n' message="${message}🌐 IP адрес: ${ip:-local}"$'\n'
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" 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}" write_log "SSH успешный вход: $user с IP ${ip:-local}"
if [ "$user" = "root" ]; then if [ "$user" = "root" ]; then
local rmsg=" 🔑 SSH ВХОД ПОД ROOT "$'\n' local rmsg=" 🔑 SSH ВХОД ПОД ROOT "$'\n'
rmsg="${rmsg}🌐 IP: ${ip:-local}"$'\n' rmsg="${rmsg}🌐 IP: ${ip:-local}"$'\n'
rmsg="${rmsg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" 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
fi fi
elif echo "$line" | grep -q "Failed password"; then elif echo "$line" | grep -q "Failed password"; then
@@ -1316,7 +1320,8 @@ monitor_ssh() {
message="${message}🌐 IP адрес: ${ip}"$'\n' message="${message}🌐 IP адрес: ${ip}"$'\n'
message="${message}📊 Попытка ${current_attempts} из ${MAX_ATTEMPTS}"$'\n' message="${message}📊 Попытка ${current_attempts} из ${MAX_ATTEMPTS}"$'\n'
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" 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 fi
write_log "SSH неудачная попытка: $user с IP $ip (попытка $current_attempts из $MAX_ATTEMPTS)" write_log "SSH неудачная попытка: $user с IP $ip (попытка $current_attempts из $MAX_ATTEMPTS)"
fi fi
@@ -1432,6 +1437,7 @@ monitor_sudo() {
message="${message}📁 Директория: ${pwd:-unknown}"$'\n' message="${message}📁 Директория: ${pwd:-unknown}"$'\n'
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
local sac_summary="${user}: ${sudo_cmd}" 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" notify_or_sac "privilege.sudo.command" "warning" "Использование sudo" "$sac_summary" "$message"
write_log "SUDO: $user выполнил $sudo_cmd${run_as:+ (USER=$run_as)}" write_log "SUDO: $user выполнил $sudo_cmd${run_as:+ (USER=$run_as)}"
fi fi
@@ -1473,7 +1479,7 @@ monitor_security_events() {
local m=" 🔐 ВНИМАНИЕ: изменение SSH host key / MITM? "$'\n' local m=" 🔐 ВНИМАНИЕ: изменение SSH host key / MITM? "$'\n'
m="${m}$(printf '%s\n' "$hk")" m="${m}$(printf '%s\n' "$hk")"
m="${m}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" 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" write_log "SECURITY: обнаружено сообщение об изменении host key"
fi fi
fi fi
@@ -1494,7 +1500,8 @@ monitor_security_events() {
bm="${bm}🌐 IP: ${ip}"$'\n' bm="${bm}🌐 IP: ${ip}"$'\n'
bm="${bm}📊 Неудачных попыток за ~${BRUTE_WINDOW_SEC}s: ${count}"$'\n' bm="${bm}📊 Неудачных попыток за ~${BRUTE_WINDOW_SEC}s: ${count}"$'\n'
bm="${bm}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" 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" write_log "SECURITY: массовый брутфорс? IP=$ip fails=${count} за окно ${BRUTE_WINDOW_SEC}s"
fi fi
fi fi
@@ -1605,7 +1612,8 @@ monitor_logind() {
msg="${msg}👤 Пользователь: ${user:-неизвестно}"$'\n' msg="${msg}👤 Пользователь: ${user:-неизвестно}"$'\n'
msg="${msg}🆔 Сессия: ${sid:-?}"$'\n' msg="${msg}🆔 Сессия: ${sid:-?}"$'\n'
msg="${msg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" 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}" write_log "LOGIND: новая сессия: ${line}"
else else
write_log "LOGIND: новая сессия (разбор user/sid не удался): ${line}" write_log "LOGIND: новая сессия (разбор user/sid не удался): ${line}"
@@ -1622,7 +1630,7 @@ monitor_logind() {
local msg_r=" 🚪 СЕССИЯ ЗАКРЫТА (systemd-logind) "$'\n' local msg_r=" 🚪 СЕССИЯ ЗАКРЫТА (systemd-logind) "$'\n'
msg_r="${msg_r}🆔 Сессия: ${sess_r:-?}"$'\n' msg_r="${msg_r}🆔 Сессия: ${sess_r:-?}"$'\n'
msg_r="${msg_r}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" 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 fi
elif [[ "$low" == *"failed"* ]]; then elif [[ "$low" == *"failed"* ]]; then
@@ -1630,7 +1638,7 @@ monitor_logind() {
local msg_f=" ❌ СБОЙ (systemd-logind) "$'\n' local msg_f=" ❌ СБОЙ (systemd-logind) "$'\n'
msg_f="${msg_f}📝 ${line}"$'\n' msg_f="${msg_f}📝 ${line}"$'\n'
msg_f="${msg_f}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')" 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" write_log "LOGIND: сбой: $line"
fi fi
fi fi
@@ -1746,7 +1754,7 @@ send_daily_report() {
message="${message} Нет активных пользователей"$'\n' message="${message} Нет активных пользователей"$'\n'
fi fi
notify_send "$message" notify_or_sac "report.daily.ssh" "info" "Ежедневный отчёт SSH" "Отчёт за сутки" "$message"
write_log "Ежедневный отчет отправлен" write_log "Ежедневный отчет отправлен"
echo "$(report_date '+%F')" >"$LAST_REPORT_FILE" echo "$(report_date '+%F')" >"$LAST_REPORT_FILE"
@@ -1804,7 +1812,7 @@ send_heartbeat() {
local message=" ❤️ Heartbeat - скрипт мониторинга работает "$'\n' local message=" ❤️ Heartbeat - скрипт мониторинга работает "$'\n'
message="${message}🕐 Время: ${timestamp_human}" message="${message}🕐 Время: ${timestamp_human}"
notify_send "$message" notify_or_sac "agent.heartbeat" "info" "Heartbeat" "ssh-monitor alive" "$message"
echo "$timestamp" >"$LAST_HEARTBEAT_FILE" echo "$timestamp" >"$LAST_HEARTBEAT_FILE"
write_log "Heartbeat отправлен" write_log "Heartbeat отправлен"
} }
@@ -1871,7 +1879,7 @@ send_startup_notification() {
message="${message}❤️ Heartbeat будет отправляться каждые 12 часов"$'\n' message="${message}❤️ Heartbeat будет отправляться каждые 12 часов"$'\n'
message="${message}📢 Каналы уведомлений (все по списку): $(notify_chain_human)" message="${message}📢 Каналы уведомлений (все по списку): $(notify_chain_human)"
notify_send "$message" notify_or_sac "agent.lifecycle" "info" "Мониторинг запущен" "ssh-monitor started" "$message"
write_log "Скрипт мониторинга запущен (белый список: $whitelist_count записей)" write_log "Скрипт мониторинга запущен (белый список: $whitelist_count записей)"
} }
@@ -1884,7 +1892,7 @@ send_shutdown_notification() {
local message=" ⚠️ СКРИПТ МОНИТОРИНГА ОСТАНОВЛЕН "$'\n' local message=" ⚠️ СКРИПТ МОНИТОРИНГА ОСТАНОВЛЕН "$'\n'
message="${message}🕐 Время остановки: ${timestamp}" message="${message}🕐 Время остановки: ${timestamp}"
notify_send "$message" notify_or_sac "agent.lifecycle" "warning" "Мониторинг остановлен" "ssh-monitor stopped" "$message"
write_log "Скрипт мониторинга остановлен" write_log "Скрипт мониторинга остановлен"
} }