Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e756674d0 | |||
| 0a80effd77 | |||
| de61c6c771 | |||
| c96e870542 | |||
| 489d071208 | |||
| 1361fd264c |
@@ -1,6 +1,6 @@
|
||||
# ssh-monitor
|
||||
|
||||
**Версия:** `2.1.1-SAC`
|
||||
**Версия:** `2.1.5-SAC`
|
||||
|
||||
Bash-мониторинг **SSH**, **sudo**, **systemd-logind**: Telegram/email, бан IP (ipset), ежедневный отчёт, heartbeat.
|
||||
|
||||
@@ -161,7 +161,7 @@ bash -n ./ssh-monitor
|
||||
|
||||
## Релизный архив
|
||||
|
||||
Версия задаётся в **`SSH_MONITOR_VERSION`** в файле **`ssh-monitor`** и дублируется в **`version.txt`** (сейчас **2.1.1-SAC**). Модуль **`sac-client.sh`** использует ту же версию для SAC `product_version`. Сборка tarball из текущего git-дерева:
|
||||
Версия задаётся в **`SSH_MONITOR_VERSION`** в файле **`ssh-monitor`** и дублируется в **`version.txt`** (та же строка, что в шапке README). Модуль **`sac-client.sh`** использует ту же версию для SAC `product_version`. Сборка tarball из текущего git-дерева:
|
||||
|
||||
```bash
|
||||
make dist
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Автообновление ssh-monitor
|
||||
|
||||
Скрипт **`update_ssh_monitor.sh`** в корне репозитория обновляет установленный **`/usr/local/bin/ssh-monitor`** из git-репозитория и перезапускает systemd-сервис. Конфиг **`/etc/ssh-monitor.conf`**, watchdog и таймеры **не изменяются**.
|
||||
Скрипт **`update_ssh_monitor.sh`** в корне репозитория обновляет установленный **`/usr/local/bin/ssh-monitor`** из git-репозитория и перезапускает systemd-сервис. Watchdog и таймеры **не изменяются**.
|
||||
|
||||
**Конфиг `/etc/ssh-monitor.conf`:** при первой установке копируется из `ssh-monitor.conf.example`. При каждом deploy/обновлении в **существующий** конфиг **дописываются** отсутствующие SAC-переменные (`UseSAC`, `SAC_URL`, `SAC_API_KEY`, spool, timeout и т.д.) и выставляется **`UseSAC=fallback`**. Telegram/SMTP и прочие ключи **не перезаписываются**.
|
||||
|
||||
Установка самого монитора и unit-файлов: [README.md](../README.md), зависимости: [install-prerequisites.ru.md](install-prerequisites.ru.md).
|
||||
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@ bash -n ./ssh-monitor
|
||||
|
||||
## Release tarball
|
||||
|
||||
Version is set as **`SSH_MONITOR_VERSION`** in **`ssh-monitor`** and **`version.txt`** (currently **2.1.1-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`** (same string as in the README header). **`sac-client.sh`** reports the same version to SAC. Build from the git tree:
|
||||
|
||||
```bash
|
||||
make dist
|
||||
|
||||
+63
-13
@@ -51,8 +51,8 @@ sac_check_health() {
|
||||
return 0
|
||||
fi
|
||||
code="$(curl -sS -o /dev/null -w '%{http_code}' \
|
||||
--connect-timeout "${SAC_TIMEOUT_SEC:-12}" \
|
||||
--max-time "${SAC_TIMEOUT_SEC:-12}" \
|
||||
--connect-timeout "${SAC_TIMEOUT_SEC:-45}" \
|
||||
--max-time "${SAC_TIMEOUT_SEC:-45}" \
|
||||
"${base}/health" || echo 000)"
|
||||
[ "$code" = "200" ]
|
||||
}
|
||||
@@ -152,8 +152,8 @@ sac_post_payload() {
|
||||
-H "Authorization: Bearer ${SAC_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Idempotency-Key: ${event_id}" \
|
||||
--connect-timeout "${SAC_TIMEOUT_SEC:-12}" \
|
||||
--max-time "${SAC_TIMEOUT_SEC:-12}" \
|
||||
--connect-timeout "${SAC_TIMEOUT_SEC:-45}" \
|
||||
--max-time "${SAC_TIMEOUT_SEC:-45}" \
|
||||
-d @"$tmp" 2>/dev/null || echo 000)"
|
||||
rm -f "$tmp"
|
||||
|
||||
@@ -170,9 +170,36 @@ sac_post_payload() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# Список spool: report.daily.* первыми (старые раньше), затем остальное.
|
||||
sac_flush_spool_list_files() {
|
||||
local dir="${1:-}"
|
||||
[ -n "$dir" ] && [ -d "$dir" ] || return 0
|
||||
SPOOL_DIR="$dir" python3 <<'PY'
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
|
||||
dir_path = os.environ.get("SPOOL_DIR", "")
|
||||
daily, other = [], []
|
||||
for path in glob.glob(os.path.join(dir_path, "*.json")):
|
||||
event_type = ""
|
||||
try:
|
||||
with open(path, encoding="utf-8") as handle:
|
||||
event_type = json.load(handle).get("type", "") or ""
|
||||
except Exception:
|
||||
pass
|
||||
mtime = os.path.getmtime(path)
|
||||
bucket = daily if event_type in ("report.daily.ssh", "report.daily.rdp") else other
|
||||
bucket.append((mtime, path))
|
||||
for bucket in (daily, other):
|
||||
for _, path in sorted(bucket):
|
||||
print(path)
|
||||
PY
|
||||
}
|
||||
|
||||
# Повторная отправка файлов из SAC_SPOOL_DIR (до max_files за вызов).
|
||||
sac_flush_spool() {
|
||||
local mode max_files="${1:-20}"
|
||||
local mode max_files="${1:-${SAC_SPOOL_FLUSH_MAX_FILES:-50}}"
|
||||
mode="$(sac_normalize_mode "${UseSAC:-off}")"
|
||||
case "$mode" in
|
||||
off) return 0 ;;
|
||||
@@ -184,17 +211,14 @@ sac_flush_spool() {
|
||||
local dir="${SAC_SPOOL_DIR:-/var/lib/ssh-monitor/sac-spool}"
|
||||
[ -d "$dir" ] || return 0
|
||||
|
||||
local f count=0
|
||||
shopt -s nullglob
|
||||
for f in "$dir"/*.json; do
|
||||
[ -f "$f" ] || continue
|
||||
local f count=0 payload
|
||||
while IFS= read -r f; do
|
||||
[ -n "$f" ] && [ -f "$f" ] || continue
|
||||
count=$((count + 1))
|
||||
[ "$count" -gt "$max_files" ] && break
|
||||
local payload
|
||||
payload="$(cat "$f")"
|
||||
sac_post_payload "$payload" || true
|
||||
done
|
||||
shopt -u nullglob
|
||||
done < <(sac_flush_spool_list_files "$dir")
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -333,6 +357,13 @@ sac_is_heartbeat_only_event() {
|
||||
[ "${1:-}" = "agent.heartbeat" ]
|
||||
}
|
||||
|
||||
sac_is_daily_report_event() {
|
||||
case "${1:-}" in
|
||||
report.daily.ssh|report.daily.rdp) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Добавляет generated_by / telegram_via в JSON details для ingest.
|
||||
sac_merge_notify_details() {
|
||||
local details_json="${1:-}"
|
||||
@@ -365,7 +396,7 @@ notify_or_sac() {
|
||||
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
|
||||
unset SAC_D_USER SAC_D_IP SAC_D_SOURCE_IP SAC_D_PORT SAC_D_COMMAND SAC_D_ATTEMPT SAC_D_MAX SAC_D_RUN_AS SAC_D_PWD SAC_D_SESSION_ID SAC_D_TTY SAC_D_PTS SAC_D_SINCE 2>/dev/null || true
|
||||
local mode
|
||||
mode="$(sac_normalize_mode "${UseSAC:-off}")"
|
||||
local tg_via=""
|
||||
@@ -393,6 +424,25 @@ notify_or_sac() {
|
||||
esac
|
||||
fi
|
||||
|
||||
# Суточный отчёт — только SAC/spool; при сбое ingest не дублировать в локальный Telegram.
|
||||
if sac_is_daily_report_event "$event_type"; then
|
||||
case "$mode" in
|
||||
off)
|
||||
return 0
|
||||
;;
|
||||
exclusive|dual|fallback)
|
||||
if sac_send_event "$event_type" "$severity" "$title" "$summary" "$details_json"; then
|
||||
return 0
|
||||
fi
|
||||
write_log "WARN: daily report не принят SAC — остаётся в spool (локальный Telegram пропущен)"
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
case "$mode" in
|
||||
off)
|
||||
notify_send "$telegram_message"
|
||||
|
||||
+41
-9
@@ -7,7 +7,7 @@ IFS=$'\n\t'
|
||||
# ============================================
|
||||
|
||||
CONFIG_FILE="/etc/ssh-monitor.conf"
|
||||
SSH_MONITOR_VERSION="2.1.1-SAC"
|
||||
SSH_MONITOR_VERSION="2.1.6-SAC"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
if [ -f "$SCRIPT_DIR/sac-client.sh" ]; then
|
||||
@@ -34,7 +34,9 @@ UseSAC="off"
|
||||
SAC_URL=""
|
||||
SAC_API_KEY=""
|
||||
SAC_SPOOL_DIR="/var/lib/ssh-monitor/sac-spool"
|
||||
SAC_TIMEOUT_SEC="12"
|
||||
SAC_TIMEOUT_SEC="45"
|
||||
SAC_SPOOL_FLUSH_MAX_FILES="50"
|
||||
SAC_SPOOL_MAX_AGE_HOURS="72"
|
||||
SAC_SEND_HEARTBEAT="1"
|
||||
SAC_FALLBACK_FAILURES="5"
|
||||
|
||||
@@ -224,7 +226,9 @@ load_config() {
|
||||
local cfg_sac_url="${SAC_URL:-}"
|
||||
local cfg_sac_api_key="${SAC_API_KEY:-}"
|
||||
local cfg_sac_spool_dir="${SAC_SPOOL_DIR:-/var/lib/ssh-monitor/sac-spool}"
|
||||
local cfg_sac_timeout_sec="${SAC_TIMEOUT_SEC:-12}"
|
||||
local cfg_sac_timeout_sec="${SAC_TIMEOUT_SEC:-45}"
|
||||
local cfg_sac_spool_flush_max="${SAC_SPOOL_FLUSH_MAX_FILES:-50}"
|
||||
local cfg_sac_spool_max_age_h="${SAC_SPOOL_MAX_AGE_HOURS:-72}"
|
||||
local cfg_mail_smtp_host="${MAIL_SMTP_HOST:-}"
|
||||
local cfg_mail_smtp_port="${MAIL_SMTP_PORT:-587}"
|
||||
local cfg_mail_smtp_user="${MAIL_SMTP_USER:-}"
|
||||
@@ -297,6 +301,8 @@ load_config() {
|
||||
SAC_API_KEY="$(trim_value "${SAC_API_KEY:-$cfg_sac_api_key}")"
|
||||
SAC_SPOOL_DIR="$(trim_value "${SAC_SPOOL_DIR:-$cfg_sac_spool_dir}")"
|
||||
SAC_TIMEOUT_SEC="$(trim_value "${SAC_TIMEOUT_SEC:-$cfg_sac_timeout_sec}")"
|
||||
SAC_SPOOL_FLUSH_MAX_FILES="$(trim_value "${SAC_SPOOL_FLUSH_MAX_FILES:-$cfg_sac_spool_flush_max}")"
|
||||
SAC_SPOOL_MAX_AGE_HOURS="$(trim_value "${SAC_SPOOL_MAX_AGE_HOURS:-$cfg_sac_spool_max_age_h}")"
|
||||
MAIL_SMTP_HOST="$(trim_value "${MAIL_SMTP_HOST:-$cfg_mail_smtp_host}")"
|
||||
MAIL_SMTP_PORT="${MAIL_SMTP_PORT:-$cfg_mail_smtp_port}"
|
||||
MAIL_SMTP_USER="$(trim_value "${MAIL_SMTP_USER:-$cfg_mail_smtp_user}")"
|
||||
@@ -1234,7 +1240,6 @@ get_active_ssh_sessions() {
|
||||
local line user tty login_time ip
|
||||
while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
[[ "$line" != *"("* ]] && continue
|
||||
user=$(awk '{print $1}' <<<"$line")
|
||||
tty=$(awk '{print $2}' <<<"$line")
|
||||
login_time=$(awk '{print $3, $4}' <<<"$line")
|
||||
@@ -1316,14 +1321,14 @@ monitor_ssh() {
|
||||
message="${message}👤 Пользователь: ${user}"$'\n'
|
||||
message="${message}🌐 IP адрес: ${ip:-local}"$'\n'
|
||||
message="${message}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||
SAC_D_USER="$user" SAC_D_IP="${ip:-}" \
|
||||
SAC_D_USER="$user" SAC_D_SOURCE_IP="${ip:-}" SAC_D_IP="${ip:-}" SAC_D_PORT="${dedupe_port:-}" \
|
||||
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')"
|
||||
SAC_D_USER="root" SAC_D_IP="${ip:-}" \
|
||||
SAC_D_USER="root" SAC_D_SOURCE_IP="${ip:-}" SAC_D_IP="${ip:-}" SAC_D_PORT="${dedupe_port:-}" \
|
||||
notify_or_sac "ssh.login.success" "warning" "SSH вход под root" "root с ${ip:-local}" "$rmsg"
|
||||
fi
|
||||
fi
|
||||
@@ -1457,8 +1462,11 @@ monitor_sudo() {
|
||||
[[ -n "$fp" && -n "${SUDO_DEDUP_BATCH[$fp]:-}" ]] && continue
|
||||
[[ -n "$fp" ]] && SUDO_DEDUP_BATCH[$fp]=1
|
||||
|
||||
local user sudo_cmd pwd run_as
|
||||
local user sudo_cmd pwd run_as sudo_tty=""
|
||||
parse_sudo_audit_line "$line" user sudo_cmd pwd run_as
|
||||
if [[ "$line" =~ TTY=([^[:space:];]+) ]]; then
|
||||
sudo_tty="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
if [ -n "$user" ] && [ -n "${sudo_cmd:-}" ]; then
|
||||
local message=" ⚠️ ИСПОЛЬЗОВАНИЕ SUDO "$'\n'
|
||||
@@ -1471,6 +1479,7 @@ monitor_sudo() {
|
||||
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:-}" \
|
||||
SAC_D_TTY="${sudo_tty:-}" \
|
||||
notify_or_sac "privilege.sudo.command" "warning" "Использование sudo" "$sac_summary" "$message"
|
||||
write_log "SUDO: $user выполнил $sudo_cmd${run_as:+ (USER=$run_as)}"
|
||||
fi
|
||||
@@ -1549,6 +1558,27 @@ monitor_security_events() {
|
||||
# Локальные сессии (systemd-logind, journalctl)
|
||||
# ============================================
|
||||
|
||||
enrich_sac_logind_session_details() {
|
||||
local sid="$1"
|
||||
[ -n "$sid" ] || return 0
|
||||
SAC_D_SESSION_ID="$sid"
|
||||
command -v loginctl >/dev/null 2>&1 || return 0
|
||||
local tty remote since
|
||||
tty=$(loginctl show-session "$sid" -p TTY --value 2>/dev/null || true)
|
||||
remote=$(loginctl show-session "$sid" -p RemoteHost --value 2>/dev/null || true)
|
||||
since=$(loginctl show-session "$sid" -p Timestamp --value 2>/dev/null || true)
|
||||
if [ -n "$tty" ] && [ "$tty" != "n/a" ]; then
|
||||
SAC_D_TTY="$tty"
|
||||
case "$tty" in pts/*) SAC_D_PTS="${tty#pts/}" ;; esac
|
||||
fi
|
||||
if [ -n "$remote" ] && [ "$remote" != "n/a" ]; then
|
||||
SAC_D_SOURCE_IP="$remote"
|
||||
fi
|
||||
if [ -n "$since" ]; then
|
||||
SAC_D_SINCE="$since"
|
||||
fi
|
||||
}
|
||||
|
||||
# Возврат 0 = не слать Telegram по logind (дубль с monitor_ssh): Type=ssh или Service=*sshd*.
|
||||
# Пустой ответ loginctl — короткие повторы (гонка с journal).
|
||||
logind_session_is_ssh_duplicate_for_notify() {
|
||||
@@ -1645,6 +1675,7 @@ monitor_logind() {
|
||||
msg="${msg}👤 Пользователь: ${user:-неизвестно}"$'\n'
|
||||
msg="${msg}🆔 Сессия: ${sid:-?}"$'\n'
|
||||
msg="${msg}🕐 Время: $(notification_date '+%d.%m.%Y %H:%M:%S')"
|
||||
enrich_sac_logind_session_details "$sid"
|
||||
SAC_D_USER="${user:-}" \
|
||||
notify_or_sac "session.logind.new" "info" "Новая сессия logind" "${user:-?} sid=${sid:-?}" "$msg"
|
||||
write_log "LOGIND: новая сессия: ${line}"
|
||||
@@ -1740,7 +1771,8 @@ send_daily_report() {
|
||||
local active_sessions
|
||||
active_sessions=$(get_active_ssh_sessions)
|
||||
local active_count
|
||||
active_count=$(who 2>/dev/null | grep -c "." || echo "0")
|
||||
active_count=$(printf '%s' "$active_sessions" | grep -c '👤' || true)
|
||||
active_count=${active_count:-0}
|
||||
|
||||
local active_bans=0
|
||||
local ban_line
|
||||
@@ -2020,7 +2052,7 @@ main() {
|
||||
monitor_logind
|
||||
check_daily_report
|
||||
check_heartbeat
|
||||
sac_flush_spool 20 2>/dev/null || true
|
||||
sac_flush_spool "${SAC_SPOOL_FLUSH_MAX_FILES:-50}" 2>/dev/null || true
|
||||
write_health_and_metrics
|
||||
|
||||
local current_time
|
||||
|
||||
@@ -35,7 +35,9 @@ SAC_API_KEY="sac_UkOsAT3UWiQS54KK5OJPBDCSucysQDrKFju28wmYiz8"
|
||||
SAC_SPOOL_DIR="/var/lib/ssh-monitor/sac-spool"
|
||||
SAC_FAIL_COUNT_FILE="/var/lib/ssh-monitor/sac-fail.count"
|
||||
SAC_FALLBACK_FAILURES="5"
|
||||
SAC_TIMEOUT_SEC="12"
|
||||
SAC_TIMEOUT_SEC="45"
|
||||
SAC_SPOOL_FLUSH_MAX_FILES="50"
|
||||
SAC_SPOOL_MAX_AGE_HOURS="72"
|
||||
|
||||
# SMTP (python3). Для авто-режима почты нужны MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO.
|
||||
MAIL_SMTP_HOST=""
|
||||
|
||||
+174
-67
@@ -74,11 +74,26 @@ update_ssh_monitor.sh — обновление или первичная уст
|
||||
|
||||
Лог действий: /var/log/update_script.log (в конце каждого прогона — блок «Итог обновления»).
|
||||
|
||||
При первой установке или апгрейде с UseSAC=off / пустым SAC_API_KEY / без sac-client.sh
|
||||
example из git перезаписывает /etc/ssh-monitor.conf (старый файл — .bak.*).
|
||||
При первой установке создаётся /etc/ssh-monitor.conf из example.
|
||||
При обновлении в существующий конфиг дописываются отсутствующие SAC-переменные
|
||||
и выставляется UseSAC=fallback (Telegram/SMTP не перезаписываются).
|
||||
EOF
|
||||
}
|
||||
|
||||
# Ключи SAC: дописываются из ssh-monitor.conf.example, если отсутствуют в /etc.
|
||||
SAC_CONFIG_KEYS=(
|
||||
UseSAC
|
||||
SAC_URL
|
||||
SAC_API_KEY
|
||||
SAC_SPOOL_DIR
|
||||
SAC_FAIL_COUNT_FILE
|
||||
SAC_FALLBACK_FAILURES
|
||||
SAC_TIMEOUT_SEC
|
||||
SAC_SPOOL_FLUSH_MAX_FILES
|
||||
SAC_SPOOL_MAX_AGE_HOURS
|
||||
)
|
||||
SAC_USE_MODE_DEFAULT="fallback"
|
||||
|
||||
find_ssh_monitor_conf_example() {
|
||||
local example="$UPDATE_DIR/$SCRIPT_NAME/ssh-monitor.conf.example"
|
||||
if [ -f "$example" ]; then
|
||||
@@ -102,28 +117,20 @@ config_conf_value() {
|
||||
printf '%s\n' "$val"
|
||||
}
|
||||
|
||||
config_needs_sac_bootstrap() {
|
||||
local sac_client use_sac api_key
|
||||
|
||||
sac_client="${LOCAL_SCRIPT_PATH%/*}/sac-client.sh"
|
||||
if [ ! -f "$sac_client" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
use_sac=$(config_conf_value "UseSAC" "$CONFIG_FILE" 2>/dev/null || true)
|
||||
api_key=$(config_conf_value "SAC_API_KEY" "$CONFIG_FILE" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$use_sac" ] && [ "$use_sac" != "off" ] \
|
||||
&& [ -n "$api_key" ] && [ "${api_key#sac_}" != "$api_key" ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
config_example_value() {
|
||||
local key="$1" example="$2"
|
||||
config_conf_value "$key" "$example" 2>/dev/null || true
|
||||
}
|
||||
|
||||
ensure_ssh_monitor_config_from_example() {
|
||||
backup_config_if_needed() {
|
||||
local file="$1" flag_var="$2"
|
||||
if [ -z "${!flag_var:-}" ] || [ "${!flag_var}" -eq 0 ]; then
|
||||
cp "$file" "${file}.bak.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
||||
printf -v "$flag_var" '%s' 1
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_ssh_monitor_config_file() {
|
||||
local example
|
||||
CONFIG_CHANGED=0
|
||||
|
||||
@@ -132,27 +139,17 @@ ensure_ssh_monitor_config_from_example() {
|
||||
return 0
|
||||
}
|
||||
|
||||
if [ -f "$CONFIG_FILE" ] && ! config_needs_sac_bootstrap; then
|
||||
log_message "INFO" "Config $CONFIG_FILE: SAC already configured, not overwriting."
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
||||
say_info "Уже существует: $CONFIG_FILE (SAC настроен, не перезаписываем)"
|
||||
say_info "Уже существует: $CONFIG_FILE (дополним SAC-переменные при необходимости)"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
cp "$CONFIG_FILE" "${CONFIG_FILE}.bak.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
||||
log_message "INFO" "Upgrading config without SAC: backup created, applying ssh-monitor.conf.example from git."
|
||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
||||
say_info "Апгрейд без SAC: резервная копия, применяем example (Telegram + fallback)."
|
||||
fi
|
||||
else
|
||||
log_message "INFO" "Creating $CONFIG_FILE from ssh-monitor.conf.example"
|
||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
||||
say_ok "Создан $CONFIG_FILE из ssh-monitor.conf.example"
|
||||
fi
|
||||
log_message "INFO" "Creating $CONFIG_FILE from ssh-monitor.conf.example"
|
||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
||||
say_ok "Создан $CONFIG_FILE из ssh-monitor.conf.example"
|
||||
fi
|
||||
|
||||
if ! cp "$example" "$CONFIG_FILE"; then
|
||||
log_message "INFO" "ERROR: Failed to copy ssh-monitor.conf.example to $CONFIG_FILE"
|
||||
return 1
|
||||
@@ -162,6 +159,82 @@ ensure_ssh_monitor_config_from_example() {
|
||||
return 0
|
||||
}
|
||||
|
||||
sync_sac_config_keys() {
|
||||
local file="$CONFIG_FILE"
|
||||
local example backed_up=0
|
||||
local key val current changed=0 added=0
|
||||
|
||||
[ -f "$file" ] || return 1
|
||||
example=$(find_ssh_monitor_conf_example) || {
|
||||
log_message "INFO" "WARNING: ssh-monitor.conf.example not found; SAC merge skipped"
|
||||
return 0
|
||||
}
|
||||
|
||||
if ! config_key_exists "UseSAC" "$file"; then
|
||||
backup_config_if_needed "$file" backed_up
|
||||
{
|
||||
printf '\n# --- Security Alert Center (SAC) ---\n'
|
||||
printf '# off | exclusive | dual | fallback — см. security-alert-center docs/agent-integration.md\n'
|
||||
} >>"$file"
|
||||
log_message "INFO" "Config $file: добавлен блок SAC (ключи отсутствовали)."
|
||||
changed=1
|
||||
fi
|
||||
|
||||
for key in "${SAC_CONFIG_KEYS[@]}"; do
|
||||
if config_key_exists "$key" "$file"; then
|
||||
continue
|
||||
fi
|
||||
val=$(config_example_value "$key" "$example")
|
||||
if [ -z "$val" ] && [ "$key" != "SAC_API_KEY" ]; then
|
||||
case "$key" in
|
||||
UseSAC) val="$SAC_USE_MODE_DEFAULT" ;;
|
||||
SAC_SPOOL_DIR) val="$SAC_SPOOL_DIR_DEFAULT" ;;
|
||||
SAC_FAIL_COUNT_FILE) val="/var/lib/ssh-monitor/sac-fail.count" ;;
|
||||
SAC_FALLBACK_FAILURES) val="5" ;;
|
||||
SAC_TIMEOUT_SEC) val="45" ;;
|
||||
SAC_SPOOL_FLUSH_MAX_FILES) val="50" ;;
|
||||
SAC_SPOOL_MAX_AGE_HOURS) val="72" ;;
|
||||
SAC_URL) val="https://sac.kalinamall.ru" ;;
|
||||
esac
|
||||
fi
|
||||
backup_config_if_needed "$file" backed_up
|
||||
set_config_kv "$key" "$val" "$file"
|
||||
log_message "INFO" "Config $file: добавлен ${key}=${val:-<пусто>}"
|
||||
changed=1
|
||||
added=$((added + 1))
|
||||
done
|
||||
|
||||
current=$(config_conf_value "UseSAC" "$file" 2>/dev/null || true)
|
||||
if [ "$current" != "$SAC_USE_MODE_DEFAULT" ]; then
|
||||
backup_config_if_needed "$file" backed_up
|
||||
set_config_kv "UseSAC" "$SAC_USE_MODE_DEFAULT" "$file"
|
||||
log_message "INFO" "Config $file: UseSAC ${current:-<нет>} → $SAC_USE_MODE_DEFAULT (deploy policy)."
|
||||
changed=1
|
||||
fi
|
||||
|
||||
current=$(config_conf_value "SAC_TIMEOUT_SEC" "$file" 2>/dev/null || true)
|
||||
if [ "$current" = "12" ]; then
|
||||
backup_config_if_needed "$file" backed_up
|
||||
set_config_kv "SAC_TIMEOUT_SEC" "45" "$file"
|
||||
log_message "INFO" "Config $file: SAC_TIMEOUT_SEC 12 → 45 (ingest timeout)."
|
||||
changed=1
|
||||
fi
|
||||
|
||||
if [ "$changed" -eq 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
||||
if [ "$added" -gt 0 ]; then
|
||||
say_info "SAC: добавлено ключей — $added; UseSAC=$SAC_USE_MODE_DEFAULT."
|
||||
else
|
||||
say_info "SAC: UseSAC=$SAC_USE_MODE_DEFAULT."
|
||||
fi
|
||||
fi
|
||||
CONFIG_CHANGED=1
|
||||
return 0
|
||||
}
|
||||
|
||||
set_config_kv() {
|
||||
local key="$1" val="$2" file="$3"
|
||||
if grep -qE "^[[:space:]]*${key}=" "$file" 2>/dev/null; then
|
||||
@@ -176,6 +249,53 @@ config_key_exists() {
|
||||
grep -qE "^[[:space:]]*${key}=" "$file" 2>/dev/null
|
||||
}
|
||||
|
||||
config_key_mentioned() {
|
||||
local key="$1" file="$2"
|
||||
grep -qE "^[[:space:]]*(#.*)?${key}=" "$file" 2>/dev/null
|
||||
}
|
||||
|
||||
sync_server_ipv4_hint_config() {
|
||||
local file="$CONFIG_FILE" backed_up=0
|
||||
|
||||
[ -f "$file" ] || return 1
|
||||
if config_key_mentioned "SERVER_IPV4" "$file"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
backup_config_if_needed "$file" backed_up
|
||||
{
|
||||
printf '\n# --- Явный IPv4 для SAC host.ipv4 (раскомментируйте при необходимости) ---\n'
|
||||
printf '# SERVER_IPV4="10.0.0.10"\n'
|
||||
} >>"$file"
|
||||
log_message "INFO" "Config $file: добавлена подсказка # SERVER_IPV4."
|
||||
CONFIG_CHANGED=1
|
||||
return 0
|
||||
}
|
||||
|
||||
sync_daily_report_enabled_config() {
|
||||
local file="$CONFIG_FILE" backed_up=0 current
|
||||
|
||||
[ -f "$file" ] || return 1
|
||||
if config_key_exists "DAILY_REPORT_ENABLED" "$file"; then
|
||||
current=$(config_conf_value "DAILY_REPORT_ENABLED" "$file" 2>/dev/null || true)
|
||||
if [ "$current" = "1" ]; then
|
||||
return 1
|
||||
fi
|
||||
backup_config_if_needed "$file" backed_up
|
||||
set_config_kv "DAILY_REPORT_ENABLED" "1" "$file"
|
||||
log_message "INFO" "Config $file: DAILY_REPORT_ENABLED ${current:-<пусто>} → 1 (отчёт с агента)."
|
||||
else
|
||||
backup_config_if_needed "$file" backed_up
|
||||
{
|
||||
printf '\n# --- Суточный отчёт report.daily.ssh (1 = с агента; 0 = только SAC) ---\n'
|
||||
printf 'DAILY_REPORT_ENABLED="1"\n'
|
||||
} >>"$file"
|
||||
log_message "INFO" "Config $file: добавлен DAILY_REPORT_ENABLED=1."
|
||||
fi
|
||||
CONFIG_CHANGED=1
|
||||
return 0
|
||||
}
|
||||
|
||||
append_heartbeat_interval_block() {
|
||||
local file="$1"
|
||||
{
|
||||
@@ -247,25 +367,6 @@ sync_heartbeat_interval_config() {
|
||||
return 0
|
||||
}
|
||||
|
||||
sync_use_sac_fallback_mode() {
|
||||
local file="$CONFIG_FILE"
|
||||
[ -f "$file" ] || return 1
|
||||
if grep -qE '^[[:space:]]*UseSAC=[[:space:]]*["'"'"']?fallback["'"'"']?[[:space:]]*(#.*)?$' "$file" 2>/dev/null; then
|
||||
return 1
|
||||
fi
|
||||
if ! grep -qE '^[[:space:]]*UseSAC=' "$file" 2>/dev/null; then
|
||||
return 1
|
||||
fi
|
||||
cp "$file" "${file}.bak.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true
|
||||
sed -i -E 's/^[[:space:]]*UseSAC=.*/UseSAC="fallback"/' "$file"
|
||||
log_message "INFO" "Config $file: UseSAC set to fallback (deploy policy on script upgrade)."
|
||||
if [ "$DEPLOY_MODE" -eq 1 ]; then
|
||||
say_info "UseSAC=fallback (рабочий режим SAC + локальный fallback)."
|
||||
fi
|
||||
CONFIG_CHANGED=1
|
||||
return 0
|
||||
}
|
||||
|
||||
parse_command_line() {
|
||||
DEPLOY_MODE=0
|
||||
while [ $# -gt 0 ]; do
|
||||
@@ -759,7 +860,11 @@ deploy_prepare_dirs() {
|
||||
|
||||
deploy_install_config() {
|
||||
say_step "Конфигурация"
|
||||
ensure_ssh_monitor_config_from_example || return 1
|
||||
ensure_ssh_monitor_config_file || return 1
|
||||
sync_sac_config_keys || true
|
||||
sync_heartbeat_interval_config || true
|
||||
sync_server_ipv4_hint_config || true
|
||||
sync_daily_report_enabled_config || true
|
||||
}
|
||||
|
||||
deploy_install_systemd() {
|
||||
@@ -1103,14 +1208,22 @@ update_script() {
|
||||
fi
|
||||
|
||||
CONFIG_CHANGED=0
|
||||
if ! ensure_ssh_monitor_config_from_example; then
|
||||
if ! ensure_ssh_monitor_config_file; then
|
||||
log_update_summary
|
||||
return 1
|
||||
fi
|
||||
if [ "${CONFIG_CHANGED:-0}" -eq 1 ]; then
|
||||
SUMMARY_CONFIG="обновлён из example"
|
||||
if sync_sac_config_keys; then
|
||||
SUMMARY_CONFIG="SAC-ключи дополнены, UseSAC=fallback"
|
||||
else
|
||||
SUMMARY_CONFIG="без изменений"
|
||||
SUMMARY_CONFIG="SAC без изменений"
|
||||
fi
|
||||
sync_heartbeat_interval_config || true
|
||||
sync_server_ipv4_hint_config || true
|
||||
sync_daily_report_enabled_config || true
|
||||
if [ "${CONFIG_CHANGED:-0}" -eq 1 ] && [ "$SUMMARY_CONFIG" = "SAC без изменений" ]; then
|
||||
SUMMARY_CONFIG="heartbeat/watchdog выровнены"
|
||||
elif [ "${CONFIG_CHANGED:-0}" -eq 1 ] && [ "$SUMMARY_CONFIG" != "SAC без изменений" ]; then
|
||||
SUMMARY_CONFIG="${SUMMARY_CONFIG}; heartbeat при необходимости"
|
||||
fi
|
||||
|
||||
if [ -n "$actual_script_path" ] && [ -f "$actual_script_path" ]; then
|
||||
@@ -1175,12 +1288,6 @@ update_script() {
|
||||
script_changed=1
|
||||
fi
|
||||
|
||||
if [ "$script_changed" -eq 1 ]; then
|
||||
sync_use_sac_fallback_mode || true
|
||||
fi
|
||||
# Выравнивать интервал heartbeat при каждом deploy, даже если бинарник не менялся.
|
||||
sync_heartbeat_interval_config || true
|
||||
|
||||
if [ "$script_changed" -eq 1 ] || [ "${SAC_CLIENT_CHANGED:-0}" -eq 1 ] || [ "${CONFIG_CHANGED:-0}" -eq 1 ]; then
|
||||
restart_ssh_monitor_service
|
||||
else
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.1.1-SAC
|
||||
2.1.6-SAC
|
||||
|
||||
Reference in New Issue
Block a user