All checks were successful
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m10s
CI / backend-tests (pull_request) Successful in 17m38s
Сегодня подлинность хоста не проверяется нигде: deploy-tradein.yml явно ставил StrictHostKeyChecking=no, а во всех пяти вызовах appleboy/ssh-action не задан fingerprint. Пока Forgejo, раннеры и прод жили на одной машине, это было почти бесплатно. После переезда 30.08 тот же канал идёт через интернет и уносит GHCR_PAT, OPENAI_API_KEY, OBJECTIVE_API_KEY, GLITCHTIP_BACKEND_DSN и сам DEPLOY_SSH_KEY. Вызовов оказалось пять, а не четыре: deploy-obsidian.yml тоже ходит на DEPLOY_HOST с DEPLOY_SSH_KEY. Отпечаток добавлен во все. Механизм инертен по конструкции самого действия, а не нашей ветвлёнкой: appleboy/ssh-action@v1.0.3 -> drone-ssh 1.7.3 -> easyssh-proxy v1.5.0, где hostKeyCallback = ssh.InsecureIgnoreHostKey() и сверка включается только при непустом Fingerprint. Незаданный секрет = сегодняшнее поведение бит в бит. Но инертность, безопасная для мержа, опасна для самого окна: забытый секрет означал бы зелёный деплой через интернет без всякой проверки. Поэтому перед каждым ssh-шагом стоит преамбула, которая при пустом секрете печатает ::warning с перечислением того, что именно едет по этому каналу. Warning, а не отказ: секрета сегодня нет ни у кого, отказ сломал бы деплой в момент мержа. Ручной ssh в "Resolve deployed base SHA" отпечаток принять не может, ему отдан отдельный секрет с known_hosts по той же схеме. Заодно расхождение ключа перестало быть неотличимым от недоступного хоста: раньше 2>/dev/null глотал "Host key verification failed" и печаталось обычное сообщение про фолбэк на полную пересборку. Свойство fail-safe сохранено - при любой ошибке RAW_SHA пустеет и включается build-all. ops/selectel-ci-access.sh - аддитивная выдача CI-доступа на Selectel по решению владельца "сейчас только добавляем, удаляем после переезда": ставит отдельный ключ робота с from="адрес раннера", добавляет узкое правило ufw, не трогая общее, и печатает план сужения, который сам не выполняет. Ни одной команды, способной отрезать текущую сессию: sshd не перезапускается, default policy ufw не трогается, в authorized_keys только дозапись. Отзыв старого ключа, сужение правил и уборка секретов - в #3075, после подтверждённого переезда. Refs #3029, #3075, #3057, #2989
111 lines
6.2 KiB
Bash
111 lines
6.2 KiB
Bash
#!/usr/bin/env bash
|
||
# Аудит нового выделенного сервера ДО того, как на него что-либо поставлено.
|
||
#
|
||
# Только чтение — ничего не меняет. Задача одна: собрать факты, по которым
|
||
# принимается решение «работаем на этой установке» или «переставляем ОС».
|
||
#
|
||
# Главный вопрос — RAID. На AR32-NVMe два диска по 1 ТБ. Если они отданы
|
||
# по отдельности или собраны в RAID0, смерть одного диска = потеря всего,
|
||
# а память на этой платформе non-ECC. Переставить ОС можно только пока
|
||
# сервер пустой, поэтому проверка идёт первой и до всего остального.
|
||
#
|
||
# Запуск: ssh <host> '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'
|