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: - Microsoft Active Directory,
- Windows domain name: company.ru
- Windows 2012 Server Domain Controller Name: DC-1
- Windows Server 2012 Domain Controller IP Address: 192.168.7.110
- Server for our application: CentOS7, Apache, Python 3.5.1, Django 1.9.1
- Hostname Linux Server with CentOS7: srv-app
- IP Address Linux Server with CentOS7: 192.168.7.105
- Application URL on Django: srv-app.company.ru
Need to do:- A user registered in Active Directory when opening any page of the site on srv-app.company.ru must automatically, without requesting a login / password, be authorized by Django. When authorizing, some information about it from Active Directory (first_name, last_name, mail; the flags is_active, is_staff, is_supersuser must be set based on the user's membership in the corresponding Active Directory groups) must be transferred to the user profile in Django.
- A user who is not registered in Active Directory should not be allowed to enter the site.
After examining a number of published articles and descriptions, it became clear that you can achieve the desired result by performing two basic steps:- Setting up transparent authentication when accessing an Apache server using Kerberos.
- Authorization in Django using LDAP access to the domain controller to obtain the necessary information about the authorizing user.
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:
- using the DNS Manager snap-in, add the srv-app host with the address 192.168.7.105
- using the “AD - users and computers” snap-in, add the user svc-apache , set the password P @ ssw0rd for it. We will need this user later to create a keytab file that will connect our Linux machine and Active Directory.
Install modules to work with Kerberos
[root@srv-app ~]
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:
- COMPANY.RU - set the kerberos (realm) domain name in linux. It should be remembered that the kerberos realm is case-sensitive (case-sensetive)
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 ~]
If there are no errors, let's see which tickets (tickets) we have:
[root@srv-app ~]
So we logged on to the CD using kerberos, now break the connection, removing
ticket received:
[root@srv-app ~]
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
- The / princ HTTP/srv-app.company.ru@COMPANY.RU key sets the unique client name (principal) on a Linux machine that will be allowed to perform kerberos authentication.
- The / mapuser svc-apache@COMPANY.RU and / pass P @ ssw0rd keys connect the principal to a specific user in Active Directory
- The / crypto ALL key sets the encryption method. Instead of / crypto ALL, you can specify a specific encryption method such as / crypto AES256-SHA1
- The / mapop set switch sets the mapping between the Linux principal and the Active Directory user account ( / mapop add will add this mapping to the keytab)
- The / ptype switch KRB5_NT_PRINCIPAL - set the type of principal in the request (the specified type is the main one and it is recommended to use it)
- The / out c: \ share \ keytab key sets the path for the output keytab file.
- If desired, additional key values ​​can be viewed using ktpass.exe / help
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 ~]
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 "/">
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:
- 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 - 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
- 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:
- When entering into any section of our Django application, by a user who has been authorized by a domain controller, we first get access to our Django application, and secondly, the Apache server places the name of an authorized user that matches the attribute sAMAccountName of this variable into the REMOTE_USER variable user in Active Directory.
- If the user is not authorized in AD, then Apache will return us the error "401 Unautorized" (With the help of the ErrorDocument option, we can redirect the unauthorized user to any guest page in this case)
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 UsersLDAPBackend 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 UsersBy 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-ldap3.
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.