?

Log in

No account? Create an account

Previous Entry | Next Entry

Предыстория.


Когда-то давно в нашем подразделении использовался SVN сервер для решения личных задач разработчиков. Было там пара десятков реп из которых только парой пользовалось несколько человек, остальные же были однопользовательскими. В следствии этого, настроено всё было примитивно - доступ по протоколу SVN://, в каждой репе по файлику с настройками и файлику с паролями ну и далее в том же стиле. И всех всё устраивало... Постепенно проекты росли и встала необходимость управлять задачами и мы поставили Redmine, правда он тоже получился почти однопользовательским и по-этому никого не волновало, что у него была отдельная база пользователей. А проекты всё продолжали плодиться и шириться, да и народу по-немногу становилось больше, потребовалось давать доступ к благам цивилизации сотрудникам других отделов. А ведь в конторе есть ещё и глобальные сервисы, к которым у каждого сотрудника есть логины и пароли. В общем скоро стало понятно, что управлять всем этим добром мягко говоря не комфортно и надо делать централизованную аутентификацию. Примерно год я пытался добиться от наших админов, что бы они настроили в конторе LDAP, но их походу не парило руками синхронизировать свои сервисы. В общем, вчера моё терпение кончилось и я сел сам разбираться. Потратил часов 10, но получил рабочую конфигурацию, чем и хочу поделиться.

Введение



Сразу оговорюсь, что я простой админ локал-хоста и в таких энтерпрайзных вещах, как сервера директорий ничего не понимал до сегодняшнего дня, так что статья из разряда "чайникам от чайника" - что и как понял, то и рассказываю. Энтерпрайзных задач тоже не стояло - пока просто сделать централизованную авторизацию сервисов, без всяких там ACL и прочих заморочек.
Конфигурированию OpenLDAP и прикручиванию к нему различных сервисов посвящено множество статей. Проблема в том, что большинство из них основной упор делают на то, что нужно сделать, некоторые уделяют внимание тому зачем это надо делать, но практически никто не говорит о том, почему надо делать именно так и как можно по-другому. Ну, по крайней мере, мне не удалось найти таких статей. В свете этого, я попробую пойти с обратной стороны - не буду много писать о том, что делать, а сконцентрируюсь на вопросах почему именно так делают.
Процесс настройки я разбил на три этапа - запуск OpenLDAP в базовой конфигурации с парой тестовых пользователей, организация ауторизации с авторегистрацией пользователей redmine из LDAP и доступ на чтение/запись в SVN. Излагать буду в том же порядке.

LDAP



Итак, лдап - это сервис директорий. Директории - это дерево. Узлы дерева могут хранить произвольную информацию (текстовую). Информация эта представляется в виде пар ключ-значения, которые называются атрибутами. Админ волен строить дерево совершенно произвольным образом, исходя из своих потребностей. С другой стороны, если позволить в каждый узел пихать что угодно будет хаос. По-этому существуют схемы и классы. Классы описывают какие атрибуты может иметь объект и какие обязан, при этом объект может принадлежать к множеству классов, таким образом можно набирать нужный список атрибутов. Ну а схемы - это описания возможных атрибутов и классов. Существует несколько готовых схем, определённых в RFC или специфичных для конкретного LDAP-сервера.
Вот в общих чертах теория. Для практики я по-быстрому поднял FreeBSD 9.1 в виртуалбоке и поставил туда OpenLDAP. Почему именно его? В принципе, серверов, реализующих LDAP несколько, можно взять любой другой, просто этот наиболее распространённый.
Конфиг сервера хранится в файле slapd.conf, откуда инклудятся файлы схем. Помимо настроек, относящихся к серверу там находятся ACL, суффикс верхнего уровня и пароль рута в зашифрованном виде. Само дерево хранится в базе данных (по умолчанию предлагается BerkeleyDB, но можно использовать и другие бекенды). Физическое расположение базы зависит от бекенда, для bdb, например, задаётся опцией directory (что бы полностью очистить базу можно тупо удалить все файлы из этой директории).
Принцип именования узлов в чём-то близок к формированию имён в DNS, возможно по-этому суффикс принято делать из двух частей:
"dc=example,dc=org"
В принципе, здесь можно писать что угодно, хоть dc=vasia,dc=pupkin - никакой связи с именами DNS тут нет (хотя некоторые клиентские софтины и могут попытаться такую связь установить). Магическое dc - это сокращение от domain component.
Рутовый пользователь домена задаётся строчкой "cn=,dc=example,dc=org" (cn - common name, имя объекта).

Как работать в базой данных? Так как оно не plain text, то в ручную править записи не получится. Нужно либо писать инструкции, что сделать, в файле ldif и применить эти инструкции командой ldapadd, либо пользоваться спецсофтом, типа ldapadmin, или ldapphpadmin. Для начала проще воспользоваться ldif'ами, а потом перейти на гуйню. Кстати, для работы из консоли есть забавные утилиты ldapvi (использует EDITOR для редактирования дерева) и shelldap (клиент в стиле шела).

Как я уже сказал, дерево можно стоить как угодно. Я решил пока создать две директории в корне - одну для юзеров и одну для групп. Скелет у меня получился такой

dn: dc=example,dc=org
objectClass: top
objectClass: dcObject
objectClass: organization
dc: example
o: MyExample

dn: ou=people,dc=example,dc=org
objectClass: top
objectClass: organizationalUnit
ou: people

dn: ou=groups,dc=example,dc=org
objectClass: top
objectClass: organizationalUnit
ou: groups

dn: cn=developers,ou=groups,dc=example,dc=org
objectClass: groupOfNames
cn: developers
member: uid=,ou=people,dc=example,dc=org
member: uid=,ou=people,dc=example,dc=org
member: uid=,ou=people,dc=example,dc=org



Здесь создаётся 4 объекта - корневая директория (не путать с rootdn который мы прописали в конфиге), две поддиректории и одна группа. DN - это имя директории (всегда пишется полностью). Самое интересное тут, конечно, это objectClass, а точнее, почему они именно такие. Зачем нужен top, честно сказать, сам толком не понял - некий абстрактный класс без атрибутов. А вот остальных могу попробовать объяснить. Дело в том, что в данном случае мы описываем некую компанию, в которой есть люди, разделённые на отделы (группы). Класс organization как раз и предназначен для описания всяких организаций. В принципе здесь спокойно можно этот класс и не вешать. Кроме того наш example по совместительству кусок доменного имени, а значит ему нужен атрибут dc для получения которого вешаем dcObject. Группы имеют тип organizationalUnit - это не совсем логично, но такова общепринятая практика. На самом деле, здесь подошёл бы любой класс, содержащий свойство ou. Ну или любой другой, просто название свойства было бы другим, что мало на что влияет. Последний абзац описывает группу разработчиков, в которую я буду добавлять тех, кто имеет доступ к SVN.
Ну и самое интересное - собственно учётные записи пользователей:
dn: uid=,ou=people,dc=example,dc=org
objectClass: top
objectClass: person
objectClass: posixAccount
objectClass: shadowAccount
objectClass: organizationalPerson
objectClass: inetOrgPerson
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/
loginShell: /bin/sh
uid: 
cn: 
mail: @my.mail
sn:: Family
givenName:: Name
displayName:: Mega User
userPassword:{SSHA}

На самом деле классов тут сильно с избытком, для простой авторизации вполне достаточно было бы, например, shadowAccount, или даже person - главное, что бы было поле userPassword. Для хранения логина в никсовых соглашениях принято использовать поле uid, альтернативно в самбовой схеме для логина используют sAMAccountName.
Кроме обычных пользователей желательно держать специального служебного объекта, который имеет право на чтение информации о пользователях (кроме пароля). Я его создал таким образом:
dn: cn=keykeeper,ou=people,dc=example,dc=org
objectClass: top
objectClass: person
sn: Special user
cn: keykeeper


Redmine


Ну тут всё просто. Инструкция по настройке есть в wiki редмайна, единственное, что она заточена под виндовую схему авторизации. Как дружить с более привычной схемой можно посмотреть в форуме. Ниже приведу скрин своей конфигурации.
Конфигурация LDAP в redmine
В ЛДАПе редмайн проводит авторизацию, но пользователь всё равно должен храниться в базе редмайна. Для того, что бы не заботиться о создании пользователей, предусмотрена галка авторегистрации (а что бы не заботиться об удалении есть плугин redmine_ldap_sync)
Можно заметить, что редмайн умеет брать из лдапа не только логин/пароль, но ещё и имя, фамилию и почту, которые потом использует для регистрации пользователя в своей базе. Именно для этого, кстати, понадобилось добавить класс inetOrgPerson.

SVN


Прикручивание SVN оказалось чуть менее тривиальной задачей, но в целом идея такая: заводим апач (обычно виртуал хост или локейшен), цепляем к нему svn по вебдав и ограничиваем доступ аутентификацией по LDAP. В принципе, можно обойтись без апача - сам svn тоже умеет авторизацию в лдапе, но настраивается чуть хуже.
Вот такой конфиг у меня получился:
<VirtualHost *:80>
        ServerName repo.host

        CustomLog /var/log/httpd/subversion.log combined
        ErrorLog /var/log/httpd/subversion-error.log
        <Location />
                DAV svn
                SVNParentPath /home/svnserve
                SVNListParentPath on
                SVNAutoversioning On
                SVNPathAuthz Off
                SVNReposName "Subversion Repository"

#               AuthzSVNAccessFile /usr/local/etc/dav_svn.authz
                AuthName "SVN repository"
                AuthType Basic

                AuthBasicProvider ldap
                AuthzLDAPAuthoritative on
                AuthLDAPUrl ldap://ldap.host/ou=People,dc=example,dc=org?uid?sub?(objectClass=*) NONE
                AuthLDAPGroupAttribute member
                Require ldap-group cn=Developers,ou=groups,dc=example,dc=org
        </Location>
</VirtualHost>


За лдап, как видно из названия, отвечают последние строчки.
AuthLDAPUrl определяет где и что искать. Require ldap-group требует, что бы юзер принадлежал определённой группе (без этого у меня, почему-то, не получилось закрыть анонимный доступ). AuthLDAPGroupAttribute говорит по какому атрибуту определять принадлежность юзера группе (впрочем, member у неё значение по умолчанию, так что можно было не прописывать). Ну и AuthzLDAPAuthoritative говорит, что авторизовывать можно только по лдапу. А вообще, всё это хорошо расписано в доках на апач - http://httpd.apache.org/docs/2.2/mod/mod_authnz_ldap.html

Comments