понедельник, 16 декабря 2019 г.

Синхронизация списков рассылки Zimbra Collaboration OSE c групами Active Directory

Когда возникла задача синхронизации списков рассылки Zimbra с группами безопасности в AD, не зная с какой стороны подойти к решению задачи, я пошёл в Google, который выдал мне ссылку на статью на Хабре, в которой файл формировался на одном сервере, потом складывался на шару, потом парсился на другом сервере. После некоторых дополнительных поисков я пришёл к единственному скрипту на python, запускающегося по расписанию на сервере с Zimbra.

Перед тем, как описывать работу скрипта нужно описать окружение в котором он будет работать.
Сервер Zimbra использует для формирования Глобального списка адресов специально созданный OU "Простые пользователи", в котором находятся все учётные записи пользователей, сделано это для того, чтобы исключить из списка адресов возможные сервисные и администраторские учётные записи. Там же есть вложенный OU "Группы рассылки" содержащий группы, соответствующие группам рассылки, который мы и будем использовать для синхронизации.

Для синхронизации списка адресов пользователей в настройках GAL и для работы скрипта используется непривилегированный пользователь ldap_sync. Чтобы у него не было проблем с чтением данных, в настройках безопасности у групп и пользователей должно быть разрешение на чтение для пользователей прошедших проверку.

Для работы скрипта кроме самого python необходимо установить с помощью pip пакеты pyldap и pyyml.
# Для Ubuntu
apt install libsasl2-dev python-dev libldap2-dev libssl-dev
apt install python-pip
pip install pyldap pyyml

# Для CentOS
yum install python-devel openldap-devel
yum groupinstall 'development tools'
pip install pyldap pyyml
Все приготовления выполнены, приступаем к созданию скрипта и сопутствующих файлов, содержащих сопоставление списков рассылки с группами в AD и аутентификационные данные пользователя, чтобы не хранить их прямо в скрипте. Начнём с самого скрипта. Он будет проверять соответствие списка членов группы в AD c адресатами в списке рассылки Zimbra. Если пользователь есть в группе AD, но его адреса нет в списке рассылки, то он будет добавлен в список, если пользователя в группе нет, а адрес в списке рассылки есть, то адрес будет удалён из списка.
mkdir /scripts
touch /scripts/zimbra_dl_sync.py
Т.к. python очень чувствителен к разметке, то нужно обратить внимание, чтобы в скрипте не было лишних пробелов и знаков табуляции в начале строки.
#!/usr/bin/python
# coding=UTF-8
lists_file = '/scripts/conf/lists.txt'
# file with config settings
conf_file = '/scripts/conf/cred.yml'
# base SCOPE
scope = 'DC=oldfag,DC=ru'
# search domain
domain = 'oldfag.ru'
# AD server
ldapserver = 'dc01'
# connection port
port = '389'
# users domain on zimbra
emaildomain = 'oldfag.ru'
# AD bind account domain
ldapbinddomain = 'oldfag'
# path to zmprov
pathtozmprov = '/opt/zimbra/bin/zmprov'
#--------------------------------------------------------------------------------------------------
import yaml, ldap, string, os, sys
# import credentials
conf = yaml.load(open(conf_file), Loader=yaml.BaseLoader)
ldapbind = conf['ldap']['user']
ldappassword = conf['ldap']['pass']

# connect to AD
ad=ldap.initialize('ldap://' + ldapserver + '.' + domain + ':' + port)
ad.protocol_version = ldap.VERSION3
ad.set_option(ldap.OPT_REFERRALS, 0)
ad.simple_bind_s(ldapbinddomain + '\\' + ldapbind, ldappassword)

# upload AD groups and Zimbra distribution lists
lists = {}
with open(lists_file, 'r') as lf:
    for line in lf:
    values = line.split(':')
    lists[values[0]] = values[1].rstrip('\n')

for list, group in lists.items():
    zml = os.popen(pathtozmprov + ' gdlm ' + list + '@' + emaildomain + ' | egrep -v "^$" | grep -v members | grep -v "#"')
    member_list = []
    res = []
    res2 = []
    member_list = zml.read().splitlines()
    try:
        res = ad.search_s(scope, ldap.SCOPE_SUBTREE, '(memberOf=CN='+group+',OU=Группы рассылок,OU=Простые пользователи,DC=oldfag,DC=ru)', ['mail'])
        # check if AD group members are in zimbra distribution list
        for rec in res:
            # skip referal records if they exist
            if rec[0]:
                for key, value in rec[1].items():
                    e_mail = str(value).split("'")[1].lower()
                    # if they are not, then add them
                    if e_mail not in member_list:
                        os.system(pathtozmprov + ' adlm %s@%s %s' % (list,emaildomain,e_mail))
                    res2.append(e_mail)
        # check if all distribution list member are in the AD group
        for member in member_list:
        e_mail = member.rstrip('\n')
        # if they are not, then  delete them
        if e_mail not in res2:
            os.system(pathtozmprov +' rdlm %s@%s %s' % (list,emaildomain,e_mail))
    except ldap.LDAPError as error_message:
        print(error_message)
ad.unbind_s()
Теперь что касается файла с сопоставлением групп и списков рассылки
mkdir /scripts/conf
touch /scripts/conf/lists.txt
Он содержит адреса групп рассылки Zimbra и имена групп безопасности в AD в виде
<адрес группы рассылки 1>:<имя группы в AD 1>
<адрес группы рассылки 2>:<имя группы в AD 2>
Так, для списка рассылки all@oldfag.ru, которому соответствует группа Все пользователи, строка в этом файле будет выглядеть вот так
all:Все пользователи
Последний файл, который нужен для работы скрипта - cred.yml, он содержит имя пользователя и пароль, от имени которого скрипт будет обращаться к AD за списком пользователей в группах.
touch /scripts/conf/cred.yml
chmod 600 /scripts/conf/cred.yml
Для файла изменены разрешения, чтобы хотя бы создать видимость безопасности.
ldap:
    user: ldap_sync
    pass: 'userpassword'
Последнее, что осталось - добавить строку в /etc/crontab.
30 * * * * root /bin/python /scripts/zimbra_dl_sync.py
Точный путь к python можно узнать выполнив команду
which python
Для корректного отображения списка рассылки при создании нового письма в настройках GAL на сервере нужно выбрать только внешний источник адресного списка, а для всех локальных списков рассылки убедиться в том, что снята галка "Скрыть в GAL" во вкладке Свойства.

2 комментария:

  1. Добрый день
    Пробовал данный метод но безуспешно(
    Можете конкретнее показать какие значения нужно изменить в скрипте для его коректной работы?
    Также можно ли организовать вывод какого нибудь лога для данного скрипта?
    Зарание спасибо.

    ОтветитьУдалить
    Ответы
    1. Чтобы всё работало нужно в самом скрипте указать ваши значения переменных, которые описаны до строки
      #---------------------------------------
      т.е. пути к конфигурационным файлам, имя контроллера домена, имена домена AD и почтового домена, а так же область для запроса к ldap.

      и фильтр для поиска групп рассылки в строке
      res = ad.search_s(scope, ldap.SCOPE_SUBTREE, '(memberOf=CN='+group+',OU=Группы рассылок,OU=Простые пользователи,DC=oldfag,DC=ru)', ['mail'])

      В файле cred.yml указать логин и пароль пользователя для подключения к AD.

      Для самого базового понимания, что происходит не так, можно попробовать запустить скрипт из консоли и посмотреть вывод.
      Для ведения лога есть очень удобный модуль для python - logger.

      Удалить