feat: exclusive pilot — spool flush and verify script
Replay spool on each monitor loop; sac_post_payload refactor; pilot-verify-exclusive.sh and --check-sac hints for exclusive mode.
This commit is contained in:
+74
-25
@@ -70,6 +70,74 @@ sac_spool_remove() {
|
|||||||
rm -f "${dir}/${event_id}.json"
|
rm -f "${dir}/${event_id}.json"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# POST готового JSON (ingest / повтор из spool). Возврат 0 при HTTP 202.
|
||||||
|
sac_post_payload() {
|
||||||
|
local payload="$1"
|
||||||
|
local event_id http_code tmp resp event_type
|
||||||
|
|
||||||
|
if ! sac_is_configured; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ "${DRY_RUN:-0}" = "1" ]; then
|
||||||
|
printf '[DRY-RUN SAC POST] %s\n' "$(printf '%s' "$payload" | head -c 120)" >&2
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
event_id="$(printf '%s' "$payload" | python3 -c 'import json,sys; print(json.load(sys.stdin)["event_id"])')" || return 1
|
||||||
|
event_type="$(printf '%s' "$payload" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("type","?"))')" || event_type="?"
|
||||||
|
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
resp="$(mktemp)"
|
||||||
|
printf '%s' "$payload" >"$tmp"
|
||||||
|
http_code="$(curl -sS -o "$resp" -w '%{http_code}' \
|
||||||
|
-X POST "$(sac_ingest_url)" \
|
||||||
|
-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}" \
|
||||||
|
-d @"$tmp" 2>/dev/null || echo 000)"
|
||||||
|
rm -f "$tmp"
|
||||||
|
|
||||||
|
if [ "$http_code" = "202" ]; then
|
||||||
|
sac_spool_remove "$event_id" 2>/dev/null || true
|
||||||
|
write_log "SAC: принято event_id=$event_id type=$event_type"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_log "WARN: SAC POST HTTP $http_code: $(tr '\n' ' ' <"$resp" | head -c 200)"
|
||||||
|
sac_spool_write "$event_id" "$payload"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Повторная отправка файлов из SAC_SPOOL_DIR (до max_files за вызов).
|
||||||
|
sac_flush_spool() {
|
||||||
|
local mode max_files="${1:-20}"
|
||||||
|
mode="$(sac_normalize_mode "${UseSAC:-off}")"
|
||||||
|
case "$mode" in
|
||||||
|
off) return 0 ;;
|
||||||
|
esac
|
||||||
|
if ! sac_is_configured; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
count=$((count + 1))
|
||||||
|
[ "$count" -gt "$max_files" ] && break
|
||||||
|
local payload
|
||||||
|
payload="$(cat "$f")"
|
||||||
|
sac_post_payload "$payload" || true
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
# send_sac_event type severity title summary [details_json]
|
# send_sac_event type severity title summary [details_json]
|
||||||
sac_send_event() {
|
sac_send_event() {
|
||||||
local event_type="$1"
|
local event_type="$1"
|
||||||
@@ -146,31 +214,7 @@ print(json.dumps(payload, ensure_ascii=False))
|
|||||||
PY
|
PY
|
||||||
)" || return 1
|
)" || return 1
|
||||||
|
|
||||||
event_id="$(printf '%s' "$payload" | python3 -c 'import json,sys; print(json.load(sys.stdin)["event_id"])')"
|
sac_post_payload "$payload"
|
||||||
|
|
||||||
local tmp resp
|
|
||||||
tmp="$(mktemp)"
|
|
||||||
resp="$(mktemp)"
|
|
||||||
printf '%s' "$payload" >"$tmp"
|
|
||||||
http_code="$(curl -sS -o "$resp" -w '%{http_code}' \
|
|
||||||
-X POST "$(sac_ingest_url)" \
|
|
||||||
-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}" \
|
|
||||||
-d @"$tmp" 2>/dev/null || echo 000)"
|
|
||||||
rm -f "$tmp"
|
|
||||||
|
|
||||||
if [ "$http_code" = "202" ]; then
|
|
||||||
sac_spool_remove "$event_id" 2>/dev/null || true
|
|
||||||
write_log "SAC: принято event_id=$event_id type=$event_type"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
write_log "WARN: SAC POST HTTP $http_code: $(tr '\n' ' ' <"$resp" | head -c 200)"
|
|
||||||
sac_spool_write "$event_id" "$payload"
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# JSON details из переменных SAC_D_* (опционально)
|
# JSON details из переменных SAC_D_* (опционально)
|
||||||
@@ -227,6 +271,11 @@ notify_or_sac() {
|
|||||||
run_check_sac() {
|
run_check_sac() {
|
||||||
printf 'Проверка SAC (ssh-monitor)\n'
|
printf 'Проверка SAC (ssh-monitor)\n'
|
||||||
printf 'UseSAC=%s\n' "${UseSAC:-off}"
|
printf 'UseSAC=%s\n' "${UseSAC:-off}"
|
||||||
|
case "$(sac_normalize_mode "${UseSAC:-off}")" in
|
||||||
|
exclusive) printf 'Режим exclusive: уведомления только в SAC (Telegram/email с агента не отправляются)\n' ;;
|
||||||
|
dual) printf 'Режим dual: SAC + локальные каналы NOTIFY_CHAIN\n' ;;
|
||||||
|
fallback) printf 'Режим fallback: SAC, при сбое — NOTIFY_CHAIN\n' ;;
|
||||||
|
esac
|
||||||
printf 'SAC_URL=%s\n' "${SAC_URL:-}"
|
printf 'SAC_URL=%s\n' "${SAC_URL:-}"
|
||||||
if sac_is_configured; then
|
if sac_is_configured; then
|
||||||
printf 'SAC ingest URL=%s\n' "$(sac_ingest_url)"
|
printf 'SAC ingest URL=%s\n' "$(sac_ingest_url)"
|
||||||
|
|||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Проверка готовности хоста к пилоту UseSAC=exclusive (фаза 2.1).
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CONFIG="${CONFIG_FILE:-/etc/ssh-monitor.conf}"
|
||||||
|
MONITOR="${SSH_MONITOR_BIN:-/usr/local/bin/ssh-monitor}"
|
||||||
|
|
||||||
|
if [ -f "$CONFIG" ]; then
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
. "$CONFIG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
fail=0
|
||||||
|
ok() { printf '[OK] %s\n' "$1"; }
|
||||||
|
bad() { printf '[FAIL] %s\n' "$1"; fail=1; }
|
||||||
|
|
||||||
|
mode="$(printf '%s' "${UseSAC:-off}" | tr '[:upper:]' '[:lower:]')"
|
||||||
|
[ "$mode" = "exclusive" ] || bad "UseSAC должен быть exclusive (сейчас: ${UseSAC:-off})"
|
||||||
|
[ -n "${SAC_URL:-}" ] && [ -n "${SAC_API_KEY:-}" ] || bad "Задайте SAC_URL и SAC_API_KEY"
|
||||||
|
[ -x "$MONITOR" ] || bad "Не найден $MONITOR"
|
||||||
|
[ -f "${MONITOR%/*}/sac-client.sh" ] || bad "Нет sac-client.sh рядом с ssh-monitor"
|
||||||
|
|
||||||
|
if "$MONITOR" --check-sac; then
|
||||||
|
ok "--check-sac"
|
||||||
|
else
|
||||||
|
bad "--check-sac"
|
||||||
|
fi
|
||||||
|
|
||||||
|
spool="${SAC_SPOOL_DIR:-/var/lib/ssh-monitor/sac-spool}"
|
||||||
|
pending=0
|
||||||
|
if [ -d "$spool" ]; then
|
||||||
|
pending=$(find "$spool" -maxdepth 1 -name '*.json' 2>/dev/null | wc -l)
|
||||||
|
fi
|
||||||
|
printf ' Spool %s: %s файл(ов)\n' "$spool" "$pending"
|
||||||
|
|
||||||
|
if [ "$fail" -eq 0 ]; then
|
||||||
|
printf '\nПилот exclusive: хост готов. Сгенерируйте SSH/sudo событие и проверьте только в SAC UI.\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
printf '\nИсправьте ошибки выше.\n'
|
||||||
|
exit 1
|
||||||
@@ -1921,6 +1921,7 @@ main() {
|
|||||||
monitor_logind
|
monitor_logind
|
||||||
check_daily_report
|
check_daily_report
|
||||||
check_heartbeat
|
check_heartbeat
|
||||||
|
sac_flush_spool 20 2>/dev/null || true
|
||||||
write_health_and_metrics
|
write_health_and_metrics
|
||||||
|
|
||||||
local current_time
|
local current_time
|
||||||
|
|||||||
@@ -426,8 +426,9 @@ print_config_variables_help() {
|
|||||||
TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
|
TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
|
||||||
— или MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO
|
— или MAIL_SMTP_HOST, MAIL_FROM, MAIL_TO
|
||||||
|
|
||||||
Security Alert Center (рекомендуется dual на пилоте):
|
Security Alert Center:
|
||||||
UseSAC=dual
|
UseSAC=off | dual | exclusive | fallback
|
||||||
|
Пилот 2.1 (только SAC): UseSAC=exclusive
|
||||||
SAC_URL=https://sac.kalinamall.ru
|
SAC_URL=https://sac.kalinamall.ru
|
||||||
SAC_API_KEY=<ключ из SAC sac-api.env / bootstrap>
|
SAC_API_KEY=<ключ из SAC sac-api.env / bootstrap>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user