Введение
Здравствуйте! В своей профессиональной деятельности я часто работаю с системами, находящимися в различных сетях, изолированных как друг от друга, так и от Интернета.
Часто эти сети содержат 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
Запускаем плейбук и наслаждаемся отчетом!
-
Если у вас есть вопросы по статье или вы хотите подробнее узнать об инструментах, которые мы не успели рассмотреть, напишите об этом в комментариях. Я рассмотрю ваши вопросы в следующих статьях! Спасибо за внимание!
ссылка на оригинал статьи https://habr.com/ru/articles/851834/
Добавить комментарий