Application: "389 Administration & Directory Server v.1.3.7", "Stunnel".
Задача: наладить равноправную взаимную "multi-master" репликацию пользовательской области данных между LDAP-серверами "389-DS", предварительно настроенными в соответствии с отдельной инструкцией на этом сайте.
В документации заявлено, что "RHDS v10" поддерживает до 20 (двадцати) одновременно работающих связей репликации "multi-master". Не уверен, но возможно у его бесплатного аналога "FDS v1.3" есть ограничение до 4 (четырёх) связей репликации "multi-master" - но нам этого вполне хватит, так как планируется одновременная работа трёх LDAP-серверов.
Последовательность настройки связей и репликации как таковой следующая:
1. Настраиваем "потребителей (consumers)" данных.
2. Настраиваем "поставщиков (suppliers)" данных.
3. Инициируем связи, поочерёдно для всех поставщиков.
4. Проверяем успешность и полноту синхронизации данных.
5. Опционально настраиваем связку с сервером, не поддерживающим SSL.
6. Обнаруженные проблемы.
2. Настраиваем "поставщиков (suppliers)" данных.
3. Инициируем связи, поочерёдно для всех поставщиков.
4. Проверяем успешность и полноту синхронизации данных.
5. Опционально настраиваем связку с сервером, не поддерживающим SSL.
6. Обнаруженные проблемы.
Сведения и подходы, используемые в этой инструкции, почерпнуты в официально документации от разработчиков "RHDS v10 (FDS v1.3)" и "RHDS v8 (FDS v1.1)".
Учитывая то, что все LDAP-сервера нашей схемы репликации будут работать в режиме "multi-master", то и настройки "потребителей (consumers)" и "поставщиков (suppliers)" везде будут сделаны по единому принципу, только с различием в именованиях.
Настраиваем "потребителей (consumers)" данных.
Прежде всего, для работы репликации на LDAP-серверах всех участников нужно активировать подсистему журналирования изменений - "Changelog":
$ vi ./changelog-enable.ldif
dn: cn=changelog5,cn=config
objectclass: top
objectclass: extensibleObject
cn: changelog5
nsslapd-changelogdir: /var/lib/dirsrv/slapd-ldap0-example-net/changelogdb
nsslapd-changelogmaxage: 7d
objectclass: top
objectclass: extensibleObject
cn: changelog5
nsslapd-changelogdir: /var/lib/dirsrv/slapd-ldap0-example-net/changelogdb
nsslapd-changelogmaxage: 7d
$ ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -W -f ./changelog-enable.ldif
В GUI "389 Console" это выглядит так:
DS Console -> Configuration -> Replication -> Supplier Settings:
Enable Changelog: on
Changelog database directory: Use default
Max changelog records: unlimited
Max changelog age: 7 days
Save.
Enable Changelog: on
Changelog database directory: Use default
Max changelog records: unlimited
Max changelog age: 7 days
Save.
На каждом LDAP-сервере, работающем в роли "потребителя (consumer)" данных, необходимо создать учётную запись "Supplier Bind DN", которая будет использоваться для аутентификации при подключении к нему "поставщиков (suppliers)" данных:
$ ldapmodify -h 127.0.0.1 -D "cn=Directory Manager" -W
dn: cn=Replication Manager,cn=config
changetype: add
objectClass: inetorgperson
objectClass: person
objectClass: top
cn: Replication Manager
sn: Supplier Bind DN Entry
userPassword: ***
passwordExpirationTime: 20380119031407Z
nsIdleTimeout: 0
^d
changetype: add
objectClass: inetorgperson
objectClass: person
objectClass: top
cn: Replication Manager
sn: Supplier Bind DN Entry
userPassword: ***
passwordExpirationTime: 20380119031407Z
nsIdleTimeout: 0
^d
Обращаю внимание, что учётная запись для репликационных связей должна быть вне зоны действия самой репликации - так как в противном случае мы не сможем задать отличающиеся параметры и пароли для разных серверов. Лучше следовать рекомендациям разработчиков и создать её в контексте настроек "cn=config".
На каждом сервере, работающем в роли "потребителя (consumer)" данных, настраиваем и активируем подсистему приёма данных репликации:
$ vi ./replication-consumer-config.ldif
dn: cn=replica,cn=dc\3Dexample\2Cdc\3Dnet,cn=mapping tree,cn=config
objectclass: top
objectclass: nsDS5Replica
objectclass: extensibleObject
cn: replica
nsds5replicaroot: dc=example,dc=net
nsds5replicaid: 1
nsds5replicatype: 3
nsds5flags: 1
nsds5ReplicaPurgeDelay: 604800
nsds5ReplicaBindDN: cn=Replication Manager,cn=config
objectclass: top
objectclass: nsDS5Replica
objectclass: extensibleObject
cn: replica
nsds5replicaroot: dc=example,dc=net
nsds5replicaid: 1
nsds5replicatype: 3
nsds5flags: 1
nsds5ReplicaPurgeDelay: 604800
nsds5ReplicaBindDN: cn=Replication Manager,cn=config
$ ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -W -f ./replication-consumer-config.ldif
В GUI "389 Console" это выглядит так:
DS Console -> Configuration -> Replication -> userRoot (for "dc=example,dc=net") -> Replica Settings:
Enable Replica: on
Replica Role: Multi Master
Common Settings:
Replica ID: 1 # (для каждого сервера отличающийся, разумеется)
Purge delay: 7 days
Update Settings:
# Локальная учётная запись, через которую разрешено принимать данные репликации.
Current Supplier DNs: cn=Replication Manager,cn=config
Save.
Enable Replica: on
Replica Role: Multi Master
Common Settings:
Replica ID: 1 # (для каждого сервера отличающийся, разумеется)
Purge delay: 7 days
Update Settings:
# Локальная учётная запись, через которую разрешено принимать данные репликации.
Current Supplier DNs: cn=Replication Manager,cn=config
Save.
Настраиваем "поставщиков (suppliers)" данных.
На каждом сервере, работающем в роли "поставщика (supplier)" данных, активируем и настраиваем подсистему отправки данных репликации - этих записей может быть несколько, по количеству целевых LDAP-серверов, куда будет осуществляться отправка данных:
$ vi ./replication-supplier-config.ldif
dn: cn=ldap1.example.net,cn=replica,cn=dc\3Dexample\2Cdc\3Dnet,cn=mapping tree,cn=config
objectclass: top
objectclass: nsDS5ReplicationAgreement
cn: ldap1.example.net
nsds5replicahost: ldap1.example.net
nsds5replicaport: 636
nsds5ReplicaTransportInfo: SSL
nsds5ReplicaBindDN: cn=Replication Manager,cn=config
nsDS5ReplicaBindCredentials: ***
nsds5replicabindmethod: SIMPLE
nsds5replicaroot: dc=example,dc=net
description: Sending replication data from ldap0.example.net to ldap1.example.net
objectclass: top
objectclass: nsDS5ReplicationAgreement
cn: ldap1.example.net
nsds5replicahost: ldap1.example.net
nsds5replicaport: 636
nsds5ReplicaTransportInfo: SSL
nsds5ReplicaBindDN: cn=Replication Manager,cn=config
nsDS5ReplicaBindCredentials: ***
nsds5replicabindmethod: SIMPLE
nsds5replicaroot: dc=example,dc=net
description: Sending replication data from ldap0.example.net to ldap1.example.net
$ ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -W -f ./replication-supplier-config.ldif
В GUI "389 Console" это выглядит так:
DS Console -> Configuration -> Replication -> userRoot (for "dc=example,dc=net") -> Context Menu -> New Replication Agreement:
Supplier:
Name: ldap0.example.net
Description: Sending replication data from ldap0.example.net to ldap1.example.net
Consumer: ldap1.example.net:636
Connection: Use SSL
Authentication mechanism:
Simple Bind (BindDN/Password):
Bind as: cn=Replication Manager,cn=config
Password: ***
Subtree: dc=example,dc=net
Select replication criteria:
Enable Fractional Replications: off # (реплицируем всё)
Provide schedule information: Always keep directories in sync
Initialaze Consumer: Do not initialise consumer
Save.
Supplier:
Name: ldap0.example.net
Description: Sending replication data from ldap0.example.net to ldap1.example.net
Consumer: ldap1.example.net:636
Connection: Use SSL
Authentication mechanism:
Simple Bind (BindDN/Password):
Bind as: cn=Replication Manager,cn=config
Password: ***
Subtree: dc=example,dc=net
Select replication criteria:
Enable Fractional Replications: off # (реплицируем всё)
Provide schedule information: Always keep directories in sync
Initialaze Consumer: Do not initialise consumer
Save.
Иногда полезно настроить блокировку передачи некоторых атрибутов - если получатель данных не нуждается во всём их множестве. В дальнейшем управление исключениями возможно как через GUI "389 Console", так и через атрибут в записи параметров репликации - например: "nsDS5ReplicatedAttributeList: (objectclass=*) $ EXCLUDE atribute1 atribute2".
Инициируем связи, поочерёдно для всех поставщиков.
После того, как "поставщика (supplier)" данных настроен, запускаем процесс репликации:
$ vi ./replication-supplier-start.ldif
dn: cn=ldap1.example.net,cn=replica,cn=dc\3Dexample\2Cdc\3Dnet,cn=mapping tree,cn=config
changetype: modify
replace: nsDS5BeginReplicaRefresh
nsDS5BeginReplicaRefresh: start
changetype: modify
replace: nsDS5BeginReplicaRefresh
nsDS5BeginReplicaRefresh: start
$ ldapadd -x -h 127.0.0.1 -D "cn=Directory Manager" -W -f ./replication-supplier-start.ldif
В GUI "389 Console" это выглядит так:
DS Console -> Configuration -> Replication -> userRoot (for "dc=example,dc=net") -> "ldap1.example.net" -> Context Menu -> Initialaze Consumer.
После запуска процедур в корне реплицируемых структур плагином "Replication Plugin" автоматически будут созданы служебные записи (по количеству связей) вроде "cn=repl keep alive 1,dc=example,dc=net" - очевидно, изменять вручную их параметры не нужно.
Проверяем успешность и полноту синхронизации данных.
Возможно, после репликации захочется убедится, что содержимое дерева данных разных серверов идентично - такое желание часто возникает, когда есть подозрения на "магию" в работе клиентских приложений.
Выгружаем все данные с исходного сервера:
$ LDAPTLS_REQCERT=allow ldapsearch -x -LLL -H ldaps://ldap0.example.net:636 -D "uid=app0,ou=Accounts,ou=Services,dc=example,dc=net" -W -b dc=example,dc=net > ./ldap0.ldif
По возможности одновременно с первым выгружаем все данные со второго сервера:
$ LDAPTLS_REQCERT=allow ldapsearch -x -LLL -H ldaps://ldap1.example.net:636 -D "uid=app0,ou=Accounts,ou=Services,dc=example,dc=net" -W -b dc=example,dc=net > ./ldap1.ldif
Если версии серверов "389-DS" одинаковы, или близки, то сравнение полученных в идентичных условиях LDIF-файлов покажет их минимальное различие или вообще отсутствие таковых:
$ diff -urBN ./ldap0.ldif ./ldap1.ldif ./out.diff
Если версии серверов существенно отличаются, то сортировка атрибутов в выдаче может смутить. Например, от "389-DS v1.3.2.16":
dn: dc=example,dc=net
objectClass: top
objectClass: domain
dc: example
....
objectClass: top
objectClass: domain
dc: example
....
...а от "389-DS v1.3.7.10":
dn: dc=example,dc=net
dc: example
objectClass: top
objectClass: domain
....
dc: example
objectClass: top
objectClass: domain
....
Очевидно, что с точки зрения протокола LDAP набор атрибутов идентичен, но порядок отличается, и такая перестановка чуть-ли не во всех записях. Простейшее сравнение утилитой "diff" покажет полный бардак между LDIF-файлами.
Для обхода этой проблемы можно применить специализированные утилиты сравнения LDIF-файлов. В комплекте с "SUN/Oracle OpenDS" идёт java-утилита "ldif-diff", но её сложно найти отдельно. Когда-то была популярна "ldapdiff", ныне для истории сохранённая в "launchpad.net", но она старовата и я её не пробовал. Есть ещё perl-скрипт "ldifdiff.pl", но им мне не приходилось пользоваться. Написанная на "Go" утилита "ldifdiff" свежее всех, доступнее и вполне справляется с задачей сравнения LDIF-файлов, опираясь на логику описания, а не последовательности атрибутов:
$ ldifdiff ./ldap0.ldif ./ldap1.ldif -i atribute1 -i atribute2 > ./out.diff
Опционально настраиваем связку с сервером, не поддерживающим SSL.
Наладка репликации между инстансами "389-DS" несложна, но имеется подводный камень на этапе обеспечения защиты от прослушивания посторонними передаваемых данных. Правильнее всего воспользоваться встроенной функциональностью шифрования соединений посредством постоянного "SSL/TLS" или вызываемого "StartTLS", но иногда вмешаться в конфигурацию какого-то из участников схемы обмена данными не представляется возможным.
Иного простого способа защитить передаваемые данные, кроме как провести трафик репликации в отдельно создаваемых шифрованных туннелях, не нашлось. Не принципиально, но из "ipsec" и "stunnel" в данном случае был выбран последний.
# aptitude install stunnel4
Включаем автозапуск сервиса:
# vi /etc/default/stunnel4
....
ENABLED=1
....
ENABLED=1
....
Дополняем конфигурацию "по умолчанию" парой дополнительных параметров:
# vi /etc/stunnel/stunnel.conf
output = /var/log/stunnel4/stunnel.log
include = /etc/stunnel/conf.d
include = /etc/stunnel/conf.d
Создаём выделенную директорию для конфигураций SSL-туннелей:
# mkdir -p /etc/stunnel/conf.d
Формируем описания SSL-туннеля:
# vi /etc/stunnel/conf.d/ldap1.example.net.conf
[ldap1.example.net]
client = yes
accept = 127.0.0.1:3891
connect = ldap1.example.net:636
verifyChain = no
client = yes
accept = 127.0.0.1:3891
connect = ldap1.example.net:636
verifyChain = no
Перезапускаем сервис:
# systemctl restart stunnel4
# systemctl status stunnel4
# systemctl status stunnel4
Проверяем, запущено ли прослушивание локального TCP-порта туннеля:
# netstat -apn | grep -i "^tcp" | grep -i "stunnel"
tcp ... 127.0.0.1:3891 0.0.0.0:* LISTEN .../stunnel4
Пробуем запросить удалённый LDAP-сервер через SSL-туннель, обращаясь к нему по незащищённому, казалось бы, соединению:
$ ldapsearch -x -v -H ldap://127.0.0.1:3891 -D "uid=app0,ou=Services,dc=example,dc=net" -W -b uid=userOne,ou=People,dc=example,dc=net
После успешной проверки связи в настройках репликции заменяем адрес удалённого LDAP-сервера, которому отправляются данные репликации, на "локальную петлю" и выделенный туннелю TCP-порт:
$ ldapmodify -h 127.0.0.1 -D "cn=Directory Manager" -W
dn: cn=ldap1.example.net,cn=replica,cn=dc\3Dexample\2Cdc\3Dnet,cn=mapping tree,cn=config
changetype: modify
replace: nsds5replicahost
nsds5replicahost: 127.0.0.1
dn: cn=ldap1.example.net,cn=replica,cn=dc\3Dexample\2Cdc\3Dnet,cn=mapping tree,cn=config
changetype: modify
replace: nsds5replicaport
nsds5replicaport: 3891
^d
changetype: modify
replace: nsds5replicahost
nsds5replicahost: 127.0.0.1
dn: cn=ldap1.example.net,cn=replica,cn=dc\3Dexample\2Cdc\3Dnet,cn=mapping tree,cn=config
changetype: modify
replace: nsds5replicaport
nsds5replicaport: 3891
^d
Я заметил, что без перезапуска LDAP-инстанса изменения параметров репликации не применяются в полном объёме (TCP-порт изменяется, а вот адрес подключения остаётся прежним):
# stop-dirsrv ldap0-example-net && start-dirsrv ldap0-example-net
Обнаруженные проблемы: атрибут "userPassword".
При наладке репликации от "389-DS v1.3.2.16" к "389-DS v1.3.7.10" выяснилось, что первый не отправляет добавления атрибута "userPassword" - просто его игнорирует со следующим сообщением в журнале событий:
....
... NSMMReplicationPlugin - agmt="cn=ldap0.example.net" (127:3891): Consumer failed to replay change (uniqueid ..., CSN ...): Protocol error (2). Will retry later.
....
... NSMMReplicationPlugin - agmt="cn=ldap0.example.net" (127:3891): Consumer failed to replay change (uniqueid ..., CSN ...): Protocol error (2). Will retry later.
....
То есть, атрибут "userPassword" реплицируются, но не всегда, а по следующему принципу:
Не реплицируются:
changetype: modify
add: userPassword
Реплицируются:
changetype: modify
replace: userPassword
changetype: modify
add: userPassword
Реплицируются:
changetype: modify
replace: userPassword
Проблема известна разработчикам, зафиксирована в трекере ошибок и уже исправлена - между инстансами "389-DS v1.3.7.10" не наблюдается.
Обнаруженные проблемы: атрибут "homeDirectory".
Аналогично вышеприведённому описанию проблемы с атрибутом "userPassword" при наладке репликации от "389-DS v1.3.2.16" к "389-DS v1.3.7.10" выяснилось, что первый не отправляет добавления атрибута "homeDirectory" - просто его игнорирует. При этом на принимающей стороне в журнале событий фиксируется предупреждение о неконсистентности набора данных:
....
... conn=55491 op=196 RESULT err=0 tag=103 nentries=0 etime=0.0007638565 csn=... - missing attribute "homeDirectory" required by object class "posixAccount"
....
... conn=55491 op=196 RESULT err=0 tag=103 nentries=0 etime=0.0007638565 csn=... - missing attribute "homeDirectory" required by object class "posixAccount"
....
Ео есть, атрибут "homeDirectory" реплицируются, но не всегда, а по следующему принципу:
Не реплицируются:
changetype: modify
add: homeDirectory
Реплицируются:
changetype: modify
replace: homeDirectory
changetype: modify
add: homeDirectory
Реплицируются:
changetype: modify
replace: homeDirectory
Обнаруженные проблемы: небольшое расхождение между журналами репликации.
При наладке репликации от "389-DS v1.3.2.16" к "389-DS v1.3.7.10" неоднократно случались расхождения на одну запись между "changelog"-ами, что сопровождалось повторяющимся при каждой репликации сообщением в журнале событий:
....
... agmt="cn=ldap0.example.net" (127:3891) - Can't locate CSN ... in the changelog (DB rc=-30988). The consumer may need to be reinitialized.
....
... agmt="cn=ldap0.example.net" (127:3891) - Can't locate CSN ... in the changelog (DB rc=-30988). The consumer may need to be reinitialized.
....
В документации разработчиков уверяют, что проблема невелика и может игнорироваться, если такое расхождение одно.