Files
glchat/deploy/security-check.sh
T

116 lines
4.2 KiB
Bash
Raw Permalink Normal View History

#!/usr/bin/env bash
# glchat: аудит безопасности инстанса (AGENT.md 9.2, 9.6, 9.7).
set -uo pipefail
GLCHAT_ETC_DIR="${GLCHAT_ETC_DIR:-/opt/glchat}"
ENV_FILE="${GLCHAT_ETC_DIR}/.env"
ISSUES=0
say_ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
say_warn() {
printf ' \033[33m!\033[0m %s\n' "$*"
ISSUES=$((ISSUES + 1))
}
say_info() { printf ' · %s\n' "$*"; }
printf '\n=== glchat security-check ===\n'
printf '\n[SSH]\n'
if sshd -T 2>/dev/null | grep -q '^passwordauthentication no'; then
say_ok "вход по паролю запрещён"
else
say_warn "PasswordAuthentication включён (AGENT.md 9.2 требует ключи)"
fi
if sshd -T 2>/dev/null | grep -qE '^permitrootlogin (no|prohibit-password)'; then
say_ok "root-логин ограничен"
else
say_warn "PermitRootLogin разрешён полностью"
fi
max_tries="$(sshd -T 2>/dev/null | awk '/^maxauthtries/ {print $2}')"
if [ -n "$max_tries" ] && [ "$max_tries" -le 3 ]; then
say_ok "MaxAuthTries=${max_tries}"
else
say_warn "MaxAuthTries=${max_tries:-unknown} (рекомендуется ≤ 3)"
fi
printf '\n[UFW]\n'
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q 'Status: active'; then
say_ok "UFW активен"
ufw status numbered 2>/dev/null | sed -n 's/^\[[ 0-9]*\]\s*/ · /p' | head -20
else
say_warn "UFW не активен"
fi
printf '\n[DOCKER-USER]\n'
if command -v iptables >/dev/null 2>&1 && iptables -nL DOCKER-USER >/dev/null 2>&1; then
if iptables -nL DOCKER-USER | grep -q glchat-block-internal; then
say_ok "внутренние порты приложения закрыты снаружи"
else
say_warn "нет правил glchat в DOCKER-USER"
fi
fi
printf '\n[fail2ban]\n'
if command -v fail2ban-client >/dev/null 2>&1; then
jails="$(fail2ban-client status 2>/dev/null | sed -n 's/.*Jail list:\s*//p')"
for jail in sshd glchat-auth recidive; do
if printf '%s' "$jails" | grep -q "$jail"; then
banned="$(fail2ban-client status "$jail" 2>/dev/null | sed -n 's/.*Currently banned:\s*//p')"
say_ok "jail ${jail} (забанено: ${banned:-0})"
else
say_warn "jail ${jail} не активен"
fi
done
else
say_warn "fail2ban не установлен"
fi
printf '\n[Слушающие порты]\n'
if command -v ss >/dev/null 2>&1; then
ss -tulnH | awk '{print $1, $5}' | sort -u | while read -r proto addr; do
case "$addr" in
127.0.0.1:* | [::1]:*) say_info "${proto} ${addr} (локальный)" ;;
0.0.0.0:* | [::]:* | *:*) say_info "${proto} ${addr}" ;;
esac
done
fi
printf '\n[Секреты и права]\n'
if [ -f "$ENV_FILE" ]; then
perms="$(stat -c '%a' "$ENV_FILE")"
if [ "$perms" = "600" ]; then
say_ok ".env: права 600"
else
say_warn ".env: права ${perms} (нужно 600)"
fi
for key in SESSION_PEPPER MASTER_KEY LIVEKIT_API_SECRET TOTP_ENCRYPTION_KEY WEBHOOK_SECRET; do
value="$(grep -E "^${key}=" "$ENV_FILE" | cut -d= -f2- || true)"
if [ -z "$value" ]; then
say_warn "${key} не задан"
elif [ "${#value}" -lt 32 ]; then
say_warn "${key} короче 32 символов"
else
say_ok "${key} задан (${#value} символов)"
fi
done
else
say_warn "не найден ${ENV_FILE}"
fi
age_key="${GLCHAT_ETC_DIR}/data/backups/age-key.txt"
if [ -f "$age_key" ]; then
say_warn "приватный age-ключ всё ещё на сервере (${age_key}): сохраните его вне сервера и удалите"
fi
printf '\n[Обновления пакетов]\n'
if command -v apt-get >/dev/null 2>&1; then
upgradable="$(apt-get -s upgrade 2>/dev/null | grep -c '^Inst' || true)"
say_info "пакетов с доступными обновлениями: ${upgradable}"
fi
if [ -f /var/run/reboot-required ]; then
say_warn "требуется перезагрузка сервера (обновления ядра)"
fi
printf '\n=== Замечаний: %d ===\n\n' "$ISSUES"
[ "$ISSUES" -eq 0 ]