Thursday, January 8, 2009

Централизованное AA на FreeBSD

Со временем количество активного оборудования, количество серверов и сервисов имеет свойство увеличиваться, а технические отделы - расти. В связи с этим все чаще встают вопросы заведения/вынесения сотрудников в/из текущих информационных систем. Данная статья описывает возможную реализации AA (аутентификацию и авторизацию) через ldap на ОС FreeBSD (и, косвенно, на других *nix). Рассмотрение реализации третьего A (аккаунтинга) выходит за рамки данной статьи, хотя и реализуется достаточно легко (например через audit(4)).
Читать далее...

Краткое описание реализации:
В качестве места хранения данных используется openldap, протокол - ldap+TLS, аутентификация/авторизация при входе в систему/получении sudo-прав происходит по паролям и открытым ключам

Используемое ПО:
  • nss_ldap (ldap-бекенд для nsswitch.conf)
  • pam_ldap (аутентификация и авторизация через ldap)
  • openssh-portable с патчем LPK (аутентификация )
  • pam_mkhomedir (создание домашней директории пользователя)
  • openldap (реализация протокола ldap)
  • sudo (авторизация через ldap)

Установка серверной части:
В первую очередь создадим CA (certification authority) для выдачи нужного нам для ldap сертификата(ов):
Для этого в базовой системе есть скрипт CA.pl (/usr/src/crypto/openssl/apps/CA.pl)
В принципе, он работает и out-of-box, однако для удобства можно сделать следующее:
  • Исправить путь в $CATOP из скрипта на тот, где планируется держать файлы CA (а это несколько директорий)
  • Исправить путь в секции Default_ca файла /etc/ssl/openssl.cnf
  • Выставить предпочтительные параметры в секции req_distinguished_name (страну, город, etc..) для упрощения дальнейших выдач сертификатов
Кроме того, для некоторого повышения надежности схемы можно отказаться от использования DNS-имен в настройках. В таком случае стоит использовать опцию subjectAltName (rfc 3280) для указания собствеенно IP-адреса сервера в сертификате.
Для этого необходимо дописать примерно следующие строчки в openssl.cnf (на момент выдачи этого сертификата):

[ req ]
...
req_extensions = v3_req

[ v3_req ]
...
subjectAltName = @alt_names

[ alt_names ]
IP.1 =1.2.3.4
Приступим собственно к выдаче:
Создаем CA (инициализация дерева директорий, создание root-сертификата):
central# ./CA.pl -newca
Show output..

CA certificate filename (or enter to create)

Making CA certificate ...
Generating a 1024 bit RSA private key
...............++++++
...........................................++++++
writing new private key to './cctest/private/cakey.pem'
Enter PEM pass phrase: somegoodpassword
Verifying - Enter PEM pass phrase: somegoodpassword
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [RU]:
State or Province Name (full name) [Moscow]:
Locality Name (eg, city) [Moscow]:
Organization Name (eg, company) [JSC Roga and Kopyta]:
Organizational Unit Name (eg, section) [Telecommunications]:
Common Name (eg, YOUR name) []:central.rogaandkopyta.ru
Email Address [tech@rogaandkopyta.ru]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Using configuration from /etc/ssl/openssl.cnf
Enter pass phrase for ./cctest/private/cakey.pem:
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number:
ef:3d:cb:f2:46:2a:3f:96
Validity
Not Before: Jan 10 13:15:04 2009 GMT
Not After : Jan 9 13:15:04 2014 GMT
Subject:
countryName = RU
stateOrProvinceName = Moscow
organizationName = JSC Roga and Kopyta
organizationalUnitName = Telecommunications
commonName = central.rogaandkopyta.ru
emailAddress = tech@rogaandkopyta.ru
X509v3 extensions:
X509v3 Subject Key Identifier:
49:43:94:4F:16:D0:E7:01:DE:60:6D:98:C8:10:C6:25:7E:4E:77:C4
X509v3 Authority Key Identifier:
keyid:49:43:94:4F:16:D0:E7:01:DE:60:6D:98:C8:10:C6:25:7E:4E:77:C4
DirName:/C=RU/ST=Moscow/O=JSC Roga and Kopyta/OU=Telecommunications/CN=central.rogaandkopyta.ru/emailAddress=tech@rogaandkopyeta.ru
serial:EF:3D:CB:F2:46:2A:3F:96

X509v3 Basic Constraints:
CA:TRUE
Certificate is to be certified until Jan 9 13:15:04 2014 GMT (1825 days)

Write out database with 1 new entries
Data Base Updated

Теперь в директории catest у нас есть root сертификат (cacert.pem) нашего CA, который мы будем выдавать сомневающимся^Wldap-клиентам

Осталось выдать сертификат (точнее, создать request и подписать его) собственно для ldap-сервера:
central# ./CA.pl -newreq
Show output..

Generating a 1024 bit RSA private key
..++++++
.......++++++
writing new private key to 'newkey.pem'
Enter PEM pass phrase: sometemppassword
Verifying - Enter PEM pass phrase: sometemppassword
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [RU]:
State or Province Name (full name) [Moscow]:
Locality Name (eg, city) [Moscow]:
Organization Name (eg, company) [JSC Roga and Kopyta]:
Organizational Unit Name (eg, section) [Telecommunications]:
Common Name (eg, YOUR name) []:central.rogaandkopyta.ru
Email Address [tech@central.rogaandkopyta.ru]:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
Request is in newreq.pem, private key is in newkey.pem

central# ./CA.pl -sign
Show output..

Using configuration from /etc/ssl/openssl.cnf
Enter pass phrase for /root/catest/private/cakey.pem: somegoodpassword
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number:
ef:3d:cb:f2:46:2a:3f:95
Validity
Not Before: Jan 10 12:42:22 2009 GMT
Not After : Jan 10 12:42:22 2010 GMT
Subject:
countryName = RU
stateOrProvinceName = Moscow
localityName = Moscow
organizationName = JSC Roga and Kopyta
organizationalUnitName = Telecommunications
commonName = central.rogaandkopyta.ru
emailAddress = tech@central.rogaandkopyta.ru
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
9A:43:A5:87:9A:9E:3A:F2:D1:AB:02:2C:46:4E:FC:49:62:28:3F:FE
X509v3 Authority Key Identifier:
keyid:7F:D8:88:76:09:5F:30:98:81:22:38:BC:B3:55:5D:43:75:FC:B3:1F

X509v3 Key Usage:
Digital Signature, Non Repudiation, Key Encipherment
X509v3 Subject Alternative Name:
IP Address:1.2.3.4
Certificate is to be certified until Jan 10 12:42:22 2010 GMT (365 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
Signed certificate is in newcert.pem
В результате мы получили следующие файлы:
  • newcert.pem - это собственно сертификат, переименовываем например в central.pem
  • newkey.pem - приватный ключ для сертификата
С ключем ситуация интереснее: openssl при генерации не дает указать пустой passthrase, а slapd не умеет ключи в файлах. Поэтому поступаем так, как нас учат в rsa(1):
central# openssl rsa -in newkey.pem -out central_key.pem
Enter pass phrase for newkey.pem: sometemppassword
writing RSA key


В результате получился нужный нам ключ, не защищенный паролем. На этом часть, непосредственно связанную с openssl можно считать [успешно] законченной и переходить к установке/настройке openldap.

В серверной части нас больше всего интересует bdb бекенд(по умолчанию) и, возможно audit log и access log.
Устанавливаем:
make -C /usr/ports/net/openldap24-server/ install clean
Настраиваем /usr/local/etc/openldap/slapd.conf (например, копируя из /usr/local/etc/openldap/slapd.default). Здесь необходимо выбрать название домена, в котором все будет жить. В данном примере это dc=roga,dc=ru. Кроме того, необходимо придумать иерархию обьектов в дереве ldap. У автора используется следующая схема:

dc=roga,dc=ru - корень
ou=People - контейнер, в которой находятся все сотрудники
ou=Tech,ou=People - контейнер с эккаунтами технического отдела (обьекты живут именно тут)
ou=Groups - контейнер с posix группами (обьекты живут тут)
ou=SUDOers - контейнер с конфигурацией sudo (обьекты живут именно тут)

Пример конфига с комментариями:

#Поддерживаемые схемы (описания возможных типов обьектов и их взаимоотношений)
# Базовые
include /usr/local/etc/openldap/schema/core.schema
include /usr/local/etc/openldap/schema/cosine.schema
include /usr/local/etc/openldap/schema/nis.schema
# Для поддержки открытых ключей, брать с http://openssh-lpk.googlecode.com/files/openssh-lpk_openldap.schema
include /usr/local/etc/openldap/schema/openssh-lpk_openldap.schema
# Ставится после пересобки sudo с LDAP (при первичной отладке лучше закомментировать)
include /usr/local/share/doc/sudo/schema.OpenLDAP


pidfile /var/run/openldap/slapd.pid
argsfile /var/run/openldap/slapd.args

modulepath /usr/local/libexec/openldap
moduleload back_bdb

# Разграничение доступа
access to dn.base="" by * read
access to dn.base="cn=Subschema" by * read

# Предоставляем доступ до хешированного пароля только специальному эккаунту (getpass)
access to attrs=userPassword
by self ssf=128 write
by dn.base="cn=getpass,ou=People,dc=roga,dc=ru" ssf=128 read
by anonymous ssf=128 auth

# Остальные данные читать может кто угодно
access to dn.sub="ou=People,dc=astel,dc=ru"
by * read

# Остальные данные читать может кто угодно

access to dn.sub="ou=Groups,dc=astel,dc=ru"
by * read

# Сами мы можем менять свой эккаунт (нпример, пароль)
access to *
by self write
by users read
by anonymous auth

# Настройки для работы SSL/TLS
TLSCipherSuite HIGH:MEDIUM:+SSLv2
TLSCACertificateFile /usr/local/etc/openldap/cacert.pem
TLSCertificateFile /usr/local/etc/openldap/central.pem
TLSCertificateKeyFile /usr/local/etc/openldap/central_key.pem

# Настройки стораджа для куска ldap-дерева, живущего под suffix
database bdb
suffix "dc=roga,dc=ru"
# DN (Distinguished name) рутового эккаунта, который может все
rootdn "cn=toor,dc=roga,dc=ru"
# Пароль (хеш), генерируется с помощью slappasswd
rootpw {SSHA}.....
directory /var/db/openldap-data
index objectClass,uid,uidNumber,gidNumber eq
Прописываем в автозагрузку:
echo slapd_enable=\"YES\" >> /etc/rc.conf
Пробуем запустить (начать стоит именно с такого способа, т.к. скрипт запуска инициализирует директории из конфига перед запуском):
/usr/local/etc/rc.d/slapd start
В случае необходимости (возможные -d можно посмотреть в slapd.conf(5)):
/usr/local/libexec/slapd -h "ldap:// ldaps://" -u ldap -g ldap -d 256
Для упрощения жизни на сервере с openldap дописываем откуда клиенту брать root сертификат для TLS:
echo TLS_CACERT /usr/local/etc/openldap/cacert.pem >> /usr/local/etc/openldap/ldap.conf
После того как slapd успешно запущен, необходимо добавить в него [придуманную раньше] структуру. Работа с сервером openldap производится утилитами ldapadd, ldapmodify и ldapsearch. Добавить обьекты из файла можно следующим образом:
ldapadd -xWZ -h -D cn=toor,dc=roga,dc=ru -f obj.ldif

(Hint: -x говорит не использовать sasl, -W спрашивает пароль, -Z включает TLS)

Пример obj.ldif для описанной выше структуры с группой и тестовым сотрудником:


# "Корень структуры"
dn: dc=roga,dc=ru
objectClass: dcObject
objectClass: organization
o: JSC Roga and Kopyta
dc: roga

dn: ou=People,dc=roga,dc=ru
description: Employees Accounts
objectClass: organizationalUnit
ou: People

dn: ou=Tech,ou=People,dc=roga,dc=ru
description: Tech people here
objectClass: organizationalUnit
ou: Tech

dn: ou=Groups,dc=roga,dc=ru
description: Unix groups OU
objectClass: organizationalUnit
ou: Groups

# Первая группа
dn: cn=tech_pd,ou=Groups,dc=roga,dc=ru
objectclass: top
objectclass: posixGroup
description: IP/services admins
cn: tech_pd
gidNumber: 10001

# эккаунт для непривилигированного хождения по лдапу
dn: cn=getnfo,ou=People,dc=roga,dc=ru
objectClass: top
objectClass: person
objectClass: organizationalPerson
description: Ldap Testovich
cn: getnfo
sn: getnfo
userPassword: {SSHA}securepass0

# эккаунт с возможностью прочитать хеш пароля (см. slapd.conf)
dn: cn=getpass,ou=People,dc=roga,dc=ru
objectClass: top
objectClass: person
objectClass: organizationalPerson
description: Ldap Testovich
cn: getpass
sn: getpass
userPassword: {SSHA}securepass1

# Первый тестовый сотрудник
dn: uid=vpupkin,ou=Tech,ou=People,dc=roga,dc=ru
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: posixAccount
objectclass: ldapPublicKey
description: Vasily V. Pupkin
userPassword: {SSHA}...
cn:
Vasily V. Pupkin
sn: Vasily V. Pupkin
uid: vpupkin
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/vpupkin
loginShell: /bin/tcsh
sshPublicKey: ssh-rsa .....

Переход на централизованный sudo рассматривается после успешной конфигурации клиентской части, поэтому в данном .ldif нет обьектов sudo.

Если все прошло успешно, можно переходить к настройке клиентской части, которая значительно проще. Если нет - смотрим на ошибки, запускаем клиент/сервер с -vvv, дебагом, etc..

Клиентская часть:
Здесь все значительно проще и сводится к следующим вещам:
  • [пере]установка портов с заданными опциями
  • подкладывание новых конфигов ldap
  • возможная схема группы, в которой должен состоять сотрудник для получения доступа
  • изменение схемы работы pam для openssh/sudo
  • изменение nsswitch.conf
  • добавление бекапного эккаунта на случай отказа ldap
  • вынос текущих эккаунтов
Почти все пункты вполне себе автоматизируются.
Конечно, всегда есть нюансы: например, в данном скрипте sshd переезжает в /usr/local и не конвертирует старый конфиг. Кроме того, необходимо будет перенести конфигурацию sudo в ldap (подробности ниже) и
Пример скрипта и краткое руководство:
Для реализации нам понадобится http сервер (напр. nginx):
Итак: собственно, скрипт и необходимые файлы
Клиентский конфиг с комментариями:

base dc=roga,dc=ru
uri ldap://1.2.3.4/

# "Обычный" DN (для "пользовательских" запросов)
binddn cn=getnfo,ou=People,dc=roga,dc=ru
bindpw secret
# привилегированный DN для запросов из-под root
rootbinddn cn=getpass,ou=People,dc=roga,dc=ru
# в случае недоступности сервера возвращаться сразу
bind_policy soft
# Если поставить persist, то из-за реализации защиты openssh необходимо будет выключать PrivilegeSeparation
nss_connect_policy oneshot
pam_filter objectclass=posixAccount
# Членство в этой группе требуется для получения доступа к серверу
pam_groupdn cn=tech_pd,ou=Groups,dc=roga,dc=ru
pam_min_uid 10000
pam_max_uid 20000

nss_base_passwd ou=People,dc=roga,dc=ru?sub
nss_base_shadow ou=People,dc=roga,dc=ru?sub
nss_base_group ou=Groups,dc=roga,dc=ru?one
# TLS включаем т.к. openssh-lpk не поддерживает SSL, а без него совсем несекьюрно
ssl start_tls

tls_cacertfile /usr/local/etc/openldap/cacert.pem
# Место, где лежат все обьекты с конфигурацией sudo
sudoers_base ou=SUDOers,dc=roga,dc=ru

В самом скрипте необходимо заменить
  • URL на тот, по которому он доступен.
  • заменить пароль в bindpw на securepass0
В остальных файлах:
sed -i '' -e 's/dc=roga,dc=ru/dc=нужный,dc=домен/g' nss_ldap.conf
sed -i '' -e 's/1.2.3.4/ldap.ip.addre.ss/g' nss_ldap.conf openldap.conf
После чего запустить на первом сервере что-то типа fetch http:../pifpaf.sh && sh pifpaf.sh
Убедиться, что скрипт отработал корректно, возможно что-то подкорректировать под нужную конфигурацию и записать придуманный в серверный части serverpass1 в /usr/local/etc/nss_ldap.secret

Настройка sudo:
В данном примере будет рассматриваться тривиальная конфигурация, когда все члены определенной группы могут пользоваться sudo (т.е. аналог %wheel ALL=(ALL) ALL).
Более подробно об импорте конфигов можно почитать тут или в устанавливаемых sudo readme.

Итак, очередной .ldif (sudoers_base ou=SUDOers,dc=roga,dc=ru) уже указано в распостраненных nss_ldap.conf:

dn: ou=SUDOers,dc=roga,dc=ru
objectClass: top
objectClass: organizationalUnit
ou: SUDOers

# Эта группа может использовать sudo как угодно
dn: cn=%tech_pd,ou=SUDOers,dc=roga,dc=ru
objectClass: top
objectClass: sudoRole
cn: %tech_pd
sudoUser: %tech_pd
sudoHost: ALL
sudoCommand: ALL

# Осторожно! Этот обьект заставит sudo игнорировать локальный sudoers!
dn: cn=defaults,ou=SUDOers,dc=roga,dc=ru
objectClass: top
objectClass: sudoRole
cn: defaults
sudoOption: ignore_local_sudoers

Резюме:
Появилась единая точка аутентификации/авторизации для сотрудников, ходящих на сервера. Появилась возможность понемногу перетаскивать аунтентификацию остальных сервисов в LDAP. Исправления/дополнения/комментарии приветствуются

0 comments:

Post a Comment