#!/usr/bin/env bash # Аудит нового выделенного сервера ДО того, как на него что-либо поставлено. # # Только чтение — ничего не меняет. Задача одна: собрать факты, по которым # принимается решение «работаем на этой установке» или «переставляем ОС». # # Главный вопрос — RAID. На AR32-NVMe два диска по 1 ТБ. Если они отданы # по отдельности или собраны в RAID0, смерть одного диска = потеря всего, # а память на этой платформе non-ECC. Переставить ОС можно только пока # сервер пустой, поэтому проверка идёт первой и до всего остального. # # Запуск: ssh 'bash -s' < ops/selectel-audit.sh set -uo pipefail section() { printf '\n=== %s ===\n' "$1"; } section "Идентификация" . /etc/os-release 2>/dev/null && echo "OS: ${PRETTY_NAME:-unknown}" echo "Kernel: $(uname -r)" echo "Hostname: $(hostname)" echo "Uptime: $(uptime -p 2>/dev/null || true)" section "CPU / память" lscpu 2>/dev/null | grep -E '^(Model name|CPU\(s\)|Thread|Core|Socket|CPU max MHz)' || nproc free -h | head -2 echo "--- ECC (пусто = не-ECC либо недоступно из ОС) ---" dmidecode -t memory 2>/dev/null | grep -iE 'error correction|^\s+Type:' | sort -u | head -5 \ || echo "dmidecode недоступен (нужен root)" section "ДИСКИ — ключевой вопрос" lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT,MODEL echo "--- программный RAID ---" if [ -f /proc/mdstat ]; then cat /proc/mdstat; else echo "/proc/mdstat отсутствует"; fi echo "--- аппаратный RAID / NVMe ---" lspci 2>/dev/null | grep -iE 'raid|nvme' || echo "контроллеров RAID не видно" nvme list 2>/dev/null || echo "nvme-cli не установлен" echo "--- LVM ---" pvs 2>/dev/null; vgs 2>/dev/null; lvs 2>/dev/null echo "--- монтирование ---" df -hT -x tmpfs -x devtmpfs echo echo ">>> ВЕРДИКТ ПО ДИСКАМ читать так:" echo ">>> raid1 / mirror в mdstat → зеркало есть, можно работать" echo ">>> raid0 / linear / дисков нет → зеркала НЕТ, переставить ОС до установки чего-либо" echo ">>> один диск в df, второй пуст → второй не используется, решить как задействовать" section "Сеть" ip -brief addr show | grep -v '^lo' ip route | head -5 echo "--- DNS ---" grep -E '^nameserver' /etc/resolv.conf 2>/dev/null | head -3 section "Что уже слушает" ss -tulpn 2>/dev/null | grep LISTEN || netstat -tulpn 2>/dev/null | grep LISTEN section "Безопасность — текущее состояние" echo "--- sshd (значимые директивы) ---" sshd -T 2>/dev/null | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication|port|permitemptypasswords)' \ || grep -E '^\s*(PermitRootLogin|PasswordAuthentication|Port)' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/* 2>/dev/null echo "--- файрвол ---" # verbose + numbered вместо голого `ufw status`: по verbose видно политику по # умолчанию, по numbered — порядок правил и их номера. И то и другое нужно, # чтобы после ops/selectel-ci-access.sh проверить две вещи сразу: что узкое # правило для адреса раннера (#3029) добавилось и что общее правило для ssh # пока живо (до переезда оно снимается ТОЛЬКО вручную и осознанно). ufw status verbose 2>/dev/null || echo "ufw не установлен" ufw status numbered 2>/dev/null nft list ruleset 2>/dev/null | head -20 || iptables -S 2>/dev/null | head -20 echo "--- пользователи с shell ---" awk -F: '$7 !~ /(nologin|false)$/ {print $1" "$7}' /etc/passwd echo "--- ключи доступа (тела ключей НЕ печатаются) ---" # Чем проверяется результат ops/selectel-ci-access.sh (#3029): видно, сколько # ключей у учётки, чем они ограничены и который из них CI-шный. Печатаются # только отпечаток, комментарий и опции — по ним ключ опознаётся, но # воспроизвести его нельзя, поэтому вывод аудита остаётся безопасным для # пересылки. Отпечаток тут тот же, что пишет sshd в auth.log при входе, так # что строки отсюда и из журнала сопоставляются напрямую. for ak in /root/.ssh/authorized_keys /home/*/.ssh/authorized_keys; do [ -f "$ak" ] || continue echo " $ak:" n=0 while IFS= read -r akline; do case "$akline" in ''|'#'*) continue ;; esac n=$((n+1)) akfp="$(printf '%s\n' "$akline" | ssh-keygen -lf - 2>/dev/null | awk '{$1=""; sub(/^ /,""); print}')" akopt="$(printf '%s\n' "$akline" | awk '{o="";for(i=1;i<=NF;i++){if($i ~ /^(ssh-|ecdsa-sha2-|sk-)/)break; o=o (o==""?"":" ") $i} print (o==""?"(без ограничений)":o)}')" printf ' %d) %s\n опции: %s\n' "$n" "${akfp:-<не разобран>}" "$akopt" done < "$ak" if [ "$n" -eq 0 ]; then echo " (пусто)"; fi done section "Swap / sysctl" swapon --show || echo "swap отсутствует" sysctl vm.swappiness vm.overcommit_memory 2>/dev/null section "Уже установленное ПО" for b in docker containerd git curl ufw fail2ban-server unattended-upgrade; do if command -v "$b" >/dev/null 2>&1; then printf '%-22s %s\n' "$b" "$($b --version 2>/dev/null | head -1)" else printf '%-22s %s\n' "$b" "НЕ установлен" fi done section "Время" timedatectl 2>/dev/null | head -5 || date printf '\n=== Аудит закончен ===\n'