6511d34ca7
Whitelist SAC maintenance commands in ssh-monitor-perms and monitor_sudo; pre-commit uses working python on Windows.
224 lines
6.8 KiB
Bash
224 lines
6.8 KiB
Bash
#!/bin/bash
|
|
# Path permission helpers for ssh-monitor, sac-client, watchdog, updater.
|
|
# shellcheck shell=bash
|
|
|
|
ssh_monitor_config_peek() {
|
|
local key="$1" file="$2" default="${3:-}"
|
|
local line val
|
|
[ -f "$file" ] || {
|
|
printf '%s\n' "$default"
|
|
return 0
|
|
}
|
|
line="$(grep -E "^[[:space:]]*${key}=" "$file" 2>/dev/null | tail -1)" || {
|
|
printf '%s\n' "$default"
|
|
return 0
|
|
}
|
|
val="${line#*=}"
|
|
val="${val#"${val%%[![:space:]]*}"}"
|
|
val="${val%"${val##*[![:space:]]}"}"
|
|
val="${val#\"}"
|
|
val="${val%\"}"
|
|
val="${val#\'}"
|
|
val="${val%\'}"
|
|
printf '%s\n' "$val"
|
|
}
|
|
|
|
ssh_monitor_truthy() {
|
|
case "${1,,}" in
|
|
1 | yes | true | on) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
ssh_monitor_path_mode() {
|
|
local path="$1"
|
|
[ -e "$path" ] || return 1
|
|
stat -c '%a' "$path" 2>/dev/null || stat -f '%OLp' "$path" 2>/dev/null
|
|
}
|
|
|
|
ssh_monitor_path_owner() {
|
|
local path="$1"
|
|
[ -e "$path" ] || return 1
|
|
stat -c '%U:%G' "$path" 2>/dev/null
|
|
}
|
|
|
|
ssh_monitor_is_root_owned() {
|
|
local path="$1" own
|
|
[ -e "$path" ] || return 0
|
|
own="$(ssh_monitor_path_owner "$path" 2>/dev/null || true)"
|
|
[ "$own" = "root:root" ]
|
|
}
|
|
|
|
ssh_monitor_secure_dir() {
|
|
local dir="$1"
|
|
[ -n "$dir" ] || return 1
|
|
mkdir -p "$dir" 2>/dev/null || return 1
|
|
chmod 700 "$dir" 2>/dev/null || true
|
|
chown root:root "$dir" 2>/dev/null || true
|
|
}
|
|
|
|
ssh_monitor_secure_file() {
|
|
local file="$1"
|
|
local dir
|
|
[ -n "$file" ] || return 1
|
|
dir="$(dirname "$file")"
|
|
ssh_monitor_secure_dir "$dir" || true
|
|
if [ ! -f "$file" ]; then
|
|
: >"$file" 2>/dev/null || touch "$file" 2>/dev/null || return 1
|
|
fi
|
|
chmod 600 "$file" 2>/dev/null || true
|
|
chown root:root "$file" 2>/dev/null || true
|
|
}
|
|
|
|
# Существующий файл или каталог: 600/700 root:root; для отсутствующего пути — только родительский каталог.
|
|
ssh_monitor_harden_path() {
|
|
local path="$1"
|
|
[ -n "$path" ] || return 0
|
|
if [ -d "$path" ]; then
|
|
ssh_monitor_secure_dir "$path"
|
|
elif [ -f "$path" ] || [ -L "$path" ]; then
|
|
ssh_monitor_secure_file "$path"
|
|
else
|
|
local parent
|
|
parent="$(dirname "$path")"
|
|
[ -n "$parent" ] && [ "$parent" != "." ] && ssh_monitor_secure_dir "$parent"
|
|
fi
|
|
}
|
|
|
|
ssh_monitor_warn_path_perms() {
|
|
local path="$1" kind="$2" max_mode="${3:-600}"
|
|
local mode own msg
|
|
[ -e "$path" ] || return 0
|
|
if ! ssh_monitor_is_root_owned "$path"; then
|
|
own="$(ssh_monitor_path_owner "$path" 2>/dev/null || echo '?')"
|
|
msg="WARN: ${kind} ${path}: владелец ${own}, ожидается root:root"
|
|
printf '%s\n' "$msg" >&2
|
|
[ -n "${write_log:-}" ] && write_log "$msg" || true
|
|
fi
|
|
mode="$(ssh_monitor_path_mode "$path" 2>/dev/null || echo "")"
|
|
if [ -n "$mode" ] && [ "$((10#$mode))" -gt "$((10#$max_mode))" ]; then
|
|
msg="WARN: ${kind} ${path}: mode ${mode}, рекомендуется не шире ${max_mode}"
|
|
printf '%s\n' "$msg" >&2
|
|
[ -n "${write_log:-}" ] && write_log "$msg" || true
|
|
fi
|
|
}
|
|
|
|
ssh_monitor_check_config_file_perms() {
|
|
local file="${1:-/etc/ssh-monitor.conf}"
|
|
local strict="${2:-0}"
|
|
local mode msg
|
|
[ -f "$file" ] || return 0
|
|
if ! ssh_monitor_is_root_owned "$file"; then
|
|
msg="WARN: конфиг ${file}: владелец $(ssh_monitor_path_owner "$file" 2>/dev/null || echo '?'), ожидается root:root"
|
|
printf '%s\n' "$msg" >&2
|
|
[ -n "${write_log:-}" ] && write_log "$msg" || true
|
|
if ssh_monitor_truthy "$strict"; then
|
|
printf '%s\n' "CONFIG_STRICT_PERMS=1: исправьте владельца конфига (chown root:root ${file})" >&2
|
|
return 1
|
|
fi
|
|
fi
|
|
mode="$(ssh_monitor_path_mode "$file" 2>/dev/null || echo "")"
|
|
case "$mode" in
|
|
600 | 400) ;;
|
|
*)
|
|
msg="WARN: конфиг ${file}: mode ${mode:-?}, рекомендуется 600 или 400 (chmod 600 ${file})"
|
|
printf '%s\n' "$msg" >&2
|
|
[ -n "${write_log:-}" ] && write_log "$msg" || true
|
|
if ssh_monitor_truthy "$strict"; then
|
|
printf '%s\n' "CONFIG_STRICT_PERMS=1: ослабленные права конфига недопустимы" >&2
|
|
return 1
|
|
fi
|
|
;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
# Подпись сервера в Telegram/webhook (как в ssh-monitor: SERVER_DISPLAY_NAME или hostname + IPv4).
|
|
ssh_monitor_server_display_name_with_ip() {
|
|
local hn ip
|
|
hn="${SERVER_DISPLAY_NAME:-}"
|
|
if [ -z "$hn" ]; then
|
|
hn="$(hostname 2>/dev/null | tr -d '\r\n')"
|
|
[ -z "$hn" ] && hn="(unknown)"
|
|
fi
|
|
ip="${SERVER_IPV4:-}"
|
|
if [ -z "$ip" ] && command -v ip >/dev/null 2>&1; then
|
|
ip="$(ip -4 route get 1.1.1.1 2>/dev/null | awk '/\bsrc\b/ {for (i = 1; i < NF; i++) if ($i == "src") { print $(i + 1); exit }}')"
|
|
[ -z "$ip" ] && ip="$(ip -4 route get 8.8.8.8 2>/dev/null | awk '/\bsrc\b/ {for (i = 1; i < NF; i++) if ($i == "src") { print $(i + 1); exit }}')"
|
|
fi
|
|
if [ -z "$ip" ] && command -v hostname >/dev/null 2>&1 && hostname -I >/dev/null 2>&1; then
|
|
ip="$(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$' | grep -Ev '^(127\.|0\.0\.0\.0)$' | head -n1)"
|
|
fi
|
|
if [ -n "$ip" ]; then
|
|
printf '%s (%s)' "$hn" "$ip"
|
|
else
|
|
printf '%s' "$hn"
|
|
fi
|
|
}
|
|
|
|
ssh_monitor_message_ensure_server_line() {
|
|
local msg="$1"
|
|
if [[ "$msg" == *'🖥️ Сервер:'* ]]; then
|
|
printf '%s' "$msg"
|
|
return 0
|
|
fi
|
|
local first rest
|
|
first="${msg%%$'\n'*}"
|
|
if [ "$first" = "$msg" ]; then
|
|
printf '%s\n🖥️ Сервер: %s' "$first" "$(ssh_monitor_server_display_name_with_ip)"
|
|
else
|
|
rest="${msg#*$'\n'}"
|
|
printf '%s\n🖥️ Сервер: %s\n%s' "$first" "$(ssh_monitor_server_display_name_with_ip)" "$rest"
|
|
fi
|
|
}
|
|
|
|
# Флаг штатного SAC/cron-update: подавляет lifecycle/watchdog Telegram на время прогона updater.
|
|
SSH_MONITOR_UPDATE_STATE_DEFAULT="/var/lib/ssh-monitor/agent-update-in-progress"
|
|
SSH_MONITOR_UPDATE_STATE_MAX_AGE_SEC="${SSH_MONITOR_UPDATE_STATE_MAX_AGE_SEC:-900}"
|
|
|
|
ssh_monitor_update_state_file() {
|
|
printf '%s\n' "${SSH_MONITOR_UPDATE_STATE_FILE:-$SSH_MONITOR_UPDATE_STATE_DEFAULT}"
|
|
}
|
|
|
|
ssh_monitor_update_in_progress() {
|
|
local f ts now age
|
|
f="$(ssh_monitor_update_state_file)"
|
|
[ -f "$f" ] || return 1
|
|
ts="$(tr -d '\r\n' <"$f" 2>/dev/null || true)"
|
|
[[ "$ts" =~ ^[0-9]+$ ]] || return 1
|
|
now=$(date '+%s')
|
|
age=$((now - ts))
|
|
[ "$age" -le "$SSH_MONITOR_UPDATE_STATE_MAX_AGE_SEC" ]
|
|
}
|
|
|
|
ssh_monitor_update_mark_begin() {
|
|
local f dir
|
|
f="$(ssh_monitor_update_state_file)"
|
|
dir="$(dirname "$f")"
|
|
ssh_monitor_secure_dir "$dir" 2>/dev/null || mkdir -p "$dir" 2>/dev/null || true
|
|
date '+%s' >"$f"
|
|
ssh_monitor_secure_file "$f" 2>/dev/null || chmod 600 "$f" 2>/dev/null || true
|
|
}
|
|
|
|
ssh_monitor_update_mark_end() {
|
|
local f
|
|
f="$(ssh_monitor_update_state_file)"
|
|
rm -f "$f" 2>/dev/null || true
|
|
}
|
|
|
|
# SAC/cron update: не слать sudo-alert на штатные команды updater/bootstrap.
|
|
ssh_monitor_sudo_is_sac_maintenance() {
|
|
local cmd="$1"
|
|
[ -n "$cmd" ] || return 1
|
|
if ssh_monitor_update_in_progress 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
case "$cmd" in
|
|
*update_ssh_monitor.sh* | *UPDATE_VIA_SAC* | *update_script.log* | \
|
|
*"/opt/scripts/update"*"ssh-monitor"* | *agent-update-in-progress*)
|
|
return 0
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|