среда, 5 февраля 2014 г.

Squid. Аутентификация и авторизация в AD.

Не так давно появилась задача разграничения доступа пользователей в интернет. Причем, всем в инет можно, кроме небольшой (относительно всей компании) группы людей.
В конторе у нас в качестве пользовательской ОС используется Windows (7+). Конечно, есть AD (2008+). Есть еще домены филиалов, с которыми установлены доверительные отношения (не помню, вроде только в одну сторону).
Надо поднимать web proxy. Требования в такой ситуации напрашиваются сами собой: нужно аутентификацию и авторизацию для прокси брать из AD (не путай эти понятия!).
В качестве инструментов хотелось использовать FreeBSD и squid.
Пошел я гуглить и искать инфу. В сети огромное количество howto'шек разной степени бестолковости. Кто в лес, кто по дрова, не понимая, нужны ли эти дрова вообще и куда их пихать. По кусочкам кое-что получилось понять, а дальше ваял сам. Тут результат понимания и описание процесса, чтобы сам потом мог легко вспомнить.
В итоге доступ в инет был организован для пользователей домена, а также, некоторых сетей (устройств), где не нужна аутентификация/авторизация.

Все по понятиям

Потенциально аутентификация в моем случае возможна несколькими способами:
- NTLM
- LDAP
- Kerberos
Первый - этакая попытка Microsoft'а сделать свой Kerberos. Но сейчас его уже закапывает сам Microsoft, отказываясь от него в пользу третьего. Да и сам по себе протокол плохой (да, да, хэш от пароля ходит по сети). Второй неплох, но требует от пользователя ручного ввода пароля при аутентификации, т.е. браузер будет просить ввести логин/пасс выводя окошко. А вот Kerberos - то, что нужно. Он безопасный и все будет выглядеть прозрачно: никаких предложений ввести пароль и т.д. Правда, он чуть сложнее в настройке, чем предыдущие варианты, и старые версии Windows его не умеют.
Авторизация - через LDAP. Других способов я не знаю. Т,е. в AD есть security группа, в которую добавлены учетные записи (или другие группы, см. ниже) неугодных пользователей.
Разворачивал все это дело я на FreeBSD 10.0-RELEASE amd64. В качестве прокси - squid. Весь софт ставился из свежих портов. На Linux'е весь смысл тот же, только софт называется немного иначе.
Все это хозяйство настраивал и проверял по частям, сначала сделал аутентификацию, проверил ее, потом сверху примостил авторизацию, проверил, а потом сделал некоторые корректировки.

База

Первым делом я разбирался с тем, как работает Kerberos. Это очень помогло в понимании того, что нужно делать. Как пример http://itband.ru/2010/12/kerberos1/ или  http://www.oszone.net/4188/Kerberos. Из описания следует требование: время на серверах и клиентах должно быть синхронизировано. Проверил время, автозапуск ntpd и т.д. Конечно, нужен еще работающий DNS.
Далее осмыслив работу Kerberos я понял, что сервер, предоставляющий сервис, на котором аутентифицируется доменный клиент тоже должен аутентифицироваться. С пользователем все просто: у него есть учетка в AD, в которой записаны логин и хэш от его пароля. Хэш от пароля и есть закрытый ключ пользователя, которым он шифрует некоторые части пакетов при аутентификации на KDC. Пользователь знает свой пароль и клиентский софт извлекает из него хэш. KDC тоже знает хэш пользователя. И тот и другой может зашифровать и расшифровать пакет этим ключом. Но что с серверами? Сервер же тоже должен иметь какой-то закрытый ключ, который должен знать как KDC, так и сам сервер.

Создание учетки для прокси в AD

Вообще, когда виндовые серверы заводятся в домен, у них появляется "учетка" в AD типа computer, для которой, скорей всего (честно, не знаю), формируется на каком-то шаге (может при развертывании какого-либо сервиса/роли) закрытый ключ. Потом этот ключ как-то попадает на этот новый сервер, я предполагаю. В моем случае сервер не виндовый, и мне нужно сделать это вручную.
В сети можно найти много статей где прокси *nix-сервер добавляют в домен, перед этим долго и мучительно настраивая samba. Это совсем необязательно. Это делается только для того, чтобы получить из AD файл keytab для "учетки" типа computer, от которой будет аутентифицироваться прокси сервер. Keytab, насколько я понял - это что-то вроде контейнера с закрытыми ключами, которыми будет пользоваться демон/сервис (squid) при аутентификации по Kerberos'у.
Есть 2 пути создания "учетки" в AD для сервера: добавление сервера в домен c помощью samba с самого *nix сервера (так создается "учетка" типа computer), или можно создать обычного пользователя (тип user) в AD любыми средствами, например, оснасткой "AD users and computers". Понятно, что второй способ проще.
Затем есть 3 способа извлечения файла keytab для созданной "учетки": с помощью samba на *nix сервере (сервер должен быть в домене), утилитой mskutil на *nix сервере, или утилитой ktpass на контроллере домена. Опять же, последний мне показался проще.
Я создал пользовательскую учетку с именем "squid", задал ей пароль "passWD". По нашим правилам я создал ее в контейнере "Special accounts", который находится на вершине дерева AD. После чего экспортировал keytab с помощью ktpass так (описание опций читай в хелпе):
> ktpass -princ HTTP/proxy.example.org@EXAMPLE.ORG -mapuser SAMPLE\squid -pass passWD  -crypto all -ptype KRB5_NT_SRV_HST -out squid.keytab
Предположим, что мой домен назван "example.org", а NetBios-имя моего домена "sample". Контроллеры домена называются dc1.example.org и dc2.example.org. Сам сервер, который я настраивал зовется proxy.example.org.
На выходе ktpass получился файл squid.keytab. Его нужно скопировать на *nix сервер.

Настройка Kerberos на FreeBSD

Во FreeBSD уже есть в базовой системе реализация Kerberos'а, так что ничего дополнительного устанавливать для его работы не надо, только настроить. Вообще в мире opensource *nix реализаций этого протокола 2 штуки: Heimdal и MIT. Первая чаще используется в BSD, вторая в Linux.
Все настройки Kerberos'а хранятся в файле /etc/krb5.conf (надо создать его). Туда для моего домена нужно записать:
[libdefaults]
 default_realm = EXAMPLE.ORG
 default_keytab_name = /usr/local/etc/squid/squid.keytab <--- тот самый из-под ktpass
[realms]
 EXAMPLE.ORG = {
 kdc = dc1.example.org
 kdc = dc2.example.org
 admin_server = dc1.example.org
 default_domain = example.org
 }
[domain_realm]
 .example.org = EXAMPLE.ORG
 example.org = EXAMPLE.ORG
Тут все более-менее понятно, если что, можно глянуть в ман krb5.conf(5). Строка default_keytab_name указывает на файл keytab, который будет использоваться по-умолчанию всеми службами, которым нужен Kerberos, если ничего другого не указано. Другой способ задания файла keytab - в переменных окружения конкретного демона, например, в скрипте запуска этого демона(как тут http://wiki.squid-cache.org/ConfigExamples/Authenticate/Kerberos). Т. е. там можно для каждого демона задать свой keytab. Замечу, что под FreeBSD можно просто добавить строку squid_krb5_ktname="path/to/keytab" в /etc/rc.conf. У меня прокси не будет выполнять других функций, для которых нужен Kerberos, так что я задал keytab в /etc/krb5.conf.
Теперь, когда все настроено и на месте можно посмотреть в keytab на предмет поддерживаемого шифрования:
# ktutil -k /usr/local/etc/squid/squid.keytab list
/usr/local/etc/squid/squid-v.keytab:
Vno  Type                     Principal                  Aliases
  4  des-cbc-crc              HTTP/proxy.example.org@EXAMPLE.ORG
  4  des-cbc-md5              HTTP/proxy.example.org@EXAMPLE.ORG
  4  arcfour-hmac-md5         HTTP/proxy.example.org@EXAMPLE.ORG
  4  aes256-cts-hmac-sha1-96  HTTP/proxy.example.org@EXAMPLE.ORG
  4  aes128-cts-hmac-sha1-96  HTTP/proxy.example.org@EXAMPLE.ORG
А потом проверить возможность аутентификации в AD через Kerberos:

# kinit -k HTTP/proxy.example.org
# klist
Credentials cache: FILE:/tmp/krb5cc_0
        Principal: HTTP/proxy.example.org@EXAMPLE.ORG
  Issued                Expires               Principal
Feb  7 14:37:49 2014  Feb  8 00:37:44 2014  krbtgt/EXAMPLE.ORG@EXAMPLE.ORG
# kdestroy
Получил тикет, посмотрел на него, удалил.

Установка squid

Squid я ставил из портов, конкретно www/squid33. Был он версии 3.3.11. Я уверен, что когда кто-то будет читать эти строки, версия будет новей и порт будет называться www/squid. Тут было несколько нюансов. Сильно забегая вперед:
изначально, для настройки аутентификации и авторизации я ставил squid, добавив к стандартным только опции AUTH_LDAP и AUTH_KERB (ныне опция называется GSSAPI_* в зависимости от выбираемой реализации). Как оказалось потом, с аутентификацией все было хорошо, но с авторизацией пришлось повозиться. Вышло так, что хелпер ext_ldap_group_acl (опция AUTH_LDAP) не умеет работать с вложенными группами. Т.е. он просто проверяет наличие пользователя в конкретной группе. Если в группе запрета интернета есть вложенная группа, а уже во вложенной наш пользователь, то ext_ldap_group_acl этого не узнает. Не удобно, ведь не хочется добавлять в группу запрета интернета  пользователей по одной учетке, хочется же целыми отделами =) или группами из филиальских доменов. Пришлось еще погуглить и нашелся другой хелпер ext_kerberos_ldap_group_acl. (стандартно он называется squid_kerb_ldap), который умеет такую штуку, только наоборот, выполняет рекурсивный поиск нужной группы на основе списка членства в группах (MemberOf) самого пользователя. Только он не был у меня установлен. И как его доустановить штатными средствами я не сразу понял. Нужно было только заглянуть в Makefile порта www/squid33 и все стало понятно. Для установки этого хелпера нужна комбинация опций AUTH_LDAP и AUTH_SASL. После переустановки порта, хелпер появился, но не работал. В логе squid'а были сообщения вроде таких:
ERROR: ldap_sasl_interactive_bind_s error: Can't contact LDAP server
ERROR: Error while binding to ldap server with SASL/GSSAPI: Can't contact LDAP server
Тут всплыли еще подробности нужно было установить openldap-client с поддержкой sasl. Для этого вместо net/openldap24-client нужно поставить net/openldap24-sasl-client. Можно еще добавить WANT_OPENLDAP_SASL=yes в /etc/make.conf чтобы потом проблем не было.
Потом опять ничего не заработало:
ERROR: ldap_sasl_interactive_bind_s error: Unknown authentication method
ERROR: Error while binding to ldap server with SASL/GSSAPI: Unknown authentication method
В /usr/ports/UPDATING нашел, что плагин gssapi был отделен от основного порта cyrus-sasl2. Доустановил плагин security/cyrus-sasl2-gssapi. Наступило счастье =).

Настройка браузера.

Тут ест одно важное замечание: в качестве адреса прокси у пользователей в браузере должно быть указано полное dns имя сервера, например, proxy.example.org. Также будет работать если dns запись является CNAME.
А вот с забитым в браузере IP прокси сервера ничего работать не будет!

Настройка squid

Действовал я пошагово, поэтому первым делом настроил и проверил аутентификацию:
auth_param negotiate program /usr/local/libexec/squid/negotiate_kerberos_auth -s HTTP/proxy.example.org@EXAMPLE.ORG
auth_param negotiate children 50 startup=10 idle=5
auth_param negotiate keep_alive on
Все очевидно. Для справки можно смотреть в доку squid'а или вывод "negotiate_kerberos_auth -h". Скажу лишь, что на этапе отладки лучше всего перед опцией -s добавить -i, так в логе cache.log появится много интересной инфы. Ссылка, где в конце есть схема работы squid с Kerberos http://wiki.squid-cache.org/ConfigExamples/Authenticate/Kerberos.
Есть вариант проверки работоспособности хелпера, который я подсмотрел у Markus Moeller, выводящий огромное количество информации. Нужно в командной строке от рута запустить:
# kinit username@EXAMPLE.ORG
# /usr/local/libexec/squid/negotiate_kerberos_auth_test proxy.example.org 1 | awk '{sub(/Token:/,"YR"); print $0}END{print "QQ"}' | /usr/local/libexec/squid/negotiate_kerberos_auth -d
Надо не забыть потом запустить kdestroy.
Затем я разобрался с авторизацией. Это конфиг:
external_acl_type NoInet ttl=3600 negative_ttl=3600 children-max=50 children-startup=10 children-idle=5 grace=15 %LOGIN /usr/local/libexec/squid/ext_kerberos_ldap_group_acl -a -i -g DenyInternet -m 64 -D EXAMPLE.ORG -u squid -p passWD
Опять же, все есть в хелпе и доке. На время дебага можно еще перед -i добавить -d. Замечу опцию -m, указывает уровень рекурсии при поиске группы, и еще опцию grace, которая хорошо ускоряет авторизацию (читай в доке). DenyInternet - это группа из AD, в которой томятся неугодные юзеры. NoInet - это имя данного внешнего acl.
Проверить работоспособность этой части можно просто запустив хелпер с нужными опциями, т.е. копируем в командную строку все, что после %LOGIN. Затем вводим имя пользователя и получаем в ответ OK или ERR. Например, из командной строки:
# /usr/local/libexec/squid/ext_kerberos_ldap_group_acl -a -i -g DenyInternet -m 64 -D EXAMPLE.ORG -u squid -p passWD
username@EXAMPLE.ORG
OK 
Пользователь входит в группу, иначе получили бы ERR.
Остальная часть конфига:
cache_dir diskd /var/squid/cache 25600 16 256
acl NoAuthNet src 192.168.8.0/24
acl NoAuthDevice src 192.168.8.184/32
acl NoInetAD external NoInet
http_access allow NoAuthDevice
http_access allow NoAuthNet
http_access deny NoInetAD all
http_access allow all
http_port 3128
cache_mgr helpdesk@example.org
Первая строчка описывает механизм работы squid'а с кэшем. В данном случае используется отдельный демон для дисковых операций с кэшем. Для его корректной работы нужно изменить некоторые sysctl'ки. Тут пример каких, и откуда они взялись https://forums.freebsd.org/viewtopic.php?f=43&t=44638. Однако, есть мнение, что все, кроме ufs "сломано" и нестабильно.
Затем описание acl'ей. Тут есть девайс и сеть, для которых аутентификация и авторизация не нужны. Потом привязываем внешний acl из настройки авторизации к другому acl NoInetAD.
Теперь собственно описание доступа (принцип работы как в ipfw). Проверка условий идет по порядку сверху вниз, слева направо, поэтому первым делом выпускаем в интернет сеть NoAuthNet и устройство NoAuthDevice.
Затем для тех кто не совпал с предыдущими правилами вызываем авторизацию, которая в свою очередь вызовет аутентификацию, т.е. не пускаем неугодных юзеров, которые состоят в группе в AD. Здесь нужно заметить, что не случайно написано слово deny, а не allow. Дело в том, что allow работает только для уже аутентифицированных пользователей и не вызывает повторный запрос на аутентификацию, если она до этого момента не состоялась. Отправку ответа "407 - Proxy authentification required" выполняет только deny. Еще можно заметить, что в конце стоит all. Это не просто так, т.к. если в описании http_access последним стоит acl с авторизацией/аутентификацией, то она будет запрашиваться снова и снова, вместо одного раза. Поэтому, если ее не будет, то неугодным пользователям будет показываться окно авторизации раз 5. Некрасиво. А так они сразу получают Access denied . Все эти особенности были вычитаны тут http://wiki.squid-cache.org/Features/Authentication. Там описано еще несколько полезных трюков по аутентификации.
И последней строчкой разрешаем выход в инет.
Вот полезная ссылка про правила работы squid'а с списками доступа http://wiki.squid-cache.org/SquidFaq/SquidAcl

На всякий случай

Не относится к всему посту, просто мало ли когда-нибудь пригодится портянка с аутентификацией и авторизацией простым LDAP:
# Authentification using LDAP
##Delete the comments to enable another one auth procedure if previous wasn't passed.
##auth_param basic program /usr/local/libexec/squid/basic_ldap_auth -R -D squid@example.org -W /usr/local/etc/squid/ldap.txt -b "dc=example,dc=org" -f "sAMAccountName=%s" 192.168.1.2
#
# Authorization using LDAP
# -K - remove realm from username
# -d - turn on the debug
#external_acl_type InetGroup ttl=600 %LOGIN /usr/local/libexec/squid/ext_ldap_group_acl -R -b "dc=example,dc=org" -f "(&(objectclass=person)(sAMAccountName=%v)(memberOf=cn=%a,ou=Security Groups,dc=example,dc=org))" -D
 squid@example.org -W /usr/local/etc/squid/ldap.txt -K -d 192.168.1.2 192.168.1.3
Тут нужно помнить и указывать путь к контейнеру, где лежит служебная учетка squid.

А вот продолжение, которое я написал после некоторого срока эксплуатации такой схемы http://timp87.blogspot.ru/2014/03/squid-ad.html.