Files
ssh-monitor/ssh-monitor-perms.sh
T
PapaTramp 6df83f1838 feat: release 2.2.1-SAC — phase 2 manifest verify and quiet SAC updates
Pinned GIT_REF/verify, release manifest, no blind reset --hard; suppress
lifecycle and watchdog Telegram during updater; healthcheck via json.dumps.
2026-07-08 11:48:34 +10:00

208 lines
6.4 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
}