Django auth ldap windows

Начало

Однажды мне пришлось заняться разработкой Web-приложения для корпоративного использования на Python+Django. И самым первым вопросом, который пришлось решать — это прозрачная авторизация на сайте или Single Sign-On (SSO).

На предприятии широко используется служба каталогов на базе Microsoft Active Directory, и к настоящему моменту практически все корпоративные приложения позволяют использовать windows-авторизацию и не вводить постоянно логины/пароли, поэтому новое приложение просто должно было удовлетворять существующему положению вещей и реализовывать указанную выше возможность для «прозрачной» авторизации пользователей.

Хотя о вопросе реализации SSO для Django написано немало статей, однако для того, чтобы реализовать то, что мне было необходимо, пришлось затратить относительно много времени. Поэтому, чтобы избавить некоторых из вас от возможных долгих поисков информации и ее сборки в работающую схему, предлагаю вам свой мануал, как сделать прозрачную авторизацию в приложении Django с использованием учетных записей Active Directory.

Итак мы имеем:

  • Служба каталогов Microsoft Active Directory,
  • Имя домена Windows: company.ru
  • Имя контроллера домена Windows 2012 Server: DC-1
  • IP Address контроллера домена Windows 2012 Server: 192.168.7.110
  • Сервер для работы нашего приложения: CentOS7, Apache, Python 3.5.1, Django 1.9.1
  • Hostname Linux Server с CentOS7: srv-app
  • IP Address Linux Server с CentOS7: 192.168.7.105
  • URL Приложения на Django: srv-app.company.ru

Нужно сделать:

  • Пользователь, зарегистрированный в Active Directory при открытии любой страницы сайта на srv-app.company.ru должен автоматически, без запроса логина/пароля, быть авторизован Django. При авторизации, в профиль пользователя в Django должна быть перенесена некоторая информация о нем из Active Directory (first_name, last_name, mail; флаги is_active, is_staff, is_supersuser должны быть установлены на основании членства пользователя в соответствующих группах Active Directory).
  • Пользователю, не зарегистрированному в Active Directory вход на сайт должен быть запрещен.

Изучив ряд опубликованных статей и описаний стало понятно, что добиться нужного результата можно, выполнив два основных шага:

  • Настройка прозрачной аутентификации при доступе к приложению сервером Apache с использованием Kerberos.
  • Авторизация в Django c использованием доступа к контроллеру домена по протоколу LDAP для получения необходимой информации об авторизующемся пользователе.

Этап 1. Настройка прозрачной аутентификации с использованием Kerberos

Совершенно очевидно, что реализация принципа SSO в сети Windows AD возможно используя протокол Kerberos. Поэтому основной задачей первого этапа настройки будет установка Kerberos в среде Linux+Apache и настройка связи с контроллером домена Windows AD.

Установка и настройка Kerberos на сервере Linux

Настройка /etc/hosts, /etc/resolv.conf на srv-app, DNS на DC-1

На srv-app добавляем в /etc/hosts:

    192.168.7.105   srv-app.company.ru

На srv-app изменяем /etc/resolv.conf (В реальности этот файл в CentOS7 генерирует NetworkManager, поэтому изменения нужно вносить в /etc/sysconfig/network-scripts/ifcfg-eth0):

    search company.ru
    nameserver 192.168.7.110

На Контроллере домена DC-1:

  • при помощи оснастки «Диспетчер DNS» добавим хост srv-app с адресом 192.168.7.105
  • при помощи оснастки «AD — пользователи и компьютеры» добавим пользователя svc-apache, установим для него пароль P@ssw0rd. Этот пользователь понадобится нам позже для создания keytab-файла, который свяжет нашу Linux-машину и Active Directory.

Устанавливаем модули для работы с Kerberos

    [root@srv-app ~]# yum install mod_auth_kerb     # Устанавливаем модуль для apache
    [root@srv-app ~]# yum install krb5-workstation    # Устанавливаем пакет для настройки и тестирования kerberos

Конфигурируем Kerberos при помощи редактирования файла /etc/krb5.conf

    [logging]
     default = FILE:/var/log/krb5libs.log
     kdc = FILE:/var/log/krb5kdc.log
     admin_server = FILE:/var/log/kadmind.log
     
    [libdefaults]
     dns_lookup_realm = false
     ticket_lifetime = 24h
     renew_lifetime = 7d
     forwardable = true
     rdns = false
     default_realm = COMPANY.RU
     default_ccache_name = KEYRING:persistent:%{uid}

    [realms]
     COMPANY.RU = {
      kdc = 192.168.7.110
      admin_server = 192.168.7.110
     }

    [domain_realm]
     .company.ru = COMPANY.RU
     company.ru = COMPANY.RU

Немного объяснений по поводу содержимого конфигурационного файла:

  • COMPANY.RU — задаем имя области kerberos (realm) в linux. Следует запомнить что kerberos realm чувствительный к регистру (case-sensetive)

Выполняем несколько проверок работы kerberos на компьютере srv-app

Ранее, на нашем контроллере домена мы создали пользователя srv-apache с паролем P@ssw0rd. Попробуем залогиниться на КД при помощи утилиты kinit:

    [root@dsrv-app ~]# kinit svc-apache@COMPANY.RU
    Password for admin@COMPANY.RU: ****

Если ошибок нет, посмотрим какие билеты (tickets) у нас имеются:

    [root@srv-app ~]# klist
    
    Ticket cache: FILE:/tmp/krb5cc_0
    Default principal: srv-apache@COMPANY.RU

    Ticket cache: KEYRING:persistent:0:0
    Default principal: svc-apache@COMPANY.RU

    Valid starting       Expires              Service principal
    20.12.2015 16:12:59  21.12.2015 02:12:59  krbtgt/COMPANY.RU@COMPANY.RU
            renew until 27.12.2015 16:12:55

Таким образом мы залогинились на КД при помощи kerberos, теперь разорвем соединение, удалив
полученный билет:

    [root@srv-app ~]# kdestroy

Если все работает, то для дальнейшей настройки нам необходимо создать файл krb5.keytab для сервиса аутентификации при помощи
Apache и mod_auth_kerb.

Генерация keytab на Контроллере Домена Windows

Сгенерировать keytab можно на контроллере домена DC-1 при помощи команды ktpass.exe:

    ktpass.exe /princ HTTP/srv-app.company.ru@COMPANY.RU /mapuser svc-apache@COMPANY.RU  /crypto ALL /ptype KRB5_NT_PRINCIPAL /mapop set /pass P@ssw0rd /out c:\share\keytab

  • Ключ /princ HTTP/srv-app.company.ru@COMPANY.RU задает уникальное имя клиента (principal) на Linux-машине, которому будет разрешено выполнять аутентификацию в kerberos.
  • Ключи /mapuser svc-apache@COMPANY.RU и /pass P@ssw0rd, связывают principal с конкретным пользователем в Active Directory
  • Ключ /crypto ALL задает способ шифрования. Вместо /crypto ALL можно указать конкретный способ шифрования например /crypto AES256-SHA1
  • Ключ /mapop set устанавливает mapping между принципалом Linux и пользовательским аккаутном Active Directory (/mapop add добавит этот маппинг в keytab)
  • Ключ /ptype KRB5_NT_PRINCIPAL — задать тип принципала в запросе (указанный тип является основным и рекомендуется использовать именно его)
  • Ключ /out c:\share\keytab задает путь для выходного файла keytab.
  • При желании дополнительно значения ключей можно посмотреть при помощи ktpass.exe /help

В итоге мы получаем файл c:\share\keytab, который необходимо скопировать на srv-app и назвать /etc/krb5.keytab. Далее необходимо предоставить доступ к этому файлу пользователю, из под учетной записи которого выполняется сервер httpd. В нашем случае это apache. Для того чтобы apache мог прочитать этот файл просто разрешаем его чтение всем пользователям:

   chmod a+r /etc/krb5.keytab

Проверить работает ли наш keytab можно следующим образом:

1. При помощи ktutil:

    [root@srv-app ~]# ktutil
    ktutil:  rkt /etc/krb5.keytab
    ktutil:  list
    slot KVNO Principal
    ---- ---- ---------------------------------------------------------------------
       1    3           HTTP/srv-app.company.ru@COMPANY.RU
       2    3           HTTP/srv-app.company.ru@COMPANY.RU
       3    3           HTTP/srv-app.company.ru@COMPANY.RU
       4    3           HTTP/srv-app.company.ru@COMPANY.RU
       5    3           HTTP/srv-app.company.ru@cCOMPANY.RU
    ktutil:  q

2. При помощи kvno:

    # логинимся на KDC
    [root@srv-app ~]# kinit svc-apache
    Password for svc-apache@COMPANY.RU:

    # запрашивем тикет для сервиса <b>HTTP/srv-app.company.ru@COMPANY.RU</b> и печатает номера версий для каждого принципала в keytab
    [root@srv-app ~]# kvno HTTP/srv-app.company.ru@COMPANY.RU
    HTTP/srv-app.company.ru@COMPANY.RU: kvno = 3
    
    # Удаляем тикет
    [root@srv-app ~]# kdestroy

Настройка Apache

Ниже приведен файл /etc/httpd/conf.d/company_main.conf, который содержит конфигурационные инструкции для настройки Kerberos-аутенификации при обращении к URI «/»:

    <Location "/">
       # Kerberos authentication:
       AuthType Kerberos
       AuthName "SRV-APP auth"
       KrbMethodNegotiate on
       KrbMethodK5Passwd off
       KrbServiceName HTTP/srv-app.company.ru@COMPANY.RU
       KrbAuthRealms COMPANY.RU
       Krb5Keytab /etc/krb5.keytab
       KrbLocalUserMapping On
       Require valid-user
    </Location>

Хочу обратить внимание на настройку KrbMethodK5Passwd off. Указанная настройка приводит к тому что при входе в указанный раздел сайта будет произведена kerberos-аутентификациия с использованием технологии Single Sign-On. При неуспешной аутентификации сразу будет ошибка «401 Unautorized». Однако, если изменить настройку на KrbMethodK5Passwd on, то после неуспешной авторизации Single Sign-On, будет предпринята попытка авторизации по имени и паролю.

И еще одна недокументированная возможность, которой мы воспользуемся: Настройка KrbLocalUserMapping On приводит к тому что в переменной REMOTE_USER будет помещено имя зарегистрированного пользователя (в случае KrbLocalUserMapping Off REMOTE_USER будет содержать username@COMPANY RU).

Дополнительную информацию по настройкам модуля mod_auth_kerb пожно прочитать здесь.

Single Sign-On (SSO) с рабочих станций Windows

Еще раз повторюсь, что вся работа по настройке kerberos аутентификации в Linux проделана для того, чтобы иметь возможность входить на страницы портала опубликованного на Linux машине с использованием корпоративных аккаунтов, хранящихся в Active Directory, кроме того для упрощения жизни пользователям этот вход должен быть «прозрачным», без запроса пароля, что достигается иcпользованием технологии Single Sign-On (SSO), которая поддерживается в Windows 7 и выше и браузером Internet Explorer (и Mozilla Firefox).

Однако для того чтобы все проходило гладко, на рабочей станции, откуда осуществляется такой вход, должны быть выполнены следующие настройки:

  1. Сайт на который осуществляется вход (в нашем случае srv-app.company.ru должен быть внесен в узлы «Местной интрасети» при помощи меню
    Internet Explorer: Сервис -> Свойства обозревателя -> Безопасность -> Местная интрасеть -> Узлы -> Дополнительно
  2. В IE должны быть включены следующие опции (как правило они включены «по умолчанию»):
    • Сервис -> Свойства обозревателя -> Дополнительно -> Безопасность -> Разрешить встроенную проверку Windows = ON
    • Сервис -> Свойства обозревателя -> Безопасность -> Местная интрасеть -> Уровень безопасности для этой зоны -> Другой -> Проверка подлинности пользователя -> Автоматический вход в сеть только в зоне интрасети

  3. Кроме того, SSO при попытке доступа на сайт по IP-адресу работать не будет. Необходимо обязательно использовать доменное имя srv-app.company.ru !

Этап 2. Авторизация пользователя в Django

Итак, в результате работы проведенной на первом этапе, мы получили следующие результаты:

  • При входе в любой раздел нашего Django-приложения, пользователем, который был авторизован контроллером домена, мы во-первых получаем доступ к нашему Django-приложению, а во-вторых сервер Apache помещает в переменную REMOTE_USER имя авторизованного пользователя, которое совпадает с аттрибутом sAMAccountName этого пользователя в Active Directory.
  • Если пользователь не авторизован в АД, то Apache вернет нам ошибку «401 Unautorized» (При помощи опции ErrorDocument мы можем в этом случае перенаправить неавторизованного пользователя на какую-либо страницу для гостей)

Однако, несмотря на то, что сервер Apache авторизовал нашего пользователя, для Django-приложения он все еще остается неизвестным и, соответственно, весь отлаженный в Django механизм аутентификации/авторизации пользователей и использования сессий остается пока незадействованным.

Использование RemoteUserBackend

Специально для таких целей в Django существует простое решение, включающее механизм авторизации в системе пользователей, уже аутентифицированных внешними приложениями, такими как IIS или Apache (способами аналогичными, примененному нами на этапе 1: mod_authnz_ldap, CAS, Cosign, WebAuth, mod_auth_sspi, mod_auth_krb).

Согласно описанию на официальном сайте djangoproject.org, для использования прозрачной аутентификации и авторизации в Django таким способом, достаточно задействовать механизм RemoteUserBackend, выполнив следующие шаги:

1. В файле настроек Django-проекта settings.py добавить django.contrib.auth.middleware.RemoteUserMiddleware в список MIDDLEWARE_CLASSES сразу после django.contrib.auth.middleware.AuthenticationMiddleware:

MIDDLEWARE_CLASSES = [
    '...',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.RemoteUserMiddleware',
    '...',
]

2. Там же заменить ModelBackend на RemoteUserBackend в списке AUTHENTICATION_BACKENDS (либо добавить этот список в settings.py):

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.RemoteUserBackend',
]

В результате этих изменений RemoteUserMiddleware будет извлекать username посредством request.META[‘REMOTE_USER’] и автоматически выполнять аутентификацию и авторизацию (login) этого пользователя при помощи RemoteUserBackend. Кроме того RemoteUserBackend при такой авторизации добавит нового пользователя в таблицу auth_user, задействуя таким образом стандартный для Django механизм работы с учетными записями.

Но, к сожалению, этот способ не позволит нам получать из Active Directory и использовать в нашем приложении нужную нам информацию (first_name, last_name, mail, участие в группах AD и др).

Использование django-auth-ldap

Итак, нам нужно получить доступ к Active Directory при помощи протокола LDAP. К счастью отличным Django-приложением для этих целей является django-auth-ldap. Установить его можно стандартно при помощи pip:

pip install django-auth-ldap

После этого придется удалить из settings.py добавленные в предыдущем разделе MIDDLEWARE_CLASSES, а именно ‘django.contrib.auth.middleware.RemoteUserMiddleware’, а список AUTHENTICATION_BACKENDS должен выглядеть следующим образом:

AUTHENTICATION_BACKENDS = (
    'django_auth_ldap.backend.LDAPBackend',
    'django.contrib.auth.backends.ModelBackend',
)

Кроме того, в settings.py необходимо включить следующие конфигурационные параметры:

settings.py

# Baseline LDAP configuration.
AUTH_LDAP_SERVER_URI = "ldap://DC-1.COMPANY.ru"
AUTH_LDAP_AUTHORIZE_ALL_USERS = True
AUTH_LDAP_PERMIT_EMPTY_PASSWORD = True

# Логин пользователя от чьего имени будут выполнятся запросы к LDAP (кроме авторизации)
AUTH_LDAP_BIND_DN = "cn=svc-apache,cn=Users,dc=company,dc=ru"
AUTH_LDAP_BIND_PASSWORD = "P@ssw0rd"

# Настройка будет пытаться найти пользователя в созданной нами OU Django и стандартной папке Users, 
# сопоставляя введенный login пользователя с аттрибутами sAMAccountName
AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(
        LDAPSearch("ou=Django,dc=company,dc=ru", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)"),
        LDAPSearch("cn=Users,dc=company,dc=ru", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)"),
)

# Set up the basic group parameters.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch("ou=Groups,ou=Django,dc=company,dc=ru",
    ldap.SCOPE_SUBTREE, "(objectClass=group)"
)
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn")

# Simple group restrictions
# AUTH_LDAP_REQUIRE_GROUP - если определено DN для этой настройки, то требуется присутсвие пользователя в этой группе
# в противном случае пользовталю будет отказано в аутентификации
# таким образом указываем, что для того чтобы пользователь был аутентифицирован он обязан находится в группе "active"
AUTH_LDAP_REQUIRE_GROUP = "cn=active,ou=Groups,ou=Django,dc=company,dc=ru"

# AUTH_LDAP_DENY_GROUP - если определено DN для этой настройки, то в случае члентсва пользователя в этой группе
# ему будет отказано в аутентификации
AUTH_LDAP_DENY_GROUP = "cn=disabled,ou=Groups,ou=Django,dc=company,dc=ru"

# Populate the Django user from the LDAP directory.
# Указываем как переносить данные из AD в стандартный профиль пользователя Django
AUTH_LDAP_USER_ATTR_MAP = {
    "first_name": "givenName",
    "last_name": "sn",
    "email": "mail"
}
# Указываем как переносить данные из AD в расширенный профиль пользователя Django
AUTH_LDAP_PROFILE_ATTR_MAP = {
    "employee_number": "employeeNumber"
}

# Указываем привязку стандартных флагов is_active, is_staff и is_superuser к членству в группах AD
# Флаг is_active при использовании django_remote_auth_ldap сам по себе не оказывает вляния на разрешение аутнтификации
# поэтому для создания обычного поведения Django также определяме настройку AUTH_LDAP_REQUIRE_GROUP (см.выше)
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
    "is_active": "cn=active,ou=Groups,ou=Django,dc=company,dc=ru",
    "is_staff": "cn=staff,ou=Groups,ou=Django,dc=company,dc=ru",
    "is_superuser": "cn=superuser,ou=Groups,ou=Django,dc=company,dc=ru"
}

# Указываем привязку флагов расширенного профиля к членству в группах AD
AUTH_LDAP_PROFILE_FLAGS_BY_GROUP = {
    "is_awesome": "cn=awesome,ou=Groups,ou=Django,dc=company,dc=ru",
}

# This is the default, but I like to be explicit.
AUTH_LDAP_ALWAYS_UPDATE_USER = True

# Use LDAP group membership to calculate group permissions.
AUTH_LDAP_FIND_GROUP_PERMS = True

# Cache group memberships for an hour to minimize LDAP traffic
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600

Django-auth-ldap — это замечательное приложение. С его помощью в профиль пользователя Django можно загрузить практически любую информацию по нему из AD, даже есть возможность «подтянуть» группы в которых этот пользователь участвует в соответствующую модель Django.

Однако для авторизации при помощи django-auth-ldap все-таки необходимо запрашивать у пользователя его имя и пароль, что нам категорически не подходит.

Хотя в документации указано что, вроде бы, можно «скрестить» django-auth-ldap и RemoteUserBackend:

Non-LDAP Users

LDAPBackend has one more feature pertaining to permissions, which is the ability to handle authorization for users that it did not authenticate. For example, you might be using RemoteUserBackend to map externally authenticated users to Django users. By setting AUTH_LDAP_AUTHORIZE_ALL_USERS, LDAPBackend will map these users to LDAP users in the normal way in order to provide authorization information. Note that this does not work with AUTH_LDAP_MIRROR_GROUPS; group mirroring is a feature of authentication, not authorization.

Но это не работает, так как нам надо. Пользователь действительно сможет авторизоваться, но это происходит ровно так, как если бы мы просто использовали RemoteUserBackend (см.предыдущий раздел). Информация из AD в профиль пользователя Django автоматически не загружается.

Конечно, это можно сделать самостоятельно, воспользовашись следующей рекомендацией:

Updating Users

By default, all mapped user fields will be updated each time the user logs in. To disable this, set AUTH_LDAP_ALWAYS_UPDATE_USER to False. If you need to populate a user outside of the authentication process—for example, to create associated model objects before the user logs in for the first time—you can call django_auth_ldap.backend.LDAPBackend.populate_user(). You’ll need an instance of LDAPBackend, which you should feel free to create yourself. populate_user() returns the User or None if the user could not be found in LDAP.

from django_auth_ldap.backend import LDAPBackend

user = LDAPBackend().populate_user('alice')
if user is None:
    raise Exception('No user named alice')

Использование django-remote-auth-ldap

Но, как оказалось, все гораздо проще. Велосипед уже придуман, и нам остается только им воспользоватся. Приложение django-remote-auth-ldap является небольшой надстройкой над django_auth_ldap и позволяет без лишних усилий авторизовать пользователя и загрузить его данные из AD во время авторизации.

Устанавливаем django-remote-auth-ldap стандартно (django-auth-ldap также необходим для работы этой надстройки):

pip install django-remote-auth-ldap

Далее, необходимо добавить в settings.py следующую настройку:

DRAL_CHECK_DOMAIN = False

Дело в том, что django-remote-auth-ldap, видимо разработан для работы c IIS, который переменную REMOTE_USER устанавливает в формате «DOMAIN/username», мы же выполнили настройку mod_auth_kerb так, что имя домена в REMOTE_USER не попадает. Указанная выше настройка заставляет django-remote-auth-ldap считать что в REMOTE_USER только одно имя пользователя без указания домена, т.е. именно так, как нам и нужно.
Ну и снова рекомендации для настройки MIDDLEWARE_CLASSES и AUTHENTICATION_BACKENDS:

1. В файле настроек Django-проекта settings.py добавить django.contrib.auth.middleware.RemoteUserMiddleware в список MIDDLEWARE_CLASSES сразу после django.contrib.auth.middleware.AuthenticationMiddleware:

MIDDLEWARE_CLASSES = [
    '...',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.RemoteUserMiddleware',
    '...',
]

2. AUTHENTICATION_BACKENDS должен выглядеть так:

AUTHENTICATION_BACKENDS = [
    'django_remote_auth_ldap.backend.RemoteUserLDAPBackend',
]

На этом все, прозрачная аутентификация в Django настроена.

Если пользователь входящий в ваше Django-приложение, уже авторизован в Active Directory, то:

1. Apache авторизует его при помощи Kerberos, допустит к страницам вашего приложения и запишет в REMOTE_USER имя авторизованного пользователя.
2. RemoteUserMiddleware «увидит» значение REMOTE_USER и иницирует аутентификацию и авторизацию указанного пользователя в Django с помощью django-remote-auth-ldap
3. django-remote-auth-ldap аутентифицирует и авторизует пользователя при помощи методов, унаследованных от приложения django-auth-ldap, которое «подтянет» в Django необходимую вам информацию из Active Directory.

Django Authentication Using LDAP

This is a Django authentication backend that authenticates against an LDAP
service. Configuration can be as simple as a single distinguished name
template, but there are many rich configuration options for working with users,
groups, and permissions.

  • Documentation: https://django-auth-ldap.readthedocs.io/
  • PyPI: https://pypi.org/project/django-auth-ldap/
  • Repository: https://github.com/django-auth-ldap/django-auth-ldap
  • License: BSD 2-Clause

Installation

Install the package with pip:

$ pip install django-auth-ldap

It requires python-ldap >= 3.1. You’ll need the OpenLDAP libraries and
headers available on your system.

To use the auth backend in a Django project, add
'django_auth_ldap.backend.LDAPBackend' to AUTHENTICATION_BACKENDS. Do
not add anything to INSTALLED_APPS.

AUTHENTICATION_BACKENDS = [
    'django_auth_ldap.backend.LDAPBackend',
]

LDAPBackend should work with custom user models, but it does assume that a
database is present.

Note

LDAPBackend does not inherit from ModelBackend. It is possible to
use LDAPBackend exclusively by configuring it to draw group membership
from the LDAP server. However, if you would like to assign permissions to
individual users or add users to groups within Django, you’ll need to have
both backends installed:

AUTHENTICATION_BACKENDS = [
    'django_auth_ldap.backend.LDAPBackend',
    'django.contrib.auth.backends.ModelBackend',
]

Example Configuration

Here is a complete example configuration from settings.py that exercises
nearly all of the features. In this example, we’re authenticating against a
global pool of users in the directory, but we have a special area set aside for
Django groups (ou=django,ou=groups,dc=example,dc=com). Remember that most
of this is optional if you just need simple authentication. Some default
settings and arguments are included for completeness.

import ldap
from django_auth_ldap.config import LDAPSearch, GroupOfNamesType


# Baseline configuration.
AUTH_LDAP_SERVER_URI = 'ldap://ldap.example.com'

AUTH_LDAP_BIND_DN = 'cn=django-agent,dc=example,dc=com'
AUTH_LDAP_BIND_PASSWORD = 'phlebotinum'
AUTH_LDAP_USER_SEARCH = LDAPSearch(
    'ou=users,dc=example,dc=com',
    ldap.SCOPE_SUBTREE,
    '(uid=%(user)s)',
)
# Or:
# AUTH_LDAP_USER_DN_TEMPLATE = 'uid=%(user)s,ou=users,dc=example,dc=com'

# Set up the basic group parameters.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
    'ou=django,ou=groups,dc=example,dc=com',
    ldap.SCOPE_SUBTREE,
    '(objectClass=groupOfNames)',
)
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr='cn')

# Simple group restrictions
AUTH_LDAP_REQUIRE_GROUP = 'cn=enabled,ou=django,ou=groups,dc=example,dc=com'
AUTH_LDAP_DENY_GROUP = 'cn=disabled,ou=django,ou=groups,dc=example,dc=com'

# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {
    'first_name': 'givenName',
    'last_name': 'sn',
    'email': 'mail',
}

AUTH_LDAP_USER_FLAGS_BY_GROUP = {
    'is_active': 'cn=active,ou=django,ou=groups,dc=example,dc=com',
    'is_staff': 'cn=staff,ou=django,ou=groups,dc=example,dc=com',
    'is_superuser': 'cn=superuser,ou=django,ou=groups,dc=example,dc=com',
}

# This is the default, but I like to be explicit.
AUTH_LDAP_ALWAYS_UPDATE_USER = True

# Use LDAP group membership to calculate group permissions.
AUTH_LDAP_FIND_GROUP_PERMS = True

# Cache distinguished names and group memberships for an hour to minimize
# LDAP traffic.
AUTH_LDAP_CACHE_TIMEOUT = 3600

# Keep ModelBackend around for per-user permissions and maybe a local
# superuser.
AUTHENTICATION_BACKENDS = (
    'django_auth_ldap.backend.LDAPBackend',
    'django.contrib.auth.backends.ModelBackend',
)

Contributing

If you’d like to contribute, the best approach is to send a well-formed pull
request, complete with tests and documentation. Pull requests should be
focused: trying to do more than one thing in a single request will make it more
difficult to process.

If you have a bug or feature request you can try logging an issue.

There’s no harm in creating an issue and then submitting a pull request to
resolve it. This can be a good way to start a conversation and can serve as an
anchor point.

Install the package with pip:

$ pip install django-auth-ldap

It requires python-ldap >= 3.0. You’ll need the OpenLDAP libraries and
headers available on your system.

To use the auth backend in a Django project, add
'django_auth_ldap.backend.LDAPBackend' to
AUTHENTICATION_BACKENDS. Do not add anything to
INSTALLED_APPS.

AUTHENTICATION_BACKENDS = ["django_auth_ldap.backend.LDAPBackend"]

LDAPBackend should work with custom user
models, but it does assume that a database is present.

Note

LDAPBackend does not inherit from
ModelBackend. It is possible to use
LDAPBackend exclusively by configuring
it to draw group membership from the LDAP server. However, if you would
like to assign permissions to individual users or add users to groups
within Django, you’ll need to have both backends installed:

AUTHENTICATION_BACKENDS = [
    "django_auth_ldap.backend.LDAPBackend",
    "django.contrib.auth.backends.ModelBackend",
]

Django will check each authentication backend in order, so you are free to
reorder these if checking
ModelBackend first is more
applicable to your application.

Рассмотренный ранее сервер Patchman на базе веб-фреймворка Django, в нашем случае функционирует в инфраструктуре, имеющей службу каталогов Microsoft Active Directory, поэтому для большего удобства работы с веб-интерфейсом Patchman возникает желание настроить на веб-сервере аутентификацию пользователей с помощью протокола Kerberos, а также авторизацию с помощью протокола LDAP через доменные группы безопасности. То есть, предполагается реализация механизма SSO (Single Sign-On) для того, чтобы пользователи не вводили какие-то локальные учётные записи каждый раз при входе в веб-интерфейс Patchman, а прозрачно получали доступ к сайту на основе своей доменной учётной записи, в контексте которой запущен клиентский браузер.Так как веб-интерфейс Patchman работает на базе веб-фреймворка Django, то, по сути, описываемая далее настройка будет относится именно к этому фреймворку и пример рассмотренной далее конфигурации может быть применим и для других проектов, реализованных на базе Django.

Всю настройку условно можно разделить на 3 части:

  • Настройка веб-сервера Apache для работы с аутентификацией Kerberos;
  • Настройка фреймворка Django на LDAP-авторизацию с помощью модуля django-auth-ldap;
  • Дополнительная конфигурация модуля django-remote-auth-ldap, чтобы «подружить» Kerberos аутентификацию с LDAP авторизацией.
Часть 1. Kerberos и Apache

В рамках данной заметки мы не будем рассматривать процедуры подключения сервера к домену или получения keytab-файла, необходимого для работы Kerberos в Linux. Предполагаем, что наш сервер Patchman уже подключен к домену, например так, как это описано в Вики-статье «Подключение Debian GNU/Linux 12 (Bookworm) к домену Active Directory с помощью SSSD и настройка PAM для доменной аутентификации и авторизации в SSHD». То есть в системе уже имеется keytab-файл, используемый для процессов доменной аутентификации Kerberos. В этом файле, как минимум, должны быть записи типа host/<server fqdn>.

Проверим содержимое записей keytab-файла:

# klist -e -k -t /etc/krb5.keytab

Регистрируем в домене для учётной записи веб-сервера SPN-запись вида HTTP/<server fqdn> и добавляем в keytab-файл соответствующие записи. Подробно эта процедура описана ранее в статье «Добавление SPN записей в keytab-файл (на стороне сервера Linux с помощью утилиты ktutil), связанный с учётной записью Computer в домене Active Directory».

Когда keytab-файл подготовлен, с помощью утилиты setfacl выставляем на него дополнительные разрешения, чтобы пользователь, от имени которого работает служба веб-сервера Apache, имел к этому файлу доступ на чтение:

# apt-get install acl
# setfacl -m u:www-data:r /etc/krb5.keytab

Проверяем результирующие права на файл:

# getfacl -p /etc/krb5.keytab
# file: /etc/krb5.keytab
# owner: root
# group: root
user::rw-
user:www-data:r--
group::---
mask::r--
other::---

Установим и активируем модуль GSSAPI для веб-сервера Apache:

# apt-get install libapache2-mod-auth-gssapi
# a2enmod auth_gssapi
# systemctl restart apache2

Настраиваем конфигурацию виртуальных каталогов веб-сервера:

# nano /etc/apache2/conf-enabled/patchman.conf

В самый конец секции описания каталога /patchman/reports/upload добавим правило, разрешающее доступ без аутентификации, но только после прохождения проверки правил «Require ip». Это нужно сделать, чтобы клиенты Patchman, отправляющие POST запрос с клиентским отчётом на данный URL не получали от веб-сервера требования на Kerberos аутентификацию. То есть данная секция примет примерно следующий вид:

<Location /patchman/reports/upload>
    # Add the IP addresses of your client networks/hosts here
    # to allow uploading of reports
    Require ip 192.168.1.0/255.255.255.0
    Require ip 127.0.0.0/255.0.0.0
    Require ip ::1/128
    Satisfy Any
</Location>

А в конец файла добавим секцию, определяющую настройки для всего каталога /patchman, где укажем, что требуется Kerberos аутентификация.

<Location /patchman>
    AuthType GSSAPI
    GssapiLocalName On
    GssapiCredStore keytab:/etc/krb5.keytab
    Require valid-user
</Location>

Перезапустим службу веб-сервера:

# systemctl restart apache2

Проверим доступ к сайту Patchman из браузера, запущенного в контексте доменного пользователя. Как минимум, мы не должны получать никаких ошибок веб-сервера и появления всплывающих окон браузера с запросом на ввод логина и пароля. При этом на клиентском компьютере в кешэ билетов Kerberos для текущего пользователя (в Windows проверяется командой klist) должен появится билет вида HTTP/<server fqdn> с именем нашего сервера Patchman.

Часть 2. Django и LDAP-авторизация с помощью модуля django-auth-ldap

В этой части мы настроим и проверим работу LDAP-авторизации при доступе пользователей к веб-страницам Patchman (пока безотносительно того, что выше мы настроили Kerberos).

Для возможности авторизации пользователей в Django с помощью протокола LDAP нам потребуется доустановить модуль django-auth-ldap. В онлайн-документации предлагается устанавливать этот модуль командой «pip install django-auth-ldap», но в стандартных репозиториях Debian 12 данный модуль есть в виде отдельного пакета, поэтому можем его установить так:

# apt-get install python3-django-auth-ldap

В качестве зависимостей к этому пакету подтянется ещё несколько дополнительных пакетов — python3-ldap, python3-pyasn1, python3-pyasn1-modules.

В нашем примере для доступа пользователей к веб-страницам Patchman будет использоваться простая двух-уровневая модель – группа администраторов и группа пользователей с уровнем прав «только на чтение». Для этих уровней доступа создадим в домене Active Directory две соответствующие локальные группы безопасности, например «Patchman-Admins» и «Patchman-Viewers«.

Отредактируем основной конфигурационный файл Patchman:

# nano /etc/patchman/local_settings.py

Добавим в конец файла ряд параметров, определяющих настройки LDAP-авторизации. Далее я буду фрагментарно приводить добавляемые блоки параметров, приводя по ним некоторые замечания.

По умолчанию в Django в составе Patchman настроен только один бакэнд аутентификации (ModelBackend), который использует встроенные учётные записи пользователей, хранящиеся в БД. В секции AUTHENTICATION_BACKENDS мы подключаем и делаем более приоритетным LDAPBackend. То есть, сначала авторизация будет проходить через LDAP, а в случае неудачи будет предпринята попытка использовать авторизацию в локальной базе пользователей. Если есть желание совсем отключить локальную авторизацию, то можно просто закомментировать строку с ModelBackend.

# Keep ModelBackend around for per-user permissions and maybe a local superuser.
AUTHENTICATION_BACKENDS = (
    "django_auth_ldap.backend.LDAPBackend",
    "django.contrib.auth.backends.ModelBackend",
)

Далее добавляем блок параметров необходимых для bind-а к LDAP каталогу. Обратите внимание на то, что я намеренно привожу некоторые закомментированные параметры, которые не нужны или не будут работать в нашем конкретном случае, но могут оказаться полезными в других инфраструктурных окружениях.

В параметре AUTH_LDAP_SERVER_URI определяем адрес LDAP сервера. В нашем случае это контроллеры домена AD. Использовать незащищённое подключение к LDAP серверу не рекомендуется, поэтому можно настроить защищённое соединение LDAPS (как в закомментированной строке) или StartTLS (как используется в нашем случае).

В параметре AUTH_LDAP_GLOBAL_OPTIONS определено, что мы доверяем любым сертификатам, которые предоставляет удалённый LDAP сервер для защиты соединения. Такая конфигурация несколько понижает уровень безопасности соединения, но избавляет нас от дополнительных манипуляций с необходимостью добавления сертификата LDAP сервера, а также сертификатов удостоверяющих центров сертификации, в перечень доверенных.

Параметры AUTH_LDAP_BIND_DN и AUTH_LDAP_BIND_PASSWORD определяют специальную сервисную учётную запись, от имени которой будет выполняться подключение к LDAP-серверу. В нашем случае эти параметры необходимы, так как анонимное подключение к LDAP-каталогу запрещено.

В том случае, если в LDAP-каталоге все учётные записи пользователей хранятся в одной структурной единице (OU), то можно попробовать улучшить данную конфигурацию включением опции AUTH_LDAP_BIND_AS_AUTHENTICATING_USER и указанием шаблона поиска учётной записи в опции AUTH_LDAP_USER_DN_TEMPLATE. В этом случае можно закомментировать/удалить опции AUTH_LDAP_BIND_DN и AUTH_LDAP_BIND_PASSWORD и не создавать отдельной сервисной учётной записи для подключения к LDAP-каталогу, так как подключение в этом случае будет выполняться от имени той учётной записи пользователя, который проходит аутентификацию. Однако, это будет иметь смысл лишь в том случае, если мы не планируем использовать SSO с Kerberos.

Параметр AUTH_LDAP_CACHE_TIMEOUT отвечает за время кеширования данных, полученных из LDAP-каталога, для сокращения объёма запросов к LDAP-серверам. Этот параметр имеет смысл использовать при условии большой активности пользователей при работе с веб-приложением.

# Baseline configuration.
#AUTH_LDAP_SERVER_URI = "ldaps://dc01.holding.com:636"
AUTH_LDAP_SERVER_URI = "ldap://dc01.holding.com,ldap://dc02.holding.com"
AUTH_LDAP_START_TLS = True
AUTH_LDAP_GLOBAL_OPTIONS = {ldap.OPT_X_TLS_REQUIRE_CERT: ldap.OPT_X_TLS_NEVER}
AUTH_LDAP_CONNECTION_OPTIONS = {ldap.OPT_REFERRALS: 0}
AUTH_LDAP_BIND_DN = "CN=Patchman-LDAP,OU=Service Accounts,DC=holding,DC=com"
AUTH_LDAP_BIND_PASSWORD = "mYp!zsw0rD"
#AUTH_LDAP_USER_DN_TEMPLATE = "CN=%(user)s,OU=staff,DC=example,DC=com"
#AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = True
# Cache distinguished names and group memberships for an hour to minimize LDAP traffic.
#AUTH_LDAP_CACHE_TIMEOUT = 3600

Далее в конфигурационный фал добавляем директивы загрузки вспомогательных функций для работы с LDAP, к которым мы будем обращаться ниже в конфигурационном файле.

import ldap
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion, LDAPGroupQuery, GroupOfNamesType

После этого добавляем блок с параметром AUTH_LDAP_GROUP_SEARCH, где определяется фильтр поиска доменных групп. Здесь должен быть указан OU, в котором мы храним наши группы доступа «Patchman-Admins» и «Patchman-Viewers«.

# Set up the basic group parameters.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
    "OU=Security Groups,DC=holding,DC=com",
    ldap.SCOPE_SUBTREE,"(objectClass=group)",
)
AUTH_LDAP_GROUP_TYPE = GroupOfNamesType(name_attr="cn")

Затем добавляем блок с параметрами AUTH_LDAP_REQUIRE_GROUP и AUTH_LDAP_DENY_GROUP, где можем указать группы, для которых явно разрешён и явно запрещён доступ к веб-приложению. В закомментированных строках приведены примеры того, как может выглядеть данная конфигурация. В нашем примере используется только параметр AUTH_LDAP_REQUIRE_GROUP и в нём мы указываем обе группы доступа, в качестве обязательных, то есть если пользователь не входит ни в одну из этих групп, доступ ему будет запрещён.

# Simple group restrictions
#AUTH_LDAP_REQUIRE_GROUP = "cn=enabled,ou=django,ou=groups,dc=example,dc=com"
#AUTH_LDAP_DENY_GROUP = "cn=disabled,ou=django,ou=groups,dc=example,dc=com"
# Or:
#from django_auth_ldap.config import LDAPGroupQuery
#AUTH_LDAP_REQUIRE_GROUP = (
#    LDAPGroupQuery("cn=enabled,ou=groups,dc=example,dc=com")
#    | LDAPGroupQuery("cn=also_enabled,ou=groups,dc=example,dc=com")
#) & ~LDAPGroupQuery("cn=disabled,ou=groups,dc=example,dc=com")
AUTH_LDAP_REQUIRE_GROUP = (
    LDAPGroupQuery("CN=Patchman-Viewers,OU=Security Groups,DC=holding,DC=com")
    | LDAPGroupQuery("CN=Patchman-Admins,OU=Security Groups,DC=holding,DC=com")
)

Далее добавляем блок с параметром AUTH_LDAP_USER_SEARCH с описанием всех OU, в которых следует искать учёную запись авторизуемого пользователя. Если все учётные записи пользователей хранятся в рамках одного конкретного OU, то согласно рекомендациям документа «Performance», вместо параметра AUTH_LDAP_USER_SEARCH можно использовать параметр AUTH_LDAP_USER_DN_TEMPLATE, который увеличивает скорость получения информации из LDAP-каталога и уменьшает количество запросов к этому каталогу. В нашем примере учётные записи пользователей веб-приложения хранятся в разных OU, поэтому мы перечисляем все возможные значения.

#AUTH_LDAP_USER_SEARCH = LDAPSearch(
#    "ou=users,dc=example,dc=com", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)"
#)
# Or:
#AUTH_LDAP_USER_DN_TEMPLATE = 'sAMAccountName=%(user)s,ou=users,dc=example,dc=com'
AUTH_LDAP_USER_SEARCH = LDAPSearchUnion(
    LDAPSearch("OU=Administrators,OU=Services,DC=holding,DC=com", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)"),
    LDAPSearch("OU=Operators,OU=Users,DC=holding,DC=com", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)"),
    LDAPSearch("OU=Testers,DC=holding,DC=com", ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)"),
)

В случае успешной LDAP-авторизации учётная запись пользователя создаётся в локальной базе данных Django, копируя при этом атрибуты из LDAP-каталога. Следующий блок описывает правила сопоставления атрибутов создаваемого локального пользователя с атрибутами пользователя из Active Directory.

# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {
    "first_name": "givenName",
    "last_name": "sn",
    "email": "mail",
}
AUTH_LDAP_ALWAYS_UPDATE_USER = True

В следующем важном блоке описываем правила сопоставления доменных групп пользователей с флагами is_active, is_staff, is_superuser, которые определяют уровень доступа к веб-приложению. Это те самые флаги, которые можно найти при просмотре свойств пользователя в административном интерфейсе Django:

Django permissions flags

Любой действующий пользователь в независимости от уровня доступа должен иметь включенный флаг is_active, поэтому в секции данного флага мы прописываем обе наши доменные группы безопасности. Пользователи с одним только включенным флагом is_active будут иметь самый ограниченный вид доступа в режиме «только на чтение» и не будут иметь доступа к веб-страницам администрирования Django. Флаги is_staff и is_superuser дают больше полномочий и определяют разные уровни доступа, но в рассматриваемом нами примере это разделение не важно, поэтому мы вписываем в эти секции этих флагов только группу административного доступа.

AUTH_LDAP_USER_FLAGS_BY_GROUP = {
    "is_active": (
        LDAPGroupQuery("CN=Patchman-Viewers,OU=Security Groups,DC=holding,DC=com")
        | LDAPGroupQuery("CN=Patchman-Admins,OU=Security Groups,DC=holding,DC=com")
    ),
    "is_staff": "CN=Patchman-Admins,OU=Security Groups,DC=holding,DC=com",
    "is_superuser": "CN=Patchman-Admins,OU=Security Groups,DC=holding,DC=com",
}
# Use LDAP group membership to calculate group permissions.
AUTH_LDAP_FIND_GROUP_PERMS = True

Ну и, наконец, завершающий блок конфигурационного файла local_settings.py может содержать секцию LOGGING. Здесь могут быть подключены разные логгеры для целей отладки. В нашем примере описано подключение расширенного логирования для бакенда django_auth_ldap, что поможет диагностировать проблемы LDAP-авторизации в лог-файле веб-сервера. Разумеется, на отлаженной продуктивной системе такой блок лучше закомментировать.

# Enable debug for django_auth_ldap in /var/log/apache2/error.log
LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {"console": {"class": "logging.StreamHandler"}},
    "loggers": {"django_auth_ldap": {"level": "DEBUG", "handlers": ["console"]}},
}

Сохраним изменения в файле local_settings.py и, учитывая то обстоятельство, что теперь этот конфигурационный файл содержит логин и пароль сервисной учётной записи для доступа к LDAP-каталогу, проведём корректировку прав доступа к этому файлу:

# chmod 640 /etc/patchman/local_settings.py

Выполним перезапуск службы веб-сервера:

# systemctl restart apache2

Теперь можем перейти на веб-страницу входа в веб-интерфейс Patchman и попытаться авторизоваться разными доменными пользователями, как входящими в группы безопасности «Patchman-Admins» и «Patchman-Viewers«, так и не входящими в эти группы. Кроме того, локальная учётная запись администратора Patchman в нашей конфигурации также должна работать. Если все проверки пройдены успешно, то можем переходить к следующей части настройки с небольшой модификацией текущей конфигурации.

Часть 3. Конфигурация модуля django-remote-auth-ldap

Итак, на стороне веб-сервера у нас имеется уже работающая аутентификация Kerberos, а на стороне веб-фреймворка Django имеется работающая form-based аутентификация с авторизацией в LDAP каталоге. Но сейчас эти два механизма никак друг с другом не пересекаются. Нам потребуется пристроить к Django модуль django-remote-auth-ldap, который выстроит связь от аутентификации Kerberos к авторизации LDAP, позволив автоматически пропускать на веб-сайт Patchman пользователя успешно прошедшего аутентификацию (при условии его членства в соответствующей доменной группе безопасности).

На странице описания модуля  предлагается установка посредствам команды «pip install django-remote-auth-ldap». Однако в Debian 12, где модули python управляются путём установки deb-пакетов, выполнение такой команды приведёт к ошибке «error: externally-managed-environment«.

На помощь в этой ситуации снова пришёл наш коллега Владимир Леттиев (aka crux) и опакетил модуль django-remote-auth-ldap под ОС Debian 12, за что ему «наше с кисточкой». Скачать готовый deb-пакет можно отсюда: python3-django-remote-auth-ldap

Размещаем пакет в локальном репозитории и привычным способом проводим его установку:

# apt-get install python3-django-remote-auth-ldap

Обратите внимание на то, что при установке пакета python3-django-remote-auth-ldap в качестве зависимости из стандартных репозиториев Debian 12 должен подтянуться ещё и пакет python3-django-appconf, который также необходим для корректной работы модуля django-remote-auth-ldap.

Внесём дополнительные коррективы в основной конфигурационный файл Patchman:

# nano /etc/patchman/local_settings.py

В секции перечисления подключенных бакендов аутентификации закомментируем ранее проверенный бакенд LDAPBackend и в начало перечисления добавим бакенд RemoteUserLDAPBackend, который реализуется модулем django_remote_auth_ldap. На самом деле модуль django_remote_auth_ldap это ни что иное, как надстройка над django_auth_ldap, наследующая все возможности этого модуля и несколько меняющая логику его работы.

# Keep ModelBackend around for per-user permissions and maybe a local superuser.
AUTHENTICATION_BACKENDS = (
    "django_remote_auth_ldap.backend.RemoteUserLDAPBackend",
    #"django_auth_ldap.backend.LDAPBackend",
    "django.contrib.auth.backends.ModelBackend",
)

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

# django-remote-auth-ldap : whether or not to check the domain against a known list - default True
DRAL_CHECK_DOMAIN = False

# django-remote-auth-ldap : whether or not to strip the domain off the username before passing to django-auth-ldap - default True
DRAL_STRIP_DOMAIN = True

Кроме того, нам потребуется сделать «грешное», но необходимое, изменение в ещё в одном конфигурационном файле:

# nano /usr/lib/python3/dist-packages/patchman/settings.py

Здесь нужно найти секцию MIDDLEWARE и сразу после сроки с AuthenticationMiddleware добавить дополнительную строку с RemoteUserMiddleware:

MIDDLEWARE = [
    '...',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django_remote_auth_ldap.middleware.RemoteUserMiddleware',
    '...',
]

Опасность такого изменения заключается в том, что при последующих обновлениях пакета сервера Patchman (python3-patchman) файл settings.py может оказаться переписанным файлом, поставляемым в составе обновлённого пакета. В таком случае соответствующую правку файла придётся повторить.

На этом настройку можно считать законченной и можно приступать к окончательному тестированию построенной конфигурации. Если при работе с бакендом аутентификации django_remote_auth_ldap возникнут проблемы, то его отладку можно подключить через ранее упомянутый параметр конфигурации LOGGING, просто заменив в этой секции имя логгера django_auth_ldap на django_remote_auth_ldap.

Authentication is a fundamental aspect of any web application. It’s critical to ensure that only authorized users have access to sensitive information. While Django comes with a robust authentication system out of the box, it may not be the best fit for your organization’s needs.

If you’re looking for a more centralized and secure way to manage user authentication, LDAP may be the answer. LDAP is a widely used protocol for accessing and maintaining information distributed across a network. Plus, it can be integrated with Django to provide centralized authentication and authorization.

In this blog, we will explore how to use LDAP authentication with Django. Note: We will not be covering how to run a Django application in a Windows domain. Instead, it will be assumed that the Django application is already deployed, and we are updating the application to integrate with the Windows domain for LDAP authentication.

What are the Benefits of Using LDAP Authentication?

The benefits of LDAP with Django are very similar to the common benefits of using LDAP for authentication and user management. Below are a few of the top benefits that truly shine through when using LDAP with Django.

  • Centralized user management: LDAP enables organizations to manage user accounts centrally. Which helps in maintaining uniformity across different systems and applications.

  • Increased Security: LDAP authentication adds an extra layer of security. Requiring users to authenticate their credentials before accessing applications or systems.

  • Reduced Administrative Efforts: LDAP authentication reduces the administrative burden of creating and managing user accounts for different systems and applications separately.

  • Improved User Experience: LDAP authentication allows users to access multiple systems and applications. Simply by using a single set of login credentials.

Setting Up Django with LDAP Authentication

There are several setting changes that are needed to get LDAP authentication working with Django. The LDAP authentication configuration that we’ll cover will be built on top of our Django Sample Application. The build out for the LDAP authentication will be in its own branch that will build on top of the swagger branch located below.

For our purposes, we will be looking at authentication with the Django API. This setup applies to authentication with a login form as well.

The first thing we are going to look at is the requirements.txt as there is a new Python library that must be installed for LDAP authentication to properly work. The new Python library is called django-python3-ldap. This will allow us to define LDAP groups that can be used for authentication. Making it easier to manage access to the application. The new requirements.txt file looks like the following:

Django~=3.2.16
python-dateutil~=2.8.2
urllib3~=1.26.12
requests~=2.28.1
djangorestframework~=3.14.0
drf-yasg~=1.21.4
django-python3-ldap~=0.15.4

The next section that needs updating is the settings.py file. This file will get the new configurations to implement some of the new features that will be used for authentication. The first variable we will be updating is the INSTALLED_APPS variable. The new app that needs to be added is django_python3_ldap. The new INSTALLED_APPS variable should look like the following:

INSTALLED_APPS = [
    'app',
    'api',
    'rest_framework',
    'personnel',
    'drf_yasg',
    'django_python3_ldap', 
    … TRUNCATED
]

After the INSTALLED_APPS configuration, you need to configure the AUTHENTICATION_BACKENDS variable. As seen below. Including only the LDAPBackend to ensure only LDAP authentication is used for the application.

AUTHENTICATION_BACKENDS = [
    'django_python3_ldap.auth.LDAPBackend',
]

Lastly, the LDAP settings need to be configured. There are three environment variables that need to be configured to work properly with your domain. For this demonstration, a .env file is being used along with the python-dotenv Python library to load the environment variables. As each environment will be different. The variables would be configured in the below examples based on a domain name of test.com:

  • DOMAIN_NAME_FQDN=test.com

  • NETBIOS_DOMAIN_NAME=TEST

  • DOMAIN_SEARCH_BASE=’DC=test,DC=com’

LOGIN_REDIRECT_URL = "/"
LDAP_AUTH_URL = os.environ.get('DOMAIN_NAME_FQDN')

LDAP_AUTH_FORMAT_USERNAME = 'django_python3_ldap.utils.format_username_active_directory'
LDAP_AUTH_USE_TLS = True
LDAP_AUTH_ACTIVE_DIRECTORY_DOMAIN = os.environ.get('NETBIOS_DOMAIN_NAME')

LDAP_AUTH_SEARCH_BASE = os.environ.get('DOMAIN_SEARCH_BASE')

LDAP_AUTH_OBJECT_CLASS = 'user'
LDAP_AUTH_FORMAT_SEARCH_FILTERS = 'demoapp.searchfilter.group_search_filter'
LDAP_AUTH_USER_FIELDS = {
    'username': 'sAMAccountName',
    'first_name': 'givenName',
    'last_name': 'sn',
    'email': 'mail',
}

After all configurations are made, the last thing to do is create the search filter. This is defined in the LDAP_AUTH_FORMAT_SEARCH_FILTERS variable from above. In this configuration, the file searchfilter.py lives in the “demoapp” app, which is the base folder for our Django application. However, you can put the file in any app. Just as long as you update the LDAP_AUTH_FORMAT_SEARCH_FILTERS variable accordingly. The function that will be used as the search filter will be called group_search_filter. Below is the configuration of that search filter.

def group_search_filter(ldap_fields):
    search_filters = format_search_filters(ldap_fields)
    search_filters.append(f"(memberOf=CN=Django_Users,OU=Groups,{os.environ.get('DOMAIN_SEARCH_BASE')})")
    
    return search_filters

The last file that needs to be updated to enforce authentication with the Django API is to update all the API views that we want to enforce authentication with. There are two new imports required.

from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated

Additionally, there are two variables that need to be defined under each APIView class to ensure all endpoints under that APIView enforce authentication. As seen below.

class Personnel(APIView):
    authentication_classes = [BasicAuthentication]
    permission_classes = [IsAuthenticated]

Testing LDAP Authentication with the API

Now that we have our Django application fully configured with LDAP authentication, we can start to test it. However, we need to make sure that the user group defined in the search filter is created and has a user defined. In this setup, we are using an Active Directory group called “Django_Users” that exists in the “Groups” OU under our base Domain. This can be seen in the screenshot below. We’ve also added a user called “test_user” to the group for testing.

For testing the API, we will be using the swagger page located at http://{IP}:8000/api/swagger. Now, if we try to make an API request that isn’t authenticated, we are prompted with a login box for a username and password. As seen below.

If we don’t provide any credentials, this will be our response:

However, if we log in using the Authorize button at the top of the Swagger page and then try the request, we have successfully validated that the user group defined in Django is being used as our authentication method for our Django application. Resulting in a successful output of data.

Conclusion

LDAP authentication offers a powerful and secure way to manage user authentication in Django. By integrating LDAP with your Django application, you can centralize your user management. Taking advantage of LDAP’s security features. We’ve covered the key steps to configure LDAP authentication for your Django application. We’ve also explored authenticating with the Django API. Demonstrating what a successful and unsuccessful request looks like.

With LDAP authentication in place, you can ensure that only authorized users have access to sensitive information. Maintaining a consistent user experience across your organization’s systems. It can also simplify user management by centralizing user authentication and authorization. Integrating LDAP with Django offers organizations a valuable tool that encompasses security, simplicity, and centralization. With that being said, are you ready to integrate LDAP in your Django application?

Thank you for taking the time to review this article and feel free to contact us if your project needs more advanced capabilities.

Понравилась статья? Поделить с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
  • Windows просмотр примененных политик
  • Via high definition audio driver ver v7700d windows 10 64 bit
  • Ipsec в домене windows
  • Windows 10 не видит себя в сети службы
  • Мой компьютер не виден в локальной сети windows 10