Карманный Ansible и защита от брутфорс-атак

в 22:11, , рубрики: Ansible, fail2ban, linux, orangepi, security

Введение

Здравствуйте! В своей профессиональной деятельности я часто работаю с системами, находящимися в различных сетях, изолированных как друг от друга, так и от Интернета.

Часто эти сети содержат Linux-хосты с разнообразным функционалом, но, как правило, имеют ряд общих конфигураций. Например, настройка точек подключения к общим сетевым папкам, безопасность, зеркала репозиториев и другие аспекты — все это требует значительных временных затрат, особенно с учетом большого количества таких устройств.

Для управления многими Linux-хостами (да и Windows, кстати, тоже) существует отличный инструмент, который я очень люблю — Ansible. Однако для его использования требуется сервер, с которого будут запускаться плейбуки. Это подразумевает необходимость настройки рабочей машины на Linux или виртуальной машины.

Не всегда удобно и целесообразно носить с собой ноутбук, и я предпочитаю использовать Windows на своем рабочем устройстве, хотя хорошо ориентируюсь в терминале Linux. Я считаю, что от каждой системы стоит заимствовать лучшее: Windows для десктопа, а Linux — для серверов и open-source решений (это мое личное мнение, и я уверен, что многие с ним не согласятся, но о вкусах не спорят).

Как упростить себе жизнь? Один из вариантов — использовать OrangePi, а если нет рабочего ноутбука, то можно управлять всем этим с мобильного телефона. К счастью, запуск настроенных плейбуков не представляет сложности даже на небольшом экране мобильного устройства.

Содержание:

  1. Настройка конфигурации и создание ролей Ansible для для Debian-хостов

  2. Настройка сервера Ansible на Orange PI и спряжение его с мобильным телефоном

Зачем это нужно?

Подытожим вышесказанное и определимся, чем может быть полезен такой проект:

  • Проведение аудита информационной безопасности

  • Работа с различными, изолированными друг от друга сетями

  • Работа в сети без интернета (при наличии локального зеркала репозитория)

  • Мобильность и простота применения

Что мы имеем:

  1. Тестовый виртуальный хост

  2. Orange Pi 3 LTS

  3. Мобильный телефон с ОС 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 части:

  1. Установка и настройка rsyslog и iptables который читает fail2ban

  2. Настраивает fail2ban, а именно передает шаблон jinja конфигурационного фала

  3. Тестируем успешность проведенной операции путем проверки статуса службы

Изучим шаблон конфигурационного файла

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 при подключении к нашему тестовому серверу

После трех попыток неверного ввода учетных данных наш хост заблокирован на 10 минут

После трех попыток неверного ввода учетных данных наш хост заблокирован на 10 минут

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 - для определения адреса клиента.

Для работы нам нужно:

  1. Определить сеть точки доступа

    Наши клиенты подключаются к сети 192.168.222.38

    Наши клиенты подключаются к сети 192.168.222.38

    Сканировать ее и найти адрес клиента

    Адрес клиента 192.168.222.127

    Адрес клиента 192.168.222.127

    Подключится к клиенту по ssh

    Открываем ssh сессию

    Открываем ssh сессию

    Разрешаем Fingerprint

    1. Карманный Ansible и защита от брутфорс-атак - 10

      Редактируем файл инвентаря и group_vars

    С телефона работать можно, все необходимые кнопки в приложении есть

    С телефона работать можно, все необходимые кнопки в приложении есть

    Запускаем плейбук и наслаждаемся отчетом!

    Как это выглядит

    Как это выглядит

Если у вас есть вопросы по статье или вы хотите подробнее узнать об инструментах, которые мы не успели рассмотреть, напишите об этом в комментариях. Я рассмотрю ваши вопросы в следующих статьях! Спасибо за внимание!

Автор: Dron_Ch

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js