📜 ⬆️ ⬇️

Django Single Sign-On and Microsoft Active Directory

Start


Once I had to start developing a web application for corporate use in Python + Django. And the very first question that had to be addressed was transparent authorization on the site or Single Sign-On (SSO) .

The enterprise widely uses the Microsoft Active Directory-based directory service, and by now almost all corporate applications allow using windows-authentication and not constantly entering logins / passwords, so the new application simply had to satisfy the existing state of things and realize the above-mentioned opportunity for “ transparent user authorization.

Although a lot of articles were written on the issue of implementing SSO for Django, however, in order to realize what I needed, I had to spend a lot of time. Therefore, in order to save some of you from possible long searches for information and its assembly into a working scheme, I offer you my manual how to make transparent authorization in the Django application using Active Directory accounts.
')
So we have:


Need to do:


After examining a number of published articles and descriptions, it became clear that you can achieve the desired result by performing two basic steps:

Step 1: Set up transparent authentication using Kerberos


It is clear that the implementation of the principle of SSO in the Windows AD network is possible using the Kerberos protocol. Therefore, the main task of the first configuration step will be to install Kerberos in a Linux + Apache environment and configure communication with a Windows AD domain controller.

Installing and configuring Kerberos on a Linux server


Configure / etc / hosts, /etc/resolv.conf on srv-app , DNS on DC-1

On the srv-app add to / etc / hosts:

192.168.7.105 srv-app.company.ru 

On srv-app we change /etc/resolv.conf (In reality, this file in CentOS7 generates NetworkManager, so changes need to be made to / etc / sysconfig / network-scripts / ifcfg-eth0):

  search company.ru nameserver 192.168.7.110 

On DC-1 Domain Controller:


Install modules to work with Kerberos


  [root@srv-app ~]# yum install mod_auth_kerb #    apache [root@srv-app ~]# yum install krb5-workstation #       kerberos 

Configuring Kerberos by editing the /etc/krb5.conf file

  [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 

Some explanations about the contents of the configuration file:


We perform several checks on the work of kerberos on the srv-app computer

Earlier, on our domain controller, we created a user srv-apache with the password P @ ssw0rd . Let's try to log in to the CD using the kinit utility:

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

If there are no errors, let's see which tickets (tickets) we have:

  [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 

So we logged on to the CD using kerberos, now break the connection, removing
ticket received:

  [root@srv-app ~]# kdestroy 

If everything works, then for further configuration we need to create the krb5.keytab file for the authentication service using
Apache and mod_auth_kerb.

Keytab generation on Windows Domain Controller

You can generate a keytab on a DC-1 domain controller using the ktpass.exe command:

  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 


As a result, we get the file c: \ share \ keytab, which must be copied to the srv-app and called /etc/krb5.keytab. Next, you must provide access to this file to the user, under the account of which the httpd server is running. In our case, this is apache . In order for apache to read this file, we just allow it to be read by all users:

  chmod a+r /etc/krb5.keytab 

Check whether our keytab works as follows:

1. With 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. With 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 setup

Below is the /etc/httpd/conf.d/company_main.conf file, which contains configuration instructions for configuring Kerberos authentication when accessing the 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> 

I want to pay attention to the KrbMethodK5Passwd off setting. The specified setting causes kerberos authentication using Single Sign-On technology to be performed when entering the specified section of the site. In case of unsuccessful authentication, there will be an error “401 Unautorized” immediately. However, if you change the setting to KrbMethodK5Passwd on , then after unsuccessful authorization Single Sign-On , an attempt will be made to authorize by name and password.

And one more undocumented feature that we will use: Configuring KrbLocalUserMapping On causes the REMOTE_USER variable to contain the name of the registered user (in the case of KrbLocalUserMapping Off, REMOTE_USER will contain username @ COMPANY RU).

More information on mod_auth_kerb module settings can be found here .

Single Sign-On (SSO) from Windows Workstations

I repeat once again that all the work on setting up kerberos authentication in Linux was done in order to be able to access portal portal pages published on a Linux machine using corporate accounts stored in Active Directory, besides, to simplify the lives of users, this input should be “transparent” , Without asking for a password, which is achieved using Single Sign-On (SSO) technology, which is supported in Windows 7 and higher and Internet Explorer (and Mozilla Firefox).

However, in order for everything to go smoothly, the following settings must be made at the workstation from which this input is made:

  1. The site to which the entrance is made (in our case, srv-app.company.ru should be entered into the “Local intranet” nodes using the menu
    Internet Explorer: Tools -> Internet Options -> Security -> Local Intranet -> Nodes -> Advanced
  2. The following options should be enabled in IE (as a rule, they are enabled by default):
    • Tools -> Internet Options -> Advanced -> Security -> Allow Windows Integrated Validation = ON
    • Tools -> Internet Options -> Security -> Local Intranet -> Security Level for this Zone -> Other -> User Authentication -> Automatic access to the network only in the intranet zone

  3. In addition, SSO will not work if you try to access the site by IP address. Be sure to use the domain name srv-app.company.ru!

Stage 2. Authorization of the user in Django


So, as a result of the work carried out in the first stage, we obtained the following results:


However, despite the fact that Apache has authorized our user, for the Django application, it is still unknown and, accordingly, the entire user authentication / authorization and session usage mechanism debugged in Django remains untapped.

Using RemoteUserBackend


Especially for such purposes in Django there is a simple solution that includes the authorization mechanism in the system of users already authenticated by external applications, such as IIS or Apache (using methods similar to those used in step 1: mod_authnz_ldap, CAS, Cosign, WebAuth, mod_auth_sspi, mod_auth_krb).

According to the description on the official website djangoproject.org, to use transparent authentication and authorization in Django in such a way, it is enough to enable the RemoteUserBackend mechanism by performing the following steps:

1. In the Django project settings file settings.py, add django.contrib.auth.middleware.RemoteUserMiddleware to the MIDDLEWARE_CLASSES list immediately after django.contrib.auth.middleware.AuthenticationMiddleware :

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

2. In the same place, replace ModelBackend with RemoteUserBackend in the AUTHENTICATION_BACKENDS list (or add this list to settings.py):

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

As a result of these changes, RemoteUserMiddleware will retrieve the username via request.META ['REMOTE_USER'] and automatically perform authentication and authorization (login) of this user using RemoteUserBackend . In addition , with such an authorization, RemoteUserBackend will add a new user to the auth_user table, thus utilizing the standard Django account management mechanism.

But, unfortunately, this method will not allow us to receive information we need from our Active Directory and use the information we need in our application (first_name, last_name, mail, participation in AD groups, etc.).

Using django-auth-ldap


So, we need to access Active Directory using the LDAP protocol. Fortunately, a great Django application for this is django-auth-ldap . You can install it as standard with pip:

 pip install django-auth-ldap 

After this, you will have to remove from settings.py the MIDDLEWARE_CLASSES added in the previous section, namely, 'django.contrib.auth.middleware.RemoteUserMiddleware' , and the AUTHENTICATION_BACKENDS list should look like this:

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

In addition, in settings.py you need to enable the following configuration parameters:

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 is a great application. With its help, you can download almost any information on it from AD to a Django user profile, even having the opportunity to “pull up” groups in which this user participates in the appropriate Django model.

However, for authorization using django-auth-ldap, you still need to ask the user for his username and password, which is absolutely not suitable for us.

Although the documentation states that, it seems, you can "cross" django-auth-ldap and RemoteUserBackend :

Non-LDAP Users
LDAPBackend has one more feature For example, you might be using RemoteUserBackend to map externally authenticated users. By setting AUTH_LDAP_AUTHORIZE_ALL_USERS, you can use the authorization information. Note that this doesn’t work with AUTH_LDAP_MIRROR_GROUPS; group mirroring is a feature of authentication, not authorization.

But it does not work, as we need. The user will actually be able to log in, but this happens exactly as if we were just using RemoteUserBackend (see previous section). Information from AD to the Django user profile is not automatically loaded.

Of course, you can do this yourself by using the following recommendation:

Updating Users
By default, all 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 be able to create associated data, you can call django_auth_ldap.backend.LDAPBackend.populate_user (). You'll need an instance of LDAPBackend, yourself. it couldn’t 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') 


Using django-remote-auth-ldap


But as it turned out, everything is much simpler. The bike has already been invented, and we can only use it. The django-remote-auth-ldap application is a small add-on over django_auth_ldap and allows you to authorize a user without any extra effort and load his data from AD during authorization.

Install django-remote-auth-ldap as standard ( django-auth-ldap is also required for this add-in to work):

 pip install django-remote-auth-ldap 

Next, you need to add the following setting in settings.py:

 DRAL_CHECK_DOMAIN = False 

The fact is that django-remote-auth-ldap , apparently designed to work with IIS, which sets the variable REMOTE_USER in the format "DOMAIN / username", we have configured mod_auth_kerb so that the domain name does not fall into REMOTE_USER. The above setting causes django-remote-auth-ldap to assume that in REMOTE_USER only one user name without specifying a domain, i.e. exactly as we need.
Well, and again recommendations for setting MIDDLEWARE_CLASSES and AUTHENTICATION_BACKENDS :

1. In the Django project settings file settings.py, add django.contrib.auth.middleware.RemoteUserMiddleware to the MIDDLEWARE_CLASSES list immediately after django.contrib.auth.middleware.AuthenticationMiddleware :

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

2. AUTHENTICATION_BACKENDS should look like this:

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

That's all, transparent authentication in Django is configured.

If the user is a member of your Django application, is already authorized in Active Directory, then:

1. Apache will authorize it with Kerberos, allow it to the pages of your application and write the name of the authorized user to REMOTE_USER.
2. RemoteUserMiddleware “sees” the value of REMOTE_USER and initiates the authentication and authorization of the specified user in Django using django-remote-auth-ldap
3. django-remote-auth-ldap authenticates and authorizes the user using methods inherited from the django-auth-ldap application, which will “pull” in Django the information you need from Active Directory.

Source: https://habr.com/ru/post/274931/


All Articles