Привет всем обитателям Хабра, до кого ещё не добрались вечные перебои сети Интернет!
Так уж сложилось, что я, как и многие мои коллеги по цеху автоматизации и ИБ (SecOps, DevOps и SRE) достаточно ленивы не любят рутинный, ручной и особенно монотонный труд, особенно когда время можно потратить на гораздо более полезные вещи.
И так как я имею под собственным управлением достаточно немалую инфраструктуру для специалиста по ИБ (около 30 продуктивных ВМ и более 10 тестовых), парк которых обновляется и пополняется весьма динамично и хаотично - у меня возник вопрос о том, каким образом мне забыть про ручную первоначальную настройку ВМ на пилотах и внедрениях.
Начать его я решил с базы для себя - заведения ВМ под управлением Linux в службу каталогов для централизованной аутентификации и управления правами (по моим расчётам, каждое такое мероприятие занимает в среднем 10-15 минут на одну машину, с постоянным лазанием в заметки, и если "всё идёт по плану").
Торопыжкам, желающим вкусить файлы конфигураций и плейбуки - рекомендую перейти к заголовку "Реализация".
Дисклеймер: плейбуки составлены с учётом того, что ВМ в инвентаре имеют ось на базе Ubuntu 18.04 и старше, сам же узел управления - ansible core 2.20.3, в качестве службы каталогов используется Active Directory.
Выбор решений
Естественно для автоматизации управления инфраструктурой на базе Linux - золотым стандартом является давно зарекомендовавший себя в сообществе Ansible.
Но используя любой подобный оркестратор мы повышаем риски по ИБ, поскольку появляется новая точка для потенциальной централизованной компрометации инфраструктуры и закрепления в ней.
Основными рисками, при компрометации узла управления Ansible - были выделены:
Утечка учётных данных для подключения к серверам и сервисам;
Несанкционированная модификация задач оркестратора (плейбуков);
Они могут привести не только к ошибкам из разряда человеческого фактора (если инженер случайно, или не совсем, добавит в task'у shutdown серверов вместо reboot'а), но и к горизонтальному перемещению в инфраструктуре, установке вредоносных компонентов (сторонних библиотек, C2-агентов и иной малвари), настройке неконтролируемого удалённого доступа и так далее по списку.

Нивелировать второй, в моей инфраструктуре, было достаточно целесообразно используя уже развёрнутые инструменты (хранилище репозиториев Gitlab с проверкой исходного кода через SAST, настройка процессов CI&CD вместе с Gitlab Runner для доставки плейбуков на управляющий узел Ansible, применение модуля контроля целостности (FIM) в OSSEC-агенте). Про это возможно, чуть позже, появится отдельная статья.
Но вот первый - достаточно неплохой вызов, т.к. необходимо применять не только харденинг ОС и наложенные средства защиты и мониторинга, но и защитить учётные данные от утечки.
Именно по этому, наперекор всем кустарным системным администратором, было принято волевое решение - убрать все учётные данные в HashiCorp Vault.
Лирическое отступление x1
У многих мог появится весьма резонный вопрос, а почему, собственно не Ansible Vault?
При использовании встроенного механизма Ansible Vault мы сталкиваемся с классической проблемой управления секретами в файловой форме. Он позволяет зашифровывать отдельные секреты или файлы инвентаря, но ключ шифрования в любом случае нужно где-либо хранить и передавать для работы оркестратора.
От этого появляется ряд проблем:
Статичность секретов – после расшифровки секрет попадает в память, и остаётся действительным неограниченное время;
Сложность ротации – чтобы сменить даже один секрет - нужно перешифровать все файлы, содержащие этот пароль, и перевыпустить ключи доступа;
Отсутствие аудита – невозможно точно определить, кто и когда запрашивал конкретный секрет;
Отсутствие управления доступом - любой, у кого есть пароль от Vault получает доступ ко всем секретам, хранящимся в зашифрованных файлах.
HCV решает эти задачи на архитектурном уровне. Он предоставляет централизованное API для хранения и выдачи секретов с контролем времени жизни (TTL). Вместо статичных зашифрованных файлов мы получаем:
Динамические секреты – например, Vault может сгенерировать уникальные учетные данные с ограниченным сроком действия, которые автоматически отзываются по истечении TTL;
Аренда и ротация секретов (leases) – каждый выданный секрет имеет время жизни, по истечении которого Vault автоматически его отзывает;
Детальный аудит – Vault журналирует каждый запрос к секретам, что позволяет отследить, когда и какое приложение запрашивало доступ;
Политики доступа – можно определить, какие действия возможно выполнить над секретами на основе ролей, используя пути к хранилищам.

Лирическое отступление x2
Однако те, кто так же как я выбрал путь ниндзя HCV - могут из-за соображений удобства, либо же по незнанию, использовать не совсем корректный способ аутентификации в самом Vault для оркестраторов задач.
Самый простой способ подключить Ansible к HCV – использовать Token (долгоживущий токен, полученный при инициализации или созданный через CLI).
Однако токен – действует до момента отзыва или истечения срока действия.
Если токен скомпрометирован (например, попал в логи или был случайно, или заведомо, захардкожен в исходный код) - злоумышленник получает доступ ко всем ресурсам, разрешённым политикой этого токена, до тех пор, пока администратор вручную его не отзовёт при обнаружении утечки.
Для таких сценариев более уместен сценарий с AppRole. Он отличается от Token тем, что заведомо предназначен для машинных задач. Работает он благодаря двум частям - role_id (идентификатор роли, часто публичный) и secret_id (секретный ключ, который можно сделать одноразовым или ограниченным по времени).
Процесс получения токена выглядит так:
Приложение отправляет в Vault запрос с
role_idиsecret_id;Vault проверяет, что пара валидна и что для данной роли разрешена аутентификация;
Vault возвращает временный токен с заданными TTL и политиками, привязанными к роли.
Из этого вытекают следующие преимущества AppRole над Token-аутентификацией:
Ограничение времени жизни (TTL) – полученный токен действует ограниченное время (например, 1 час), после чего Vault автоматически его отзывает;
Возможность отзыва
secret_id– даже еслиsecret_idбыл скомпрометирован, его можно отозвать в Vault, не затрагивая другие экземпляры приложения (если они используют другиеsecret_id);Интеграция с Ansible – модуль
community.hashi_vault.vault_loginпозволяет выполнять аутентификацию по approle прямо в плейбуке.
Небольшой How-To по approle c использованием HCV CLI (т.к. создать его из веб-интерфейса методом "натыкивания" невозможно):
Включаем AppRole-аутентификацию:
vault auth enable approleСоздаём политику доступа к секретам в файле ansible-policy.hcl (можно сделать в веб-интерфейсе):
path "test/data/domain_join" {capabilities = ["read"]}Применяем политику (можно не делать, если создавали через веб-интерфейс):
vault policy write ansible-policy ansible-policy.hclСоздаём approle с привязкой к политике:
vault write auth/approle/role/ansible-role \ token_policies="ansible-policy" \ token_ttl=1h \ token_max_ttl=4hПолучаем role_id и генерируем secret_id:
vault read auth/approle/role/ansible-role/role-idvault write -f auth/approle/role/ansible-role/secret-id
Ну а теперь, разобрав весьма спорные моменты, можем приступать к реализации.
Реализация
Структуру для данного проекта, на текущем этапе, решил упростить максимально, насколько это в принципе возможно в моём случае:
domain_join
- group_vars
-- all.yml (содержит все переменные, необходимые для работы плейбука)
- inventory.ini (содержит ВМ, которые необходимо ввести в домен)
- playbook.yml (непосредственно плейбук)
Содержание файла all.yml:
vault_addr: "{{ lookup('env', 'VAULT_ADDR') }}" vault_role_id: "{{ lookup('env', 'VAULT_APPROLE_ROLE_ID') }}" vault_secret_id: "{{ lookup('env', 'VAULT_APPROLE_SECRET_ID') }}" vault_domain_data: >- {{ lookup( 'community.hashi_vault.hashi_vault', '<тут путь до вашего KV хранилища>', url=vault_addr, auth_method='approle', role_id=vault_role_id, secret_id=vault_secret_id ) }} domain_fqdn: "{{ vault_domain_data.domain_fqdn }}" domain_fqdn_uppercase: "{{ vault_domain_data.domain_fqdn_uppercase }}" dc_ip: "{{ vault_domain_data.dc_ip }}" user_samaccountname: "{{ vault_domain_data.user_samaccountname }}" user_pwd: "{{ vault_domain_data.user_pwd }}" computer_ou: "{{ vault_domain_data.computer_ou }}" ansible_user: "{{ vault_domain_data.local_user }}" ansible_password: "{{ vault_domain_data.local_password }}" ansible_become_password: "{{ vault_domain_data.local_password }}" # здесь имя доменной группы, члены которой могут зайти на сервер allowed_group: "linux admins" # здесь имя доменной группы, имеющей права sudo, и их привилегии sudoers_group_line: "%linux\\ admins ALL=(ALL:ALL) ALL" # при стандартной инсталляции Ubuntu - создаётся отдельный пользователь # с правами sudo, поэтому ставим become в true, а method в sudo ansible_become: true ansible_become_method: sudo
Для любителей точечной настройки прав sudo: ссылка на официальный мануал.
Пример файла inventory.ini:
[linux_domain_members] srv-01 ansible_host=192.168.88.11 srv-02 ansible_host=192.168.88.14 srv-03 ansible_host=192.168.88.22 srv-04 ansible_host=192.168.88.38 srv-05 ansible_host=192.168.88.88
Содержание playbook.yml:
- name: Join Linux hosts to AD domain and configure access hosts: linux_domain_members become: true pre_tasks: - name: Ensure required tools for debconf are installed apt: name: debconf-utils state: present update_cache: true - name: Preseed krb5-config default realm debconf: name: krb5-config question: krb5-config/default_realm value: "{{ domain_fqdn_uppercase }}" vtype: string tasks: - name: Remove systemd-timesyncd to avoid conflict with ntp apt: name: systemd-timesyncd state: absent become: yes - name: Install required packages apt: name: - krb5-user - samba - sssd - sssd-tools - libnss-sss - libpam-sss - ntp - ntpdate - realmd - adcli - packagekit state: present update_cache: true - name: Configure ntp to use domain controller copy: dest: /etc/ntp.conf content: | server {{ dc_ip }} owner: root group: root mode: '0644' - name: Stop NTP service before manual time sync service: name: ntp state: stopped - name: Sync time with domain using ntpdate command: "ntpdate {{ domain_fqdn }}" register: ntpdate_result changed_when: > 'adjust time server' in ntpdate_result.stdout or 'step time server' in ntpdate_result.stdout failed_when: ntpdate_result.rc != 0 - name: Start and enable NTP service service: name: ntp state: started enabled: true - name: Check if host is already joined to the domain command: realm list register: realm_list changed_when: false failed_when: false - name: Join host to domain command: > realm join --user={{ user_samaccountname }} --computer-ou='{{ computer_ou }}' {{ domain_fqdn }} args: stdin: "{{ user_pwd }}" when: domain_fqdn_uppercase not in realm_list.stdout register: realm_join changed_when: realm_join.rc == 0 failed_when: realm_join.rc != 0 - name: Permit specific AD group to access the server command: > realm permit --groups "{{ allowed_group }}" register: realm_permit changed_when: "'already allowed' not in realm_permit.stderr | default('')" failed_when: realm_permit.rc != 0 - name: Ensure use_fully_qualified_names is set to False in sssd.conf lineinfile: path: /etc/sssd/sssd.conf regexp: '^\s*use_fully_qualified_names\s*=' line: 'use_fully_qualified_names = False' backrefs: false notify: - restart sssd - name: Ensure mkhomedir line exists after pam_sss.so lineinfile: path: /etc/pam.d/common-session regexp: '^session required pam_mkhomedir\.so skel=/etc/skel/ umask=0077$' line: 'session required pam_mkhomedir.so skel=/etc/skel/ umask=0077' insertafter: 'session optional\s+pam_sss\.so' state: present backrefs: false - name: Configure sudoers for linux administrators group copy: dest: /etc/sudoers.d/admins content: "{{ sudoers_group_line }}\n" owner: root group: root mode: '0440' validate: '/usr/sbin/visudo -cf %s' handlers: - name: restart ntp service: name: ntp state: restarted - name: restart sssd service: name: sssd state: restarted
Команда для запуска плейбука:
ansible-playbook -i inventory.ini playbook.yml
Итоги
С использованием данного плейбука - возможно буквально за 3 минуты ввести в домен неограниченное количество ВМ, что при иных условиях сулило бы часы мучений и страданий от ручного труда.
Надеюсь что данный плейбук сможет сэкономить Вам львиную долю времени на действительно интересные задачи, не создавая рисков по ИБ.
Всем добра, и поменьше рутины!
