Безопасность: hardening SSH и репозитории dnf/yum

Рейтинг: 62.1% · 15 голосов
Подробный курс по Ansible с прицелом на экзамен RHCE EX294: установка и инвентарь, плейбуки, переменные и факты, Vault, циклы и условия, шаблоны Jinja2, роли и коллекции, Execution Environments и ansible-navigator, управление системами (диски, LVM, cron, SELinux, firewalld). Примеры на RHEL/Fedora. Актуально на 2026.
Ответить
Аватара пользователя
Maksim_DevOps
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Безопасность: hardening SSH и репозитории dnf/yum

Сообщение Maksim_DevOps »

Оглавление курса (47)
  1. Что такое Ansible и зачем он нужен: автоматизация без агентов
  2. Сертификация RHCE и экзамен EX294: что внутри и как готовиться
  3. Архитектура Ansible: control node, узлы, модули и плагины
  4. Установка Ansible на control node: dnf, pip и версии ansible-core
  5. Подготовка управляемых узлов: SSH, пользователь и sudo
  6. Инвентарь Ansible: hosts, группы и переменные
  7. Настройка Ansible: файл ansible.cfg и приоритеты конфигурации
  8. Host patterns и инструменты командной строки Ansible
  9. Ad hoc команды Ansible: быстрые задачи без плейбука
  10. Ansible playbook: что это такое и как устроен
  11. Структура плейбука: play, task, модули и переменные
  12. Запуск плейбуков: проверка, теги, ограничения и отладка
  13. Параллелизм и порядок выполнения: forks, serial, strategy
  14. Факты Ansible: сбор информации об узлах
  15. Переменные Ansible: типы, объявление и приоритет
  16. Регистрация результатов и специальные переменные
  17. Ansible Vault: шифрование паролей и секретов
  18. Организация инвентаря: group_vars, host_vars и вложенные группы
  19. Динамический инвентарь и инвентарные плагины
  20. Циклы в Ansible: loop, списки и словари
  21. Повтор задачи до условия: until, retries и delay
  22. Условия в Ansible: директива when и логика
  23. Jinja2 в Ansible: выражения, фильтры и подстановки
  24. Обработчики Ansible: handlers, notify и flush_handlers
  25. Обработка ошибок: failed_when, changed_when и ignore_errors
  26. Блоки в Ansible: block, rescue и always
  27. Управление файлами: модули file, copy, fetch и stat
  28. Архивы и сборка файлов: archive, unarchive, assemble
  29. Точечное редактирование файлов: lineinfile и blockinfile
  30. Шаблоны Jinja2: модуль template и динамические конфиги
  31. Jinja2 продвинуто: фильтры, циклы, макросы и lookup
  32. Модули Ansible: ansible-doc и поиск нужного модуля
  33. Разработка собственного модуля Ansible на Python
  34. Роли Ansible и Ansible Galaxy: переиспользование
  35. Разработка роли Ansible: создание с нуля
  36. Зависимости ролей и requirements.yml
  37. Коллекции Ansible: ansible.posix, community и своя коллекция
  38. Execution Environments: контейнеры для запуска Ansible
  39. Сборка Execution Environment с ansible-builder
  40. ansible-navigator: запуск плейбуков в Execution Environment
  41. Управление SSH-ключами через Ansible: authorized_key
  42. Управление дисками: filesystem, mount и parted
  43. LVM через Ansible: тома, группы и расширение
  44. Планировщик задач: cron и systemd timers через Ansible
  45. Безопасность: hardening SSH и репозитории dnf/yum (вы здесь)
  46. SELinux через Ansible: булевы, контексты и порты
  47. Сквозной проект и подготовка к экзамену EX294
Боль: свежеразвёрнутый сервер - это дыра. Root логинится по SSH, пароли включены, репозитории неизвестно откуда, gpgcheck выключен, firewalld открыт нараспашку. Один админ закрутил гайки руками, другой забыл, третий сделал не так. На парке из 50 машин такой ручной hardening превращается в лотерею. Ansible решает это раз и навсегда: один плейбук - и каждый хост приведён к одному и тому же безопасному состоянию, идемпотентно, с проверкой конфига до перезапуска службы.

В этом уроке разберём три кирпича системной безопасности, которые на экзамене RHCE (EX294, версия под RHEL 9) спрашивают почти гарантированно: hardening sshd, управление репозиториями dnf/yum и настройку firewalld через коллекцию ansible.posix. Всё на FQCN, всё валидное, без псевдокода.

Ansible ssh настройка: хардим sshd без риска убить доступ

Главный страх при автоматизации SSH - выкатить кривой конфиг, перезапустить sshd и потерять доступ к серверу. Поэтому правило номер один: никогда не перезапускай sshd, не проверив конфиг. У sshd есть встроенный тест синтаксиса - . Ansible умеет вызывать его прямо в момент записи файла через параметр validate. Если тест падает - файл не применяется, служба не трогается, ты не заперт снаружи.

Два подхода к правке /etc/ssh/sshd_config. Первый, точечный - ansible.builtin.lineinfile, когда надо поменять одну-две директивы:

Код: Выделить всё

- name: Запретить root-логин по SSH
  ansible.builtin.lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?\s*PermitRootLogin'
    line: 'PermitRootLogin no'
    validate: 'sshd -t -f %s'
  notify: restart sshd

- name: Отключить парольную аутентификацию
  ansible.builtin.lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?\s*PasswordAuthentication'
    line: 'PasswordAuthentication no'
    validate: 'sshd -t -f %s'
  notify: restart sshd
Обрати внимание на regexp: он ловит и закомментированную строку (#PermitRootLogin), и уже раскомментированную. Без regexp lineinfile просто добавит новую строку в конец, и старая директива останется выше - получишь дубль и неопределённое поведение. %s в validate - это путь к временному файлу, который Ansible подставляет сам перед тем, как заменить рабочий конфиг.

Второй подход, целостный - ansible.builtin.template. Кладёшь весь sshd_config из шаблона .j2, и validate проверяет его целиком:

Код: Выделить всё

- name: Развернуть захардженный sshd_config из шаблона
  ansible.builtin.template:
    src: sshd_config.j2
    dest: /etc/ssh/sshd_config
    owner: root
    group: root
    mode: '0600'
    validate: 'sshd -t -f %s'
  notify: restart sshd
Шаблон удобнее, когда ты владеешь всем файлом и хочешь жёсткий эталон; lineinfile - когда правишь чужой конфиг точечно и не хочешь снести то, что не твоё. На экзамене чаще ждут именно управляемый результат, так что шаблон с переменными - крепкий выбор.

Теперь handler. Перезапуск выносим в handlers, чтобы служба дёргалась только при реальном изменении файла, а не на каждом прогоне:

Код: Выделить всё

handlers:
  - name: restart sshd
    ansible.builtin.service:
      name: sshd
      state: restarted
В RHEL служба называется sshd, под Debian/Ubuntu - ssh. Это частая ловушка при кросс-дистрибутивных ролях.

Врезка про apt. На Debian/Ubuntu пакеты ставятся через ansible.builtin.apt, а правка sshd_config и логика validate ровно та же - sshd -t универсален. Меняется только имя службы (ssh) и менеджер пакетов. RU-дистрибутивы RED OS и Astra Linux - семейство RHEL и DEB соответственно, так что переносится один в один.

Изображение

Ansible yum repository и ansible dnf: репозитории и пакеты под контролем

Чтобы ставить пакеты, сначала нужен источник, которому ты доверяешь. Модуль ansible.builtin.yum_repository пишет .repo-файл в /etc/yum.repos.d/ декларативно - ты описываешь желаемое состояние, а не дёргаешь dnf config-manager руками:

Код: Выделить всё

- name: Добавить внутренний репозиторий компании
  ansible.builtin.yum_repository:
    name: corp-base
    description: Corp Internal Base Repo
    baseurl: https://repo.corp.local/rhel/$releasever/$basearch/
    gpgcheck: true
    gpgkey: https://repo.corp.local/RPM-GPG-KEY-corp
    enabled: true
    file: corp
Что важно знать. Параметр name - это id секции в .repo-файле, а не имя файла; за имя файла отвечает file (тут получится /etc/yum.repos.d/corp.repo, и в нём можно держать несколько секций). gpgcheck: true - не для галочки: без проверки подписи ты ставишь пакеты, которым нельзя доверять. Если включил gpgcheck, обязательно дай gpgkey, иначе dnf не сможет проверить подпись и установка упадёт. Переменные $releasever и $basearch раскрываются самим dnf в рантайме, поэтому одно описание работает и на RHEL 8, и на RHEL 9.

Импортировать GPG-ключ заранее можно через ansible.builtin.rpm_key:

Код: Выделить всё

- name: Импортировать GPG-ключ репозитория
  ansible.builtin.rpm_key:
    state: present
    key: https://repo.corp.local/RPM-GPG-KEY-corp
Дальше пакеты - модуль ansible.builtin.dnf (под RHEL 9 это рабочая лошадка; ansible.builtin.yum - устаревший псевдоним, на новых системах он всё равно вызывает dnf):

Код: Выделить всё

- name: Поставить набор базовых пакетов
  ansible.builtin.dnf:
    name:
      - vim-enhanced
      - tmux
      - firewalld
    state: present

- name: Обновить все пакеты до последних версий
  ansible.builtin.dnf:
    name: '*'
    state: latest
state: present ставит, если нет (идемпотентно); state: latest подтягивает обновления; state: absent сносит. Можно ставить группы (name: '@Development Tools') и целые модули. Под Debian тот же смысл несёт ansible.builtin.apt с update_cache: true.

Ansible firewalld: порты, сервисы и зоны через ansible.posix

Захардить SSH мало - нужно ещё закрыть всё лишнее на сетевом уровне. На RHEL это firewalld, а управляет им модуль ansible.posix.firewalld (именно из коллекции ansible.posix, в ansible.builtin его нет - частая причина ошибки "module not found" на экзамене, если забыл коллекцию).

Ключевой момент, на котором срезаются: пара параметров permanent и immediate. permanent: true пишет правило в постоянную конфигурацию (переживёт reboot и reload), но НЕ применяет его в текущей сессии. immediate: true применяет прямо сейчас, но без permanent не переживёт перезагрузку. Поэтому в проде почти всегда нужны ОБА:

Код: Выделить всё

- name: Открыть сервис https в firewalld (сейчас и навсегда)
  ansible.posix.firewalld:
    service: https
    state: enabled
    permanent: true
    immediate: true

- name: Открыть произвольный порт 8080/tcp
  ansible.posix.firewalld:
    port: 8080/tcp
    state: enabled
    permanent: true
    immediate: true

- name: Привязать интерфейс к зоне internal
  ansible.posix.firewalld:
    zone: internal
    interface: eth1
    state: enabled
    permanent: true
Запомни нюанс: если задать permanent: false, immediate по умолчанию становится true. А операции с зонами (создание/привязка зоны) firewalld умеет только в permanent-режиме - immediate там не сработает, и после правок зон нужен reload демона. Сервис firewalld должен быть запущен и включён в автозагрузку:

Код: Выделить всё

- name: firewalld запущен и в автозагрузке
  ansible.builtin.service:
    name: firewalld
    state: started
    enabled: true
Про SELinux. Если служба слушает нестандартный порт, мало открыть его в firewalld - SELinux тоже должен его разрешить. Для этого есть модуль community.general.seport (тип портовой метки), но это уже отдельная тема; здесь держи в голове, что firewalld и SELinux - два разных слоя.

Типичные грабли
  • Перезапуск sshd без validate. Кривой конфиг + restart = потеря доступа. Всегда validate: 'sshd -t -f %s'.
  • lineinfile без regexp. Получаешь дубликаты директив вместо замены. Regexp должен ловить и закомментированный вариант.
  • Забыл коллекцию ansible.posix. firewalld - это НЕ builtin. Без установленной коллекции (или без неё в EE) задача падает.
  • permanent без immediate. Правило записано, но в текущей сессии порт всё ещё закрыт - и наоборот.
  • gpgcheck: true без gpgkey. Установка пакетов из такого репозитория падает на проверке подписи.
  • Имя службы sshd vs ssh. RHEL - sshd, Debian/Ubuntu - ssh. Жёсткое имя ломает кросс-дистрибутивную роль.
Мини-лаба: повтори руками
  • Напиши плейбук, который через template кладёт sshd_config с PermitRootLogin no и PasswordAuthentication no, валидирует через sshd -t и перезапускает службу только при изменении (handler).
  • Прогони плейбук дважды. Второй прогон должен быть полностью green (changed=0) - это проверка идемпотентности.
  • Через yum_repository добавь любой тестовый репозиторий с gpgcheck: true и gpgkey, затем поставь из системных репо пакет tmux модулем ansible.builtin.dnf.
  • Открой firewalld порт 8080/tcp с permanent: true и immediate: true. Проверь результат на хосте: firewall-cmd --list-ports и firewall-cmd --permanent --list-ports должны совпасть.
  • Намеренно сломай шаблон (например, PermitRootLogin maybe) и убедись, что задача падает на validate и НЕ перезапускает sshd.
Контрольные вопросы
  • Зачем нужен validate в задаче template/lineinfile при правке sshd_config и что именно подставляется вместо %s?
  • В чём разница между permanent и immediate в ansible.posix.firewalld и почему в проде обычно ставят оба?
  • Почему gpgcheck: true в yum_repository требует ещё и gpgkey, и чем это грозит, если ключ забыть?
  • В какой коллекции живёт модуль firewalld и почему его нельзя вызвать как ansible.builtin.firewalld?
Итог

Базовый hardening RHEL-хоста через Ansible - это три связки: sshd с validate и handler restart, доверенные репозитории через yum_repository с включённым gpgcheck плюс установка пакетов модулем dnf, и закрытый периметр через ansible.posix.firewalld с парой permanent+immediate. Всё это - прямые пункты программы EX294, и всё идемпотентно: прогнал плейбук - получил предсказуемое безопасное состояние на любом числе машин. В следующих уроках навесим сверху SELinux и пользователей с ключами, чтобы hardening стал полным.
👍1 ❤️1 🔥 😄 🤔2
Аватара пользователя
PandasCoder
Сообщения: 1
Зарегистрирован: 20 май 2026, 22:44

Re: Безопасность: hardening SSH и репозитории dnf/yum

Сообщение PandasCoder »

Спасибо за акцент на validate, реально однажды убил себе доступ к удалёнке кривым PasswordAuthentication. Теперь только sshd -t -f %s и handler.
👍1 ❤️ 🔥1 😄 🤔
Аватара пользователя
ceph_admin
Сообщения: 1
Зарегистрирован: 20 май 2026, 15:16

Re: Безопасность: hardening SSH и репозитории dnf/yum

Сообщение ceph_admin »

А подскажите, если правлю sshd через lineinfile несколькими тасками, validate же гоняется на каждой задаче по очереди? Не конфликтнёт, если первая правка ещё не записана, а вторая уже валидирует файл?
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Планировщик задач: cron и systemd timers через Ansible
Следующая глава →
SELinux через Ansible: булевы, контексты и порты

Все главы курса «Ansible: автоматизация и подготовка к RHCE (EX294)»

Поделиться темой: ✈ Telegram VK

Вернуться в «Ansible: автоматизация и подготовка к RHCE (EX294)»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость