Короткий ответ
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 statusinactive, открыт Postgres в интернет;aa-statusprofiles in complain;- unattended-upgrades не установлен.
Отличие от точечных поломок: SSH, UFW, nft — когда baseline уже есть и что-то сломалось.
Возможные причины
- Образ провайдера с паролем root.
- Ansible без
ufwроли. - «Временно» открыли 0.0.0.0/0.
- Отключили AppArmor из-за одного DENIED.
- Нет NTP — TLS/Kerberos сюрпризы, см. время.
- 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 3sudo 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
timedatectlsynchronized 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. Этот чеклист короче и проверяем.