Как уже было сказано ранее, объекты POSIX-групп в каталоге — это записи, построенные на объектном классе posixGroup
, определённом в RFC 2307. Этот RFC был принят в далёком 1998 году с целью представления в виде записей каталога объектов сетевой информационной системы (NIS) UNIX. Как и в настоящих POSIX-группах NIS, у таких записей имеется числовой идентификатор группы (в обязательном атрибуте gidNumber
), а в качестве членов в атрибутах memberUid
указываются текстовые имена учётных записей пользователей UNIX:
dn: cn=Cool Users,ou=Groups,dc=mycompany,dc=ru objectClass: posixGroup cn: Cool Users gidNumber: 10000 memberUid: ivanov memberUid: petrov memberUid: sidorov memberUid: antonova
Как наследие NIS, объекты POSIX-групп повсеместно использовались во времена расцвета samba3, и до сих пор многие библиотеки систем NSS/PAM, взаимодействующие с каталогами LDAP, по умолчанию работают именно с такими группами.
С точки зрения каталога, объекты POSIX-групп нельзя рассматривать как записи-группы, так как они не дают возможности напрямую сопоставлять значения атрибутов членства с записями-членами группы в каталоге. Они представляют собой записи-списки, в которых перечислены текстовые имена учётных записей пользователей UNIX, а сопоставление членства в группе с реальной записью-членом в каталоге полностью возлагается на клиентское приложение. Именно поэтому с POSIX-группами не работают ориентированные на группы наложения OpenLDAP refint и memberof, их также нельзя использовать в групповых условиях ACL OpenLDAP. Тем не менее, если в каталоге всё же используются POSIX-группы, с помощью механизма наборов в ACL OpenLDAP их также можно задействовать для организации разграничения доступа к содержимому каталога.
Попробуем воссоздать ситуацию, которую мы рассматривали для "обычных" групп каталога: имеются две группы, — OpenLDAP Admins и OpenLDAP Operators. Членам первой группы будет разрешено менять пароли учётных записей пользователей в каталоге, а членам второй — изменять любые данные в каталоге (за исключением паролей пользователей). Только теперь наши записи будут POSIX-группами:
# POSIX-группа "Администраторы каталога"
dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru
objectClass: posixGroup
cn: OpenLDAP Admins
gidNumber: 10001
memberUid: ivanov
# POSIX-группа "Операторы каталога"
dn: cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
objectClass: posixGroup
cn: OpenLDAP Operators
gidNumber: 10002
memberUid: antonova
Начинать, как всегда, будем с исходного положения. Добавим наши группы в каталог:
$ ldapadd -x -D cn=manager,ou=System,dc=mycompany,dc=ru -W -f ./002-add_groups.ldif Enter LDAP Password: adding new entry "cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru" adding new entry "cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru"
Теперь дополним имеющиеся в записи базы данных правила ACL (атрибуты olcAccess
) условиями by
, отражающими требования нашей задачи:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: to attrs=userPassword
by self write
by set="[cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru]/memberUid & user/uid" write
by anonymous auth
by * none
olcAccess: to *
by self write
by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/memberUid & user/uid" write
by * read
Интересующие нас условия задаются в строках 6 и 11. По структуре они аналогичны, поэтому подробнее рассмотрим работу такого условия в первом из ACL.
При попытке пользователя обратиться к атрибуту userPassword
какой-либо записи сработает первый ACL (строки 4-8). Если пользователь авторизован от имени той же записи, к которой он пытается обратиться (то есть пользователь обращается к своей собственной записи каталога), то сработает условие by
в строке 5, и пользователь получит неограниченный доступ к атрибуту userPassword
собственной записи. Пока всё просто и очевидно. Если же пользователь, уже прошедший аутентификацию (то есть ключевое слово user
условия-набора окажется непустым), обращается к атрибуту userPassword
, произойдёт проверка условия by
в строке 6. Заключённое в кавычки условие-набор в этой строке состоит из двух поднаборов, соединённых знаком пересечения этих поднаборов (&
). Вместо ключевого слова user
во втором поднаборе будет подставлена запись каталога, соответствующая учётной записи пользователя, прошедшего аутентификацию, и из этой записи каталога будут извлечены значения атрибутов uid
, которые и составят второй поднабор. DN записи, фигурирующей в первом поднаборе, указано явно в квадратных скобках (cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru), из этой записи будут извлечены значения атрибутов memberUid
, они составят первый поднабор. Если при пересечении двух поднаборов будут найдены совпадающие значения (то есть одно из значений атрибута uid
учётной записи пользователя совпадёт с одним из значений атрибута memberUid
записи POSIX-группы), то условие будет считаться совпавшим, и подключившемуся пользователю будут предоставлены права на изменения атрибута userPassword
. Если же такого пересечения найдено не будет, то условие by
не совпадёт, и проверка перейдёт к условию by
в строке 7.
Подробно работа такого условия-набора рассмотрена в разделе 8.5.2 руководства администратора OpenLDAP 2.4. Применительно к нашему примеру, пользователь uid=ivanov,ou=People,dc=mycompany,dc=ru получит доступ на изменение атрибута userPassword
, поскольку значение атрибута uid
его записи перечислено в значениях атрибута memberUid
записи POSIX-группы cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru. Остальные же пользователи не получат такого доступа, поскольку значения их атрибутов uid
не перечислены в значениях атрибута memberUid
этой POSIX-группы.
Попробуем убедиться в этом. Применим изменения ACL и проверим полученную в результате запись базы данных:
$ ldapmodify -x -D cn=config -W -f ./101-modify_acl_1.ldif Enter LDAP Password: modifying entry "olcDatabase={1}mdb,cn=config" $ ldapsearch -xLLL -o ldif-wrap=no -D cn=config -W -b olcDatabase={1}mdb,cn=config olcAccess Enter LDAP Password: dn: olcDatabase={1}mdb,cn=config olcAccess: {0}to attrs=userPassword by self write by set="[cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru]/memberUid & user/uid" write by anonymous auth by * none olcAccess: {1}to * by self write by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/memberUid & user/uid" write by * read
Сначала проверим работу первого из полученных ACL утилитой slapacl
. Попробуем протестировать доступ к атрибуту userPassword
записи uid=petrov,ou=People,dc=mycompany,dc=ru от имени пользователей ivanov и antonova:
# slapacl -D uid=ivanov,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru userPassword authcDN: "uid=ivanov,ou=people,dc=mycompany,dc=ru" userPassword: write(=wrscxd) # slapacl -D uid=antonova,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru userPassword authcDN: "uid=antonova,ou=people,dc=mycompany,dc=ru" userPassword: none(=0)
Наши тесты, как и ожидалось, показывают, что у пользователя ivanov достаточно привилегий для смены пароля пользователя petrov, а у пользователя antonova нет прав даже на чтение этого пароля.
Теперь проведём "натурные" испытания. Попробуем с помощью утилиты ldappasswd
изменить пароль пользователя petrov от имени пользователя antonova:
$ ldappasswd -x -v -D uid=antonova,ou=People,dc=mycompany,dc=ru -w antonovaPassword -s pertovNewPassword uid=petrov,ou=People,dc=mycompany,dc=ru ldap_initialize( <DEFAULT> ) Result: Insufficient access (50)
Попытка изменения пароля не удалась. Теперь попробуем сделать то же самое от имени пользователя ivanov:
$ ldappasswd -x -v -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -s pertovNewPassword uid=petrov,ou=People,dc=mycompany,dc=ru ldap_initialize( <DEFAULT> ) Result: Success (0)
На этот раз утилита ldappasswd
отработала успешно. На всякий случай проверим, что у пользователя petrov новый пароль, выполнив простейший поиск от его имени:
$ ldapsearch -xLLL -b dc=mycompany,dc=ru -D uid=petrov,ou=People,dc=mycompany,dc=ru -w pertovNewPassword '(objectClass=posixGroup)' 1.1 dn: cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru dn: cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
Итак, наш первый ACL работает как ожидалось. Убедимся в работоспособности второго ACL. Начнём с тестов утилитой slapacl
, проверим доступ к атрибуту cn
записи uid=petrov,ou=People,dc=mycompany,dc=ru от имени пользователей ivanov и antonova:
# slapacl -D uid=ivanov,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru cn authcDN: "uid=ivanov,ou=people,dc=mycompany,dc=ru" cn: read(=rscxd) # slapacl -D uid=antonova,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru cn authcDN: "uid=antonova,ou=people,dc=mycompany,dc=ru" cn: write(=wrscxd)
Пользователь ivanov не перечислен в качестве члена POSIX-группы cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru, поэтому он получает доступ на чтение атрибута cn
(как попадающий под условие by * read
). Пользователь antonova перечислена в качестве члена этой POSIX-группы, поэтому она имеет доступ на изменение атрибута cn
. Всё в порядке.
Выполним также реальную проверку, для чего попробуем изменить значение атрибута cn
записи uid=petrov,ou=People,dc=mycompany,dc=ru:
dn: uid=petrov,ou=People,dc=mycompany,dc=ru
changetype: modify
replace: cn
cn: Petr Petrovich Petrov
Сначала попробуем применить эти изменения от имени пользователя ivanov:
$ ldapmodify -x -D uid=ivanov,ou=People,dc=mycompany,dc=ru -w ivanovPassword -f ./003-modify_petrov.ldif modifying entry "uid=petrov,ou=People,dc=mycompany,dc=ru" ldap_modify: Insufficient access (50)
Попытка неудачная — у пользователя ivanov недостаточно прав для осуществления модификации. Попробуем выполнить то же самое от имени пользователя antonova:
$ ldapmodify -x -D uid=antonova,ou=People,dc=mycompany,dc=ru -w antonovaPassword -f ./003-modify_petrov.ldif modifying entry "uid=petrov,ou=People,dc=mycompany,dc=ru"
Изменения успешно применились. Убедимся в этом:
$ ldapsearch -xLLL -b uid=petrov,ou=People,dc=mycompany,dc=ru -D uid=petrov,ou=People,dc=mycompany,dc=ru -w pertovNewPassword cn dn: uid=petrov,ou=People,dc=mycompany,dc=ru cn: Petr Petrovich Petrov
Итак, второй наш ACL также работает. Задача решена. Но что если, по аналогии с ситуацией с "обычными" группами, мы не хотим ограничивать администраторов каталога полномочиями только изменять пароли, но хотим дать им права изменять любые атрибуты любых записей наравне с операторами каталога? По понятным причинам сценария вложенных групп и их рекурсивного обхода в случае POSIX-групп воссоздать не получится. Тем не менее, пути решения такой задачи есть. Первый, самый простой, очевидный и не требующий дополнительных перенастроек каталога — аккуратно вести членов группы OpenLDAP Operators, добавляя туда (удаляя оттуда) тех же членов, что и в группу OpenLDAP Admins:
dn: uid=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
changetype: modify
add: memberUid
memberUid: ivanov
Но не все администраторы склонны полагаться на свою аккуратность, а тем более на аккуратность коллег по цеху. Поэтому вторым, также вполне очевидным решением, будет добавление ещё одного условия by
во второй ACL:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
delete: olcAccess
olcAccess: {1}
-
add: olcAccess
olcAccess: {1}to *
by self write
by set="[cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru]/memberUid & user/uid" write
by set="[cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru]/memberUid & user/uid" write
by * read
Но мы попробуем объединить два наших набора в один, заодно продемонстрировав различные варианты операторов в наборах:
dn: olcDatabase={1}mdb,cn=config
changetype: modify
delete: olcAccess
olcAccess: {1}
-
add: olcAccess
olcAccess: {1}to *
by self write
by set="([cn=OpenLDAP ] + ([Admins] | [Operators]) + [,ou=Groups,dc=mycompany,dc=ru])/memberUid & user/uid" write
by * read
Рассмотрим подробно набор в строке 9. Он состоит из двух поднаборов, соединённых оператором пересечения &
. Второй поднабор (после оператора &
) остался прежним: он будет составлен из значений атрибута uid
записи каталога, соответствующей учётной записи того пользователя, который прошёл аутентификацию при подключении к каталогу. Первый же поднабор (перед оператором &
) выглядит устрашающе, тем не менее, в нём довольно просто разобраться, если делать это по частям.
Перед началом разбора обратим внимание на строковые значения, заключённые в квадратные скобки. Если они не представляют собой валидное DN (как во всех наших предыдущих примерах с условиями-наборами), то воспринимаются механизмом разборки ACL OpenLDAP не как указатель на запись каталога, а как обычные строковые литералы. Итак, в данном случае cn=OpenLDAP , Admins, Operators и ,ou=Groups,dc=mycompany,dc=ru — это просто строки, из которых мы будем составлять условие поднабора.
Далее нам следует познакомиться с двумя ранее не рассмотренными операторами механизма наборов ACL OpenLDAP. Оператор объединения |
предназначен для формирования единого набора из двух смежных с ним поднаборов. Оператор конкатенации +
предназначен для "склеивания" значений двух смежных поднаборов, причём в итоговом наборе все значения первого поднабора будут "склеены" со всеми значениями второго поднабора, то есть количество значений в итоговом наборе будет равно произведению количества значений первого поднабора на количество значений второго поднабора. Как и арифметические операторы, операторы механизма наборов ACL OpenLDAP имеют приоритет выполнения: у &
, |
и +
он одинаков, а вот оператор "действия" /
имеет более высокий приоритет. По аналогии с арифметикой, для изменения порядка применения операторов используются круглые скобки.
Вооружившись этими нехитрыми знаниями, можно приступать к разбору первого поднабора из нашего примера. Самое приоритетное действие у нас во внутренних круглых скобках: ([Admins] | [Operators]). Здесь создаётся набор, представляющий собой объединение двух строковых значений:
Admins Operators
Далее полученный набор с помощью первого оператора конкатенации +
"склеивается" с набором, состоящим из одного строкового элемента cn=OpenLDAP . В итоге получаем такой набор:
cn=OpenLDAP Admins cn=OpenLDAP Operators
Далее с помощью второго оператора конкатенации +
этот набор "склеивается" с ещё одним набором, также состоящим из одного строкового элемента ,ou=Groups,dc=mycompany,dc=ru. В итоге получаем такой набор:
cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru cn=OpenLDAP Operators,ou=Groups,dc=mycompany,dc=ru
На этом "высокоприоритетные" действия во внешних фигурных скобках заканчиваются. В итоге мы получили набор, состоящий из двух валидных DN, которые можно использовать для указания на две записи каталога, в данном случае на наши записи POSIX-групп. Далее с помощью оператора действия /
из этих записей будут извлечены значения атрибутов memberUid
, которые и составят итоговый первый поднабор нашего условия-набора:
ivanov antonova
Дальнейшая работа условия-набора зависит от пользователя, выполняющего операцию в каталоге. Если в записи каталога, соответствующей учётке этого пользователя, значением атрибута uid
будет ivanov или antonova, то пересечение (&
) с первым поднабором будет найдено, и условие сработает.
От теоретических рассуждений вернёмся к нашему каталогу. Применим изменения и проверим, что получилось:
$ ldapmodify -x -D cn=config -W -f ./103-modify_acl_3.ldif Enter LDAP Password: modifying entry "olcDatabase={1}mdb,cn=config" $ ldapsearch -xLLL -o ldif-wrap=no -D cn=config -W -b olcDatabase={1}mdb,cn=config olcAccess Enter LDAP Password: dn: olcDatabase={1}mdb,cn=config olcAccess: {0}to attrs=userPassword by self write by set="[cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru]/memberUid & user/uid" write by anonymous auth by * none olcAccess: {1}to * by self write by set="([cn=OpenLDAP ] + ([Admins] | [Operators]) + [,ou=Groups,dc=mycompany,dc=ru])/memberUid & user/uid" write by * read
Протестируем работу полученного ACL утилитой slapacl
. Проверим доступ к атрибуту cn
записи uid=petrov,ou=People,dc=mycompany,dc=ru от имени пользователей ivanov, antonova и sidorov:
# slapacl -D uid=ivanov,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru cn authcDN: "uid=ivanov,ou=people,dc=mycompany,dc=ru" cn: write(=wrscxd) # slapacl -D uid=antonova,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru cn authcDN: "uid=antonova,ou=people,dc=mycompany,dc=ru" cn: write(=wrscxd) # slapacl -D uid=sidorov,ou=People,dc=mycompany,dc=ru -b uid=petrov,ou=People,dc=mycompany,dc=ru cn authcDN: "uid=sidorov,ou=people,dc=mycompany,dc=ru" cn: read(=rscxd)
Первые два пользователя, являясь членами POSIX-групп OpenLDAP Admins и OpenLDAP Operators, получили доступ на изменение атрибута cn
, а пользователь sidorov, не являющийся членом этих групп, получил доступ только на чтение атрибута (согласно условию by * read
). Наш ACL работает.
В ходе этого урока мы познакомились с записями каталога, представляющими собой объекты POSIX-групп, их предназначением, а также механизмом, позволяющим использовать эти объекты для разграничения доступа к самому каталогу OpenLDAP. Более подробно мы остановились на разных вариантах построения условий-наборов в ACL OpenLDAP, разобрав работу различных операторов механизма наборов. Итоговые настройки каталога dc=mycompany,dc=ru выглядят так (для удобства чтения ACL мы разбили их вывод на несколько строк):
$ ldapsearch -xLLL -o ldif-wrap=no -D cn=config -W -b olcDatabase={1}mdb,cn=config Enter LDAP Password: dn: olcDatabase={1}mdb,cn=config objectClass: olcMdbConfig olcDatabase: {1}mdb olcDbDirectory: /var/lib/ldap/dc=mycompany,dc=ru olcSuffix: dc=mycompany,dc=ru olcAccess: {0}to attrs=userPassword by self write by set="[cn=OpenLDAP Admins,ou=Groups,dc=mycompany,dc=ru]/memberUid & user/uid" write by anonymous auth by * none olcAccess: {1}to * by self write by set="([cn=OpenLDAP ] + ([Admins] | [Operators]) + [,ou=Groups,dc=mycompany,dc=ru])/memberUid & user/uid" write by * read olcRootDN: cn=manager,ou=System,dc=mycompany,dc=ru olcRootPW: {SSHA}PKFrwbIL/zLd3gabPPLxn1vNq2jQHj4g olcDbIndex: objectClass eq olcDbIndex: cn eq,sub,subinitial