Скрытый текст
Перед началом небольшой дисклеймер: данный плагин не является официальным. Он не поддерживается технической поддержкой и продуктовой командой ALD Pro. Перед его использованием, необходимо оценить возможные риски и протестировать, до использования в продакш среде.
Введение: Почему автоматизация рутины стала необходимостью
ALD PRO предоставляет управление ролями через WebUI и API. В WebUI массовое создание/клонирование ролей занимает много времени и плохо подходит для интеграции в автоматизацию. Требование было практичным: быстро копировать существующую роль для новых организационных подразделений (OU), сохраняя привилегии/политики и приводя роль в корректное состояние.
В этой статье я расскажу о четырёх этапах эволюции решения:
Первая версия на bash с работой через LDAP
Переход на API ALD PRO с тем же bash
Рефакторинг на Python для надёжности и гибкости
Полная интеграция как плагин FreeIPA

Каждый этап решал конкретные проблемы предыдущего и добавлял новые возможности.
Этап 1: Наивный подход — работа напрямую с LDAP
Первая версия скрипта пыталась работать напрямую с LDAP-деревом FreeIPA. Идея была проста: найти запись роли, скопировать её атрибуты и создать новую запись в нужном OU.
Проблемы, с которыми я столкнулся:
Сложность определения корректного DN для целевого OU
Проблемы с копированием привилегий и политик
Отсутствие валидации данных на стороне ALD PRO
Необходимость ручного управления состоянием роли (активация, деактивация)
Этот подход быстро показал свою ограниченность. ALD PRO — не просто LDAP-каталог, а сложная система с собственной логикой, которая не отражается напрямую в атрибутах LDAP.
Этап 2: Переход на API ALD PRO — bash-скрипт с поддержкой Kerberos
Осознав ограничения LDAP-подхода, я изучил REST API ALD PRO. Оказалось, что система предоставляет полноценный JSON API для управления ролями.
Аутентификация: Kerberos или логин/пароль
Kerberos (negotiate) удобен для SSO в инфраструктуре FreeIPA/ALD.
Логин/пароль полезен для изолированных стендов или при отсутствии Kerberos.
Ключевые особенности bash-реализации:
#!/bin/bash # Функция для аутентификации с поддержкой Kerberos и логина/пароля authenticate() { if [[ -n "$ADMIN_USER" && -n "$ADMIN_PASSWORD" ]]; then use_kerberos="false" fi if [[ "$use_kerberos" != "true" ]]; then # Базовая аутентификация response=$(curl -X 'POST' \ "https://$SERVER_URL/ad/api/ds/login" \ -d "{\"data\":{\"login\":\"$username\",\"password\":\"$password\"}}" \ -c curl.cookie -s --insecure -w "|%{http_code}") else # Kerberos-аутентификация response=$(curl -X 'POST' \ -H "referer:https://$SERVER_URL/ad/ui" \ -c curl.cookie --negotiate -u : \ --insecure -w "|%{http_code}" \ "https://$SERVER_URL/ad/api/ds/login/kerberos") fi }
Что было реализовано в bash-скрипте:
Двойная аутентификация: поддержка как Kerberos (для интегрированных систем), так и логина/пароля
Безопасный ввод пароля: маскировка вводимых символов
Полный цикл работы с ролью: получение → создание → обновление → активация
Обработка ошибок: анализ HTTP-кодов и JSON-ответов
Работа с пробелами в именах ролей: URL-кодирование специальных символов
Пример использования:
./clone_api_roles.sh \ -r 'ALDPRO - Automation Tasks Administrators' \ -n 'New Automation Role' \ -o 'ou=buh,ou=ald.pro,cn=orgunits,cn=accounts,dc=ald,dc=pro' \ -U aldprodc1.ald.pro
Недостатки bash-реализации:
Сложная работа с JSON (через
jq, но всё равно громоздко)Ограниченная обработка ошибок
Трудности с поддержкой и расширением
Зависимость от внешних утилит (
curl,jq,sed)
Этап 3: Рефакторинг на Python — структурирование логики и нормальная обработка ошибок
Переход на Python решает основные проблемы bash-скрипта:
нативный JSON;
исключения и типизированные структуры;
модульность (разделение на аутентификацию, работу с ролью, валидацию входных данных);
возможность тестов (моки API).
Важная оговорка: Kerberos negotiate
В первом варианте, мне удобнее было использовать curl --negotiate как проверенный способ аутентификации в конкретной среде. Поэтому Python-версия выступала как "оркестратор", а HTTP-вызов делался через subprocess.
Если окружение позволяет, этот слой можно заменить на requests + GSSAPI/SSPI (в зависимости от платформы, можно использовать второй плагин в репозитории plagun_with_req).
Архитектура Python-реализации:
class ALDProRoleCloner: """Класс для работы с ролями в ALD PRO через API с использованием curl""" def _run_curl(self, method: str, url: str, data: Optional[Dict] = None) -> Optional[Dict]: """Выполняет curl запрос""" try: cmd = [ 'curl', '-s', '-k', '--negotiate', '-u', ':', '-X', method, '-H', 'Accept: application/json', '-H', 'Content-Type: application/json', '-b', self.cookie_file.name, '-c', self.cookie_file.name, ] # ... выполнение запроса и обработка ответа
Ключевые улучшения:
Объектно-ориентированный подход: инкапсуляция логики в классы
Типизация: аннотации типов для лучшей читаемости
Временные файлы: автоматическая очистка cookie-файлов
Гибкая конфигурация: возможность задания всех параметров через аргументы
Но оставалась проблема: скрипт всё ещё был внешним инструментом, не интегрированным в экосистему FreeIPA. Пользователям приходилось запоминать отдельную команду, не было единого интерфейса.
Этап 4: Архитектура плагинов FreeIPA - как это работает изнутри
Прежде чем перейти к нашему кейсу, давайте разберемся, как вообще устроены плагины FreeIPA. Понимание архитектуры поможет оценить, почему интеграция в виде плагина — это наиболее правильный подход для подобных задач.
Основные компоненты плагина FreeIPA
FreeIPA построена на модульной архитектуре, где каждый плагин — это Python-класс, расширяющий базовые классы фреймворка. Вот ключевые элементы:
1. Класс команды (наследник Command):
from ipalib import Command, Str, _ class my_command(Command): """Документация команды""" # Имя команды (будет доступно как ipa my-command) name = 'my_command' # Определение параметров takes_args = ( Str('username', cli_name='user', label=_('Username'), doc=_('User to process')), ) # Определение вывода has_output = ( ('result', dict), ('summary', str), ) def execute(self, username, **options): # Логика команды return { 'result': {'processed': username}, 'summary': _('User %s processed') % username }
2. Регистрация плагина:
from ipalib import api def register(): # Регистрируем команду в API FreeIPA api.add_plugin(my_command)
3. Типы параметров:
Str— строковые значенияInt— целые числаFlag— флаги (True/False)Bytes— бинарные данныеPassword— пароли (с маскировкой)
Знак вопроса в конце имени параметра ('username?') делает его необязательным.
Жизненный цикл плагина
Загрузка: FreeIPA при старте сканирует директорию
/usr/lib/python3/dist-packages/ipaserver/plugins/и импортирует все модули, указанные вinit.py.Инициализация: Вызывается функция
register()каждого плагина, которая добавляет команды в общее пространство имён FreeIPA.Выполнение:
Пользователь вызывает команду через CLI, WebUI или API
FreeIPA находит соответствующий плагин
Автоматически валидируются параметры
Вызывается метод
execute()с переданными аргументамиРезультат форматируется согласно
has_output
Интеграция: После регистрации команда становится доступной через:
CLI:
ipa my-command --user=testWebUI: появляется в интерфейсе (если настроено)
JSON-RPC API: для удалённых вызовов
Преимущества подхода с плагинами
Единый интерфейс: Все команды, как встроенные, так и кастомные, доступны через единый CLI
ipa.Наследование инфраструктуры: Плагины автоматически получают:
Аутентификацию и авторизацию FreeIPA
Логирование в стандартные журналы
Поддержку internationalization (i18n)
Валидацию параметров
Интеграция с WebUI: При необходимости плагин можно подключить к веб-интерфейсу FreeIPA.
Безопасность: Команды наследуют систему привилегий FreeIPA, можно тонко настраивать права доступа через
ipa privilege-*иipa permission-*.
Лучшие практики разработки плагинов
Используйте i18n: Все строки, видимые пользователю, оборачивайте в
_():
summary = _('Operation completed successfully')
Валидируйте входные данные: Используйте ограничения параметров:
Int('port', minvalue=1, maxvalue=65535)
Правильно обрабатывайте ошибки: Используйте стандартные исключения FreeIPA:
from ipalib import errors raise errors.NotFound(reason=_('User not found'))
Логируйте действия: Используйте встроенный логгер:
import logging log = logging.getLogger(__name__) log.debug('Processing user: %s', username)
Пишите документацию: Подробные docstring становятся справкой в CLI.
Теперь, понимая основы архитектуры плагинов FreeIPA, давайте посмотрим, как эти принципы были применены в нашем случае с клонированием ролей ALD PRO.
Этап 5: Плагин FreeIPA: команда в ipa CLI и единая авторизация
Финальный этап — создание полноценного плагина FreeIPA. Это позволило:
Интегрировать команду в стандартный CLI FreeIPA (
ipa)Использовать встроенную аутентификацию и авторизацию
Обеспечить единый интерфейс со всеми другими командами FreeIPA
Автоматически генерировать документацию и справку
Структура нашего плагина для клонирования ролей:
@register() class role_clone(Command): """Копирование ролей в ALD PRO""" name = 'role_clone' takes_args = () takes_options = ( Str('srcrole', cli_name='srcrole', label=_('Source role name'), doc=_('Имя существующей роли для копирования (например: "ALDPRO - Automation Tasks Administrators")'), no_convert=True, ), Str('newrole', cli_name='newrole', label=_('New role name'), doc=_('Имя новой роли'), no_convert=True, ), # ... другие параметры ) has_output = ( output.Output('summary', type=str, doc=_('Summary of the operation'), ), output.Output('result', type=dict, doc=_('Result information'), ), ) def execute(self, *args, **options): """Основная логика команда""" # Проверяем корректность статуса # Проверяем Kerberos билет # Шаг 1: Аутентификация через Kerberos # Шаг 2: Получение данных о существующей роли # Извлекаем необходимые данные # Определяем какой OU использовать # Определяем какое описание использовать # Шаг 3: Создание новой роли # Шаг 4: Обновление роли через PATCH # Шаг 5: Активация роли (выполняется только если статус active) # Возвращаем результат
Как работает плагин изнутри:
Регистрация в FreeIPA: Плагин добавляется в
/usr/lib/python3/dist-packages/ipaserver/plugins/и регистрируется через декоратор@register().Интеграция с CLI: После установки плагина команда становится доступной через стандартный интерфейс:
ipa help role-clone # просмотр справки ipa role-clone --srcrole="Source Role" --newrole="New Role" --server="aldpro.example.com"
3. Использование Kerberos: Плагин автоматически использует текущий Kerberos-билет пользователя, что обеспечивает единый вход (SSO):
def check_kerberos_ticket(self) -> bool: """Проверка наличия Kerberos билета""" try: result = subprocess.run( ['klist'], capture_output=True, text=True, timeout=5 ) return result.returncode == 0 except Exception: return False
4. Полный цикл работы с API: Плагин выполняет те же этапы, что и bash-скрипт, но с лучшей обработкой ошибок и интеграцией в FreeIPA.
Пример использования плагина:
# Получение Kerberos-билета (если ещё нет) kinit admin # Клонирование роли с указанием целевого OU ipa role-clone \ --srcrole="ALDPRO - Automation Tasks Administrators" \ --newrole="ALDPRO - Automation Tasks Administrators OU1" \ --targetou="ou=ou1,ou=ald.pro,cn=orgunits,cn=accounts,dc=ald,dc=pro" \ --server="aldprodc2.ald.pro" \ --description="Автоматизация задач для OU1" \ --status="active"
Преимущества плагина перед standalone-скриптом:
Единый интерфейс: Используется привычный
ipaCLIИнтегрированная аутентификация: Работает с Kerberos-билетами FreeIPA
Автоматическая документация:
--helpгенерируется автоматическиКонсистентность ошибок: Ошибки форматируются так же, как в других командах FreeIPA
Возможность интеграции с WebUI: Теоретически плагин можно подключить и к веб-интерфейсу
Технические детали реализации
Обработка пробелов в именах ролей
ALD PRO допускает пробелы в именах роле��, что требует специальной обработки в URL:
def encode_role_name(role_name): """Кодирование имени роли для URL""" return role_name.replace(' ', '%20') # Использование в запросах encoded_role = encode_role_name("Role with spaces") url = f"https://{server}/ad/api/ds/roles/{encoded\\_role}"
Работа с состоянием ролей
Роли в ALD PRO имеют несколько состояний:
editing— роль создана, но не активированаactive— роль активна и применяетсяinactive— роль неактивна
Плагин управляет этим состоянием через PATCH-запросы:
update_data = { "role_description": description, "role_ou_dn": target_ou, "role_ou_nested": True, "role_state": "active" # или "inactive" }
Обработка ошибок API
Каждый запрос к API проверяется на HTTP-код и содержание JSON-ответа:
def _run_curl(self, method, url, data=None): # Выполнение запроса result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) if result.returncode != 0: raise Exception(f"curl error: {result.stderr}") # Парсинг JSON response = json.loads(result.stdout) if not response.get('success'): error_msg = response.get('error', {}).get('message', 'Unknown error') raise Exception(f"API error: {error_msg}") return response
Сравнение подходов
Критерий | Bash-скрипт | Python-скрипт | FreeIPA плагин |
|---|---|---|---|
Сложность разработки | Средняя | Высокая | Очень высокая |
Интеграция с FreeIPA | Нет | Нет | Полная |
Аутентификация | Логин/пароль или Kerberos | Логин/пароль или Kerberos | Kerberos (е��иный вход) |
Обработка ошибок | Базовая | Продвинутая | Интегрированная |
Удобство использования | Отдельная команда | Отдельная команда |
|
Поддержка | Требует bash-знаний | Требует Python | Стандартный интерфейс FreeIPA |
Заключение
Путь от простого bash-скрипта до полноценного плагина FreeIPA занял несколько месяцев, но результат того стоил.
Экономию времени: Клонирование роли теперь занимает секунды вместо минут ручной работы
Автоматизацию: Процесс интегрирован в CI/CD и скрипты развёртывания
Надёжность: Встроенная обработка ошибок и проверка данных
Единый интерфейс: Администраторы работают через привычный
ipaCLI
Этот проект также показал важность выбора правильного уровня интеграции при работе со сложными системами управления идентификацией. Начав с внешнего скрипта, я постепенно пришел к пониманию, что настоящая ценность — в глубокой интеграции с платформой.
Советы для разработчиков, которые хотят повторить этот путь:
Начинайте с изучения API системы, а не прямого доступа к данным (LDAP, БД);
Используйте язык, который хорошо интегрируется с целевой платформой (Python для FreeIPA);
Не бойтесь рефакторинга — каждый этап улучшал плагин и фильтровал ошибки;
Документируйте процесс — это поможет и вам, и другим разработчикам.
