PAM module for authentication against a Django web service (or for that matter any web service that implements pam-django's very simple API).
Find a file
2022-02-05 16:56:23 +01:00
debian fix: Fix libpam-django.dirs. 2022-02-05 16:56:23 +01:00
.gitignore feature: Initial commit. 2022-02-05 00:29:26 +01:00
.gitlab-ci.yml feature: Initial commit. 2022-02-05 00:29:26 +01:00
LICENSE feature: Initial commit. 2022-02-05 00:29:26 +01:00
Makefile fix: Put pam_django.so into /lib instead of /usr/lib. 2022-02-05 14:01:55 +01:00
pam_django.c feature: Initial commit. 2022-02-05 00:29:26 +01:00
README.md feature: Initial commit. 2022-02-05 00:29:26 +01:00

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.