Итак задача заключалась в следующем: необходимо было настроить прозрачную авторизацию через 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. На всякий случай приведу пример нашего конфига:
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
Полезные ссылки
ссылка на оригинал статьи https://habr.com/post/427839/
Добавить комментарий