Механизм манипуляции данными MRuby для OpenLDAP

Выступление HAMANO Tsukasa (Open Source Solution Technology Corporation) на конференции LDAPCon 2017, Брюссель, октябрь 2017 года. Оригинал статьи (PDF) и слайды к этому выступлению (на английском языке).

Тезисы

Часто возникают задачи, когда требуется использовать различные источники данных (облачные базы данных или NoSQL) в качестве хранилища данных о пользователях так, чтобы общение с их внешним интерфейсом происходило по протоколу LDAP. В OpenLDAP предусмотрена довольно гибкая возможность разработки дополнительных модулей механизмов манипуляции данными по Вашему желанию, но это требует больших трудозатрат на разработку и временных ресурсов. Для ускорения разработки подобных механизмов в OpenLDAP уже есть модуль back-perl, но разработка на его основе всё ещё малопрактична, поскольку в этом модуле используется только один интерпретатор, а OpenLDAP — многопоточное приложение. В этом документе представлен новый механизм манипуляции данными OpenLDAP back-mruby, позволяющий практически любому легко и просто разрабатывать свои механизмы доступа к данным. Также здесь объясняются причины, почему мы предпочли использовать именно Ruby, а не Perl или Python.

1. Появление MRuby

Существует множество реализаций языка Ruby. Наиболее популярна реализация, которая является официальной, — CRuby. MRuby — это новая реализация, совместимая со спецификацией Ruby 1.9. MRuby имеет небольшой размер по сравнению с обычными реализациями Ruby и отлично работает на небольших устройствах. Также предполагается, что MRuby лучше подходит для встраивания в приложения, чем для выполнения в качестве отдельного приложения.

Matz, создатель CRuby, начал проект MRuby в 2010 году. Этот проект поддерживается METI (Министерство экономики, торговли и промышленности правительства Японии). В настоящий момент MRuby разрабатывается на github.com и распространяется под лицензией MIT.

2. Ограничения back-perl

В 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

3. Новый механизм: back-mruby

В итоге мы начали разработку механизма манипуляции данными MRuby для OpenLDAP. В рамках одного процесса мы можем создать столько виртуальных машин MRuby, сколько потребуется. MRuby решает проблему блокировок. Пространства памяти виртуальных машин полностью независимы и не влияют друг на друга. В механизме back-mruby каждому потоку slapd назначается собственная виртуальная машина MRuby. Таким образом, потоки OpenLDAP могут эффективно выполнять код Ruby в параллельном режиме.

Рисунок 3: Модель процесса back-mruby

4. Использование

4.1. Конфигурация

Конфигурация механизма back-mruby очень проста. Требуется только задать параметр rubyfile в конфигурационном файле slapd.conf как показано ниже. slapd загрузит файл с Ruby-скриптом, в котором определены обработчики запросов LDAP.

В файле slapd.conf указывается:

rubyfile example.rb

4.2. Пример операции BIND

Рассмотрим примеры определения обработчиков запросов 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

4.3. Пример операции SEARCH

Функция обработчика запроса 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

5. Задачи на будущее