Инструмент для синхронизации динамических групп рассылки между Active Directory (Exchange) и Yandex 360.
Проект предназначен для автоматической синхронизации динамических групп рассылки из Active Directory в облачную платформу Yandex 360. Скрипт обеспечивает:
- Синхронизацию структуры групп из AD в Y360
- Синхронизацию членов групп на основе CSV файлов
- Получение списков членов динамических групп из AD
- Управление группами в Y360 (создание, обновление, удаление)
- Диагностику и сохранение данных для анализа
- Автоматическое создание групп из AD в Y360
- Обновление параметров существующих групп
- Удаление групп из Y360, отсутствующих в AD
- Сопоставление по уникальному идентификатору (objectGUID)
- Добавление пользователей в группы Y360
- Чтение списков членов из CSV файлов
- Сопоставление пользователей по email адресам
- Экспорт списков членов групп из Y360
- Поддержка различных типов членов (пользователи, подразделения)
- Сохранение результатов в CSV файлы
- Подключение к AD через LDAP/LDAPS протокол
- Получение динамических групп рассылки
- Получение пользователей и их атрибутов
- Поддержка глобального каталога (Global Catalog)
- Сохранение списков членов групп из Y360 в файлы
- Детальное логирование всех операций
- Режим тестового прогона (DRY_RUN)
- Python 3.7 или выше
- Доступ к контроллеру домена Active Directory
- OAuth токен для Yandex 360 API
pip install -r requirements_ldap.txtОсновные библиотеки:
ldap3- для работы с LDAP/Active Directoryrequests- для работы с Yandex 360 APIpython-dotenv- для загрузки переменных окружения
Все параметры настраиваются в файле .env_ldap. Создайте этот файл в корне проекта на основе примера ниже.
Обязательный параметр
OAuth токен для доступа к API Yandex 360.
OAUTH_TOKEN = "y0_AgAAAAB0It5oAAvg"Как получить:
- Перейдите в Админ-панель Yandex 360
- Создайте OAuth приложение
- Получите токен с правами доступа к группам и пользователям
Необходимые права:
ya360_admin:domain_groups_readya360_admin:domain_groups_writeya360_admin:domain_users_read
Обязательный параметр
Идентификатор вашей организации в Yandex 360.
ORG_ID = 1234567Как узнать:
- В админ-панели Yandex 360 перейдите в раздел "Настройки" → "Основные"
- Или используйте API:
https://api360.yandex.net/directory/v1/org
Обязательный параметр
Адрес контроллера домена для подключения по LDAP протоколу.
LDAP_HOST = 'dc01.domain.ru'Рекомендации:
- Если в AD несколько доменов - обязательно используйте DNS имя
- В Linux должно быть настроено разрешение имен через локальный DNS
- Если домен один, можно указывать IP адрес
Обязательный параметр
Использовать ли защищенное соединение LDAPS (LDAP over SSL).
LDAPS_ENABLED = FalseЗначения:
True- использовать LDAPS (порт должен быть 636)False- использовать обычный LDAP
Примечание: При использовании LDAPS необходим действующий SSL сертификат на контроллере домена.
Обязательный параметр
Порт для подключения к LDAP серверу.
LDAP_PORT = 3268Стандартные порты:
389- стандартный LDAP порт (один домен)636- LDAPS порт (LDAP over SSL)3268- порт глобального каталога (Global Catalog)3269- порт глобального каталога с SSL
Когда использовать 3268:
- В AD несколько доменов (лес доменов)
- Нужен доступ ко всем объектам во всех доменах
- Динамические группы могут содержать пользователей из разных доменов
Обязательный параметр
Учетная запись для подключения к LDAP.
LDAP_USER = 'contoso\ldap_connector'Формат: netbios_domain_name\samaccountname
Требования к учетной записи:
- Доступ на чтение из указанных DN (Base DN)
- Не требуется права администратора домена
- Рекомендуется создать отдельную служебную учетную запись
Обязательный параметр
Пароль для учетной записи LDAP.
LDAP_PASSWORD = 'password'Рекомендации по безопасности:
- Используйте сложный пароль
- Не публикуйте файл
.env_ldapв публичные репозитории - Добавьте
.env_ldapв.gitignore
Обязательный параметр
Откуда в каталоге будет начинаться поиск пользователей, которые были мигрированы в Yandex 360.
LDAP_USER_BASE_DN = 'OU=Office,DC=domain,DC=ru'Формат: Distinguished Name (DN)
Примеры:
# Поиск в конкретном OU
LDAP_USER_BASE_DN = 'OU=Users,OU=Moscow,DC=contoso,DC=com'
# Поиск во всем домене
LDAP_USER_BASE_DN = 'DC=contoso,DC=com'
# Поиск в нескольких OU (указывать родительский OU)
LDAP_USER_BASE_DN = 'OU=Company,DC=contoso,DC=com'Обязательный параметр
LDAP фильтр для поиска учетных записей, которые были мигрированы в Yandex 360.
LDAP_USER_SEARCH_FILTER = '(memberOf=CN=Yandex360,OU=Groups,OU=Office,DC=domain,DC=ru)'Формат: LDAP Query Filter
Примеры:
# Пользователи - члены группы Yandex360
LDAP_USER_SEARCH_FILTER = '(memberOf=CN=Yandex360,OU=Groups,DC=domain,DC=ru)'
# Все активные пользователи
LDAP_USER_SEARCH_FILTER = '(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))'
# Пользователи с заполненным email
LDAP_USER_SEARCH_FILTER = '(&(objectClass=user)(mail=*))'
# Комбинированный фильтр
LDAP_USER_SEARCH_FILTER = '(&(memberOf=CN=Yandex360,OU=Groups,DC=domain,DC=ru)(mail=*))'Обязательный параметр
Список атрибутов пользователей, которые возвращаются в результате поиска.
ATTRIB_USER_LIST = distinguishedName,mail,displayName,department,objectCategory,sAMAccountName,cn,objectClassФормат: Список через запятую (без пробелов)
Стандартные атрибуты:
distinguishedName- полный путь объекта в ADmail- email адресdisplayName- отображаемое имяdepartment- подразделениеsAMAccountName- логин пользователяcn- Common NameobjectClass- класс объектаobjectCategory- категория объектаuserPrincipalName- UPN (user@domain.com)
Обязательный параметр
Откуда в каталоге будет начинаться поиск групп рассылки.
LDAP_GROUP_BASE_DN = 'OU=Office,DC=domain,DC=ru'Формат: Distinguished Name (DN)
Примеры:
# Поиск в конкретном OU с группами
LDAP_GROUP_BASE_DN = 'OU=Distribution Groups,OU=Office,DC=contoso,DC=com'
# Поиск во всем домене
LDAP_GROUP_BASE_DN = 'DC=contoso,DC=com'Обязательный параметр
LDAP фильтр для поиска динамических групп рассылки, которые нужно синхронизировать в Yandex 360.
LDAP_GROUP_SEARCH_FILTER = '(memberOf=CN=Yandex360,OU=Groups,OU=Office,DC=domain,DC=ru)'Формат: LDAP Query Filter
Примеры:
# Динамические группы - члены группы Yandex360
LDAP_GROUP_SEARCH_FILTER = '(memberOf=CN=Yandex360,OU=Groups,DC=domain,DC=ru)'
# Все динамические группы рассылки
LDAP_GROUP_SEARCH_FILTER = '(objectClass=msExchDynamicDistributionList)'
# Динамические группы с определенным префиксом в имени
LDAP_GROUP_SEARCH_FILTER = '(&(objectClass=msExchDynamicDistributionList)(cn=Группа*))'
# Динамические группы с фильтром по описанию
LDAP_GROUP_SEARCH_FILTER = '(&(objectClass=msExchDynamicDistributionList)(description=*Y360*))'Важно: Скрипт автоматически фильтрует только объекты с objectClass = msExchDynamicDistributionList, поэтому дополнительно указывать этот класс в фильтре не обязательно.
Обязательный параметр
Список атрибутов групп, которые возвращаются в результате поиска.
ATTRIB_GROUP_LIST = distinguishedName,mail,displayName,description,objectCategory,sAMAccountName,msExchQueryFilter,cn,objectClass,objectGUIDФормат: Список через запятую (без пробелов)
Обязательные атрибуты:
objectGUID-⚠️ КРИТИЧЕСКИ ВАЖНО для синхронизации группdisplayName- имя группыmail- email адрес группыobjectClass- класс объекта
Дополнительные атрибуты:
distinguishedName- полный путь объекта в ADdescription- описание группыsAMAccountName- краткое имя группыcn- Common NamemsExchQueryFilter- LDAP фильтр для динамической группыobjectCategory- категория объекта
objectGUID обязательно должен быть в списке для корректной работы синхронизации групп!
Обязательный параметр
Режим тестового прогона (без внесения изменений).
DRY_RUN = FalseЗначения:
True- тестовый режим (все операции только логируются, изменения не применяются)False- рабочий режим (изменения применяются)
Рекомендация: При первом запуске всегда используйте DRY_RUN = True для проверки работы скрипта.
Опциональный параметр
Путь к каталогу с CSV файлами членов групп для синхронизации (используется функцией sync_group_members).
GROUPS_MEMBERS_FILE_DIR = .Значения:
.- текущая директория (по умолчанию)/path/to/files- абсолютный путь./members_files- относительный путь
Формат CSV файлов:
- Имя файла:
Группа_рассылки_{displayName}.csv - Разделитель: точка с запятой (
;) - Email адреса: во втором столбце (индекс 1)
- Первая строка: заголовки (опционально)
- Кодировка: UTF-8
Пример структуры файла:
Минимальный формат (два столбца):
Иванов Иван;ivanov@domain.ru
Петров Петр;petrov@domain.ru
Сидорова Мария;sidorova@domain.ruРасширенный формат (с дополнительными полями):
"DisplayName";"Email";"Должность";"Отдел";"Департамент";"Организация";"Адрес"
"Иванов Иван";"ivanov@example.com";"Секретарь";"Приемная директора";"";"Компания ABC";"Широкая ул., д.1"
"Петров Петр";"petrov@example.com";"";"Руководство";"";"Компания ABC";"Широкая ул., д.1"
"Сидорова Мария";"sidorova@example.com";"Менеджер";"Отдел продаж";"Коммерческий департамент";"Компания ABC";"Широкая ул., д.1"Примечания:
- Скрипт использует только первые два столбца (имя и email)
- Дополнительные столбцы игнорируются, но могут быть полезны для справки
- Строка заголовков автоматически пропускается, если не содержит валидный email во втором столбце
- Значения могут быть в кавычках (опционально)
Опциональный параметр
Сохранять ли списки членов групп из Y360 в файлы для анализа и диагностики.
ENABLE_DIAGNOSTICS = FalseЗначения:
True- включить сохранение диагностических данныхFalse- отключить (по умолчанию)
Для чего нужно:
- Анализ состава групп в Y360
- Сравнение с данными из AD
- Отладка проблем синхронизации
- Аудит изменений
Опциональный параметр
Каталог, где будут сохраняться файлы с членами групп из Y360 (при включенной диагностике).
Y360_GROUP_MEMBERS_DIR = ./y360_diagnosticsЗначения:
./y360_diagnostics- относительный путь (по умолчанию)/path/to/diagnostics- абсолютный путь
Примечание: Каталог будет создан автоматически при первом сохранении данных.
git clone https://github.com/your-repo/SyncDynamicGroupsFromFile.git
cd SyncDynamicGroupsFromFilepip install -r requirements_ldap.txtСоздайте файл .env_ldap в корне проекта:
cp .env_ldap.example .env_ldap
nano .env_ldapОтредактируйте .env_ldap, заполнив все обязательные параметры:
# Yandex 360
OAUTH_TOKEN = "ваш_oauth_токен"
ORG_ID = ваш_org_id
# LDAP соединение
LDAP_HOST = 'dc01.domain.ru'
LDAPS_ENABLED = False
LDAP_PORT = 3268
LDAP_USER = 'domain\ldap_user'
LDAP_PASSWORD = 'password'
# Поиск пользователей
LDAP_USER_BASE_DN = 'OU=Office,DC=domain,DC=ru'
LDAP_USER_SEARCH_FILTER = '(memberOf=CN=Yandex360,OU=Groups,OU=Office,DC=domain,DC=ru)'
ATTRIB_USER_LIST = distinguishedName,mail,displayName,department,objectCategory,sAMAccountName,cn,objectClass
# Поиск групп
LDAP_GROUP_BASE_DN = 'OU=Office,DC=domain,DC=ru'
LDAP_GROUP_SEARCH_FILTER = '(memberOf=CN=Yandex360,OU=Groups,OU=Office,DC=domain,DC=ru)'
ATTRIB_GROUP_LIST = distinguishedName,mail,displayName,description,objectCategory,sAMAccountName,msExchQueryFilter,cn,objectClass,objectGUID
# Режим работы
DRY_RUN = True
GROUPS_MEMBERS_FILE_DIR = .
# Диагностика
ENABLE_DIAGNOSTICS = False
Y360_GROUP_MEMBERS_DIR = ./y360_diagnostics# В режиме DRY_RUN = True (тестовый режим)
python sync_dd_from_file.pyСкрипт выполнит следующие операции:
- Получит список пользователей из Yandex 360
- Получит список групп из Active Directory
- Синхронизирует группы между AD и Y360
- Синхронизирует членство в группах
После проверки результатов в тестовом режиме измените в .env_ldap:
DRY_RUN = FalseИ запустите скрипт для реальной синхронизации:
python sync_dd_from_file.pyfrom sync_dd_from_file import (
get_settings,
get_ldap_dynamic_groups,
get_all_groups_from_api360,
sync_ad_groups_to_y360
)
# Загрузка настроек
settings = get_settings()
if settings is None:
print("Ошибка в настройках!")
exit(1)
# Получение групп из AD
ad_groups = get_ldap_dynamic_groups(settings)
print(f"Найдено групп в AD: {len(ad_groups)}")
# Получение групп из Y360
y360_groups = get_all_groups_from_api360(settings)
print(f"Найдено групп в Y360: {len(y360_groups)}")
# Синхронизация
success, stats = sync_ad_groups_to_y360(settings, ad_groups, y360_groups)
print(f"\nРезультаты синхронизации:")
print(f" Создано групп: {stats['created_count']}")
print(f" Обновлено групп: {stats['updated_count']}")
print(f" Удалено групп: {stats['deleted_count']}")
print(f" Ошибок: {stats['errors_count']}")from sync_dd_from_file import (
get_settings,
get_ldap_dynamic_groups,
get_all_groups_from_api360,
get_all_api360_users,
sync_group_members
)
# Загрузка настроек
settings = get_settings()
# Получение данных
ad_groups = get_ldap_dynamic_groups(settings)
y360_groups = get_all_groups_from_api360(settings)
y360_users = get_all_api360_users(settings)
# Синхронизация членов групп
success, stats = sync_group_members(
settings,
ad_groups,
y360_groups,
y360_users,
members_files_dir="./members_csv"
)
print(f"Обработано групп: {stats['y360_groups_processed']}")
print(f"Добавлено пользователей: {stats['users_added_count']}")from sync_dd_from_file import get_settings, get_group_members_by_api
settings = get_settings()
group_id = 12345 # ID группы в Y360
success, members = get_group_members_by_api(settings, group_id)
if success:
print(f"Пользователей: {len(members.get('users', []))}")
print(f"Подразделений: {len(members.get('departments', []))}")
print(f"Групп: {len(members.get('groups', []))}")
# Вывод email пользователей
for user in members.get('users', []):
print(f" - {user['nickname']}")Запрашивает всех пользователей из LDAP каталога, фильтрует только объекты с objectClass = person.
Запрашивает динамические группы рассылки из LDAP каталога, фильтрует только объекты с objectClass = msExchDynamicDistributionList.
Читает список email адресов членов группы из CSV файла в формате Группа_рассылки_{displayName}.csv.
Получает все группы организации из Yandex 360 с пагинацией.
Получает список всех пользователей организации из Yandex 360 с кешированием (обновляется каждые 15 минут).
Создает новую группу в Yandex 360 с указанными параметрами (name, label, description, externalId, members).
Изменяет параметры существующей группы в Yandex 360.
Удаляет группу из Yandex 360 по идентификатору.
Получает список участников группы (пользователи, подразделения, группы) из Yandex 360.
Добавляет участника (user, group, department) в группу в Yandex 360.
Удаляет участника из группы в Yandex 360.
Синхронизирует динамические группы рассылки из Active Directory в Yandex 360:
- Создает отсутствующие в Y360 группы
- Обновляет параметры существующих групп (name, label, description)
- Удаляет из Y360 группы с префиксом "DDG", которых нет в AD
- Сопоставление по уникальному идентификатору objectGUID
Возвращает статистику:
ad_groups_count- количество групп в ADy360_groups_count- количество групп в Y360created_count- количество созданных группupdated_count- количество обновленных группdeleted_count- количество удаленных группerrors_count- количество ошибокskipped_count- количество пропущенных групп
Синхронизирует списки членов групп между Active Directory и Yandex 360:
- Для каждой группы Y360 с externalId "DDG" получает текущих членов
- Читает список членов из CSV файла для соответствующей группы AD
- Добавляет недостающих пользователей в группу Y360
- Удаляет из Y360 пользователей, которых нет в списке AD
- Учитывает алиасы пользователей при сравнении
Возвращает статистику:
y360_groups_processed- количество обработанных группad_groups_found- количество найденных групп в ADusers_added_count- количество добавленных пользователейusers_removed_count- количество удаленных пользователейusers_not_found_count- пользователей не найдено в Y360errors_count- количество ошибокskipped_groups_count- количество пропущенных групп
Загружает и проверяет настройки из переменных окружения файла .env_ldap.
Проверяет, что OAuth токен действителен для указанной организации.
Создает копию словаря с замаскированными чувствительными данными (пароли, токены) для безопасного логирования.
Сохраняет список членов группы из Yandex 360 в CSV файл для диагностики (только при ENABLE_DIAGNOSTICS = True).
При запуске скрипта sync_dd_from_file.py напрямую (не импортируя как модуль) выполняется полная последовательность синхронизации:
- Загрузка настроек из файла
.env_ldap - Получение пользователей из Yandex 360
- Получение групп из Active Directory
- Получение групп из Yandex 360
- Синхронизация групп (создание, обновление, удаление)
- Повторное получение групп из Y360 (обновленный список)
- Синхронизация членства в группах (добавление/удаление пользователей)
- Вывод итоговой статистики
python sync_dd_from_file.pyВ режиме DRY_RUN = True все операции только логируются без внесения реальных изменений.
В начале файла sync_dd_from_file.py определены важные константы, которые можно изменить при необходимости:
LOG_FILE = "sync_deps.log" # Имя файла логов
EMAIL_DOMAIN = "domain.ru" # Email домен
DEFAULT_360_API_URL = "https://api360.yandex.net" # URL API Yandex 360
MAX_RETRIES = 3 # Максимум попыток при ошибке API
RETRIES_DELAY_SEC = 2 # Задержка между попытками (сек)
SLEEP_TIME_BETWEEN_API_CALLS = 0.5 # Задержка между вызовами API (сек)
ALL_USERS_REFRESH_IN_MINUTES = 15 # Время кеширования пользователей (мин)
USERS_PER_PAGE_FROM_API = 1000 # Пользователей на страницу API
DEPARTMENTS_PER_PAGE_FROM_API = 100 # Подразделений на страницу API
GROUPS_PER_PAGE_FROM_API = 1000 # Групп на страницу APISyncDynamicGroupsFromFile/
├── sync_dd_from_file.py # Основной скрипт со всеми функциями
├── .env_ldap # Файл конфигурации (не в git!)
├── requirements_ldap.txt # Зависимости Python
├── sync_deps.log # Файл логов (создается автоматически)
└── README.md # Документация проекта
.env_ldap содержит конфиденциальные данные!
- Добавьте
.env_ldapв.gitignore:
echo ".env_ldap" >> .gitignore- Ограничьте права доступа к файлу:
chmod 600 .env_ldap-
Используйте отдельную служебную учетную запись для LDAP подключения с минимально необходимыми правами.
-
Регулярно меняйте пароли и OAuth токены.
- Создайте отдельное OAuth приложение для этого скрипта
- Предоставьте только необходимые права доступа
- Храните токен в безопасном месте
- Не публикуйте токен в публичных репозиториях
Скрипт ведет подробное логирование всех операций в файл sync_deps.log.
- DEBUG - детальная информация для отладки
- INFO - информационные сообщения о ходе работы
- WARNING - предупреждения о возможных проблемах
- ERROR - ошибки выполнения операций
# Последние 50 строк
tail -n 50 sync_deps.log
# Отслеживание в реальном времени
tail -f sync_deps.log
# Поиск ошибок
grep ERROR sync_deps.log
# Поиск информации о конкретной группе
grep "Название группы" sync_deps.logЛоги автоматически ротируются при достижении размера 10 МБ. Хранится до 20 файлов истории.
Решение:
- Проверьте доступность контроллера домена:
ping dc01.domain.ru
telnet dc01.domain.ru 3268- Проверьте правильность учетных данных
- Проверьте формат имени пользователя:
domain\username - Убедитесь, что учетная запись не заблокирована
Решение:
- Проверьте срок действия токена
- Убедитесь, что токен имеет необходимые права
- Проверьте правильность ORG_ID
Решение:
- Убедитесь, что в
ATTRIB_GROUP_LISTесть атрибутobjectGUID - Проверьте фильтр поиска групп
LDAP_GROUP_SEARCH_FILTER - Убедитесь, что группы являются динамическими (
msExchDynamicDistributionList) - Проверьте логи в
sync_deps.log
Решение:
- Проверьте
LDAP_USER_BASE_DN- правильный ли путь - Проверьте фильтр
LDAP_USER_SEARCH_FILTER - Убедитесь, что у пользователей заполнен атрибут
mail - Проверьте, что пользователи есть в указанном OU
Решение:
- Проверьте формат имени файла:
Группа_рассылки_{displayName}.csv - Убедитесь, что разделитель - точка с запятой (
;) - Проверьте кодировку файла (должна быть UTF-8)
- Проверьте путь в
GROUPS_MEMBERS_FILE_DIR
- Официальная документация API - главная страница документации
- Groups API Reference - справочник по работе с группами
- Users API Reference - справочник по работе с пользователями
- Departments API Reference - справочник по работе с подразделениями
- LDAP3 Documentation - официальная документация библиотеки ldap3
- LDAP3 Tutorial - учебное руководство по ldap3
- Microsoft AD Schema - схема Active Directory
- Dynamic Distribution Groups - документация по динамическим группам рассылки в Exchange
- LDAP Filters - синтаксис LDAP фильтров
Если вы нашли ошибку или у вас есть предложение по улучшению, создайте Issue в репозитории.
Pull Requests приветствуются! Пожалуйста, следуйте стилю кода проекта.
Проект распространяется под лицензией MIT.
Если у вас есть вопросы или нужна помощь, обращайтесь к администратору проекта.
Версия: 1.0
Последнее обновление: 2025-11-16
Автор: alavret