fix(ops): hardening sshd молча не применялся из-за приоритета cloud-init
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped

Найдено при первом живом запуске на Poincare.

Ubuntu кладёт в /etc/ssh/sshd_config.d/ файл 50-cloud-init.conf с
PasswordAuthentication yes. sshd берёт ПЕРВОЕ встреченное значение директивы,
а не последнее, поэтому наш 60-hardening.conf проигрывал по имени: после
reload парольный вход для root остался бы открытым.

Коварство в том, что `sshd -t` при этом отвечает «конфиг валиден», и скрипт
рапортовал об успехе. Сервер уже под перебором (fail2ban держал 6 адресов),
так что тихий отказ здесь стоил бы дорого.

Три правки:
- файл называется 00-hardening.conf и читается первым; старый 60-* удаляется,
  чтобы повторный запуск не оставил два конфликтующих;
- после записи проверяется не синтаксис, а ИТОГ через `sshd -T`: если
  passwordauthentication != no, скрипт падает и НЕ перезапускает sshd;
- комментарий про версии Docker приведён в соответствие с поведением —
  ставится последняя из репозитория, версии прода служат ориентиром для
  предупреждения (приехали 29.7.2 / 5.5.0 против 29.4.1 / v5.1.3).
This commit is contained in:
bot-backend 2026-08-22 12:56:22 +03:00
parent 6484116079
commit fb75bce22d

View file

@ -28,7 +28,11 @@ ADMIN_PUBKEY="${ADMIN_PUBKEY:-}"
SSH_PORT="${SSH_PORT:-22}"
SWAP_GB="${SWAP_GB:-8}"
# Версии — те же, что на текущем прод-хосте, чтобы поведение compose не разъехалось.
# Версии на текущем прод-хосте — ориентир для сверки, НЕ то, что ставится.
# Репозиторий Docker отдаёт последнюю версию; закреплять старую ради совпадения
# значило бы отказаться от исправлений безопасности. Расхождение печатается
# предупреждением, чтобы оно не прошло незамеченным (22.08.2026 приехали
# docker 29.7.2 и compose 5.5.0 против 29.4.1 / v5.1.3 на Beget).
WANT_DOCKER="29.4.1"
WANT_COMPOSE="v5.1.3"
@ -177,7 +181,12 @@ systemctl enable --now fail2ban >/dev/null
systemctl is-active fail2ban
log "10. Ужесточение sshd"
cat > /etc/ssh/sshd_config.d/60-hardening.conf <<SSHD
# Имя начинается с 00, а не с 60, намеренно: sshd берёт ПЕРВОЕ встреченное
# значение директивы, а Ubuntu кладёт рядом 50-cloud-init.conf с
# PasswordAuthentication yes. При имени 60-* парольный вход молча остаётся
# включённым, и `sshd -t` при этом рапортует, что всё в порядке.
rm -f /etc/ssh/sshd_config.d/60-hardening.conf
cat > /etc/ssh/sshd_config.d/00-hardening.conf <<SSHD
Port $SSH_PORT
PermitRootLogin prohibit-password
PubkeyAuthentication yes
@ -190,6 +199,13 @@ SSHD
sshd -t || die "конфиг sshd невалиден — НЕ перезапускаю, доступ сохранён"
echo "конфиг валиден"
# Синтаксическая валидность ничего не говорит о том, какое значение победило.
# Проверяем итог так, как его увидит sshd после перезапуска.
eff_pass="$(sshd -T 2>/dev/null | awk '/^passwordauthentication /{print $2}')"
eff_root="$(sshd -T 2>/dev/null | awk '/^permitrootlogin /{print $2}')"
echo "итог: passwordauthentication=$eff_pass permitrootlogin=$eff_root"
[ "$eff_pass" = "no" ] || die "PasswordAuthentication остался '$eff_pass' — какой-то файл в sshd_config.d перебивает наш; НЕ перезапускаю"
cat <<EOF
============================================================