Введение
Здравствуйте! В своей профессиональной деятельности я часто работаю с системами, находящимися в различных сетях, изолированных как друг от друга, так и от Интернета.
Часто эти сети содержат Linux-хосты с разнообразным функционалом, но, как правило, имеют ряд общих конфигураций. Например, настройка точек подключения к общим сетевым папкам, безопасность, зеркала репозиториев и другие аспекты — все это требует значительных временных затрат, особенно с учетом большого количества таких устройств.
Для управления многими Linux-хостами (да и Windows, кстати, тоже) существует отличный инструмент, который я очень люблю — Ansible. Однако для его использования требуется сервер, с которого будут запускаться плейбуки. Это подразумевает необходимость настройки рабочей машины на Linux или виртуальной машины.
Не всегда удобно и целесообразно носить с собой ноутбук, и я предпочитаю использовать Windows на своем рабочем устройстве, хотя хорошо ориентируюсь в терминале Linux. Я считаю, что от каждой системы стоит заимствовать лучшее: Windows для десктопа, а Linux — для серверов и open-source решений (это мое личное мнение, и я уверен, что многие с ним не согласятся, но о вкусах не спорят).
Как упростить себе жизнь? Один из вариантов — использовать OrangePi, а если нет рабочего ноутбука, то можно управлять всем этим с мобильного телефона. К счастью, запуск настроенных плейбуков не представляет сложности даже на небольшом экране мобильного устройства.
Содержание:
-
Настройка конфигурации и создание ролей Ansible для для Debian-хостов
-
Настройка сервера Ansible на Orange PI и спряжение его с мобильным телефоном
Зачем это нужно?
Подытожим вышесказанное и определимся, чем может быть полезен такой проект:
-
Проведение аудита информационной безопасности
-
Работа с различными, изолированными друг от друга сетями
-
Работа в сети без интернета (при наличии локального зеркала репозитория)
-
Мобильность и простота применения
Что мы имеем:
-
Тестовый виртуальный хост
-
Orange Pi 3 LTS
-
Мобильный телефон с ОС Android
Настройка Ansible
В данной статье мы коснемся темы информационной безопасности и подготовим роли, централизованно закрывающие на указанных хостах все порты, кроме тех, которые нам нужны. Также мы защитим наши машины от брутфорса (перебора паролей) с помощью fail2ban. Обычно я настраиваю sudo и делаю авторизацию по RSA-ключам, но в этой статье мы рассматривать это не будем, чтобы не делать статью слишком объемной. Если этот вопрос вам интересен, напишите в комментариях, и мы его рассмотрим в дальнейшем.
Установим Ansible
sudo apt install ansible
Роли подготовим заранее в тестовой среде, а потом через git перенесем на наш мобильный сервер для дальнейшей работы. Начнем с окружения, которое крайне важно для Ansible. Для его корректной работы должна быть создана правильная структура каталогов. Конфигураций может быть несколько, но мой рабочий вариант без лишних деталей выглядит следующим образом:
.
├── ansible.cfg
├── inventory
│ ├── group_vars
│ ├── hosts
│ ├── host_vars
│ │ └── ansible-test
│ └── secrets.yml
├── playbooks
│ └── test
│ └── test.yml
├── requirements.txt
├── requirements.yml
├── roles
│ └── secure
│ ├── fail2ban
│ └── ufw
└── Vagrantfile
requirements.txt и requirements.yml
предназначены для загрузки зависимостей: requirements.yml используется для зависимостей с ansible-galaxy, а requirements.txt — для библиотек в виртуальное окружение Python. Также рекомендую использовать Ansible-lint — отличную библиотеку для проверки ролей. Настоятельно советую изучить этот инструмент, если вы хотите писать чистые роли и избегать возможных ошибок в дальнейшем применении.
ansible.cfg
В этом файле мы указываем пути к нашим ролям, ключам, методам авторизации, а также способу хранения кэша, что сокращает время тестирования новых ролей.
[defaults]
skip_ansible_lint = True
host_key_checking = False
inventory = inventory/hosts
roles_path = roles/secure
private_key_file = ~/.ssh/id_rsa
become_method = sudo
become_user = root
# кеширование фактов
gathering = smart
# 1 час
fact_caching_timeout = 3600
# кеш в jsonfact_caching = jsonfile
fact_caching_connection = /tmp/ansible_fact_cache
inventory
Директория в которой мы определяем с чем мы будем работать.
group_vars/all.yml удобен для указания переменных, общих для всех ролей. Это могут быть адреса серверов, имена служб, прокси и, в общем, все, что периодически нужно в наших ролях и что не хотелось бы дублировать.
Файлы, описывающие конфигурацию хостов, хранятся в директории host_vars, в файле secrets.yml удобно хранить пароли и прочие конфиденциальные данные. Эти факты я рекомендую хранить в шифрованном виде с использованием Ansible Vault. Также важно добавить этот файлы в .gitignore, чтобы хранить его локально на устройстве и не отправлять в удаленный репозиторий.
.gitignore
# Игнорировать все файлы в директории inventory/host_vars/
inventory/host_vars/*
При шифровании мы вводим пароль, который в дальнейшем будем использовать при запуске плейбуков:
ansible-vault encrypt inventory/secrets.yml # шифровать
ansible-vault edit inventory/secrets.yml # редактировать
ansible-vault decrypt inventory/secrets.yml # расшифровать
hosts
В этом файле мы укажем хосты, а также группы, в которые входят эти хосты.
[test]
ansible-test
[localhost]
127.0.0.1 ansible_connection=local
Также в этом файле можно указать адреса и пароли наших хостов, но с точки зрения безопасности это не самое лучшее решение, поэтому их мы укажем в отдельных файлах, соответствующих именам хостов в директории host_vars
host_vars/ansible-test
ansible_become: true
ansible_host: 192.168.2.16
ansible_user: vagrant
ansible_ssh_pass: vagrant
ansible_become_pass: vagrant
Не забываем зашифровать файл через ansible-vault
ansible-vault encrypt inventory/host_vars/ansible-test
Vagrantfile
Этот файл мы настраиваем с учетом нашего гипервизора, к теме нашей статьи настройка vagrant не относится, но если кратко, то этот конфигурационный файл позволят описать все аспекты желаемой виртуальной машины и запустить ее одной командой.
Запускаем наш Vagrantfile на гипервизоре или создаем тестовую виртуальную машину самостоятельно. Я предпочитаю автоматизацию, поэтому доверю все заранее прописанному коду.
Для того чтобы использовать парольную аутентификацию Ansible вместо аутентификации по ключам, используемой по умолчанию, нам необходимо установить соответствующую утилиту:
sudo apt install sshpass
Теперь мы готовы проверить, насколько правильно настроен наш инвентарь. Не забывайте указывать запрос пароля для Ansible Vault:
ansible all -m ping --ask-vault-pass
Настройка ролей
Роль UFW
Есть два подхода в работе с Ansible, которые зависят от масштабности ваших задач. Если вы хотите выполнить простое действие, например, установить пакеты, скопировать файлы или перенести данные, вам будет достаточно использовать одиночный Ansible файл с указанием хостов, переменных и плейбуков. Он легко переносится, прост в оформлении и достаточно удобен для выполнения простых действий. Однако с ростом масштабов работать с такими файлами становится сложнее, и тут на помощь приходят роли.
Роли позволяют структурировать ваши плейбуки и упростить процесс управления конфигурацией, разбивая его на более мелкие и понятные части. Каждая роль представляет собой набор задач, обработчиков, переменных и других файлов, сгруппированных по вашему выбору. Это упрощает повторное использование кода и делает его более читаемым. Главное правило – каждая роль должна выполнять конкретное завершённое действие и быть максимально универсальной. В сложных ролях я, например, добавляю переменные опций для большей вариативности и универсальности, чтобы не создавать множество ролей, а объединять несколько смежных в одну.
В нашем примере мы создадим две роли: первая — ufw, которая настроит файрвол, и вторая — fail2ban, которая обеспечит защиту от брутфорс-атак.
Для создания всей структуры каталогов роли используем команду
ansible-galaxy role init roles/secure/ufw
перейдем в каталог роли
cd roles/secure/ufw
Изучим структуру
tree
.
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
Роль простая, тут нам нужно будет исправить только 2 файла, файл с задачами tasks/main.yml и файл с переменными по умолчанию defaults/main.yml
tasks/main.yml
---
# tasks file for roles/common/ufw
- name: Ensure UFW is installed
ansible.builtin.apt:
name: ufw
state: present
become: true
- name: Set default outgoing policy to allow
community.general.ufw:
default: allow
direction: outgoing
become: true
- name: Allow SSH connections
community.general.ufw:
rule: allow
port: 22
proto: tcp
become: true
- name: Ensure UFW is enabled and set to start on boot
community.general.ufw:
state: enabled
become: true
- name: Add custom rules
community.general.ufw:
rule: "{{ item.rule }}"
port: "{{ item.port }}"
proto: "{{ item.proto }}"
loop: "{{ ufw__rules }}"
become: true
В первой задаче мы убеждаемся, что ufw у нас установлен. Если он не установлен, Ansible выполнит его установку. Прекрасное свойство Ansible, называемое идемпотентностью, не позволит выполнить одно и то же действие несколько раз, поэтому мы можем не беспокоиться о возможных ошибках при повторном запуске кода.
Однако важно помнить, что это правило касается задач, запускаемых модулями Ansible, и не относится к командам, выполняемым через модули терминала!
Например такой модуль не будет иметь свойства идемпотентности
- name: Установить пакет ufw через команду
command: apt-get install -y ufw
А такой будет
- name: Ensure UFW is installed
ansible.builtin.apt:
name: ufw
state: present
become: true
Список всех модулей и опций можно просмотреть на официальном сайте Ansible
- name: Set default outgoing policy to allow
community.general.ufw:
default: allow
direction: outgoing
become: true
Эта задача устанавливает политику для исходящих соединений по умолчанию в состоянии "разрешить". Это означает, что все исходящие соединения (т.е. соединения, инициируемые с вашего сервера к внешним ресурсам) будут разрешены, если не указано иное в различных правилах брандмауэра.
- name: Allow SSH connections
community.general.ufw:
rule: allow
port: 22
proto: tcp
become: true
В этом примере мы разрешаем 22 порт для работы по ssh и включаем ufw в автозапуск, одновременно запуская его с настроенными правилами.
- name: Add custom rules
community.general.ufw:
rule: "{{ item.rule }}"
port: "{{ item.port }}"
proto: "{{ item.proto }}"
loop: "{{ ufw__rules }}"
become: true
Здесь уже интереснее, мы используем цикл для разрешения указанных портов, из переменной ufw__rules, которую укажем по умолчанию и сможем переназначать при запуске плейбука.
Зададим переменные по умолчанию:
defaults/main.yml
---
# defaults file for roles/common/ufw
ufw__rules: # настройка UFW
- { rule: 'allow', port: '80', proto: 'tcp' }
- { rule: 'allow', port: '443', proto: 'tcp' }
Роль готова, осталось ее протестировать. Для этого создадим плейбук и заполним его:
mkdir -p playbooks/test
touch playbooks/test/test.yml
playbooks/test/test.yml
- name: Testing ufw
hosts: test
roles:
- ufw
vars:
# - ufw
ufw__rules:
- { rule: 'allow', port: '53', proto: 'udp' }
В этом примере мы открываем порт для службы DNS, чтобы продемонстрировать, как игнорировать переменные по умолчанию из роли UFW.
Запуск плейбука - Testing ufw
После написания плейбука, запустим его с помощью следующей команды:
ansible-playbook playbooks/test/test.yml --ask-vault-pass
Из вывода команды можно сделать вывод, что все прошло успешно и все порты, кроме необходимых у нас закрыты.
Роль fail2ban
Для создания всей структуры каталогов роли используем команду
ansible-galaxy role init roles/secure/fail2ban
перейдем в каталог роли
cd roles/secure/fail2ban
tasks/main.yml
---
# tasks file for roles/secure/fail2ban
# sshd logs
- name: Update_apt_cache
ansible.builtin.apt:
update_cache: yes
- name: Install rsyslog
ansible.builtin.apt:
name: rsyslog
state: present
- name: Install iptables
ansible.builtin.apt:
name: iptables
state: present
- name: Ensure the log directory exists
ansible.builtin.file:
path: /var/log/sshd/
state: directory
mode: '0755'
- name: Create SSH log file
ansible.builtin.file:
path: /var/log/sshd/sshd.log
state: touch
owner: sshd
group: adm
mode: '0640'
- name: Configure rsyslog for SSH logging
ansible.builtin.copy:
dest: /etc/rsyslog.d/50-ssh.conf
content: |
if $programname == 'sshd' then /var/log/sshd/sshd.log
& stop
owner: root
group: root
mode: '0644'
- name: Restart rsyslog service
ansible.builtin.systemd:
name: rsyslog
state: restarted
- name: Enable log sshd
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#SyslogFacility AUTH'
line: 'SyslogFacility AUTH'
state: present
- name: Level log sshd
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#LogLevel INFO'
line: 'LogLevel INFO'
state: present
# fail2ban
- name: Install Fail2Ban
ansible.builtin.apt:
name: fail2ban
state: present
- name: Restart sshd service to apply changes
ansible.builtin.systemd:
name: sshd
state: restarted
- name: Config fail2ban
ansible.builtin.template:
src: jail.local.j2
dest: /etc/fail2ban/jail.local
owner: root
group: root
mode: '0644'
notify:
- Restart_service
- name: Start and autostart fail2ban
ansible.builtin.service:
name: fail2ban
state: started
enabled: true
# test
- name: Pause
ansible.builtin.pause:
seconds: 3
tags: test
- name: Check service status
ansible.builtin.service:
name: fail2ban
state: started
register: service_status
tags: test
- name: Info
ansible.builtin.assert:
that:
- service_status.status.ActiveState == 'active'
fail_msg: "[error] - Служба не запущена"
success_msg: "[info] - Служба запущена"
В этом файле задачи разбиты на 3 части:
-
Установка и настройка rsyslog и iptables который читает fail2ban
-
Настраивает fail2ban, а именно передает шаблон jinja конфигурационного фала
-
Тестируем успешность проведенной операции путем проверки статуса службы
Изучим шаблон конфигурационного файла
templates/jail.local.j2
[DEFAULT]
bantime = {{ fail2ban__bantime }}
findtime = {{ fail2ban__findtime }}
maxretry = {{ fail2ban__maxretry }}
allowipv6 = true
[sshd]
enabled = true
port = ssh
filter = sshd
action = iptables-multiport[name=sshd, port=ssh, protocol=tcp]
logpath = /var/log/sshd/sshd.log
В этот шаблон мы передаем 3 переменные, которые определим по умолчаниюю.
defaults/main.yml
---
# defaults file for roles/secure/fail2ban
fail2ban__bantime: 600 # время бана
fail2ban__findtime: 600 # частота бана
fail2ban__maxretry: 5 # число неудачных попыток
Не забываем про обработчик которой вызываем в задаче под именем Restart_service
handlers/main.yml
---
# handlers file for roles/secure/fail2ban
- name: Restart_service
ansible.builtin.systemd:
name: fail2ban
state: restarted
Добавляем в плейбук нашу роль
playbooks/test/test.yml
- name: Testing ufw + fail2ban
hosts: test
roles:
- ufw
- fail2ban
vars:
# - ufw
ufw__rules:
- { rule: 'allow', port: '53', proto: 'udp' }
# - fail2ban
fail2ban__bantime: 600 # время бана
fail2ban__findtime: 600 # частота бана
fail2ban__maxretry: 3 # число неудачных попыток
Я люблю переопределять ключевые переменные в плейбуках для более гибкого управления ролями.
Запуск плейбука - Testing ufw + fail2ban
ansible-playbook playbooks/test/test.yml --ask-vault-pass
Проверим работу fail2ban умышленно ошибаясь в авторизации по ssh при подключении к нашему тестовому серверу
1. Fail2ban через логи ssh обнаружил 3 неверные попытки авторизации
2. Fail2ban создал правило для блокировки IP-адреса на уровне брандмауэра iptables
, что позволяет блокировать доступ к порту 22 (SSH) для IP-адреса
Для демонстрации важности защиты хоста о брутфорса я продемонстрирую лог со своего сервера в облаке, без настроенного fail2ban:
sudo journalctl -r | grep 'invalid user' | head -n 20
Настройка сервера Ansible на Orange PI
Для установки Orenge Pi зайдем на официальный сайт и загрузим последний актуальный дистрибутив с драйверами по адресу
Записываем образ на SD карту с помощью Rufus (или аналогичной программы). После этого вставляем карту в Orange Pi, загружаем устройство и подключаем его к нашей сети с помощью кабеля. При наличии DHCP сервера найдем IP-адрес, выданного устройству, и подключимся через любимый SSH клиент (Putty, PowerShell), введя логин и пароль по умолчанию (логин: orangepi, пароль: orangepi). Если возникнут какие-либо проблемы, можно подключить монитор и клавиатуру непосредственно к устройству и настроить сеть локально.
Настройка сети
По умолчанию на Orange Pi установлен NetworkManager, который настроен на получение IP-адреса по DHCP. Мы не будем настраивать статический IP на проводном сетевом адаптере, а подключимся к Wi-Fi точке доступа нашего телефона. Для этого просканируем доступные Wi-Fi сети:
nmcli device wifi list
Из списка доступных точек доступа выберем нужную и введем пароль для доступа.
nmcli device wifi connect --ask
Также настроим автоподключение к точке доступа мобильного телефона, чтобы устройство автоматически подключалось к сети при перезагрузке.
nmcli connection modify connection.autoconnect yes
Конфиг беспроводного подключения доступен по адресу /etc/NetworkManager/system-connections/
1.2 Настройка системы
В первую очередь сменим репозитории с Китайских на стандартные
sudo nano /etc/apt/sources.list
deb http://deb.debian.org/debian/ bookworm main non-free-firmware
deb-src http://deb.debian.org/debian/ bookworm main non-free-firmware
deb http://security.debian.org/debian-security bookworm-security main non-free-firmware
deb-src http://security.debian.org/debian-security bookworm-security main non-free-firmware
deb http://deb.debian.org/debian/ bookworm-updates main non-free-firmware
deb-src http://deb.debian.org/debian/ bookworm-updates main non-free-firmware
Обновим кэш репозиториев и саму систему
sudo apt update
sudo apt upgrade
Создадим нового пользователя взамен стандартного (в моем случаем логин пользователя будет "ch")
sudo useradd -m -s /bin/bash ch
groups # просмотрим в каких группах дефолтный пользователь
sudo usermod -aG tty,disk,dialout,sudo,audio,video,plugdev,games,users,systemd-journal,input,netdev,ssh ch
sudo passwd ch
И удалим старого пользователя
sudo userdel -r orangepi
sudo rm -rf /var/mail/orangepi
Настроим время
sudo timedatectl set-timezone Europe/Moscow
timedatectl
Имя хоста
sudo hostnamectl set-hostname opi
Не забываем сделать правки в файлах /etc/hosts и /etc/hostname после чего перезагружаем систему
sudo reboot
1.3 Подключение к Github
Создадим rsa ключи для доступа к закрытому репозиторию на GitHub
ssh-keygen -t rsa
Открываем публичный ключ, копируем его содержимое в личный кабинет на Github в разделе Settings -> SSH and GPG keys -> SSH keys
sudo cat ~/.ssh/id_rsa.pub
После чего тестируем соединение
ssh -T git@github.com
После авторизации, копируем репозиторий с нашими рабочими ролями Ansible
git@github.com:/.gite
Подключение телефона к Orange PI и запуск плейбука
Для подключения по ssh с мобильного телефона на ОС Android я использую 2 приложения, ConnectBot - как ssh клиент, и Network Utilites - для определения адреса клиента.
Для работы нам нужно:
-
Определить сеть точки доступа
Сканировать ее и найти адрес клиента
Подключится к клиенту по ssh
Разрешаем Fingerprint
-
Редактируем файл инвентаря и group_vars
Запускаем плейбук и наслаждаемся отчетом!
-
Если у вас есть вопросы по статье или вы хотите подробнее узнать об инструментах, которые мы не успели рассмотреть, напишите об этом в комментариях. Я рассмотрю ваши вопросы в следующих статьях! Спасибо за внимание!
Автор: Dron_Ch