Короткий ответ

Baseline — набор проверяемых состояний: SSH только ключ для admin, root login нет, UFW active с deny incoming и allow нужного, AppArmor enforce, обновления security идут, время синхронизировано, journal persistent с лимитом. Hardening не равен ufw disable, apparmor=0, PermitRootLogin yes «чтобы ansible жил». Каждую позицию снимайте командой в отчёт и кладите вывод в заявку. Не применяйте 200 sysctl с gist.

Хост host.example (10.0.20.10), пользователь admin с sudo.

Симптомы и как отличить

Это не инцидент «лежит сервис», а отсутствие стандарта:

  • после cloud-image PermitRootLogin prohibit-password или yes;
  • ufw status inactive, открыт Postgres в интернет;
  • aa-status profiles in complain;
  • unattended-upgrades не установлен.

Отличие от точечных поломок: SSH, UFW, nft — когда baseline уже есть и что-то сломалось.

Возможные причины

  1. Образ провайдера с паролем root.
  2. Ansible без ufw роли.
  3. «Временно» открыли 0.0.0.0/0.
  4. Отключили AppArmor из-за одного DENIED.
  5. Нет NTP — TLS/Kerberos сюрпризы, см. время.
  6. Journal volatile — нет расследований, см. journald.

Диагностика

Сохраните вывод:

mkdir -p /home/admin/baseline && cd /home/admin/baseline
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauthentication|pubkeyauthentication|kbdinteractive|x11forwarding|allowtcpforwarding|maxauthtries|clientalive'
sudo ufw status verbose
sudo nft list ruleset | head
sudo aa-status
timedatectl
resolvectl status
apt-config dump | grep Unattended
systemctl is-enabled unattended-upgrades
dpkg -l unattended-upgrades | grep ^ii
sudo sysctl kernel.randomize_va_space net.ipv4.ip_forward
getent passwd | awk -F: '$3==0 {print}'
sudo grep -E '^[^#].*ALL=\(ALL' /etc/sudoers /etc/sudoers.d/* 2>/dev/null
ss -tlnp
journalctl --disk-usage

Сверьте слушающие порты с политикой. Всё лишнее — задача закрыть, не «документировать как фичу».

Решение

Чеклист внедрения. Каждый шаг — проверка «как проверить» ниже.

1. Учётные записи и SSH

  • Пользователь admin, sudo через sudo группу, не NOPASSWD на ALL без необходимости.
  • Ключ в /home/admin/.ssh/authorized_keys, mode 600.
  • Drop-in /etc/ssh/sshd_config.d/60-hardening.conf:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
X11Forwarding no
MaxAuthTries 4
ClientAliveInterval 30
ClientAliveCountMax 3
sudo sshd -t && sudo systemctl reload ssh

Не отключайте SSH ради «меньше поверхность», не имея BMC.

2. Firewall

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
# далее точечные allow с source CIDR
sudo ufw enable
sudo ufw status numbered

Если политика — чистый nft, не включайте UFW параллельно. Один движок.

3. Обновления

sudo apt-get update
sudo dpkg -l unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

В /etc/apt/apt.conf.d/50unattended-upgrades включите ${distro_id}ESMApps только если у вас Ubuntu Pro; иначе security карманы LTS. Авторебут — только если согласован.

4. AppArmor

sudo aa-status
sudo systemctl enable --now apparmor

Профили в enforce. Новый сервис — профиль, не aa-teardown.

5. Время, DNS, journal

  • timedatectl synchronized yes.
  • DNS через netplan, stub resolved.
  • journald Storage=persistent, SystemMaxUse задан.

6. Минимальный sysctl (документированный)

Не пачка из 80 строк. Разумный минимум на не-роутере:

net.ipv4.ip_forward = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
kernel.randomize_va_space = 2

Файл /etc/sysctl.d/60-baseline.conf + sysctl --system. На роутере ip_forward=1 осознанно.

7. Пакеты и сервисы

systemctl list-units --type=service --state=running

Выключите лишнее (cups на сервере БД и т.п.). Не disable systemd-journald, ssh, cron без замены.

Как внедрять пакетно, чтобы не потерять SSH

Окно работ: консоль BMC открыта, второй пользователь с ключом подтверждён, снимок VM. Порядок: SSH drop-in и sshd -t → проверка нового логина во втором окне → UFW allow OpenSSH → остальные allow → ufw enable → проверка с внешней машины → AppArmor/unattended/sysctl.

Не включайте PasswordAuthentication no в той же секунде, что и новый ключ, не проверив ssh -o BatchMode=yes. Cloud-init может перезаписать 50-cloud-init.conf позже — заложите sshd_config.d/99-final.conf с лексически поздним именем.

Для nft-only площадок замените шаг UFW на загрузку проверенного /etc/nftables.conf с allow 22 с jump-хоста. Смысл тот же: default deny incoming, явные allow, без flush вслепую.

Повторяемость: ansible/cloud-init с теми же командами проверки в конце playbook (sshd -T, ufw status, aa-status). Baseline без автоматической проверки через месяц снова «как получится».

Отдельно зафиксируйте список слушающих портов до и после hardening (ss -tlnp в файлы). Любой новый listen без тикета — регрессия. Это проверяемый артефакт, не ощущение «стало безопаснее».

Как проверить, что проблема устранена

Повторите съёмку baseline. Ожидание:

sudo sshd -T | grep permitrootlogin   # no
sudo ufw status | head -n 1           # active
sudo aa-status | grep 'enforce mode'
timedatectl | grep 'synchronized: yes'
ss -tlnp                              # только согласованные порты
ssh -o BatchMode=yes admin@10.0.20.10 true

Попытка ssh root@10.0.20.10 должна получить отказ (и не пароль). Скан портов с jump-хоста совпадает с ufw allow.

Сохраните отчёт в заявку/git — это и есть «проверяемый» baseline, не скриншот.

Если не помогло

  • Ansible каждый раз перетирает sshd: исправьте роль, не воюйте руками.
  • Облако открывает 0.0.0.0/0 в SG — гостевой UFW недостаточен, закройте SG.
  • Нужен пароль для аварий — храните в сейфе, не включайте PasswordAuthentication постоянно.
  • Дальше: auditd, fail2ban, отдельный раздел linux-security на сайте, не раздувайте эту статью всем CIS.

Профилактика

  • Образ золотой машины / cloud-init с тем же чеклистом.
  • Ежеквартальный повтор команд baseline.
  • Мониторинг: ufw active, ssh PermitRootLogin, пакеты security.
  • Change management: любое ufw delete и listen-порт в тикете.

FAQ

Нужен ли ufw deny outgoing?

Для high-security да, с явному allow DNS/NTP/репо. На обычном app-сервере часто allow outgoing. Не копируйте deny outgoing без DNS allow — сломаете apt и NTP.

SELinux на Ubuntu?

Не штатно. AppArmor. Не включайте SELinux «как на RHEL» без проекта.

PermitRootLogin prohibit-password достаточно?

Лучше no и отдельный admin. Ключ на root всё ещё root.

Автообновления сломают сервис

unattended только security, needrestart настроить, ядра — в окно. Не отключайте updates полностью.

Можно ли hardening «скриптом с github»?

Только если вы прочитали каждый sysctl. Иначе получите сломанный IPv6 и SSH. Этот чеклист короче и проверяем.