Выступление HAMANO Tsukasa (Open Source Solution Technology Corporation) на конференции LDAPCon 2017, Брюссель, октябрь 2017 года. Оригинал статьи (PDF) и слайды к этому выступлению (на английском языке).
Часто возникают задачи, когда требуется использовать различные источники данных (облачные базы данных или NoSQL) в качестве хранилища данных о пользователях так, чтобы общение с их внешним интерфейсом происходило по протоколу LDAP. В OpenLDAP предусмотрена довольно гибкая возможность разработки дополнительных модулей механизмов манипуляции данными по Вашему желанию, но это требует больших трудозатрат на разработку и временных ресурсов. Для ускорения разработки подобных механизмов в OpenLDAP уже есть модуль back-perl, но разработка на его основе всё ещё малопрактична, поскольку в этом модуле используется только один интерпретатор, а OpenLDAP — многопоточное приложение. В этом документе представлен новый механизм манипуляции данными OpenLDAP back-mruby, позволяющий практически любому легко и просто разрабатывать свои механизмы доступа к данным. Также здесь объясняются причины, почему мы предпочли использовать именно Ruby, а не Perl или Python.
Существует множество реализаций языка Ruby. Наиболее популярна реализация, которая является официальной, — CRuby. MRuby — это новая реализация, совместимая со спецификацией Ruby 1.9. MRuby имеет небольшой размер по сравнению с обычными реализациями Ruby и отлично работает на небольших устройствах. Также предполагается, что MRuby лучше подходит для встраивания в приложения, чем для выполнения в качестве отдельного приложения.
Matz, создатель CRuby, начал проект MRuby в 2010 году. Этот проект поддерживается METI (Министерство экономики, торговли и промышленности правительства Японии). В настоящий момент MRuby разрабатывается на github.com и распространяется под лицензией MIT.
В OpenLDAP уже существует механизм манипуляции данными Perl (back-perl). Он позволяет разрабатывать гибкие механизмы доступа к данным для OpenLDAP путём реализации обработчиков запросов LDAP на языке Perl. back-perl больше подходит для интеграции с высокоуровневыми хранилищами данных, нежели с низкоуровневыми базами данных, такими как BDB и LMDB. Он может использоваться для добавления интерфейса протокола LDAP к различным моделям хранилищ данных, таким как реляционные СУБД, NoSQL или облачные сервисы хранения данных.
Однако, у back-perl имеется ограничение, заключающееся в том, что у процесса slapd есть только один интерпретатор Perl. Теоретически в рамках одного процесса может быть создано несколько интерпретаторов Perl, но API для работы с интерпретатором не является потокобезопасным, поэтому такие интерпретаторы не могут выполняться параллельно. Следовательно, применение back-perl не является практичным, поскольку в нём в единицу времени выполняется только один обработчик Perl, хотя процесс slapd является многопоточным.
Рисунок 1: Модель процесса back-perl
Сначала мы пытались заменить интерпретатор Perl механизма back-perl на Python или CRuby, но у них имеется такая же проблема. Python позволяет создавать несколько подинтерпретаторов в одном процессе, но им требуется получать GIL (глобальную блокировку интерпретатора), когда объекты python находятся в работе.
Рисунок 2: Модель процесса back-python
В итоге мы начали разработку механизма манипуляции данными MRuby для OpenLDAP. В рамках одного процесса мы можем создать столько виртуальных машин MRuby, сколько потребуется. MRuby решает проблему блокировок. Пространства памяти виртуальных машин полностью независимы и не влияют друг на друга. В механизме back-mruby каждому потоку slapd назначается собственная виртуальная машина MRuby. Таким образом, потоки OpenLDAP могут эффективно выполнять код Ruby в параллельном режиме.
Рисунок 3: Модель процесса back-mruby
Конфигурация механизма back-mruby очень проста. Требуется только задать параметр rubyfile
в конфигурационном файле slapd.conf как показано ниже. slapd загрузит файл с Ruby-скриптом, в котором определены обработчики запросов LDAP.
В файле slapd.conf указывается:
rubyfile example.rb
Рассмотрим примеры определения обработчиков запросов LDAP в Ruby-скрипте. В первом из них показана реализация обработчика запроса BIND, в котором успешная аутентификация происходит с вероятностью 1/2.
def bind(op)
if rand(2) == 0
LDAP_SUCCESS
else
LDAP_INVALID_CREDENTIALS
end
end
В следующем примере показана реализация обработчика запроса BIND со статическими записями пользователей. Данные LDAP-запроса передаются в качестве аргумента op
, в который просто отображается структура op
процесса slapd. К примеру, нормализованное DN будет доступно как op['ndn']
.
def bind(op)
# User DN and password mappings
users = {
'uid=alice,dc=example,dc=com' => 'secret1',
'uid=bob,dc=example,dc=com' => 'secret2',
}
ndn = op['ndn']
cred = users[ndn]
if cred == op['req']['cred']
LDAP_SUCCESS
else
LDAP_INVALID_CREDENTIALS
end
end
Функция обработчика запроса Search должна возвращать список записей в формате LDIF или код ответа. В следующем примере показано, как реализуется обработчик запроса SEARCH, обслуживающий запросы с диапазонами base и one.
def search(op)
scope = op["req"]["scope"]
if scope == LDAP_SCOPE_BASE
search_base(op)
elsif scope == LDAP_SCOPE_ONE
search_one(op)
else
LDAP_NO_SUCH_OBJECT
end
end
# Returns base entry.
def search_base(op)
ldif = <<END_OF_LDIF
dn: dc=example,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
dc: example
o: example
END_OF_LDIF
[ ldif ]
end
# Returns two user entries.
def search_one(op)
entries = []
ldif = <<END_OF_LDIF
dn: uid=%s,dc=example,dc=com
objectClass: top
objectClass: account
uid: %s
END_OF_LDIF
entries << ldif % (["alice"] * 2)
entries << ldif % (["bob"] * 2)
entries
end