Недавно нам с коллегами понадобилось реализовать прозрачную (SSO) авторизацию в нашем проекте. Сейчас довольно мало информации по теме особенно на русском языке. По этой причине решено было поделиться с потомками реализацией подобного функционала.
Итак задача заключалась в следующем: необходимо было настроить прозрачную авторизацию через GSSAPI от пользователя на сервер, а так же иметь потом возможность от имени этого пользователя ходить в БД.
У нас имелось:
Изначально была идея делегировать авторизацию пользователей в приложении веб серверу, настроив на нем авторизацию по GSSAPI, а Django указать, что мы будем работать с RemoteUser. В рамках данного описания, я не буду рассказывать, как настроить nginx на работу по GSSAPI, а Django на делегацию авторизации на веб сервер, это хорошо задокументированная часть, да и статей по этому поводу довольно много. После настройки и проведенных тестов — мы поняли, что это абсолютно не то, что нам нужно. Да мы можем провести авторизацию и получить user principal name, но мы не имеем прав от имени этого пользователя ничего сделать.
Далее нами были предприняты попытки найти что-то стоящее на просторах интернета. Они увенчались относительным успехом, были найдены следующие пакеты для Django: django-kerberos, django-auth-spnego, django-auth-kerbero. По сути все эти пакеты делали одно и тоже, некоторые не обновлялись уже давно и пришлось долго «танцевать с бубном», что бы хоть что то заработало. Они предоставляли такой же функционал как и связка nginx(GSSAPI)+Django(RemouteUser). Все они помогли прийти к решению проблемы своим исходным кодом.
Далее были найдены следующие пакеты для работы с GSSAPI в Python: ccs-pykerberos и python-gssapi, по сути они импортируют реализацию стандарта RFC2744 и RFC4559 в Python. С помощью пакета ccs-pykerberos у нас как раз и получилось реализовать задуманный функционал, далее я покажу немного кода, где реализуется общение с LDAP`ом и пользователем, а так же запрос в БД от его имени.
Сначала нужно проверить передан ли нам заголовок авторизации, если нет — мы должны направить в ответ заголовок с Negotiate, и снова ждать от пользователя Negotiate токен. Этот токен выглядит как большая портянка закодированная в base64. После получения токена, мы инициализируем (авторизуем) сервер нашего приложения в LDAP сервисе, используя функцию authGSSServerInit(). Далее мы авторизуемся в LDAP сервисе от имени пользователя, используя для этого как раз тот токен, который получили из заголовка, функция authGSSServerStep(). Потом мы получаем principal пользователя, который будем использовать в качестве user, при выполнении запроса в БД. А так же, нам необходимо сформировать кэш битела Kerberos, который будет использован автоматически для того, что бы авторизовать нас в PostgreSQL, функция authGSSServerStoreDelegate(). Данная функция есть только в самой последней версии этого пакета, поэтому нужно клонировать себе исходники с git и сделать build.
Браузер должен быть настроен на отдачу Negotiate, по умолчанию эта опция отключена. Все тесты проводились на Firefox в CentOS7, так же на всех серверах был установлен CentOS7.
В добавок, у нас может возникнуть проблема, при которой кэш билета, сформированный нашей функцией, не будет виден нашему процессу и соответственно мы получим, что пользователь не авторизовался в GSSAPI. Она решается настройкой кеширования билетов в krb5.conf. На всякий случай приведу пример нашего конфига:
Данный кусок кода создан для того, что бы помочь понять как происходит авторизация и делегация прав, далее с этими знаниями можно строить декораторы авторизации и бэки общения с базой. Пакет ccs-pykerberos был разработан компанией Apple, для своих внутренних нужд, соответственно приведу ссылку на их код, где они его используют. Нам он очень помог в понимании того, что они разработали ccs-calendarserver/twistedcaldav/authkerb.py
Итак задача заключалась в следующем: необходимо было настроить прозрачную авторизацию через GSSAPI от пользователя на сервер, а так же иметь потом возможность от имени этого пользователя ходить в БД.
У нас имелось:
- настроенный сервер Kerberos+LDAP
- сервер PostgreSQL, настроенный на авторизацию исключительно по GSSAPI
- сервер приложения Django+UWSGI+nginx, с настроенным Kerberos
Изначально была идея делегировать авторизацию пользователей в приложении веб серверу, настроив на нем авторизацию по GSSAPI, а Django указать, что мы будем работать с RemoteUser. В рамках данного описания, я не буду рассказывать, как настроить nginx на работу по GSSAPI, а Django на делегацию авторизации на веб сервер, это хорошо задокументированная часть, да и статей по этому поводу довольно много. После настройки и проведенных тестов — мы поняли, что это абсолютно не то, что нам нужно. Да мы можем провести авторизацию и получить user principal name, но мы не имеем прав от имени этого пользователя ничего сделать.
Далее нами были предприняты попытки найти что-то стоящее на просторах интернета. Они увенчались относительным успехом, были найдены следующие пакеты для Django: django-kerberos, django-auth-spnego, django-auth-kerbero. По сути все эти пакеты делали одно и тоже, некоторые не обновлялись уже давно и пришлось долго «танцевать с бубном», что бы хоть что то заработало. Они предоставляли такой же функционал как и связка nginx(GSSAPI)+Django(RemouteUser). Все они помогли прийти к решению проблемы своим исходным кодом.
Далее были найдены следующие пакеты для работы с GSSAPI в Python: ccs-pykerberos и python-gssapi, по сути они импортируют реализацию стандарта RFC2744 и RFC4559 в Python. С помощью пакета ccs-pykerberos у нас как раз и получилось реализовать задуманный функционал, далее я покажу немного кода, где реализуется общение с LDAP`ом и пользователем, а так же запрос в БД от его имени.
from django.shortcuts import render
from django.template.response import TemplateResponse
import kerberos
import psycopg2
def index(request):
if 'HTTP_AUTHORIZATION' in request.META:
kind, initial_client_token = request.META['HTTP_AUTHORIZATION'].split(' ', 1)
if kind == 'Negotiate':
service = 'HTTP@django-server-pricipal.che.ru'
_ignore_result, krb_context = kerberos.authGSSServerInit(service)
kerberos.authGSSServerStep(krb_context, initial_client_token)
principal = kerberos.authGSSServerUserName(krb_context)
_ignore_result = kerberos.authGSSServerStoreDelegate(krb_context)
conn = psycopg2.connect(
host='postgresql-server-host',
user=principal,
dbname='request-db',
)
cursor = conn.cursor()
cursor.execute("SELECT version()")
records = cursor.fetchall()
else:
unauthorized_template_name = 'gssapi_test/unauthorized.html'
response = TemplateResponse(request, 'gssapi_test/index.html', status=401)
response['WWW-Authenticate'] = 'Negotiate'
return response
content = {'records': records}
return render(request, 'gssapi_test/index.html', content)
Сначала нужно проверить передан ли нам заголовок авторизации, если нет — мы должны направить в ответ заголовок с Negotiate, и снова ждать от пользователя Negotiate токен. Этот токен выглядит как большая портянка закодированная в base64. После получения токена, мы инициализируем (авторизуем) сервер нашего приложения в LDAP сервисе, используя функцию authGSSServerInit(). Далее мы авторизуемся в LDAP сервисе от имени пользователя, используя для этого как раз тот токен, который получили из заголовка, функция authGSSServerStep(). Потом мы получаем principal пользователя, который будем использовать в качестве user, при выполнении запроса в БД. А так же, нам необходимо сформировать кэш битела Kerberos, который будет использован автоматически для того, что бы авторизовать нас в PostgreSQL, функция authGSSServerStoreDelegate(). Данная функция есть только в самой последней версии этого пакета, поэтому нужно клонировать себе исходники с git и сделать build.
Браузер должен быть настроен на отдачу Negotiate, по умолчанию эта опция отключена. Все тесты проводились на Firefox в CentOS7, так же на всех серверах был установлен CentOS7.
В добавок, у нас может возникнуть проблема, при которой кэш билета, сформированный нашей функцией, не будет виден нашему процессу и соответственно мы получим, что пользователь не авторизовался в GSSAPI. Она решается настройкой кеширования билетов в krb5.conf. На всякий случай приведу пример нашего конфига:
/etc/krb5.conf
includedir /etc/krb5.conf.d/
includedir /var/lib/sss/pubconf/krb5.include.d/
[libdefaults]
default_realm = DOMAIN.RU
dns_lookup_realm = false
dns_lookup_kdc = false
rdns = false
ticket_lifetime = 24h
forwardable = true
udp_preference_limit = 0
# если раскомментировать опцию - работать не будет
#default_ccache_name = KEYRING:persistent:%{uid}
#Нужно для определения параметра KRB5_KTNAME в облатси видимости приложения
default_keytab_name = FILE:/etc/httpd/http.keytab
[realms]
DOMAIN.RU = {
kdc = ldap-server-host.domain.ru:88
master_kdc = ldap-server-host.domain.ru:88
admin_server = ldap-server-host.domain.ru:749
kpasswd_server = ldap-server-host.domain.ru:464
default_domain = domain.ru
pkinit_anchors = FILE:/etc/domain/ca.crt
}
[domain_realm]
.domain.ru = DOMAIN.RU
domain.ru = DOMAIN.RU
.domain.ru = DOMAIN.RU
Данный кусок кода создан для того, что бы помочь понять как происходит авторизация и делегация прав, далее с этими знаниями можно строить декораторы авторизации и бэки общения с базой. Пакет ccs-pykerberos был разработан компанией Apple, для своих внутренних нужд, соответственно приведу ссылку на их код, где они его используют. Нам он очень помог в понимании того, что они разработали ccs-calendarserver/twistedcaldav/authkerb.py