В этом уроке разберём три кирпича системной безопасности, которые на экзамене RHCE (EX294, версия под RHEL 9) спрашивают почти гарантированно: hardening sshd, управление репозиториями dnf/yum и настройку firewalld через коллекцию ansible.posix. Всё на FQCN, всё валидное, без псевдокода.
Ansible ssh настройка: хардим sshd без риска убить доступ
Главный страх при автоматизации SSH - выкатить кривой конфиг, перезапустить sshd и потерять доступ к серверу. Поэтому правило номер один: никогда не перезапускай sshd, не проверив конфиг. У sshd есть встроенный тест синтаксиса -
Код: Выделить всё
sshd -tДва подхода к правке /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
Второй подход, целостный - 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
Теперь handler. Перезапуск выносим в handlers, чтобы служба дёргалась только при реальном изменении файла, а не на каждом прогоне:
Код: Выделить всё
handlers:
- name: restart sshd
ansible.builtin.service:
name: sshd
state: restarted
Врезка про 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
Импортировать GPG-ключ заранее можно через ansible.builtin.rpm_key:
Код: Выделить всё
- name: Импортировать GPG-ключ репозитория
ansible.builtin.rpm_key:
state: present
key: https://repo.corp.local/RPM-GPG-KEY-corp
Код: Выделить всё
- name: Поставить набор базовых пакетов
ansible.builtin.dnf:
name:
- vim-enhanced
- tmux
- firewalld
state: present
- name: Обновить все пакеты до последних версий
ansible.builtin.dnf:
name: '*'
state: latest
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
Код: Выделить всё
- name: firewalld запущен и в автозагрузке
ansible.builtin.service:
name: firewalld
state: started
enabled: true
Типичные грабли
- Перезапуск 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 стал полным.