| debian | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| LICENSE | ||
| Makefile | ||
| pam_django.c | ||
| README.md | ||
pam-django: Authenticating against a Django application
Introduction
Django is a very popular web framework. Sometimes a service is implemented as a Django app but then other services are added that are being offered to the same users. It would suck for every service to have their own separate user database. This PAM module lets PAM-enabled services refer to a Django web application for username/password validation.
(Note that this PAM module is technically not limited to talking to Django apps. Any web service that supports its very basic HTTP API will work. See below for details.)
Many services can't talk to PAM but they can talk to an LDAP server. It is possible to use OpenLDAP to present an LDAP view of the Django user and group models, and to delegate authentication to Django via pam_django.
Installation
Install the development libraries for PAM and libcurl:
$ sudo apt install libpam0g-dev libcurl4-openssl-dev
Use the Makefile to compile the PAM module and install it in
/usr/lib/x86_64-linux-gnu/security:
$ make
$ sudo make install
(this has only been tested on Debian GNU/Linux).
Create a PAM service file in /etc/pam.d/foo (for a service called
foo), with the following content:
auth required pam_django.so auth_url=URL auth_cookie=COOKIE
account required pam_django.so
Here URL stands for the URL of your Django app's authentication view
(see below) and COOKIE for the secret authentication cookie which is
used to keep out unwanted crackers.
You can use the pamtester program (in the pamtester package in
Debian) to see whether the module works:
$ pamtester foo bar authenticate
tries to authenticate user bar via the foo PAM service.
The Django side
The pam_django.so module collects a username and password and makes
an HTTP (or HTTPS) request to the auth_url given in the PAM service
file, like
POST http://www.example.com/pam/authenticate/ HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
…
user=bar&password=quux&cookie=Ke4QxzqM45p
(here auth_url is assumed to be
http://www.example.com/pam/authenticate). The server is supposed to
send a plain-text reply that starts with either the characters OK
(if the authentication was successful) or NO (if it wasn't).
A Django view that does the required checks could look like this:
@csrf_exempt
@require_POST
def pam_authenticate(request):
if request.POST.get('cookie', '') != settings.PAM_AUTH_COOKIE:
return http.HttpResponseBadRequest(
"Boo on you", content_type="text/plain")
username = request.POST.get('user', '')
password = request.POST.get('password', '')
if not username or not password:
return http.HttpResponseForbidden(
"NO\n", content_type="text/plain")
result = authenticate(username=username, password=password)
if result is not None:
return http.HttpResponse("OK\n", content_type="text/plain")
return http.HttpResponseForbidden(
"NO\n", content_type="text/plain")
This uses the Django authenticate() method to perform the actual
authentication. Obviously, a similar view could be implemented using
any other web framework.
The view above expects the cookie parameter in the request to match
the PAM_AUTH_COOKIE setting. You should therefore add something like
PAM_AUTH_COOKIE = 'Ke4QxzqM45p'
to your application's settings.py file. Python's
secrets.token_urlsafe() function is a great way of obtaining
suitable random cookies.